From 5b75a3f00b9b1c7c4ff06f6f871716dd0df659be Mon Sep 17 00:00:00 2001 From: jlandowner Date: Thu, 13 Jun 2024 17:38:37 +0900 Subject: [PATCH 1/2] Support sharing workspaces - Add allowedUsers in network rule - Update authorization to allow users who can access at least 1 network rule can get the workspace information - Update authorization to allow users who can access its main network rule can update the workspace - Update UI to show shared workspaces --- api/v1alpha1/user_types.go | 7 +- api/v1alpha1/workspace_types.go | 13 +- api/v1alpha1/zz_generated.deepcopy.go | 12 + .../crds/cosmo-workspace.github.io_users.yaml | 50 +++ .../cosmo-workspace.github.io_workspaces.yaml | 4 + .../cosmo-username-headers-addon.yaml | 1 + .../cosmo-workspace.github.io_users.yaml | 50 +++ .../cosmo-workspace.github.io_workspaces.yaml | 4 + .../cosmo-username-headers-addon.yaml | 1 + .../cosmo-username-headers.yaml | 1 + internal/cmd/workspace/create.go | 2 +- internal/cmd/workspace/get.go | 47 ++- internal/cmd/workspace/get_network.go | 5 +- internal/cmd/workspace/update.go | 3 +- internal/cmd/workspace/upsert_network.go | 8 +- internal/controllers/user_controller.go | 28 +- internal/controllers/workspace_controller.go | 35 +++ internal/dashboard/workspace_handler.go | 56 +++- .../dashboard/workspace_network_handler.go | 5 +- pkg/apiconv/workspace.go | 2 + pkg/apiconv/workspace_test.go | 14 +- pkg/kosmo/workspace.go | 38 ++- pkg/workspace/traefik_ingressroute.go | 17 +- proto/gen/dashboard/v1alpha1/workspace.pb.go | 131 ++++---- .../v1alpha1/workspace_service.pb.go | 224 +++++++------- .../v1alpha1/workspace_service.pb.validate.go | 4 + proto/gen/index.md | 4 +- .../dashboard/v1alpha1/workspace.proto | 3 +- .../v1alpha1/workspace_service.proto | 1 + .../cosmoauth/cosmoauth_middleware.go | 26 +- .../gen/dashboard/v1alpha1/workspace_pb.ts | 10 +- .../v1alpha1/workspace_service_pb.ts | 6 + .../organisms/NetworkRuleActionDialog.tsx | 210 +++++++++++-- .../src/views/organisms/WorkspaceModule.tsx | 21 ++ .../src/views/pages/WorkspacePage.tsx | 287 +++++++++++------- 35 files changed, 967 insertions(+), 363 deletions(-) diff --git a/api/v1alpha1/user_types.go b/api/v1alpha1/user_types.go index e3f54b42..fb888ef3 100644 --- a/api/v1alpha1/user_types.go +++ b/api/v1alpha1/user_types.go @@ -81,9 +81,10 @@ type UserSpec struct { } type UserStatus struct { - Phase corev1.NamespacePhase `json:"phase,omitempty"` - Namespace ObjectRef `json:"namespace,omitempty"` - Addons []ObjectRef `json:"addons,omitempty"` + Phase corev1.NamespacePhase `json:"phase,omitempty"` + Namespace ObjectRef `json:"namespace,omitempty"` + Addons []ObjectRef `json:"addons,omitempty"` + SharedWorkspaces []ObjectRef `json:"sharedWorkspaces,omitempty"` } type UserAddon struct { diff --git a/api/v1alpha1/workspace_types.go b/api/v1alpha1/workspace_types.go index 705ec45c..8bdc5606 100644 --- a/api/v1alpha1/workspace_types.go +++ b/api/v1alpha1/workspace_types.go @@ -80,12 +80,13 @@ const ( // NetworkRule is an abstract network configuration rule for workspace type NetworkRule struct { - Protocol string `json:"protocol"` - PortNumber int32 `json:"portNumber"` - CustomHostPrefix string `json:"customHostPrefix,omitempty"` - HTTPPath string `json:"httpPath,omitempty"` - TargetPortNumber *int32 `json:"targetPortNumber,omitempty"` - Public bool `json:"public"` + Protocol string `json:"protocol"` + PortNumber int32 `json:"portNumber"` + CustomHostPrefix string `json:"customHostPrefix,omitempty"` + HTTPPath string `json:"httpPath,omitempty"` + TargetPortNumber *int32 `json:"targetPortNumber,omitempty"` + Public bool `json:"public"` + AllowedUsers []string `json:"allowedUsers,omitempty"` } func HTTPUniqueKey(host, httpPath string) string { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 34aa3f16..1cdcf172 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -275,6 +275,11 @@ func (in *NetworkRule) DeepCopyInto(out *NetworkRule) { *out = new(int32) **out = **in } + if in.AllowedUsers != nil { + in, out := &in.AllowedUsers, &out.AllowedUsers + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkRule. @@ -587,6 +592,13 @@ func (in *UserStatus) DeepCopyInto(out *UserStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.SharedWorkspaces != nil { + in, out := &in.SharedWorkspaces, &out.SharedWorkspaces + *out = make([]ObjectRef, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserStatus. diff --git a/charts/cosmo/crds/cosmo-workspace.github.io_users.yaml b/charts/cosmo/crds/cosmo-workspace.github.io_users.yaml index 552983ba..cbd24fc8 100644 --- a/charts/cosmo/crds/cosmo-workspace.github.io_users.yaml +++ b/charts/cosmo/crds/cosmo-workspace.github.io_users.yaml @@ -191,6 +191,56 @@ spec: x-kubernetes-map-type: atomic phase: type: string + sharedWorkspaces: + items: + description: ObjectRef is a reference of resource which is created + by the Instance + properties: + apiVersion: + description: API version of the referent. + type: string + creationTimestamp: + format: date-time + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + TODO: this design is not final and this field is subject to change in the future. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic + type: array type: object type: object served: true diff --git a/charts/cosmo/crds/cosmo-workspace.github.io_workspaces.yaml b/charts/cosmo/crds/cosmo-workspace.github.io_workspaces.yaml index fed0aa5e..4e5e6f96 100644 --- a/charts/cosmo/crds/cosmo-workspace.github.io_workspaces.yaml +++ b/charts/cosmo/crds/cosmo-workspace.github.io_workspaces.yaml @@ -53,6 +53,10 @@ spec: description: NetworkRule is an abstract network configuration rule for workspace properties: + allowedUsers: + items: + type: string + type: array customHostPrefix: type: string httpPath: diff --git a/charts/cosmo/templates/cosmo-username-headers-addon.yaml b/charts/cosmo/templates/cosmo-username-headers-addon.yaml index 541cd1e0..8e63d5d9 100644 --- a/charts/cosmo/templates/cosmo-username-headers-addon.yaml +++ b/charts/cosmo/templates/cosmo-username-headers-addon.yaml @@ -24,6 +24,7 @@ spec: headers: customRequestHeaders: X-Cosmo-UserName: '{{ print "{{USER_NAME}}" }}' + X-Cosmo-UserName-{{ print "{{USER_NAME}}" }}: '1' customResponseHeaders: X-Cosmo-UserName: '{{ print "{{USER_NAME}}" }}' {{- end }} diff --git a/config/crd/bases/cosmo-workspace.github.io_users.yaml b/config/crd/bases/cosmo-workspace.github.io_users.yaml index 552983ba..cbd24fc8 100644 --- a/config/crd/bases/cosmo-workspace.github.io_users.yaml +++ b/config/crd/bases/cosmo-workspace.github.io_users.yaml @@ -191,6 +191,56 @@ spec: x-kubernetes-map-type: atomic phase: type: string + sharedWorkspaces: + items: + description: ObjectRef is a reference of resource which is created + by the Instance + properties: + apiVersion: + description: API version of the referent. + type: string + creationTimestamp: + format: date-time + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + TODO: this design is not final and this field is subject to change in the future. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic + type: array type: object type: object served: true diff --git a/config/crd/bases/cosmo-workspace.github.io_workspaces.yaml b/config/crd/bases/cosmo-workspace.github.io_workspaces.yaml index fed0aa5e..4e5e6f96 100644 --- a/config/crd/bases/cosmo-workspace.github.io_workspaces.yaml +++ b/config/crd/bases/cosmo-workspace.github.io_workspaces.yaml @@ -53,6 +53,10 @@ spec: description: NetworkRule is an abstract network configuration rule for workspace properties: + allowedUsers: + items: + type: string + type: array customHostPrefix: type: string httpPath: diff --git a/config/user-addon/traefik-middleware/cosmo-username-headers-addon.yaml b/config/user-addon/traefik-middleware/cosmo-username-headers-addon.yaml index 7db12238..9f585892 100644 --- a/config/user-addon/traefik-middleware/cosmo-username-headers-addon.yaml +++ b/config/user-addon/traefik-middleware/cosmo-username-headers-addon.yaml @@ -23,5 +23,6 @@ spec: headers: customRequestHeaders: X-Cosmo-UserName: '{{USER_NAME}}' + X-Cosmo-UserName-{{USER_NAME}}: '1' customResponseHeaders: X-Cosmo-UserName: '{{USER_NAME}}' diff --git a/config/user-addon/traefik-middleware/cosmo-username-headers.yaml b/config/user-addon/traefik-middleware/cosmo-username-headers.yaml index 527382b3..951fe866 100644 --- a/config/user-addon/traefik-middleware/cosmo-username-headers.yaml +++ b/config/user-addon/traefik-middleware/cosmo-username-headers.yaml @@ -6,5 +6,6 @@ spec: headers: customRequestHeaders: X-Cosmo-UserName: "{{USER_NAME}}" + X-Cosmo-UserName-{{USER_NAME}}: "1" customResponseHeaders: X-Cosmo-UserName: "{{USER_NAME}}" diff --git a/internal/cmd/workspace/create.go b/internal/cmd/workspace/create.go index 9dd99667..a341ac38 100644 --- a/internal/cmd/workspace/create.go +++ b/internal/cmd/workspace/create.go @@ -122,7 +122,7 @@ func (o *CreateOption) RunE(cmd *cobra.Command, args []string) error { } fmt.Fprintln(cmd.OutOrStdout(), color.GreenString("Successfully created workspace %s", o.WorkspaceName)) - OutputTable(cmd.OutOrStdout(), []*dashv1alpha1.Workspace{ws}) + OutputTable(cmd.OutOrStdout(), o.UserName, []*dashv1alpha1.Workspace{ws}) return nil } diff --git a/internal/cmd/workspace/get.go b/internal/cmd/workspace/get.go index 50c27093..fb14aad2 100644 --- a/internal/cmd/workspace/get.go +++ b/internal/cmd/workspace/get.go @@ -13,6 +13,7 @@ import ( "github.com/cosmo-workspace/cosmo/pkg/apiconv" "github.com/cosmo-workspace/cosmo/pkg/cli" "github.com/cosmo-workspace/cosmo/pkg/clog" + "github.com/cosmo-workspace/cosmo/pkg/kosmo" dashv1alpha1 "github.com/cosmo-workspace/cosmo/proto/gen/dashboard/v1alpha1" ) @@ -105,7 +106,7 @@ func (o *GetOption) RunE(cmd *cobra.Command, args []string) error { users = u } for _, user := range users { - wss, err := o.ListWorkspaces(ctx, user.Name) + wss, err := o.ListWorkspaces(ctx, user.Name, !o.AllUsers) if err != nil { return err } @@ -116,14 +117,18 @@ func (o *GetOption) RunE(cmd *cobra.Command, args []string) error { workspaces = o.ApplyFilters(workspaces) + username := o.UserName + if o.AllUsers { + username = "" + } if o.OutputFormat == "yaml" { o.OutputYAML(cmd.OutOrStdout(), workspaces) return nil } else if o.OutputFormat == "wide" { - OutputWideTable(cmd.OutOrStdout(), workspaces) + OutputWideTable(cmd.OutOrStdout(), username, workspaces) return nil } else { - OutputTable(cmd.OutOrStdout(), workspaces) + OutputTable(cmd.OutOrStdout(), username, workspaces) return nil } } @@ -136,11 +141,11 @@ func (o *GetOption) ListUsers(ctx context.Context) ([]*dashv1alpha1.User, error) } } -func (o *GetOption) ListWorkspaces(ctx context.Context, userName string) ([]*dashv1alpha1.Workspace, error) { +func (o *GetOption) ListWorkspaces(ctx context.Context, userName string, includeShared bool) ([]*dashv1alpha1.Workspace, error) { if o.UseKubeAPI { - return o.listWorkspacesByKubeClient(ctx, userName) + return o.listWorkspacesByKubeClient(ctx, userName, includeShared) } else { - return o.listWorkspacesWithDashClient(ctx, userName) + return o.listWorkspacesWithDashClient(ctx, userName, includeShared) } } @@ -156,12 +161,14 @@ func (o *GetOption) listUsersWithDashClient(ctx context.Context) ([]*dashv1alpha return res.Msg.Items, nil } -func (o *GetOption) listWorkspacesWithDashClient(ctx context.Context, userName string) ([]*dashv1alpha1.Workspace, error) { +func (o *GetOption) listWorkspacesWithDashClient(ctx context.Context, userName string, includeShared bool) ([]*dashv1alpha1.Workspace, error) { req := &dashv1alpha1.GetWorkspacesRequest{ - UserName: userName, - WithRaw: ptr.To(o.OutputFormat == "yaml"), + UserName: userName, + WithRaw: ptr.To(o.OutputFormat == "yaml"), + IncludeShared: ptr.To(includeShared), } c := o.CosmoDashClient + o.Logr.DebugAll().Info("WorkspaceServiceClient.GetWorkspaces", "req", req) res, err := c.WorkspaceServiceClient.GetWorkspaces(ctx, cli.NewRequestWithToken(req, o.CliConfig)) if err != nil { return nil, fmt.Errorf("failed to connect dashboard server: %w", err) @@ -210,11 +217,15 @@ func (o *GetOption) OutputYAML(w io.Writer, objs []*dashv1alpha1.Workspace) { fmt.Fprintln(w, strings.Join(docs, "---\n")) } -func OutputTable(out io.Writer, workspaces []*dashv1alpha1.Workspace) { +func OutputTable(out io.Writer, username string, workspaces []*dashv1alpha1.Workspace) { data := [][]string{} for _, v := range workspaces { - data = append(data, []string{v.OwnerName, v.Name, v.Spec.Template, v.Status.Phase, v.Status.MainUrl}) + mainURL := v.Status.MainUrl + if username != "" && v.OwnerName != username { + mainURL = "[shared workspace. see shared URLs by `cosmoctl ws get-network`]" + } + data = append(data, []string{v.OwnerName, v.Name, v.Spec.Template, v.Status.Phase, mainURL}) } cli.OutputTable(out, @@ -222,15 +233,19 @@ func OutputTable(out io.Writer, workspaces []*dashv1alpha1.Workspace) { data) } -func OutputWideTable(out io.Writer, workspaces []*dashv1alpha1.Workspace) { +func OutputWideTable(out io.Writer, username string, workspaces []*dashv1alpha1.Workspace) { data := [][]string{} for _, v := range workspaces { + mainURL := v.Status.MainUrl + if v.OwnerName != username { + mainURL = `[shared workspace. see shared URLs by "cosmoctl ws get-network"]` + } vars := make([]string, 0, len(v.Spec.Vars)) for k, vv := range v.Spec.Vars { vars = append(vars, fmt.Sprintf("%s=%s", k, vv)) } - data = append(data, []string{v.OwnerName, v.Name, v.Spec.Template, strings.Join(vars, ","), v.Status.Phase, v.Status.MainUrl}) + data = append(data, []string{v.OwnerName, v.Name, v.Spec.Template, strings.Join(vars, ","), v.Status.Phase, mainURL}) } cli.OutputTable(out, @@ -238,9 +253,11 @@ func OutputWideTable(out io.Writer, workspaces []*dashv1alpha1.Workspace) { data) } -func (o *GetOption) listWorkspacesByKubeClient(ctx context.Context, userName string) ([]*dashv1alpha1.Workspace, error) { +func (o *GetOption) listWorkspacesByKubeClient(ctx context.Context, userName string, includeShared bool) ([]*dashv1alpha1.Workspace, error) { c := o.KosmoClient - workspaces, err := c.ListWorkspacesByUserName(ctx, userName) + workspaces, err := c.ListWorkspacesByUserName(ctx, userName, func(o *kosmo.ListWorkspacesOptions) { + o.IncludeShared = includeShared + }) if err != nil { return nil, err } diff --git a/internal/cmd/workspace/get_network.go b/internal/cmd/workspace/get_network.go index 430b1cea..77bbfc75 100644 --- a/internal/cmd/workspace/get_network.go +++ b/internal/cmd/workspace/get_network.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "strconv" + "strings" "time" "github.com/spf13/cobra" @@ -109,11 +110,11 @@ func (o *GetNetworkOption) OutputTable(w io.Writer, workspace *dashv1alpha1.Work data := [][]string{} for _, v := range workspace.Spec.Network { - data = append(data, []string{fmt.Sprintf("%d", v.PortNumber), v.CustomHostPrefix, v.HttpPath, strconv.FormatBool(v.Public), v.Url}) + data = append(data, []string{fmt.Sprintf("%d", v.PortNumber), v.CustomHostPrefix, v.HttpPath, strconv.FormatBool(v.Public), strings.Join(v.AllowedUsers, ","), v.Url}) } cli.OutputTable(w, - []string{"PORT", "CUSTOM_HOST_PREFIX", "HTTP_PATH", "PUBLIC", "URL"}, + []string{"PORT", "CUSTOM_HOST_PREFIX", "HTTP_PATH", "PUBLIC", "ALLOWED_USERS", "URL"}, data) } diff --git a/internal/cmd/workspace/update.go b/internal/cmd/workspace/update.go index bcb73c4b..0dd1f596 100644 --- a/internal/cmd/workspace/update.go +++ b/internal/cmd/workspace/update.go @@ -130,7 +130,7 @@ func (o *UpdateOption) RunE(cmd *cobra.Command, args []string) error { } fmt.Fprintln(cmd.OutOrStdout(), color.GreenString("Successfully updated workspace %s", o.WorkspaceName)) - OutputTable(cmd.OutOrStdout(), []*dashv1alpha1.Workspace{ws}) + OutputTable(cmd.OutOrStdout(), o.UserName, []*dashv1alpha1.Workspace{ws}) return nil } @@ -171,6 +171,7 @@ func (o *UpdateOption) GetWorkspaceWithDashClient(ctx context.Context) (*dashv1a UserName: o.UserName, } c := o.CosmoDashClient + o.Logr.DebugAll().Info("WorkspaceServiceClient.GetWorkspace", "req", req) res, err := c.WorkspaceServiceClient.GetWorkspace(ctx, cli.NewRequestWithToken(req, o.CliConfig)) if err != nil { return nil, fmt.Errorf("failed to connect dashboard server: %w", err) diff --git a/internal/cmd/workspace/upsert_network.go b/internal/cmd/workspace/upsert_network.go index 63641dae..b4ec3f9d 100644 --- a/internal/cmd/workspace/upsert_network.go +++ b/internal/cmd/workspace/upsert_network.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "strconv" + "strings" "time" "github.com/fatih/color" @@ -26,6 +27,7 @@ type UpsertNetworkOption struct { PortNumber int32 HTTPPath string Public bool + AllowedUsers []string rule cosmov1alpha1.NetworkRule } @@ -40,6 +42,7 @@ func UpsertNetworkCmd(cmd *cobra.Command, cliOpt *cli.RootOptions) *cobra.Comman cmd.Flags().StringVar(&o.CustomHostPrefix, "host-prefix", "", "custom host prefix") cmd.Flags().StringVar(&o.HTTPPath, "path", "/", "path for Ingress path when using ingress") cmd.Flags().BoolVar(&o.Public, "public", false, "disable authentication for this port") + cmd.Flags().StringSliceVarP(&o.AllowedUsers, "share-with", "s", []string{}, "allow user to access this network rule") return cmd } @@ -73,6 +76,7 @@ func (o *UpsertNetworkOption) Complete(cmd *cobra.Command, args []string) error PortNumber: o.PortNumber, HTTPPath: o.HTTPPath, Public: o.Public, + AllowedUsers: o.AllowedUsers, } o.rule.Default() @@ -160,9 +164,9 @@ func (o *UpsertNetworkOption) UpsertNetworkRuleByKubeClient(ctx context.Context) func (o *UpsertNetworkOption) OutputTable(w io.Writer, v *dashv1alpha1.NetworkRule) { data := [][]string{ - {fmt.Sprintf("%d", v.PortNumber), v.CustomHostPrefix, v.HttpPath, strconv.FormatBool(v.Public), v.Url}, + {fmt.Sprintf("%d", v.PortNumber), v.CustomHostPrefix, v.HttpPath, strconv.FormatBool(v.Public), strings.Join(v.AllowedUsers, ","), v.Url}, } cli.OutputTable(w, - []string{"PORT", "CUSTOM_HOST_PREFIX", "HTTP_PATH", "PUBLIC", "URL"}, + []string{"PORT", "CUSTOM_HOST_PREFIX", "HTTP_PATH", "PUBLIC", "SHARE", "URL"}, data) } diff --git a/internal/controllers/user_controller.go b/internal/controllers/user_controller.go index d8938b2a..7169abf1 100644 --- a/internal/controllers/user_controller.go +++ b/internal/controllers/user_controller.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "slices" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" @@ -168,6 +169,31 @@ func (r *UserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. err = addonErrs[0] } + log.Debug().Info("checking shared workspace garbage collection", "sharedWorkspaces", user.Status.SharedWorkspaces) + shouldRemoveSharedRef := make([]cosmov1alpha1.ObjectRef, 0) + for _, sharedRef := range user.Status.SharedWorkspaces { + var ws cosmov1alpha1.Workspace + err := r.Get(ctx, client.ObjectKey{Name: sharedRef.Name, Namespace: sharedRef.Namespace}, &ws) + if err != nil { + log.Debug().Info("remove shared workspace in user status", "workspace", sharedRef, "error", err) + shouldRemoveSharedRef = append(shouldRemoveSharedRef, sharedRef) + continue + } + if !slices.ContainsFunc(ws.Spec.Network, func(r cosmov1alpha1.NetworkRule) bool { + return slices.Contains(r.AllowedUsers, user.Name) + }) { + log.Debug().Info("remove shared workspace in user status", "workspace", sharedRef, "error", "not contained in allowed users") + shouldRemoveSharedRef = append(shouldRemoveSharedRef, sharedRef) + continue + } + } + if len(shouldRemoveSharedRef) > 0 { + log.Info("shared workspace removed from user status", "removed", shouldRemoveSharedRef) + user.Status.SharedWorkspaces = slices.DeleteFunc(user.Status.SharedWorkspaces, func(r cosmov1alpha1.ObjectRef) bool { + return slices.Contains(shouldRemoveSharedRef, r) + }) + } + // update user status if !equality.Semantic.DeepEqual(currentUser, &user) { log.Debug().PrintObjectDiff(currentUser, &user) @@ -178,7 +204,7 @@ func (r *UserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. } if user.Status.Phase != "AddonFailed" && !cosmov1alpha1.KeepResourceDeletePolicy(&user) { - log.Debug().Info("checking garbage collection") + log.Debug().Info("checking useraddon garbage collection") shouldDeletes := objectRefNotExistsInMap(lastAddons, currAddonsMap) for _, d := range shouldDeletes { if skip, err := prune(ctx, r.Client, d); err != nil { diff --git a/internal/controllers/workspace_controller.go b/internal/controllers/workspace_controller.go index 3cb2791a..50b9142b 100644 --- a/internal/controllers/workspace_controller.go +++ b/internal/controllers/workspace_controller.go @@ -3,6 +3,7 @@ package controllers import ( "context" "fmt" + "slices" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" @@ -142,6 +143,40 @@ func (r *WorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( log.Info("status updated") } + // update shared workspace user status + for _, netRule := range ws.Spec.Network { + for _, AllowedUsers := range netRule.AllowedUsers { + var user cosmov1alpha1.User + err := r.Get(ctx, client.ObjectKey{Name: AllowedUsers}, &user) + if err != nil { + kosmo.WorkspaceEventf(r.Recorder, &ws, corev1.EventTypeWarning, "UpdateAllowedUserStatusFailed", "Failed to update share user status: %v", err) + continue + } + + wsRef := cosmov1alpha1.ObjectRef{ + ObjectReference: corev1.ObjectReference{ + Name: ws.Name, + Namespace: ws.Namespace, + }, + } + if len(user.Status.SharedWorkspaces) > 0 { + if !slices.ContainsFunc(user.Status.SharedWorkspaces, func(v cosmov1alpha1.ObjectRef) bool { return v == wsRef }) { + user.Status.SharedWorkspaces = append(user.Status.SharedWorkspaces, wsRef) + } + } else { + user.Status.SharedWorkspaces = []cosmov1alpha1.ObjectRef{wsRef} + } + + if err := r.Status().Update(ctx, &user); err != nil { + if !apierrs.IsConflict(err) { + kosmo.UserEventf(r.Recorder, &user, corev1.EventTypeWarning, "UpdateUserStatusFailed", "Failed to update user status: %v", err) + } + return ctrl.Result{}, err + } + log.Info("user status updated", "user", user.Name, "add", wsRef, "result", user.Status.SharedWorkspaces) + } + } + log.Debug().Info("finish reconcile") return ctrl.Result{}, nil } diff --git a/internal/dashboard/workspace_handler.go b/internal/dashboard/workspace_handler.go index d2aebf62..7bbe5130 100644 --- a/internal/dashboard/workspace_handler.go +++ b/internal/dashboard/workspace_handler.go @@ -2,10 +2,14 @@ package dashboard import ( "context" + "fmt" "net/http" + "slices" connect_go "github.com/bufbuild/connect-go" + apierrs "k8s.io/apimachinery/pkg/api/errors" + cosmov1alpha1 "github.com/cosmo-workspace/cosmo/api/v1alpha1" "github.com/cosmo-workspace/cosmo/pkg/apiconv" "github.com/cosmo-workspace/cosmo/pkg/clog" "github.com/cosmo-workspace/cosmo/pkg/kosmo" @@ -51,7 +55,9 @@ func (s *Server) GetWorkspaces(ctx context.Context, req *connect_go.Request[dash return nil, ErrResponse(log, err) } - wss, err := s.Klient.ListWorkspacesByUserName(ctx, req.Msg.UserName) + wss, err := s.Klient.ListWorkspacesByUserName(ctx, req.Msg.UserName, func(opt *kosmo.ListWorkspacesOptions) { + opt.IncludeShared = req.Msg.IncludeShared != nil && *req.Msg.IncludeShared + }) if err != nil { return nil, ErrResponse(log, err) } @@ -69,7 +75,7 @@ func (s *Server) GetWorkspace(ctx context.Context, req *connect_go.Request[dashv log := clog.FromContext(ctx).WithCaller() log.Debug().Info("request", "req", req) - if err := userAuthentication(ctx, req.Msg.UserName); err != nil { + if err := s.sharedWorkspaceAuthorization(ctx, req.Msg.WsName, req.Msg.UserName, false); err != nil { return nil, ErrResponse(log, err) } @@ -110,7 +116,7 @@ func (s *Server) UpdateWorkspace(ctx context.Context, req *connect_go.Request[da log := clog.FromContext(ctx).WithCaller() log.Debug().Info("request", "req", req) - if err := userAuthentication(ctx, req.Msg.UserName); err != nil { + if err := s.sharedWorkspaceAuthorization(ctx, req.Msg.WsName, req.Msg.UserName, true); err != nil { return nil, ErrResponse(log, err) } @@ -129,3 +135,47 @@ func (s *Server) UpdateWorkspace(ctx context.Context, req *connect_go.Request[da log.Info(res.Message, "username", req.Msg.UserName, "workspaceName", req.Msg.WsName) return connect_go.NewResponse(res), nil } + +func (s *Server) sharedWorkspaceAuthorization(ctx context.Context, wsName, wsOwnerName string, update bool) error { + log := clog.FromContext(ctx).WithCaller() + + if err := userAuthentication(ctx, wsOwnerName); err == nil { + // pass if caller is the owner of the workspace + return nil + } + + caller := callerFromContext(ctx) + if !slices.ContainsFunc(caller.Status.SharedWorkspaces, func(sharedRef cosmov1alpha1.ObjectRef) bool { + return cosmov1alpha1.UserNameByNamespace(sharedRef.Namespace) == wsOwnerName + }) { + return NewForbidden(fmt.Errorf("invalid user authentication")) + } + + // only users who are allowed to access main rule can update workspace + ws, err := s.Klient.GetWorkspaceByUserName(ctx, wsName, wsOwnerName) + if err != nil { + return ErrResponse(log, err) + } + + if !slices.ContainsFunc(ws.Spec.Network, func(r cosmov1alpha1.NetworkRule) bool { + return slices.Contains(r.AllowedUsers, caller.Name) + }) { + return NewForbidden(fmt.Errorf("invalid user authentication")) + } + + if update { + mainRuleIndex := slices.IndexFunc(ws.Spec.Network, func(n cosmov1alpha1.NetworkRule) bool { + return n.CustomHostPrefix == ws.Status.Config.ServiceMainPortName + }) + if mainRuleIndex < 0 { + return ErrResponse(log, ErrResponse(log, apierrs.NewInternalError(fmt.Errorf("main rule not found")))) + } + + // check caller is allowed to access main rule + if !slices.Contains(ws.Spec.Network[mainRuleIndex].AllowedUsers, caller.Name) { + return NewForbidden(fmt.Errorf("invalid user authentication")) + } + } + + return nil +} diff --git a/internal/dashboard/workspace_network_handler.go b/internal/dashboard/workspace_network_handler.go index ac2a685a..bd344930 100644 --- a/internal/dashboard/workspace_network_handler.go +++ b/internal/dashboard/workspace_network_handler.go @@ -15,7 +15,7 @@ func (s *Server) UpsertNetworkRule(ctx context.Context, req *connect_go.Request[ log := clog.FromContext(ctx).WithCaller() log.Debug().Info("request", "req", req) - if err := userAuthentication(ctx, req.Msg.UserName); err != nil { + if err := s.sharedWorkspaceAuthorization(ctx, req.Msg.WsName, req.Msg.UserName, true); err != nil { return nil, ErrResponse(log, err) } @@ -26,6 +26,7 @@ func (s *Server) UpsertNetworkRule(ctx context.Context, req *connect_go.Request[ CustomHostPrefix: m.NetworkRule.CustomHostPrefix, HTTPPath: m.NetworkRule.HttpPath, Public: m.NetworkRule.Public, + AllowedUsers: m.NetworkRule.AllowedUsers, } netRule, err := s.Klient.AddNetworkRule(ctx, m.WsName, m.UserName, r, int(m.Index)) @@ -44,7 +45,7 @@ func (s *Server) DeleteNetworkRule(ctx context.Context, req *connect_go.Request[ log := clog.FromContext(ctx).WithCaller() log.Debug().Info("request", "req", req) - if err := userAuthentication(ctx, req.Msg.UserName); err != nil { + if err := s.sharedWorkspaceAuthorization(ctx, req.Msg.WsName, req.Msg.UserName, true); err != nil { return nil, ErrResponse(log, err) } diff --git a/pkg/apiconv/workspace.go b/pkg/apiconv/workspace.go index 7d5b35ef..1ebf3854 100644 --- a/pkg/apiconv/workspace.go +++ b/pkg/apiconv/workspace.go @@ -77,6 +77,7 @@ func C2D_NetworkRule(v cosmov1alpha1.NetworkRule) *dashv1alpha1.NetworkRule { CustomHostPrefix: v.CustomHostPrefix, HttpPath: v.HTTPPath, Public: v.Public, + AllowedUsers: v.AllowedUsers, } } @@ -95,6 +96,7 @@ func D2C_NetworkRule(v *dashv1alpha1.NetworkRule) cosmov1alpha1.NetworkRule { CustomHostPrefix: v.CustomHostPrefix, HTTPPath: v.HttpPath, Public: v.Public, + AllowedUsers: v.AllowedUsers, } r.Default() return r diff --git a/pkg/apiconv/workspace_test.go b/pkg/apiconv/workspace_test.go index 895bd5e2..3e045145 100644 --- a/pkg/apiconv/workspace_test.go +++ b/pkg/apiconv/workspace_test.go @@ -186,9 +186,10 @@ func TestC2D_Workspace(t *testing.T) { Public: true, }, { - Protocol: "http", - PortNumber: 8443, - HTTPPath: "/", + Protocol: "http", + PortNumber: 8443, + HTTPPath: "/", + AllowedUsers: []string{"share1"}, }, }, }, @@ -223,9 +224,10 @@ func TestC2D_Workspace(t *testing.T) { Url: "https://xxx.example.com/path", }, { - PortNumber: 8443, - HttpPath: "/", - Url: "https://port8443.example.com/", + PortNumber: 8443, + HttpPath: "/", + Url: "https://port8443.example.com/", + AllowedUsers: []string{"share1"}, }, }, }, diff --git a/pkg/kosmo/workspace.go b/pkg/kosmo/workspace.go index 35aa524f..c683a67f 100644 --- a/pkg/kosmo/workspace.go +++ b/pkg/kosmo/workspace.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "slices" "sort" "time" @@ -46,26 +47,51 @@ func (c *Client) GetWorkspace(ctx context.Context, name, namespace string) (*cos return &ws, nil } -func (c *Client) ListWorkspacesByUserName(ctx context.Context, username string) ([]cosmov1alpha1.Workspace, error) { - return c.ListWorkspaces(ctx, cosmov1alpha1.UserNamespace(username)) +type ListWorkspacesOptions struct { + IncludeShared bool } -func (c *Client) ListWorkspaces(ctx context.Context, namespace string) ([]cosmov1alpha1.Workspace, error) { +func (c *Client) ListWorkspacesByUserName(ctx context.Context, username string, optFunc ...func(*ListWorkspacesOptions)) ([]cosmov1alpha1.Workspace, error) { log := clog.FromContext(ctx).WithCaller() - if _, err := c.GetUser(ctx, cosmov1alpha1.UserNameByNamespace(namespace)); err != nil { + var o ListWorkspacesOptions + for _, f := range optFunc { + f(&o) + } + + user, err := c.GetUser(ctx, username) + if err != nil { return nil, err } + // list owned workspaces wsList := cosmov1alpha1.WorkspaceList{} - opts := &client.ListOptions{Namespace: namespace} + opts := &client.ListOptions{Namespace: cosmov1alpha1.UserNamespace(username)} if err := c.List(ctx, &wsList, opts); err != nil { - log.Error(err, "failed to list workspaces", "namespace", namespace) + log.Error(err, "failed to list workspaces", "namespace", cosmov1alpha1.UserNamespace(username)) return nil, fmt.Errorf("failed to list workspaces: %w", err) } sort.Slice(wsList.Items, func(i, j int) bool { return wsList.Items[i].Name < wsList.Items[j].Name }) + // list shared workspaces + if o.IncludeShared { + for _, sharedRef := range user.Status.SharedWorkspaces { + ws := cosmov1alpha1.Workspace{} + + err := c.Get(ctx, client.ObjectKey{Name: sharedRef.Name, Namespace: sharedRef.Namespace}, &ws) + if err != nil { + log.Error(err, "failed to get shared workspace", "name", sharedRef.Name, "namespace", sharedRef.Namespace) + } else { + if slices.ContainsFunc(ws.Spec.Network, func(r cosmov1alpha1.NetworkRule) bool { + return slices.Contains(r.AllowedUsers, user.Name) + }) { + wsList.Items = append(wsList.Items, ws) + } + } + } + } + return wsList.Items, nil } diff --git a/pkg/workspace/traefik_ingressroute.go b/pkg/workspace/traefik_ingressroute.go index bfe22d76..555d00c5 100644 --- a/pkg/workspace/traefik_ingressroute.go +++ b/pkg/workspace/traefik_ingressroute.go @@ -68,11 +68,18 @@ func (c *TraefikIngressRouteConfig) TraefikRoute(r cosmov1alpha1.NetworkRule, ws } match := strings.Join(matches[:], " && ") - var middlewares []traefikv1.MiddlewareRef - if r.Public { - middlewares = []traefikv1.MiddlewareRef{} - } else { - middlewares = []traefikv1.MiddlewareRef{c.UserNameHeaderMiddleware, c.AuthenMiddleware} + middlewares := make([]traefikv1.MiddlewareRef, 0) + if !r.Public { + // at first apply allowed user's middlewares to set the header X-Cosmo-UserName-${User} + for _, allowedUser := range r.AllowedUsers { + ref := traefikv1.MiddlewareRef{ + Name: c.UserNameHeaderMiddleware.Name, + Namespace: cosmov1alpha1.UserNamespace(allowedUser), + } + middlewares = append(middlewares, ref) + } + // at last apply owner's middleware to override the header X-Cosmo-UserName. + middlewares = append(middlewares, c.UserNameHeaderMiddleware, c.AuthenMiddleware) } backendSvcName := instance.InstanceResourceName(ws.Name, ws.Status.Config.ServiceName) diff --git a/proto/gen/dashboard/v1alpha1/workspace.pb.go b/proto/gen/dashboard/v1alpha1/workspace.pb.go index a071e6ea..fe06ce46 100644 --- a/proto/gen/dashboard/v1alpha1/workspace.pb.go +++ b/proto/gen/dashboard/v1alpha1/workspace.pb.go @@ -31,11 +31,12 @@ type NetworkRule struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - PortNumber int32 `protobuf:"varint,1,opt,name=port_number,json=portNumber,proto3" json:"port_number,omitempty"` - CustomHostPrefix string `protobuf:"bytes,2,opt,name=custom_host_prefix,json=customHostPrefix,proto3" json:"custom_host_prefix,omitempty"` - HttpPath string `protobuf:"bytes,3,opt,name=http_path,json=httpPath,proto3" json:"http_path,omitempty"` - Url string `protobuf:"bytes,4,opt,name=url,proto3" json:"url,omitempty"` - Public bool `protobuf:"varint,5,opt,name=public,proto3" json:"public,omitempty"` + PortNumber int32 `protobuf:"varint,1,opt,name=port_number,json=portNumber,proto3" json:"port_number,omitempty"` + CustomHostPrefix string `protobuf:"bytes,2,opt,name=custom_host_prefix,json=customHostPrefix,proto3" json:"custom_host_prefix,omitempty"` + HttpPath string `protobuf:"bytes,3,opt,name=http_path,json=httpPath,proto3" json:"http_path,omitempty"` + Url string `protobuf:"bytes,4,opt,name=url,proto3" json:"url,omitempty"` + Public bool `protobuf:"varint,5,opt,name=public,proto3" json:"public,omitempty"` + AllowedUsers []string `protobuf:"bytes,6,rep,name=allowed_users,json=allowedUsers,proto3" json:"allowed_users,omitempty"` } func (x *NetworkRule) Reset() { @@ -105,6 +106,13 @@ func (x *NetworkRule) GetPublic() bool { return false } +func (x *NetworkRule) GetAllowedUsers() []string { + if x != nil { + return x.AllowedUsers + } + return nil +} + type WorkspaceSpec struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -183,7 +191,7 @@ type WorkspaceStatus struct { Phase string `protobuf:"bytes,1,opt,name=phase,proto3" json:"phase,omitempty"` MainUrl string `protobuf:"bytes,2,opt,name=main_url,json=mainUrl,proto3" json:"main_url,omitempty"` - LastStartedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=lastStartedAt,proto3" json:"lastStartedAt,omitempty"` + LastStartedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=last_started_at,json=lastStartedAt,proto3" json:"last_started_at,omitempty"` } func (x *WorkspaceStatus) Reset() { @@ -328,7 +336,7 @@ var file_dashboard_v1alpha1_workspace_proto_rawDesc = []byte{ 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x22, 0xb0, 0x01, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, + 0x74, 0x6f, 0x22, 0xd5, 0x01, 0x0a, 0x0b, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x2c, 0x0a, 0x0b, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0b, 0xfa, 0x42, 0x08, 0x1a, 0x06, 0x10, 0x80, 0x80, 0x04, 0x20, 0x00, 0x52, 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, @@ -339,59 +347,62 @@ var file_dashboard_v1alpha1_workspace_proto_rawDesc = []byte{ 0x09, 0x52, 0x08, 0x68, 0x74, 0x74, 0x70, 0x50, 0x61, 0x74, 0x68, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x70, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x22, 0xfc, 0x01, 0x0a, 0x0d, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x73, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x73, 0x12, - 0x3f, 0x0a, 0x04, 0x76, 0x61, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, - 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, - 0x2e, 0x56, 0x61, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x76, 0x61, 0x72, 0x73, - 0x12, 0x39, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1f, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, - 0x6c, 0x65, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x1a, 0x37, 0x0a, 0x09, 0x56, - 0x61, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x22, 0x84, 0x01, 0x0a, 0x0f, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x68, 0x61, 0x73, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x12, 0x19, - 0x0a, 0x08, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x6d, 0x61, 0x69, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x40, 0x0a, 0x0d, 0x6c, 0x61, 0x73, - 0x74, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x6c, 0x61, - 0x73, 0x74, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0xd1, 0x01, 0x0a, 0x09, - 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, - 0x0a, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x04, - 0x73, 0x70, 0x65, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x64, 0x61, 0x73, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, + 0x5f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x6c, + 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x55, 0x73, 0x65, 0x72, 0x73, 0x22, 0xfc, 0x01, 0x0a, 0x0d, 0x57, + 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x1a, 0x0a, 0x08, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x73, 0x12, 0x3f, 0x0a, 0x04, 0x76, 0x61, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x56, 0x61, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x04, 0x76, 0x61, 0x72, 0x73, 0x12, 0x39, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, + 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, + 0x1a, 0x37, 0x0a, 0x09, 0x56, 0x61, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x86, 0x01, 0x0a, 0x0f, 0x57, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, + 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x68, + 0x61, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x61, 0x69, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x42, + 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x22, 0xd1, 0x01, 0x0a, 0x09, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x21, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x3b, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, - 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, - 0x70, 0x65, 0x63, 0x12, 0x3b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x15, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, - 0x03, 0x72, 0x61, 0x77, 0x88, 0x01, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x72, 0x61, 0x77, 0x42, - 0xe2, 0x01, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, - 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x0e, 0x57, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x4f, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x2d, 0x77, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, - 0x72, 0x64, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x64, 0x61, 0x73, 0x68, - 0x62, 0x6f, 0x61, 0x72, 0x64, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, - 0x44, 0x58, 0x58, 0xaa, 0x02, 0x12, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, - 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x12, 0x44, 0x61, 0x73, 0x68, 0x62, - 0x6f, 0x61, 0x72, 0x64, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x1e, - 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, - 0x13, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x72, 0x61, 0x77, 0x88, 0x01, 0x01, 0x42, 0x06, + 0x0a, 0x04, 0x5f, 0x72, 0x61, 0x77, 0x42, 0xe2, 0x01, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x64, + 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x42, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x50, 0x01, 0x5a, 0x4f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x2d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, + 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, + 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x3b, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, 0x02, 0x12, 0x44, 0x61, 0x73, + 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, + 0x02, 0x12, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5c, 0x56, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x1e, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x13, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, + 0x64, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -418,7 +429,7 @@ var file_dashboard_v1alpha1_workspace_proto_goTypes = []interface{}{ var file_dashboard_v1alpha1_workspace_proto_depIdxs = []int32{ 4, // 0: dashboard.v1alpha1.WorkspaceSpec.vars:type_name -> dashboard.v1alpha1.WorkspaceSpec.VarsEntry 0, // 1: dashboard.v1alpha1.WorkspaceSpec.network:type_name -> dashboard.v1alpha1.NetworkRule - 5, // 2: dashboard.v1alpha1.WorkspaceStatus.lastStartedAt:type_name -> google.protobuf.Timestamp + 5, // 2: dashboard.v1alpha1.WorkspaceStatus.last_started_at:type_name -> google.protobuf.Timestamp 1, // 3: dashboard.v1alpha1.Workspace.spec:type_name -> dashboard.v1alpha1.WorkspaceSpec 2, // 4: dashboard.v1alpha1.Workspace.status:type_name -> dashboard.v1alpha1.WorkspaceStatus 5, // [5:5] is the sub-list for method output_type diff --git a/proto/gen/dashboard/v1alpha1/workspace_service.pb.go b/proto/gen/dashboard/v1alpha1/workspace_service.pb.go index a754aed3..760bb3be 100644 --- a/proto/gen/dashboard/v1alpha1/workspace_service.pb.go +++ b/proto/gen/dashboard/v1alpha1/workspace_service.pb.go @@ -502,8 +502,9 @@ type GetWorkspacesRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - UserName string `protobuf:"bytes,1,opt,name=user_name,json=userName,proto3" json:"user_name,omitempty"` - WithRaw *bool `protobuf:"varint,2,opt,name=with_raw,json=withRaw,proto3,oneof" json:"with_raw,omitempty"` + UserName string `protobuf:"bytes,1,opt,name=user_name,json=userName,proto3" json:"user_name,omitempty"` + WithRaw *bool `protobuf:"varint,2,opt,name=with_raw,json=withRaw,proto3,oneof" json:"with_raw,omitempty"` + IncludeShared *bool `protobuf:"varint,3,opt,name=includeShared,proto3,oneof" json:"includeShared,omitempty"` } func (x *GetWorkspacesRequest) Reset() { @@ -552,6 +553,13 @@ func (x *GetWorkspacesRequest) GetWithRaw() bool { return false } +func (x *GetWorkspacesRequest) GetIncludeShared() bool { + if x != nil && x.IncludeShared != nil { + return *x.IncludeShared + } + return false +} + type GetWorkspacesResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -937,118 +945,122 @@ var file_dashboard_v1alpha1_workspace_service_proto_rawDesc = []byte{ 0x3b, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x52, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x60, 0x0a, 0x14, - 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x1e, 0x0a, 0x08, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x72, 0x61, 0x77, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x07, 0x77, 0x69, 0x74, 0x68, 0x52, 0x61, 0x77, 0x88, 0x01, - 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x72, 0x61, 0x77, 0x22, 0x66, - 0x0a, 0x15, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x12, 0x33, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1d, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, - 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xbc, 0x01, 0x0a, 0x18, 0x55, 0x70, 0x73, 0x65, 0x72, - 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, - 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x07, 0x77, 0x73, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, - 0x02, 0x10, 0x01, 0x52, 0x06, 0x77, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x42, 0x0a, 0x0c, 0x6e, - 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1f, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, - 0x6c, 0x65, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, - 0x69, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x79, 0x0a, 0x19, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4e, - 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x42, 0x0a, 0x0c, - 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, - 0x75, 0x6c, 0x65, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, - 0x22, 0x78, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, - 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x09, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x07, 0x77, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x77, 0x73, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x70, 0x0a, 0x17, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, + 0x65, 0x52, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x9d, 0x01, 0x0a, + 0x14, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x08, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x72, 0x61, 0x77, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x07, 0x77, 0x69, 0x74, 0x68, 0x52, 0x61, 0x77, 0x88, + 0x01, 0x01, 0x12, 0x29, 0x0a, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x53, 0x68, 0x61, + 0x72, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x01, 0x52, 0x0d, 0x69, 0x6e, 0x63, + 0x6c, 0x75, 0x64, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, + 0x09, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x72, 0x61, 0x77, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x69, + 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x22, 0x66, 0x0a, 0x15, + 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, - 0x3b, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x52, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x32, 0x83, 0x06, 0x0a, - 0x10, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x6a, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x12, 0x2a, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x2b, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, - 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x12, 0x2a, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x64, - 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x0f, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x2a, 0x2e, 0x64, - 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, - 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x27, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, - 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x6f, - 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, + 0x33, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x57, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x28, 0x2e, 0x64, 0x61, 0x73, 0x68, - 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, + 0x68, 0x61, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x05, 0x69, + 0x74, 0x65, 0x6d, 0x73, 0x22, 0xbc, 0x01, 0x0a, 0x18, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x24, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x08, 0x75, + 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x07, 0x77, 0x73, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, + 0x01, 0x52, 0x06, 0x77, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x42, 0x0a, 0x0c, 0x6e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1f, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, + 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x22, 0x79, 0x0a, 0x19, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x42, 0x0a, 0x0c, 0x6e, 0x65, + 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1f, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, + 0x65, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x22, 0x78, + 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, + 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x09, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, + 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x20, 0x0a, 0x07, 0x77, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x77, 0x73, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x70, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x3b, 0x0a, + 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1d, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, + 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x32, 0x83, 0x06, 0x0a, 0x10, 0x57, + 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, + 0x6a, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x12, 0x2a, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, + 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x0f, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x2a, + 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x64, 0x61, 0x73, + 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x2a, 0x2e, 0x64, 0x61, 0x73, + 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, + 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x12, 0x27, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, - 0x0a, 0x11, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x64, + 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x28, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, + 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x29, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, 0x0a, 0x11, + 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, + 0x65, 0x12, 0x2c, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2d, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, + 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x70, + 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x2c, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4e, 0x65, 0x74, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x70, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, - 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x2c, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, - 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, - 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x42, 0xe9, 0x01, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, - 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x15, 0x57, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x4f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x2d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, - 0x65, 0x6e, 0x2f, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2f, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, 0x02, 0x12, - 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0xca, 0x02, 0x12, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5c, 0x56, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x1e, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, - 0x61, 0x72, 0x64, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x13, 0x44, 0x61, 0x73, 0x68, 0x62, - 0x6f, 0x61, 0x72, 0x64, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x42, 0xe9, 0x01, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, + 0x72, 0x64, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x15, 0x57, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x4f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x2d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x65, 0x6e, + 0x2f, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x3b, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, 0x02, 0x12, 0x44, 0x61, + 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0xca, 0x02, 0x12, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5c, 0x56, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x1e, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, + 0x64, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x13, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, + 0x72, 0x64, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/gen/dashboard/v1alpha1/workspace_service.pb.validate.go b/proto/gen/dashboard/v1alpha1/workspace_service.pb.validate.go index e89f6d6f..08888d13 100644 --- a/proto/gen/dashboard/v1alpha1/workspace_service.pb.validate.go +++ b/proto/gen/dashboard/v1alpha1/workspace_service.pb.validate.go @@ -1094,6 +1094,10 @@ func (m *GetWorkspacesRequest) validate(all bool) error { // no validation rules for WithRaw } + if m.IncludeShared != nil { + // no validation rules for IncludeShared + } + if len(errors) > 0 { return GetWorkspacesRequestMultiError(errors) } diff --git a/proto/gen/index.md b/proto/gen/index.md index 9b4be009..cbde62f4 100644 --- a/proto/gen/index.md +++ b/proto/gen/index.md @@ -1126,6 +1126,7 @@ | http_path | [string](#string) | | | | url | [string](#string) | | | | public | [bool](#bool) | | | +| allowed_users | [string](#string) | repeated | | @@ -1195,7 +1196,7 @@ | ----- | ---- | ----- | ----------- | | phase | [string](#string) | | | | main_url | [string](#string) | | | -| lastStartedAt | [google.protobuf.Timestamp](#google-protobuf-Timestamp) | | | +| last_started_at | [google.protobuf.Timestamp](#google-protobuf-Timestamp) | | | @@ -1375,6 +1376,7 @@ | ----- | ---- | ----- | ----------- | | user_name | [string](#string) | | | | with_raw | [bool](#bool) | optional | | +| includeShared | [bool](#bool) | optional | | diff --git a/proto/proto/dashboard-apis/dashboard/v1alpha1/workspace.proto b/proto/proto/dashboard-apis/dashboard/v1alpha1/workspace.proto index 3dc13b25..6cf23428 100644 --- a/proto/proto/dashboard-apis/dashboard/v1alpha1/workspace.proto +++ b/proto/proto/dashboard-apis/dashboard/v1alpha1/workspace.proto @@ -15,6 +15,7 @@ message NetworkRule { string http_path = 3; string url = 4; bool public = 5; + repeated string allowed_users = 6; } message WorkspaceSpec { @@ -27,7 +28,7 @@ message WorkspaceSpec { message WorkspaceStatus { string phase = 1; string main_url = 2; - google.protobuf.Timestamp lastStartedAt = 3; + google.protobuf.Timestamp last_started_at = 3; } message Workspace { diff --git a/proto/proto/dashboard-apis/dashboard/v1alpha1/workspace_service.proto b/proto/proto/dashboard-apis/dashboard/v1alpha1/workspace_service.proto index 0d9edd84..d2d960a6 100644 --- a/proto/proto/dashboard-apis/dashboard/v1alpha1/workspace_service.proto +++ b/proto/proto/dashboard-apis/dashboard/v1alpha1/workspace_service.proto @@ -76,6 +76,7 @@ message GetWorkspaceResponse { message GetWorkspacesRequest { string user_name = 1; optional bool with_raw = 2; + optional bool includeShared = 3; } message GetWorkspacesResponse { diff --git a/traefik-plugins/src/github.com/cosmo-workspace/cosmoauth/cosmoauth_middleware.go b/traefik-plugins/src/github.com/cosmo-workspace/cosmoauth/cosmoauth_middleware.go index e6762981..1729c570 100644 --- a/traefik-plugins/src/github.com/cosmo-workspace/cosmoauth/cosmoauth_middleware.go +++ b/traefik-plugins/src/github.com/cosmo-workspace/cosmoauth/cosmoauth_middleware.go @@ -77,6 +77,7 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h } func (p *CosmoAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) { + LoggerINFO.Printf("headers: %v", r.Header) // Bypass manifest.json not to check session. By default, manifest.json is requested without cookie. // https://developer.mozilla.org/en-US/docs/Web/Manifest @@ -91,7 +92,7 @@ func (p *CosmoAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } if ses.IsNew { - LoggerINFO.Println("not authorized") + LoggerDEBUG.Println("not authorized") p.redirectToLoginPage(w, r) return } @@ -102,9 +103,26 @@ func (p *CosmoAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) { // check user name is owner's userName := r.Header.Get("X-Cosmo-UserName") if userName != "" && sesInfo.UserName != userName { - LoggerINFO.Print("forbidden", " storedUserName=", sesInfo.UserName, " ownerName=", userName) - p.forbidden(w, r) - return + + // check workspace is shared + allowed := false + for header := range r.Header { + shareUser, found := strings.CutPrefix(strings.ToLower(header), strings.ToLower("X-Cosmo-UserName-")) + if found { + LoggerDEBUG.Println("X-Cosmo-UserName-header", " ownerName=", userName, " shareUser=", shareUser) + + if sesInfo.UserName == shareUser { + LoggerINFO.Println("shared workspace", " ownerName=", userName, " shareUser=", shareUser) + allowed = true + break + } + } + } + if !allowed { + LoggerINFO.Print("forbidden", " storedUserName=", sesInfo.UserName, " ownerName=", userName) + p.forbidden(w, r) + return + } } // set deadline on request if enabled diff --git a/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/workspace_pb.ts b/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/workspace_pb.ts index a57af150..744d04a5 100644 --- a/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/workspace_pb.ts +++ b/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/workspace_pb.ts @@ -39,6 +39,11 @@ export class NetworkRule extends Message { */ public = false; + /** + * @generated from field: repeated string allowed_users = 6; + */ + allowedUsers: string[] = []; + constructor(data?: PartialMessage) { super(); proto3.util.initPartial(data, this); @@ -52,6 +57,7 @@ export class NetworkRule extends Message { { no: 3, name: "http_path", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 4, name: "url", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 5, name: "public", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, + { no: 6, name: "allowed_users", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): NetworkRule { @@ -141,7 +147,7 @@ export class WorkspaceStatus extends Message { mainUrl = ""; /** - * @generated from field: google.protobuf.Timestamp lastStartedAt = 3; + * @generated from field: google.protobuf.Timestamp last_started_at = 3; */ lastStartedAt?: Timestamp; @@ -155,7 +161,7 @@ export class WorkspaceStatus extends Message { static readonly fields: FieldList = proto3.util.newFieldList(() => [ { no: 1, name: "phase", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "main_url", kind: "scalar", T: 9 /* ScalarType.STRING */ }, - { no: 3, name: "lastStartedAt", kind: "message", T: Timestamp }, + { no: 3, name: "last_started_at", kind: "message", T: Timestamp }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): WorkspaceStatus { diff --git a/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/workspace_service_pb.ts b/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/workspace_service_pb.ts index 82b8e3a8..0164dd2a 100644 --- a/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/workspace_service_pb.ts +++ b/web/dashboard-ui/src/proto/gen/dashboard/v1alpha1/workspace_service_pb.ts @@ -393,6 +393,11 @@ export class GetWorkspacesRequest extends Message { */ withRaw?: boolean; + /** + * @generated from field: optional bool includeShared = 3; + */ + includeShared?: boolean; + constructor(data?: PartialMessage) { super(); proto3.util.initPartial(data, this); @@ -403,6 +408,7 @@ export class GetWorkspacesRequest extends Message { static readonly fields: FieldList = proto3.util.newFieldList(() => [ { no: 1, name: "user_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "with_raw", kind: "scalar", T: 8 /* ScalarType.BOOL */, opt: true }, + { no: 3, name: "includeShared", kind: "scalar", T: 8 /* ScalarType.BOOL */, opt: true }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): GetWorkspacesRequest { diff --git a/web/dashboard-ui/src/views/organisms/NetworkRuleActionDialog.tsx b/web/dashboard-ui/src/views/organisms/NetworkRuleActionDialog.tsx index 9a0d6f1a..d0a765be 100644 --- a/web/dashboard-ui/src/views/organisms/NetworkRuleActionDialog.tsx +++ b/web/dashboard-ui/src/views/organisms/NetworkRuleActionDialog.tsx @@ -1,23 +1,32 @@ -import { Close, ExpandLess, ExpandMore } from "@mui/icons-material"; +import { Add, Close, ExpandLess, ExpandMore } from "@mui/icons-material"; import { Alert, Box, Button, - Checkbox, + Chip, Collapse, Dialog, DialogActions, DialogContent, DialogTitle, Divider, - FormControlLabel, + FormControl, + Grid, IconButton, + InputLabel, + MenuItem, + Select, Stack, TextField, Typography, } from "@mui/material"; import { useState } from "react"; -import { Controller, UseFormRegisterReturn, useForm } from "react-hook-form"; +import { + Controller, + UseFormRegisterReturn, + useFieldArray, + useForm, +} from "react-hook-form"; import { DialogContext } from "../../components/ContextProvider"; import { NetworkRule, @@ -49,16 +58,46 @@ export const NetworkRuleUpsertDialog: React.VFC<{ defaultOpenHttpOptions, isMain, }) => { - console.log("NetworkRuleUpsertDialog", networkRule); + console.log("NetworkRuleUpsertDialog", { + ...networkRule, + inputAccessMode: networkRule?.public ? "public" : "private", + inputAllowedUsers: + networkRule?.allowedUsers?.map((v) => ({ name: v })) || [], + }); const networkRuleModule = useNetworkRule(); + + type NetworkRuleFormData = NetworkRule & { + inputAllowedUsers: { name: string }[]; + inputAllowedUser: string; + inputAccessMode: string; + }; + const { register, handleSubmit, - setValue, + getValues, control, + setValue, formState: { errors }, - } = useForm({ - defaultValues: networkRule || { portNumber: 8080, httpPath: "/" }, + } = useForm({ + defaultValues: networkRule + ? { + ...networkRule, + inputAccessMode: networkRule?.public ? "public" : "private", + inputAllowedUsers: + networkRule?.allowedUsers?.map((v) => ({ name: v })) || [], + } + : { + portNumber: 8080, + httpPath: "/", + inputAccessMode: "private", + inputAllowedUsers: [], + inputAllowedUser: "", + }, + }); + const { fields, append, remove } = useFieldArray({ + control, + name: "inputAllowedUsers", }); const [openHttpOptions, setOpenHttpOptions] = useState( @@ -69,15 +108,25 @@ export const NetworkRuleUpsertDialog: React.VFC<{ setOpenHttpOptions(!openHttpOptions); }; - const upsertRule = (newRule: NetworkRule) => { + const upsertRule = (newRule: NetworkRuleFormData) => { if (!(newRule.httpPath || "").startsWith("/")) { newRule.httpPath = "/" + newRule.httpPath; } + newRule.public = newRule.inputAccessMode == "public" ? true : false; + newRule.allowedUsers = !newRule.public + ? newRule.inputAllowedUsers.map((v) => v.name) + : []; + networkRuleModule .upsertNetwork(workspace, newRule, index) .then(() => onClose()); }; + const [openAllowedUsers, setOpenAllowedUsers] = useState( + networkRule?.public ? false : true + ); + const [openInputAllowedUser, setOpenInputAllowedUser] = useState(false); + return ( @@ -182,29 +231,136 @@ export const NetworkRuleUpsertDialog: React.VFC<{ )} - ( - - )} - /> - } - label={ - <> + + Access Mode + ( + + )} + /> + {/* */} + + {openAllowedUsers && ( + + + + Allowed users + + + setOpenInputAllowedUser(!openInputAllowedUser) + } + > + {openInputAllowedUser ? : } + + + {openInputAllowedUser && ( + { + if (!getValues(`inputAllowedUser`)) return; + if ( + fields + .map((v) => v.name) + .includes(getValues(`inputAllowedUser`)) + ) + return; + append({ name: getValues(`inputAllowedUser`) }); + setValue(`inputAllowedUser`, ""); + }} + > + + + ), + }} + /> + )} + + + {fields.map((v, i) => ( + + remove(i)} + /> + + ))} + + + + )} + {isMain && ( + + Users who can access a main URL can update this workspace + + )}