mirror of
				https://github.com/1Panel-dev/KubePi.git
				synced 2025-10-31 10:36:27 +08:00 
			
		
		
		
	feat(rbac): 集群级别rbac重构
This commit is contained in:
		| @@ -6,12 +6,14 @@ import ( | |||||||
| 	"github.com/KubeOperator/ekko/pkg/kubernetes" | 	"github.com/KubeOperator/ekko/pkg/kubernetes" | ||||||
| 	"github.com/kataras/iris/v12" | 	"github.com/kataras/iris/v12" | ||||||
| 	"github.com/kataras/iris/v12/context" | 	"github.com/kataras/iris/v12/context" | ||||||
|  | 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
|  | 	"strings" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (h *Handler) ListApiGroups() iris.Handler { | func (h *Handler) ListApiGroups() iris.Handler { | ||||||
| 	return func(ctx *context.Context) { | 	return func(ctx *context.Context) { | ||||||
| 		name := ctx.Params().GetString("name") | 		name := ctx.Params().GetString("name") | ||||||
| 		c, err := h.clusterService.Get(name,common.DBOptions{}) | 		c, err := h.clusterService.Get(name, common.DBOptions{}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| 			ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) | 			ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) | ||||||
| @@ -37,8 +39,8 @@ func (h *Handler) ListApiGroups() iris.Handler { | |||||||
| func (h *Handler) ListApiGroupResources() iris.Handler { | func (h *Handler) ListApiGroupResources() iris.Handler { | ||||||
| 	return func(ctx *context.Context) { | 	return func(ctx *context.Context) { | ||||||
| 		name := ctx.Params().GetString("name") | 		name := ctx.Params().GetString("name") | ||||||
| 		groupVersion := ctx.Params().GetString("group") | 		groupName := ctx.Params().GetString("group") | ||||||
| 		c, err := h.clusterService.Get(name,common.DBOptions{}) | 		c, err := h.clusterService.Get(name, common.DBOptions{}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| 			ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) | 			ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) | ||||||
| @@ -51,13 +53,39 @@ func (h *Handler) ListApiGroupResources() iris.Handler { | |||||||
| 			ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) | 			ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		apiResources, err := client.ServerResourcesForGroupVersion(groupVersion) | 		gss, rss, err := client.ServerGroupsAndResources() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| 			ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) | 			ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		ctx.Values().Set("data", apiResources.APIResources) | 		var groupVersions []v1.GroupVersionForDiscovery | ||||||
|  | 		resourceSet := map[string]struct{}{} | ||||||
|  |  | ||||||
|  | 		if groupName == "core" { | ||||||
|  | 			groupName = "" | ||||||
|  | 		} | ||||||
|  | 		for i := range gss { | ||||||
|  | 			if gss[i].Name == groupName { | ||||||
|  | 				groupVersions = append(groupVersions, gss[i].Versions...) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		for i := range rss { | ||||||
|  | 			for j := range groupVersions { | ||||||
|  | 				if groupVersions[j].GroupVersion == rss[i].GroupVersion { | ||||||
|  | 					for k := range rss[i].APIResources { | ||||||
|  | 						if !strings.Contains(rss[i].APIResources[k].Name, "/") { | ||||||
|  | 							resourceSet[rss[i].APIResources[k].Name] = struct{}{} | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		var resources []string | ||||||
|  | 		for k := range resourceSet { | ||||||
|  | 			resources = append(resources, k) | ||||||
|  | 		} | ||||||
|  | 		ctx.Values().Set("data", resources) | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import ( | |||||||
| 	"github.com/KubeOperator/ekko/internal/service/v1/cluster" | 	"github.com/KubeOperator/ekko/internal/service/v1/cluster" | ||||||
| 	"github.com/KubeOperator/ekko/internal/service/v1/clusterbinding" | 	"github.com/KubeOperator/ekko/internal/service/v1/clusterbinding" | ||||||
| 	"github.com/KubeOperator/ekko/internal/service/v1/common" | 	"github.com/KubeOperator/ekko/internal/service/v1/common" | ||||||
|  | 	"github.com/KubeOperator/ekko/internal/service/v1/project" | ||||||
| 	pkgV1 "github.com/KubeOperator/ekko/pkg/api/v1" | 	pkgV1 "github.com/KubeOperator/ekko/pkg/api/v1" | ||||||
| 	"github.com/KubeOperator/ekko/pkg/certificate" | 	"github.com/KubeOperator/ekko/pkg/certificate" | ||||||
| 	"github.com/KubeOperator/ekko/pkg/kubernetes" | 	"github.com/KubeOperator/ekko/pkg/kubernetes" | ||||||
| @@ -26,12 +27,14 @@ import ( | |||||||
| type Handler struct { | type Handler struct { | ||||||
| 	clusterService        cluster.Service | 	clusterService        cluster.Service | ||||||
| 	clusterBindingService clusterbinding.Service | 	clusterBindingService clusterbinding.Service | ||||||
|  | 	projectService        project.Service | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewHandler() *Handler { | func NewHandler() *Handler { | ||||||
| 	return &Handler{ | 	return &Handler{ | ||||||
| 		clusterService:        cluster.NewService(), | 		clusterService:        cluster.NewService(), | ||||||
| 		clusterBindingService: clusterbinding.NewService(), | 		clusterBindingService: clusterbinding.NewService(), | ||||||
|  | 		projectService:        project.NewService(), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -131,7 +134,6 @@ func (h *Handler) CreateCluster() iris.Handler { | |||||||
| 		txOptions := common.DBOptions{DB: tx} | 		txOptions := common.DBOptions{DB: tx} | ||||||
| 		req.CreatedBy = profile.Name | 		req.CreatedBy = profile.Name | ||||||
|  |  | ||||||
|  |  | ||||||
| 		if err := client.CreateDefaultClusterRoles(); err != nil { | 		if err := client.CreateDefaultClusterRoles(); err != nil { | ||||||
| 			_ = tx.Rollback() | 			_ = tx.Rollback() | ||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| @@ -152,12 +154,9 @@ func (h *Handler) CreateCluster() iris.Handler { | |||||||
| 				CreatedBy: profile.Name, | 				CreatedBy: profile.Name, | ||||||
| 			}, | 			}, | ||||||
| 			Metadata: v1.Metadata{ | 			Metadata: v1.Metadata{ | ||||||
| 				Name: fmt.Sprintf("%s-%s-cluster-binding-", req.Name, profile.Name), | 				Name: fmt.Sprintf("%s-%s-cluster-binding", req.Name, profile.Name), | ||||||
| 			}, |  | ||||||
| 			Subject: v1Cluster.Subject{ |  | ||||||
| 				Name: profile.Name, |  | ||||||
| 				Kind: "User", |  | ||||||
| 			}, | 			}, | ||||||
|  | 			UserRef:    profile.Name, | ||||||
| 			ClusterRef: req.Name, | 			ClusterRef: req.Name, | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -186,31 +185,44 @@ func (h *Handler) CreateCluster() iris.Handler { | |||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if _, err := kc.RbacV1().ClusterRoleBindings().Create(goContext.TODO(), &rbacV1.ClusterRoleBinding{ | 		roleBindingName := "ekko:admin-cluster" | ||||||
| 			ObjectMeta: metav1.ObjectMeta{ | 		obj, err := kc.RbacV1().ClusterRoleBindings().Get(goContext.TODO(), roleBindingName, metav1.GetOptions{}) | ||||||
| 				Name: fmt.Sprintf("ekko:cluster-admin-%s", profile.Name), | 		if err != nil { | ||||||
| 				Annotations: map[string]string{ | 			if !strings.Contains(strings.ToLower(err.Error()), "not found") { | ||||||
| 					"created-by": profile.Name, | 				_ = tx.Rollback() | ||||||
|  | 				ctx.StatusCode(iris.StatusInternalServerError) | ||||||
|  | 				ctx.Values().Set("message", err.Error()) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if obj == nil || obj.Name == "" { | ||||||
|  | 			if _, err := kc.RbacV1().ClusterRoleBindings().Create(goContext.TODO(), &rbacV1.ClusterRoleBinding{ | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Name: roleBindingName, | ||||||
|  | 					Annotations: map[string]string{ | ||||||
|  | 						"builtin": "true", | ||||||
|  | 					}, | ||||||
|  | 					Labels: map[string]string{ | ||||||
|  | 						"user-name": profile.Name, | ||||||
|  | 					}, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 				Subjects: []rbacV1.Subject{ | ||||||
| 			Subjects: []rbacV1.Subject{ | 					{ | ||||||
| 				{ | 						Kind: "User", | ||||||
| 					Kind: "User", | 						Name: profile.Name, | ||||||
| 					Name: profile.Name, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 				RoleRef: rbacV1.RoleRef{ | ||||||
| 			RoleRef: rbacV1.RoleRef{ | 					Name: "Admin Cluster", | ||||||
| 				Name: "cluster-admin", | 					Kind: "ClusterRole", | ||||||
| 				Kind: "ClusterRole", | 				}, | ||||||
| 			}, | 			}, metav1.CreateOptions{}); err != nil { | ||||||
| 		}, metav1.CreateOptions{}); err != nil { | 				_ = tx.Rollback() | ||||||
| 			_ = tx.Rollback() | 				ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 				ctx.Values().Set("message", err.Error()) | ||||||
| 			ctx.Values().Set("message", err.Error()) | 				return | ||||||
| 			return | 			} | ||||||
| 		} | 		} | ||||||
| 		_ = tx.Commit() | 		_ = tx.Commit() | ||||||
|  |  | ||||||
| 		ctx.Values().Set("data", &req) | 		ctx.Values().Set("data", &req) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -236,6 +248,18 @@ func (h *Handler) SearchClusters() iris.Handler { | |||||||
| 		ctx.Values().Set("data", pkgV1.Page{Items: clusters, Total: total}) | 		ctx.Values().Set("data", pkgV1.Page{Items: clusters, Total: total}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | func (h *Handler) GetCluster() iris.Handler { | ||||||
|  | 	return func(ctx *context.Context) { | ||||||
|  | 		name := ctx.Params().GetString("name") | ||||||
|  | 		c, err := h.clusterService.Get(name, common.DBOptions{}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.StatusCode(iris.StatusBadRequest) | ||||||
|  | 			ctx.Values().Set("message", fmt.Sprintf("get clusters failed: %s", err.Error())) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		ctx.Values().Set("data", c) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (h *Handler) ListClusters() iris.Handler { | func (h *Handler) ListClusters() iris.Handler { | ||||||
| 	return func(ctx *context.Context) { | 	return func(ctx *context.Context) { | ||||||
| @@ -266,7 +290,7 @@ func (h *Handler) ListClusters() iris.Handler { | |||||||
| 					return | 					return | ||||||
| 				} | 				} | ||||||
| 				for j := range mbs { | 				for j := range mbs { | ||||||
| 					if mbs[j].Subject.Kind == "User" && mbs[j].Subject.Name == profile.Name { | 					if mbs[j].UserRef == profile.Name { | ||||||
| 						resultClusters = append(resultClusters, clusters[i]) | 						resultClusters = append(resultClusters, clusters[i]) | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| @@ -323,18 +347,19 @@ func Install(parent iris.Party) { | |||||||
| 	sp := parent.Party("/clusters") | 	sp := parent.Party("/clusters") | ||||||
| 	sp.Post("", handler.CreateCluster()) | 	sp.Post("", handler.CreateCluster()) | ||||||
| 	sp.Get("", handler.ListClusters()) | 	sp.Get("", handler.ListClusters()) | ||||||
|  | 	sp.Get("/:name", handler.GetCluster()) | ||||||
| 	sp.Delete("/:name", handler.DeleteCluster()) | 	sp.Delete("/:name", handler.DeleteCluster()) | ||||||
| 	sp.Post("/search", handler.SearchClusters()) | 	sp.Post("/search", handler.SearchClusters()) | ||||||
| 	sp.Get("/:name/members", handler.GetClusterMembers()) | 	sp.Get("/:name/members", handler.ListClusterMembers()) | ||||||
| 	sp.Post("/:name/members", handler.CreateClusterMember()) | 	sp.Post("/:name/members", handler.CreateClusterMember()) | ||||||
| 	sp.Delete("/:name/members/:member", handler.DeleteClusterMember()) | 	sp.Delete("/:name/members/:member", handler.DeleteClusterMember()) | ||||||
| 	sp.Put("/:name/members/:member", handler.UpdateClusterMember()) | 	sp.Put("/:name/members/:member", handler.UpdateClusterMember()) | ||||||
| 	sp.Get("/:name/members/:member", handler.GetClusterMember()) | 	sp.Get("/:name/members/:member", handler.GetClusterMember()) | ||||||
| 	sp.Get("/:name/clusterroles", handler.GetClusterRoles()) | 	sp.Get("/:name/clusterroles", handler.ListClusterRoles()) | ||||||
| 	sp.Post("/:name/clusterroles", handler.CreateClusterRole()) | 	sp.Post("/:name/clusterroles", handler.CreateClusterRole()) | ||||||
| 	sp.Put("/:name/clusterroles/:clusterrole", handler.UpdateClusterRole()) | 	sp.Put("/:name/clusterroles/:clusterrole", handler.UpdateClusterRole()) | ||||||
| 	sp.Delete("/:name/clusterroles/:clusterrole", handler.DeleteClusterRole()) | 	sp.Delete("/:name/clusterroles/:clusterrole", handler.DeleteClusterRole()) | ||||||
| 	sp.Get("/:name/apigroups", handler.ListApiGroups()) | 	sp.Get("/:name/apigroups", handler.ListApiGroups()) | ||||||
| 	sp.Get("/:name/apigroups/{group:path}", handler.ListApiGroupResources()) | 	sp.Get("/:name/apigroups/{group:path}", handler.ListApiGroupResources()) | ||||||
|  | 	sp.Get("/:name/namespaces", handler.ListNamespace()) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ package cluster | |||||||
| import ( | import ( | ||||||
| 	goContext "context" | 	goContext "context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/KubeOperator/ekko/internal/api/v1/session" |  | ||||||
| 	"github.com/KubeOperator/ekko/internal/service/v1/common" | 	"github.com/KubeOperator/ekko/internal/service/v1/common" | ||||||
| 	"github.com/KubeOperator/ekko/pkg/kubernetes" | 	"github.com/KubeOperator/ekko/pkg/kubernetes" | ||||||
| 	"github.com/kataras/iris/v12" | 	"github.com/kataras/iris/v12" | ||||||
| @@ -24,6 +23,13 @@ func (h *Handler) UpdateClusterRole() iris.Handler { | |||||||
| 			ctx.Values().Set("message", fmt.Sprintf("delete cluster failed: %s", err.Error())) | 			ctx.Values().Set("message", fmt.Sprintf("delete cluster failed: %s", err.Error())) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  | 		for i := range req.Rules { | ||||||
|  | 			for j := range req.Rules[i].APIGroups { | ||||||
|  | 				if req.Rules[i].APIGroups[j] == "core" { | ||||||
|  | 					req.Rules[i].APIGroups[j] = "" | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 		c, err := h.clusterService.Get(name, common.DBOptions{}) | 		c, err := h.clusterService.Get(name, common.DBOptions{}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| @@ -63,6 +69,15 @@ func (h *Handler) CreateClusterRole() iris.Handler { | |||||||
| 			ctx.Values().Set("message", fmt.Sprintf("delete cluster failed: %s", err.Error())) | 			ctx.Values().Set("message", fmt.Sprintf("delete cluster failed: %s", err.Error())) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		for i := range req.Rules { | ||||||
|  | 			for j := range req.Rules[i].APIGroups { | ||||||
|  | 				if req.Rules[i].APIGroups[j] == "core" { | ||||||
|  | 					req.Rules[i].APIGroups[j] = "" | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		c, err := h.clusterService.Get(name, common.DBOptions{}) | 		c, err := h.clusterService.Get(name, common.DBOptions{}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| @@ -76,16 +91,11 @@ func (h *Handler) CreateClusterRole() iris.Handler { | |||||||
| 			ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) | 			ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		u := ctx.Values().Get("profile") |  | ||||||
| 		profile := u.(session.UserProfile) |  | ||||||
| 		req.Annotations = map[string]string{ | 		req.Annotations = map[string]string{ | ||||||
| 			"ekko-i18n":  "none", | 			"builtin":    "false", | ||||||
| 			"created-by": profile.Name, |  | ||||||
| 			"created-at": time.Now().Format("2006-01-02 15:04:05"), | 			"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
| 		} | 		} | ||||||
| 		req.Labels = map[string]string{ | 		req.Labels[kubernetes.LabelManageKey] = "ekko" | ||||||
| 			"manage": "ekko", |  | ||||||
| 		} |  | ||||||
| 		resp, err := client.RbacV1().ClusterRoles().Create(goContext.TODO(), &req, metav1.CreateOptions{}) | 		resp, err := client.RbacV1().ClusterRoles().Create(goContext.TODO(), &req, metav1.CreateOptions{}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| @@ -134,7 +144,7 @@ func (h *Handler) DeleteClusterRole() iris.Handler { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (h *Handler) GetClusterRoles() iris.Handler { | func (h *Handler) ListClusterRoles() iris.Handler { | ||||||
| 	return func(ctx *context.Context) { | 	return func(ctx *context.Context) { | ||||||
| 		name := ctx.Params().GetString("name") | 		name := ctx.Params().GetString("name") | ||||||
| 		c, err := h.clusterService.Get(name, common.DBOptions{}) | 		c, err := h.clusterService.Get(name, common.DBOptions{}) | ||||||
| @@ -150,7 +160,7 @@ func (h *Handler) GetClusterRoles() iris.Handler { | |||||||
| 			ctx.Values().Set("message", fmt.Sprintf("get kubernetes client failed: %s", err.Error())) | 			ctx.Values().Set("message", fmt.Sprintf("get kubernetes client failed: %s", err.Error())) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		items, err := client.RbacV1().ClusterRoles().List(goContext.TODO(), metav1.ListOptions{LabelSelector: "manage=ekko"}) | 		items, err := client.RbacV1().ClusterRoles().List(goContext.TODO(), metav1.ListOptions{LabelSelector: "kubeoperator.io/manage=ekko"}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| 			ctx.Values().Set("message", fmt.Sprintf("get cluster roles failed: %s", err.Error())) | 			ctx.Values().Set("message", fmt.Sprintf("get cluster roles failed: %s", err.Error())) | ||||||
|   | |||||||
| @@ -16,7 +16,6 @@ import ( | |||||||
| 	"github.com/kataras/iris/v12/context" | 	"github.com/kataras/iris/v12/context" | ||||||
| 	rbacV1 "k8s.io/api/rbac/v1" | 	rbacV1 "k8s.io/api/rbac/v1" | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	"strings" |  | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -46,10 +45,7 @@ func (h *Handler) UpdateClusterMember() iris.Handler { | |||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		binding, err := h.clusterBindingService.GetBindingByClusterNameAndSubject(name, v1Cluster.Subject{ | 		binding, err := h.clusterBindingService.GetBindingByClusterNameAndUserName(name, req.Name, common.DBOptions{}) | ||||||
| 			Kind: req.Kind, |  | ||||||
| 			Name: req.Name, |  | ||||||
| 		}, common.DBOptions{}) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| 			ctx.Values().Set("message", fmt.Sprintf("get cluster binding failed: %s", err.Error())) | 			ctx.Values().Set("message", fmt.Sprintf("get cluster binding failed: %s", err.Error())) | ||||||
| @@ -57,7 +53,7 @@ func (h *Handler) UpdateClusterMember() iris.Handler { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		clusterRoleBindings, err := client.RbacV1().ClusterRoleBindings().List(goContext.TODO(), metav1.ListOptions{ | 		clusterRoleBindings, err := client.RbacV1().ClusterRoleBindings().List(goContext.TODO(), metav1.ListOptions{ | ||||||
| 			LabelSelector: fmt.Sprintf("subject-name=%s,subject-kind=%s", binding.Subject.Name, binding.Subject.Kind), | 			LabelSelector: fmt.Sprintf("user-name=%s", binding.UserRef), | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| @@ -96,25 +92,22 @@ func (h *Handler) UpdateClusterMember() iris.Handler { | |||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		u := ctx.Values().Get("profile") |  | ||||||
| 		profile := u.(session.UserProfile) |  | ||||||
| 		for i := range forCreate { | 		for i := range forCreate { | ||||||
| 			b := rbacV1.ClusterRoleBinding{ | 			b := rbacV1.ClusterRoleBinding{ | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
| 					Name: fmt.Sprintf("%s-%s-%s", name, req.Name, forCreate[i]), | 					Name: fmt.Sprintf("%s-%s-%s", name, req.Name, forCreate[i]), | ||||||
| 					Labels: map[string]string{ | 					Labels: map[string]string{ | ||||||
| 						"manage":       "ekko", | 						"kubeoperator.io/manage": "ekko", | ||||||
| 						"subject-name": req.Name, | 						"user-name":              req.Name, | ||||||
| 						"subject-kind": req.Kind, |  | ||||||
| 					}, | 					}, | ||||||
| 					Annotations: map[string]string{ | 					Annotations: map[string]string{ | ||||||
| 						"created-by": profile.Name, | 						"builtin":    "false", | ||||||
| 						"created-at": time.Now().Format("2006-01-02 15:04:05"), | 						"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 				Subjects: []rbacV1.Subject{ | 				Subjects: []rbacV1.Subject{ | ||||||
| 					{ | 					{ | ||||||
| 						Kind: req.Kind, | 						Kind: "User", | ||||||
| 						Name: req.Name, | 						Name: req.Name, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| @@ -140,27 +133,6 @@ func (h *Handler) GetClusterMember() iris.Handler { | |||||||
| 		name := ctx.Params().GetString("name") | 		name := ctx.Params().GetString("name") | ||||||
| 		memberName := ctx.Params().Get("member") | 		memberName := ctx.Params().Get("member") | ||||||
|  |  | ||||||
| 		var kind string |  | ||||||
| 		if ctx.URLParamExists("kind") { |  | ||||||
| 			kind = ctx.URLParam("kind") |  | ||||||
| 		} |  | ||||||
| 		subject := v1Cluster.Subject{ |  | ||||||
| 			Name: memberName, |  | ||||||
| 		} |  | ||||||
| 		if kind != "" { |  | ||||||
| 			switch strings.ToLower(kind) { |  | ||||||
| 			case "user": |  | ||||||
| 				subject.Kind = "User" |  | ||||||
| 			case "group": |  | ||||||
| 				subject.Kind = "Group" |  | ||||||
| 			default: |  | ||||||
| 				ctx.StatusCode(iris.StatusInternalServerError) |  | ||||||
| 				ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", fmt.Errorf("invalid kind %s", kind))) |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			subject.Kind = kind |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		c, err := h.clusterService.Get(name, common.DBOptions{}) | 		c, err := h.clusterService.Get(name, common.DBOptions{}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| @@ -175,7 +147,7 @@ func (h *Handler) GetClusterMember() iris.Handler { | |||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		binding, err := h.clusterBindingService.GetBindingByClusterNameAndSubject(name, subject, common.DBOptions{}) | 		binding, err := h.clusterBindingService.GetBindingByClusterNameAndUserName(name, memberName, common.DBOptions{}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| 			ctx.Values().Set("message", fmt.Sprintf("get cluster binding failed: %s", err.Error())) | 			ctx.Values().Set("message", fmt.Sprintf("get cluster binding failed: %s", err.Error())) | ||||||
| @@ -183,7 +155,7 @@ func (h *Handler) GetClusterMember() iris.Handler { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		clusterRoleBindings, err := client.RbacV1().ClusterRoleBindings().List(goContext.TODO(), metav1.ListOptions{ | 		clusterRoleBindings, err := client.RbacV1().ClusterRoleBindings().List(goContext.TODO(), metav1.ListOptions{ | ||||||
| 			LabelSelector: fmt.Sprintf("subject-name=%s,subject-kind=%s", binding.Subject.Name, binding.Subject.Kind), | 			LabelSelector: fmt.Sprintf("user-name=%s", binding.UserRef), | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| @@ -193,8 +165,7 @@ func (h *Handler) GetClusterMember() iris.Handler { | |||||||
|  |  | ||||||
| 		var member Member | 		var member Member | ||||||
| 		member.ClusterRoles = make([]string, 0) | 		member.ClusterRoles = make([]string, 0) | ||||||
| 		member.Kind = binding.Subject.Kind | 		member.Name = binding.UserRef | ||||||
| 		member.Name = binding.Subject.Name |  | ||||||
| 		set := collectons.NewStringSet() | 		set := collectons.NewStringSet() | ||||||
| 		for i := range clusterRoleBindings.Items { | 		for i := range clusterRoleBindings.Items { | ||||||
| 			set.Add(clusterRoleBindings.Items[i].RoleRef.Name) | 			set.Add(clusterRoleBindings.Items[i].RoleRef.Name) | ||||||
| @@ -205,7 +176,7 @@ func (h *Handler) GetClusterMember() iris.Handler { | |||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (h *Handler) GetClusterMembers() iris.Handler { | func (h *Handler) ListClusterMembers() iris.Handler { | ||||||
| 	return func(ctx *context.Context) { | 	return func(ctx *context.Context) { | ||||||
| 		name := ctx.Params().GetString("name") | 		name := ctx.Params().GetString("name") | ||||||
| 		bindings, err := h.clusterBindingService.GetClusterBindingByClusterName(name, common.DBOptions{}) | 		bindings, err := h.clusterBindingService.GetClusterBindingByClusterName(name, common.DBOptions{}) | ||||||
| @@ -214,17 +185,12 @@ func (h *Handler) GetClusterMembers() iris.Handler { | |||||||
| 			ctx.Values().Set("message", err.Error()) | 			ctx.Values().Set("message", err.Error()) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		subjectMap := map[v1Cluster.Subject]v1Cluster.Binding{} | 		var members []Member | ||||||
| 		for i := range bindings { | 		for i := range bindings { | ||||||
| 			subjectMap[bindings[i].Subject] = bindings[i] |  | ||||||
| 		} |  | ||||||
| 		members := make([]Member, 0) |  | ||||||
| 		for key := range subjectMap { |  | ||||||
| 			members = append(members, Member{ | 			members = append(members, Member{ | ||||||
| 				Name:        key.Name, | 				Name:        bindings[i].UserRef, | ||||||
| 				Kind:        key.Kind, | 				BindingName: bindings[i].Name, | ||||||
| 				BindingName: subjectMap[key].Name, | 				CreateAt:    bindings[i].CreateAt, | ||||||
| 				CreateAt:    subjectMap[key].CreateAt, |  | ||||||
| 			}) | 			}) | ||||||
| 		} | 		} | ||||||
| 		ctx.Values().Set("data", members) | 		ctx.Values().Set("data", members) | ||||||
| @@ -238,7 +204,7 @@ func (h *Handler) CreateClusterMember() iris.Handler { | |||||||
| 		err := ctx.ReadJSON(&req) | 		err := ctx.ReadJSON(&req) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.StatusCode(iris.StatusBadRequest) | 			ctx.StatusCode(iris.StatusBadRequest) | ||||||
| 			ctx.Values().Set("message", fmt.Sprintf("delete cluster failed: %s", err.Error())) | 			ctx.Values().Set("message", fmt.Sprintf("create cluster member failed: %s", err.Error())) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		u := ctx.Values().Get("profile") | 		u := ctx.Values().Get("profile") | ||||||
| @@ -249,12 +215,9 @@ func (h *Handler) CreateClusterMember() iris.Handler { | |||||||
| 				CreatedBy: profile.Name, | 				CreatedBy: profile.Name, | ||||||
| 			}, | 			}, | ||||||
| 			Metadata: v1.Metadata{ | 			Metadata: v1.Metadata{ | ||||||
| 				Name: fmt.Sprintf("%s-%s-cluster-binding-", name, req.Name), | 				Name: fmt.Sprintf("%s-%s-cluster-binding", name, req.Name), | ||||||
| 			}, |  | ||||||
| 			Subject: v1Cluster.Subject{ |  | ||||||
| 				Name: req.Name, |  | ||||||
| 				Kind: req.Kind, |  | ||||||
| 			}, | 			}, | ||||||
|  | 			UserRef:    req.Name, | ||||||
| 			ClusterRef: name, | 			ClusterRef: name, | ||||||
| 		} | 		} | ||||||
| 		c, err := h.clusterService.Get(name, common.DBOptions{}) | 		c, err := h.clusterService.Get(name, common.DBOptions{}) | ||||||
| @@ -264,15 +227,13 @@ func (h *Handler) CreateClusterMember() iris.Handler { | |||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		k := kubernetes.NewKubernetes(*c) | 		k := kubernetes.NewKubernetes(*c) | ||||||
| 		if req.Kind == "User" { | 		cert, err := k.CreateCommonUser(req.Name) | ||||||
| 			cert, err := k.CreateCommonUser(req.Name) | 		if err != nil { | ||||||
| 			if err != nil { | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| 				ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.Values().Set("message", fmt.Sprintf("create common user failed: %s", err.Error())) | ||||||
| 				ctx.Values().Set("message", fmt.Sprintf("create common user failed: %s", err.Error())) | 			return | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			binding.Certificate = cert |  | ||||||
| 		} | 		} | ||||||
|  | 		binding.Certificate = cert | ||||||
| 		if err := h.clusterBindingService.CreateClusterBinding(&binding, common.DBOptions{}); err != nil { | 		if err := h.clusterBindingService.CreateClusterBinding(&binding, common.DBOptions{}); err != nil { | ||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| 			ctx.Values().Set("message", err.Error()) | 			ctx.Values().Set("message", err.Error()) | ||||||
| @@ -284,25 +245,23 @@ func (h *Handler) CreateClusterMember() iris.Handler { | |||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| 			ctx.Values().Set("message", fmt.Sprintf("get k8s client failed: %s", err.Error())) | 			ctx.Values().Set("message", fmt.Sprintf("get k8s client failed: %s", err.Error())) | ||||||
| 			return | 			return | ||||||
|  |  | ||||||
| 		} | 		} | ||||||
| 		for i := range req.ClusterRoles { | 		for i := range req.ClusterRoles { | ||||||
| 			binding := rbacV1.ClusterRoleBinding{ | 			binding := rbacV1.ClusterRoleBinding{ | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
| 					Name: fmt.Sprintf("%s-%s-%s", name, req.Name, req.ClusterRoles[i]), | 					Name: fmt.Sprintf("%s-%s", req.Name, req.ClusterRoles[i]), | ||||||
| 					Labels: map[string]string{ | 					Labels: map[string]string{ | ||||||
| 						"manage":       "ekko", | 						"kubeoperator.io/manage": "ekko", | ||||||
| 						"subject-name": req.Name, | 						"user-name":              req.Name, | ||||||
| 						"subject-kind": req.Kind, |  | ||||||
| 					}, | 					}, | ||||||
| 					Annotations: map[string]string{ | 					Annotations: map[string]string{ | ||||||
| 						"created-by": profile.Name, | 						"builtin":    "false", | ||||||
| 						"created-at": time.Now().Format("2006-01-02 15:04:05"), | 						"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 				Subjects: []rbacV1.Subject{ | 				Subjects: []rbacV1.Subject{ | ||||||
| 					{ | 					{ | ||||||
| 						Kind: req.Kind, | 						Kind: "User", | ||||||
| 						Name: req.Name, | 						Name: req.Name, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| @@ -318,6 +277,42 @@ func (h *Handler) CreateClusterMember() iris.Handler { | |||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 		// 创建Rolebinding | ||||||
|  | 		for i := range req.NamespaceRoles { | ||||||
|  | 			for j := range req.NamespaceRoles[i].Roles { | ||||||
|  | 				b := rbacV1.RoleBinding{ | ||||||
|  | 					ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 						Namespace: req.NamespaceRoles[i].Namespace, | ||||||
|  | 						Name:      fmt.Sprintf("%s-%s", req.Name, req.NamespaceRoles[i].Roles[j]), | ||||||
|  | 						Labels: map[string]string{ | ||||||
|  | 							"kubeoperator.io/manage": "ekko", | ||||||
|  | 							"user-name":              req.Name, | ||||||
|  | 						}, | ||||||
|  | 						Annotations: map[string]string{ | ||||||
|  | 							"builtin":    "false", | ||||||
|  | 							"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 					Subjects: []rbacV1.Subject{ | ||||||
|  | 						{ | ||||||
|  | 							Kind: "User", | ||||||
|  | 							Name: req.Name, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 					RoleRef: rbacV1.RoleRef{ | ||||||
|  | 						Kind: "ClusterRole", | ||||||
|  | 						Name: req.NamespaceRoles[i].Roles[j], | ||||||
|  | 					}, | ||||||
|  | 				} | ||||||
|  | 				_, err := client.RbacV1().RoleBindings(req.NamespaceRoles[i].Namespace).Create(goContext.TODO(), &b, metav1.CreateOptions{}) | ||||||
|  | 				if err != nil { | ||||||
|  | 					ctx.StatusCode(iris.StatusInternalServerError) | ||||||
|  | 					ctx.Values().Set("message", fmt.Sprintf("create role binding failed: %s", err.Error())) | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		ctx.Values().Set("data", req) | 		ctx.Values().Set("data", req) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -325,11 +320,7 @@ func (h *Handler) CreateClusterMember() iris.Handler { | |||||||
| func (h *Handler) DeleteClusterMember() iris.Handler { | func (h *Handler) DeleteClusterMember() iris.Handler { | ||||||
| 	return func(ctx *context.Context) { | 	return func(ctx *context.Context) { | ||||||
| 		name := ctx.Params().GetString("name") | 		name := ctx.Params().GetString("name") | ||||||
| 		member := ctx.Params().GetString("member") | 		memberName := ctx.Params().GetString("member") | ||||||
| 		var kind string |  | ||||||
| 		if ctx.URLParamExists("kind") { |  | ||||||
| 			kind = ctx.URLParam("kind") |  | ||||||
| 		} |  | ||||||
| 		u := ctx.Values().Get("profile") | 		u := ctx.Values().Get("profile") | ||||||
| 		profile := u.(session.UserProfile) | 		profile := u.(session.UserProfile) | ||||||
| 		c, err := h.clusterService.Get(name, common.DBOptions{}) | 		c, err := h.clusterService.Get(name, common.DBOptions{}) | ||||||
| @@ -338,29 +329,13 @@ func (h *Handler) DeleteClusterMember() iris.Handler { | |||||||
| 			ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) | 			ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		if c.CreatedBy == profile.Name { | 		if c.CreatedBy == memberName { | ||||||
| 			ctx.StatusCode(iris.StatusBadRequest) | 			ctx.StatusCode(iris.StatusBadRequest) | ||||||
| 			ctx.Values().Set("message", fmt.Sprintf("can not delete cluster importer %s", profile.Name)) | 			ctx.Values().Set("message", fmt.Sprintf("can not delete cluster importer %s", profile.Name)) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		subject := v1Cluster.Subject{ | 		binding, err := h.clusterBindingService.GetBindingByClusterNameAndUserName(c.Name, memberName, common.DBOptions{}) | ||||||
| 			Name: member, |  | ||||||
| 		} |  | ||||||
| 		if kind != "" { |  | ||||||
| 			switch strings.ToLower(kind) { |  | ||||||
| 			case "user": |  | ||||||
| 				subject.Kind = "User" |  | ||||||
| 			case "group": |  | ||||||
| 				subject.Kind = "Group" |  | ||||||
| 			default: |  | ||||||
| 				ctx.StatusCode(iris.StatusInternalServerError) |  | ||||||
| 				ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", fmt.Errorf("invalid kind %s", kind))) |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			subject.Kind = kind |  | ||||||
| 		} |  | ||||||
| 		binding, err := h.clusterBindingService.GetBindingByClusterNameAndSubject(c.Name, subject, common.DBOptions{}) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| 			ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) | 			ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) | ||||||
| @@ -388,7 +363,7 @@ func (h *Handler) DeleteClusterMember() iris.Handler { | |||||||
| 		if err = client.RbacV1().ClusterRoleBindings().DeleteCollection(goContext.TODO(), | 		if err = client.RbacV1().ClusterRoleBindings().DeleteCollection(goContext.TODO(), | ||||||
| 			metav1.DeleteOptions{}, | 			metav1.DeleteOptions{}, | ||||||
| 			metav1.ListOptions{ | 			metav1.ListOptions{ | ||||||
| 				LabelSelector: fmt.Sprintf("subject-name=%s,subject-kind=%s", subject.Name, subject.Kind), | 				LabelSelector: fmt.Sprintf("user-name=%s", memberName), | ||||||
| 			}); err != nil { | 			}); err != nil { | ||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
| 			ctx.Values().Set("message", fmt.Sprintf("delete cluster role failed: %s", err.Error())) | 			ctx.Values().Set("message", fmt.Sprintf("delete cluster role failed: %s", err.Error())) | ||||||
|   | |||||||
							
								
								
									
										39
									
								
								internal/api/v1/cluster/namespace.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								internal/api/v1/cluster/namespace.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | package cluster | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	goContext "context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"github.com/KubeOperator/ekko/internal/service/v1/common" | ||||||
|  | 	"github.com/KubeOperator/ekko/pkg/kubernetes" | ||||||
|  | 	"github.com/kataras/iris/v12" | ||||||
|  | 	"github.com/kataras/iris/v12/context" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func (h *Handler) ListNamespace() iris.Handler { | ||||||
|  | 	return func(ctx *context.Context) { | ||||||
|  | 		name := ctx.Params().GetString("name") | ||||||
|  | 		c, err := h.clusterService.Get(name, common.DBOptions{}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
|  | 			ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		k := kubernetes.NewKubernetes(*c) | ||||||
|  | 		client, err := k.Client() | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
|  | 			ctx.Values().Set("message", fmt.Sprintf("get k8s client failed: %s", err.Error())) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		ns, err := client.CoreV1().Namespaces().List(goContext.TODO(), metav1.ListOptions{}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
|  | 			ctx.Values().Set("message", fmt.Sprintf("get k8s client failed: %s", err.Error())) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		ctx.Values().Set("data", ns.Items) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										63
									
								
								internal/api/v1/cluster/project.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								internal/api/v1/cluster/project.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | package cluster | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	v1Project "github.com/KubeOperator/ekko/internal/model/v1/project" | ||||||
|  | 	"github.com/KubeOperator/ekko/internal/service/v1/common" | ||||||
|  | 	"github.com/KubeOperator/ekko/pkg/kubernetes" | ||||||
|  | 	"github.com/asdine/storm/v3" | ||||||
|  | 	"github.com/kataras/iris/v12" | ||||||
|  | 	"github.com/kataras/iris/v12/context" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func (h *Handler) CreateProject() iris.Handler { | ||||||
|  | 	return func(ctx *context.Context) { | ||||||
|  | 		name := ctx.Params().GetString("name") | ||||||
|  | 		var req v1Project.Project | ||||||
|  | 		if err := ctx.ReadJSON(&req); err != nil { | ||||||
|  | 			ctx.StatusCode(iris.StatusBadRequest) | ||||||
|  | 			ctx.Values().Set("message", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		c, err := h.clusterService.Get(name, common.DBOptions{}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
|  | 			ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		k := kubernetes.NewKubernetes(*c) | ||||||
|  | 		cert, err := k.CreateCommonUser(req.Name) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
|  | 			ctx.Values().Set("message", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		req.Certificate = cert | ||||||
|  | 		if err := h.projectService.Create(c.Name, &req, common.DBOptions{}); err != nil { | ||||||
|  | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
|  | 			ctx.Values().Set("message", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		ctx.Values().Set("data", req) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (h *Handler) ListProject() iris.Handler { | ||||||
|  | 	return func(ctx *context.Context) { | ||||||
|  | 		name := ctx.Params().GetString("name") | ||||||
|  | 		c, err := h.clusterService.Get(name, common.DBOptions{}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
|  | 			ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		ps, err := h.projectService.List(c.Name, common.DBOptions{}) | ||||||
|  | 		if err != nil && !errors.Is(err, storm.ErrNotFound) { | ||||||
|  | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
|  | 			ctx.Values().Set("message", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		ctx.Values().Set("data", ps) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -12,10 +12,15 @@ type Cluster struct { | |||||||
| 	CaDataStr   string `json:"caDataStr"` | 	CaDataStr   string `json:"caDataStr"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type Member struct { | type NamespaceRoles struct { | ||||||
| 	Name         string    `json:"name"` | 	Namespace string   `json:"namespace"` | ||||||
| 	Kind         string    `json:"kind"` | 	Roles     []string `json:"roles"` | ||||||
| 	ClusterRoles []string  `json:"clusterRoles"` | } | ||||||
| 	CreateAt     time.Time `json:"createAt"` |  | ||||||
| 	BindingName  string    `json:"bindingName"` | type Member struct { | ||||||
|  | 	Name           string           `json:"name"` | ||||||
|  | 	ClusterRoles   []string         `json:"clusterRoles"` | ||||||
|  | 	BindingName    string           `json:"bindingName"` | ||||||
|  | 	CreateAt       time.Time        `json:"createAt"` | ||||||
|  | 	NamespaceRoles []NamespaceRoles `json:"namespaceRoles"` | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,7 +4,6 @@ import ( | |||||||
| 	"encoding/pem" | 	"encoding/pem" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/KubeOperator/ekko/internal/api/v1/session" | 	"github.com/KubeOperator/ekko/internal/api/v1/session" | ||||||
| 	v1Cluster "github.com/KubeOperator/ekko/internal/model/v1/cluster" |  | ||||||
| 	"github.com/KubeOperator/ekko/internal/service/v1/cluster" | 	"github.com/KubeOperator/ekko/internal/service/v1/cluster" | ||||||
| 	"github.com/KubeOperator/ekko/internal/service/v1/clusterbinding" | 	"github.com/KubeOperator/ekko/internal/service/v1/clusterbinding" | ||||||
| 	"github.com/KubeOperator/ekko/internal/service/v1/common" | 	"github.com/KubeOperator/ekko/internal/service/v1/common" | ||||||
| @@ -41,10 +40,7 @@ func (h *Handler) KubernetesAPIProxy() iris.Handler { | |||||||
| 		} | 		} | ||||||
| 		u := ctx.Values().Get("profile") | 		u := ctx.Values().Get("profile") | ||||||
| 		profile := u.(session.UserProfile) | 		profile := u.(session.UserProfile) | ||||||
| 		binding, err := h.clusterBindingService.GetBindingByClusterNameAndSubject(name, v1Cluster.Subject{ | 		binding, err := h.clusterBindingService.GetBindingByClusterNameAndUserName(name, profile.Name, common.DBOptions{}) | ||||||
| 			Kind: "User", |  | ||||||
| 			Name: profile.Name, |  | ||||||
| 		}, common.DBOptions{}) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.StatusCode(iris.StatusForbidden) | 			ctx.StatusCode(iris.StatusForbidden) | ||||||
| 			ctx.Values().Set("message", fmt.Sprintf("user %s not cluster %s member ", profile.Name, name)) | 			ctx.Values().Set("message", fmt.Sprintf("user %s not cluster %s member ", profile.Name, name)) | ||||||
|   | |||||||
| @@ -1,33 +1,40 @@ | |||||||
| package session | package session | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	goContext "context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	v1Role "github.com/KubeOperator/ekko/internal/model/v1/role" | 	v1Role "github.com/KubeOperator/ekko/internal/model/v1/role" | ||||||
|  | 	"github.com/KubeOperator/ekko/internal/service/v1/cluster" | ||||||
| 	"github.com/KubeOperator/ekko/internal/service/v1/common" | 	"github.com/KubeOperator/ekko/internal/service/v1/common" | ||||||
| 	"github.com/KubeOperator/ekko/internal/service/v1/role" | 	"github.com/KubeOperator/ekko/internal/service/v1/role" | ||||||
| 	"github.com/KubeOperator/ekko/internal/service/v1/rolebinding" | 	"github.com/KubeOperator/ekko/internal/service/v1/rolebinding" | ||||||
| 	"github.com/KubeOperator/ekko/internal/service/v1/user" | 	"github.com/KubeOperator/ekko/internal/service/v1/user" | ||||||
| 	pkgV1 "github.com/KubeOperator/ekko/pkg/api/v1" | 	pkgV1 "github.com/KubeOperator/ekko/pkg/api/v1" | ||||||
| 	"github.com/KubeOperator/ekko/pkg/collectons" | 	"github.com/KubeOperator/ekko/pkg/collectons" | ||||||
|  | 	"github.com/KubeOperator/ekko/pkg/kubernetes" | ||||||
| 	"github.com/asdine/storm/v3" | 	"github.com/asdine/storm/v3" | ||||||
| 	"github.com/kataras/iris/v12" | 	"github.com/kataras/iris/v12" | ||||||
| 	"github.com/kataras/iris/v12/context" | 	"github.com/kataras/iris/v12/context" | ||||||
| 	"github.com/kataras/iris/v12/sessions" | 	"github.com/kataras/iris/v12/sessions" | ||||||
| 	"golang.org/x/crypto/bcrypt" | 	"golang.org/x/crypto/bcrypt" | ||||||
|  | 	v1 "k8s.io/api/rbac/v1" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type Handler struct { | type Handler struct { | ||||||
| 	userService         user.Service | 	userService        user.Service | ||||||
| 	roleService         role.Service | 	roleService        role.Service | ||||||
| 	rolebindingService  rolebinding.Service | 	clusterService     cluster.Service | ||||||
|  | 	rolebindingService rolebinding.Service | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewHandler() *Handler { | func NewHandler() *Handler { | ||||||
| 	return &Handler{ | 	return &Handler{ | ||||||
| 		userService:         user.NewService(), | 		clusterService:     cluster.NewService(), | ||||||
| 		roleService:         role.NewService(), | 		userService:        user.NewService(), | ||||||
| 		rolebindingService:  rolebinding.NewService(), | 		roleService:        role.NewService(), | ||||||
|  | 		rolebindingService: rolebinding.NewService(), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -184,11 +191,106 @@ func (h *Handler) GetProfile() iris.Handler { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (h *Handler) ListUserNamespace() iris.Handler { | ||||||
|  | 	return func(ctx *context.Context) { | ||||||
|  | 		name := ctx.Params().GetString("cluster_name") | ||||||
|  | 		c, err := h.clusterService.Get(name, common.DBOptions{}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
|  | 			ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		session := sessions.Get(ctx) | ||||||
|  | 		u := session.Get("profile") | ||||||
|  | 		profile := u.(UserProfile) | ||||||
|  |  | ||||||
|  | 		k := kubernetes.NewKubernetes(*c) | ||||||
|  | 		client, err := k.Client() | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
|  | 			ctx.Values().Set("message", fmt.Sprintf("get k8s client failed: %s", err.Error())) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		rbs, err := client.RbacV1().RoleBindings("").List(goContext.TODO(), metav1.ListOptions{}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
|  | 			ctx.Values().Set("message", fmt.Sprintf("get k8s client failed: %s", err.Error())) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		namespaceSet := collectons.NewStringSet() | ||||||
|  | 		for i := range rbs.Items { | ||||||
|  | 			for j := range rbs.Items[i].Subjects { | ||||||
|  | 				if rbs.Items[i].Subjects[j].Kind == "User" && rbs.Items[i].Subjects[j].Name == profile.Name { | ||||||
|  | 					namespaceSet.Add(rbs.Items[i].Namespace) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		ctx.Values().Set("data", namespaceSet.ToSlice()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (h *Handler) GetClusterProfile() iris.Handler { | ||||||
|  | 	return func(ctx *context.Context) { | ||||||
|  | 		session := sessions.Get(ctx) | ||||||
|  | 		clusterName := ctx.Params().GetString("cluster_name") | ||||||
|  | 		c, err := h.clusterService.Get(clusterName, common.DBOptions{}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
|  | 			ctx.Values().Set("message", err.Error()) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		k := kubernetes.NewKubernetes(*c) | ||||||
|  | 		client, err := k.Client() | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
|  | 			ctx.Values().Set("message", fmt.Sprintf("get k8s client failed: %s", err.Error())) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		u := session.Get("profile") | ||||||
|  | 		profile := u.(UserProfile) | ||||||
|  | 		clusterRoleBindings, err := client.RbacV1().ClusterRoleBindings().List(goContext.TODO(), metav1.ListOptions{ | ||||||
|  | 			LabelSelector: fmt.Sprintf("user-name=%s", profile.Name), | ||||||
|  | 		}) | ||||||
|  |  | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
|  | 			ctx.Values().Set("message", fmt.Sprintf("get cluster-role-binding failed: %s", err.Error())) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		roleSet := map[string]struct{}{} | ||||||
|  | 		for i := range clusterRoleBindings.Items { | ||||||
|  | 			for j := range clusterRoleBindings.Items[i].Subjects { | ||||||
|  | 				if clusterRoleBindings.Items[i].Subjects[j].Kind == "User" { | ||||||
|  | 					roleSet[clusterRoleBindings.Items[i].RoleRef.Name] = struct{}{} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		var roles []v1.ClusterRole | ||||||
|  | 		for key := range roleSet { | ||||||
|  | 			r, err := client.RbacV1().ClusterRoles().Get(goContext.TODO(), key, metav1.GetOptions{}) | ||||||
|  | 			if err != nil { | ||||||
|  | 				ctx.StatusCode(iris.StatusInternalServerError) | ||||||
|  | 				ctx.Values().Set("message", fmt.Sprintf("get cluster-role failed: %s", err.Error())) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			roles = append(roles, *r) | ||||||
|  | 		} | ||||||
|  | 		crp := ClusterUserProfile{ | ||||||
|  | 			UserProfile:  profile, | ||||||
|  | 			ClusterRoles: roles, | ||||||
|  | 		} | ||||||
|  | 		ctx.Values().Set("data", &crp) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func Install(parent iris.Party) { | func Install(parent iris.Party) { | ||||||
| 	handler := NewHandler() | 	handler := NewHandler() | ||||||
| 	sp := parent.Party("/sessions") | 	sp := parent.Party("/sessions") | ||||||
| 	sp.Post("", handler.Login()) | 	sp.Post("", handler.Login()) | ||||||
| 	sp.Delete("", handler.Logout()) | 	sp.Delete("", handler.Logout()) | ||||||
| 	sp.Get("", handler.GetProfile()) | 	sp.Get("", handler.GetProfile()) | ||||||
|  | 	sp.Get("/:cluster_name", handler.GetClusterProfile()) | ||||||
| 	sp.Get("/status", handler.IsLogin()) | 	sp.Get("/status", handler.IsLogin()) | ||||||
|  | 	sp.Get("/:cluster_name/namespaces", handler.ListUserNamespace()) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
| package session | package session | ||||||
|  |  | ||||||
|  | import v1 "k8s.io/api/rbac/v1" | ||||||
|  |  | ||||||
| type LoginCredential struct { | type LoginCredential struct { | ||||||
| 	Username string `json:"username"` | 	Username string `json:"username"` | ||||||
| 	Email    string `json:"email"` | 	Email    string `json:"email"` | ||||||
| @@ -12,3 +14,8 @@ type UserProfile struct { | |||||||
| 	Email               string              `json:"email"` | 	Email               string              `json:"email"` | ||||||
| 	ResourcePermissions map[string][]string `json:"resourcePermissions"` | 	ResourcePermissions map[string][]string `json:"resourcePermissions"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type ClusterUserProfile struct { | ||||||
|  | 	UserProfile | ||||||
|  | 	ClusterRoles []v1.ClusterRole `json:"clusterRoles"` | ||||||
|  | } | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/KubeOperator/ekko/internal/api/v1/session" | 	"github.com/KubeOperator/ekko/internal/api/v1/session" | ||||||
| 	v1 "github.com/KubeOperator/ekko/internal/model/v1" | 	v1 "github.com/KubeOperator/ekko/internal/model/v1" | ||||||
| 	v1Cluster "github.com/KubeOperator/ekko/internal/model/v1/cluster" |  | ||||||
| 	v1Role "github.com/KubeOperator/ekko/internal/model/v1/role" | 	v1Role "github.com/KubeOperator/ekko/internal/model/v1/role" | ||||||
| 	"github.com/KubeOperator/ekko/internal/server" | 	"github.com/KubeOperator/ekko/internal/server" | ||||||
| 	"github.com/KubeOperator/ekko/internal/service/v1/clusterbinding" | 	"github.com/KubeOperator/ekko/internal/service/v1/clusterbinding" | ||||||
| @@ -136,10 +135,7 @@ func (h *Handler) DeleteUser() iris.Handler { | |||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		cbs, err := h.clusterBindingService.GetBindingsBySubject(v1Cluster.Subject{ | 		cbs, err := h.clusterBindingService.GetBindingsByUserName(userName, txOptions) | ||||||
| 			Kind: "User", |  | ||||||
| 			Name: userName, |  | ||||||
| 		}, txOptions) |  | ||||||
| 		if err != nil && !errors.As(err, &storm.ErrNotFound) { | 		if err != nil && !errors.As(err, &storm.ErrNotFound) { | ||||||
| 			_ = tx.Rollback() | 			_ = tx.Rollback() | ||||||
| 			ctx.StatusCode(iris.StatusInternalServerError) | 			ctx.StatusCode(iris.StatusInternalServerError) | ||||||
|   | |||||||
| @@ -2,15 +2,10 @@ package cluster | |||||||
|  |  | ||||||
| import v1 "github.com/KubeOperator/ekko/internal/model/v1" | import v1 "github.com/KubeOperator/ekko/internal/model/v1" | ||||||
|  |  | ||||||
| type Subject struct { |  | ||||||
| 	Kind string `json:"kind"` |  | ||||||
| 	Name string `json:"name"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type Binding struct { | type Binding struct { | ||||||
| 	v1.BaseModel `storm:"inline"` | 	v1.BaseModel `storm:"inline"` | ||||||
| 	v1.Metadata  `storm:"inline"` | 	v1.Metadata  `storm:"inline"` | ||||||
| 	Subject      Subject `json:"subject" storm:"inline"` | 	UserRef      string `json:"UserRef" storm:"inline"` | ||||||
| 	ClusterRef   string  `json:"clusterRef" storm:"index"` | 	ClusterRef   string `json:"clusterRef" storm:"index"` | ||||||
| 	Certificate  []byte  `json:"certificate"` | 	Certificate  []byte `json:"certificate"` | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								internal/model/v1/project/project.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								internal/model/v1/project/project.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | package project | ||||||
|  |  | ||||||
|  | import v1 "github.com/KubeOperator/ekko/internal/model/v1" | ||||||
|  |  | ||||||
|  | type Project struct { | ||||||
|  | 	v1.BaseModel `storm:"inline"` | ||||||
|  | 	v1.Metadata  `storm:"inline"` | ||||||
|  | 	Users        []string `json:"users"` | ||||||
|  | 	ClusterRef   string   `json:"clusterRef" storm:"index"` | ||||||
|  | 	ProjectName  string   `json:"projectName"` | ||||||
|  | 	Certificate  []byte   `json:"certificate"` | ||||||
|  | } | ||||||
| @@ -14,10 +14,9 @@ type Service interface { | |||||||
| 	GetClusterBindingByClusterName(clusterName string, options common.DBOptions) ([]v1Cluster.Binding, error) | 	GetClusterBindingByClusterName(clusterName string, options common.DBOptions) ([]v1Cluster.Binding, error) | ||||||
| 	CreateClusterBinding(binding *v1Cluster.Binding, options common.DBOptions) error | 	CreateClusterBinding(binding *v1Cluster.Binding, options common.DBOptions) error | ||||||
| 	UpdateClusterBinding(name string, binding *v1Cluster.Binding, options common.DBOptions) error | 	UpdateClusterBinding(name string, binding *v1Cluster.Binding, options common.DBOptions) error | ||||||
|  | 	GetBindingByClusterNameAndUserName(clusterName string, userName string, options common.DBOptions) (*v1Cluster.Binding, error) | ||||||
|  | 	GetBindingsByUserName(userName string, options common.DBOptions) ([]v1Cluster.Binding, error) | ||||||
| 	Delete(name string, options common.DBOptions) error | 	Delete(name string, options common.DBOptions) error | ||||||
| 	GetBindingByClusterNameAndSubject(clusterName string, subject v1Cluster.Subject, options common.DBOptions) (*v1Cluster.Binding, error) |  | ||||||
| 	GetBindingsBySubject(subject v1Cluster.Subject, options common.DBOptions) ([]v1Cluster.Binding, error) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewService() Service { | func NewService() Service { | ||||||
| @@ -45,9 +44,9 @@ func (s *service) UpdateClusterBinding(name string, binding *v1Cluster.Binding, | |||||||
| 	return db.Update(binding) | 	return db.Update(binding) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (s *service) GetBindingsBySubject(subject v1Cluster.Subject, options common.DBOptions) ([]v1Cluster.Binding, error) { | func (s *service) GetBindingsByUserName(userName string, options common.DBOptions) ([]v1Cluster.Binding, error) { | ||||||
| 	db := s.GetDB(options) | 	db := s.GetDB(options) | ||||||
| 	query := db.Select(q.Eq("Subject", subject)) | 	query := db.Select(q.Eq("UserRef", userName)) | ||||||
| 	var rbs []v1Cluster.Binding | 	var rbs []v1Cluster.Binding | ||||||
| 	if err := query.Find(&rbs); err != nil { | 	if err := query.Find(&rbs); err != nil { | ||||||
| 		return rbs, err | 		return rbs, err | ||||||
| @@ -55,9 +54,9 @@ func (s *service) GetBindingsBySubject(subject v1Cluster.Subject, options common | |||||||
| 	return rbs, nil | 	return rbs, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (s *service) GetBindingByClusterNameAndSubject(clusterName string, subject v1Cluster.Subject, options common.DBOptions) (*v1Cluster.Binding, error) { | func (s *service) GetBindingByClusterNameAndUserName(clusterName string, userName string, options common.DBOptions) (*v1Cluster.Binding, error) { | ||||||
| 	db := s.GetDB(options) | 	db := s.GetDB(options) | ||||||
| 	query := db.Select(q.And(q.Eq("ClusterRef", clusterName), q.Eq("Subject", subject))) | 	query := db.Select(q.And(q.Eq("ClusterRef", clusterName), q.Eq("UserRef", userName))) | ||||||
| 	var rb v1Cluster.Binding | 	var rb v1Cluster.Binding | ||||||
| 	if err := query.First(&rb); err != nil { | 	if err := query.First(&rb); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|   | |||||||
							
								
								
									
										71
									
								
								internal/service/v1/project/project.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								internal/service/v1/project/project.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | package project | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	v1Project "github.com/KubeOperator/ekko/internal/model/v1/project" | ||||||
|  | 	"github.com/KubeOperator/ekko/internal/service/v1/common" | ||||||
|  | 	"github.com/google/uuid" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Service interface { | ||||||
|  | 	common.DBService | ||||||
|  | 	Create(clusterName string, r *v1Project.Project, options common.DBOptions) error | ||||||
|  | 	Get(name string, options common.DBOptions) (*v1Project.Project, error) | ||||||
|  | 	List(clusterName string, options common.DBOptions) ([]v1Project.Project, error) | ||||||
|  | 	Delete(name string, options common.DBOptions) error | ||||||
|  | 	Update(name string, role *v1Project.Project, options common.DBOptions) error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewService() Service { | ||||||
|  | 	return &service{} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type service struct { | ||||||
|  | 	common.DefaultDBService | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s service) Create(clusterName string, p *v1Project.Project, options common.DBOptions) error { | ||||||
|  | 	db := s.GetDB(options) | ||||||
|  | 	p.UUID = uuid.New().String() | ||||||
|  | 	p.CreateAt = time.Now() | ||||||
|  | 	p.UpdateAt = time.Now() | ||||||
|  | 	p.ClusterRef = clusterName | ||||||
|  | 	return db.Save(p) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s service) Get(name string, options common.DBOptions) (*v1Project.Project, error) { | ||||||
|  | 	db := s.GetDB(options) | ||||||
|  | 	var p v1Project.Project | ||||||
|  | 	if err := db.One("Name", name, &p); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &p, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s service) List(clusterName string, options common.DBOptions) ([]v1Project.Project, error) { | ||||||
|  | 	db := s.GetDB(options) | ||||||
|  |  | ||||||
|  | 	rs := make([]v1Project.Project, 0) | ||||||
|  | 	if err := db.Find("ClusterRef", clusterName, &rs); err != nil { | ||||||
|  | 		return rs, err | ||||||
|  | 	} | ||||||
|  | 	return rs, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s service) Delete(name string, options common.DBOptions) error { | ||||||
|  | 	db := s.GetDB(options) | ||||||
|  |  | ||||||
|  | 	item, err := s.Get(name, options) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if item.BuiltIn { | ||||||
|  | 		return errors.New("can not delete this resource,because it created by system") | ||||||
|  | 	} | ||||||
|  | 	return db.DeleteStruct(item) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s service) Update(name string, role *v1Project.Project, options common.DBOptions) error { | ||||||
|  | 	panic("implement me") | ||||||
|  | } | ||||||
							
								
								
									
										419
									
								
								pkg/kubernetes/constants.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										419
									
								
								pkg/kubernetes/constants.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,419 @@ | |||||||
|  | package kubernetes | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	rbacV1 "k8s.io/api/rbac/v1" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	LabelManageKey   = "kubeoperator.io/manage" | ||||||
|  | 	LabelRoleTypeKey = "kubeoperator.io/role-type" | ||||||
|  |  | ||||||
|  | 	RoleTypeCluster   = "cluster" | ||||||
|  | 	RoleTypeNamespace = "namespace" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var initClusterRoles = []rbacV1.ClusterRole{ | ||||||
|  | 	{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name: "Admin Cluster", | ||||||
|  | 			Annotations: map[string]string{ | ||||||
|  | 				"builtin":    "true", | ||||||
|  | 				"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
|  | 			}, | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				LabelManageKey:   "ekko", | ||||||
|  | 				LabelRoleTypeKey: RoleTypeCluster, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		Rules: []rbacV1.PolicyRule{ | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{"*"}, | ||||||
|  | 				Resources: []string{"*"}, | ||||||
|  | 				Verbs:     []string{"*"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name: "View Cluster", | ||||||
|  | 			Annotations: map[string]string{ | ||||||
|  | 				"builtin":    "true", | ||||||
|  | 				"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
|  | 			}, | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				LabelManageKey:   "ekko", | ||||||
|  | 				LabelRoleTypeKey: RoleTypeCluster}, | ||||||
|  | 		}, | ||||||
|  | 		Rules: []rbacV1.PolicyRule{ | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{"*"}, | ||||||
|  | 				Resources: []string{"*"}, | ||||||
|  | 				Verbs:     []string{"list", "get", "watch"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name: "Manage Namespaces", | ||||||
|  | 			Annotations: map[string]string{ | ||||||
|  | 				"builtin":    "true", | ||||||
|  | 				"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
|  | 			}, | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				LabelManageKey:   "ekko", | ||||||
|  | 				LabelRoleTypeKey: RoleTypeCluster}, | ||||||
|  | 		}, | ||||||
|  | 		Rules: []rbacV1.PolicyRule{ | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{""}, | ||||||
|  | 				Resources: []string{"namespaces"}, | ||||||
|  | 				Verbs:     []string{"*"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name: "View Namespaces", | ||||||
|  | 			Annotations: map[string]string{ | ||||||
|  | 				"builtin":    "true", | ||||||
|  | 				"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
|  | 			}, | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				LabelManageKey:   "ekko", | ||||||
|  | 				LabelRoleTypeKey: RoleTypeCluster}, | ||||||
|  | 		}, | ||||||
|  | 		Rules: []rbacV1.PolicyRule{ | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{""}, | ||||||
|  | 				Resources: []string{"namespaces"}, | ||||||
|  | 				Verbs:     []string{"list", "get", "watch"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name: "View Nodes", | ||||||
|  | 			Annotations: map[string]string{ | ||||||
|  | 				"builtin":    "true", | ||||||
|  | 				"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
|  | 			}, | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				LabelManageKey:   "ekko", | ||||||
|  | 				LabelRoleTypeKey: RoleTypeCluster}, | ||||||
|  | 		}, | ||||||
|  | 		Rules: []rbacV1.PolicyRule{ | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{""}, | ||||||
|  | 				Resources: []string{"nodes"}, | ||||||
|  | 				Verbs:     []string{"*"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name: "View Nodes", | ||||||
|  | 			Annotations: map[string]string{ | ||||||
|  | 				"builtin":    "true", | ||||||
|  | 				"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
|  | 			}, | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				LabelManageKey:   "ekko", | ||||||
|  | 				LabelRoleTypeKey: RoleTypeCluster}, | ||||||
|  | 		}, | ||||||
|  | 		Rules: []rbacV1.PolicyRule{ | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{""}, | ||||||
|  | 				Resources: []string{"nodes"}, | ||||||
|  | 				Verbs:     []string{"list", "get", "watch"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name: "View Events", | ||||||
|  | 			Annotations: map[string]string{ | ||||||
|  | 				"builtin":    "true", | ||||||
|  | 				"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
|  | 			}, | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				LabelManageKey:   "ekko", | ||||||
|  | 				LabelRoleTypeKey: RoleTypeCluster}, | ||||||
|  | 		}, | ||||||
|  | 		Rules: []rbacV1.PolicyRule{ | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{""}, | ||||||
|  | 				Resources: []string{"events"}, | ||||||
|  | 				Verbs:     []string{"list", "get", "watch"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name: "Manage Network", | ||||||
|  | 			Annotations: map[string]string{ | ||||||
|  | 				"builtin":    "true", | ||||||
|  | 				"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
|  | 			}, | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				LabelManageKey:   "ekko", | ||||||
|  | 				LabelRoleTypeKey: RoleTypeNamespace, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		Rules: []rbacV1.PolicyRule{ | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{""}, | ||||||
|  | 				Resources: []string{"services", "endpoints"}, | ||||||
|  | 				Verbs:     []string{"*"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{"networking.k8s.io"}, | ||||||
|  | 				Resources: []string{"ingresses", "networkpolicies"}, | ||||||
|  | 				Verbs:     []string{"*"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name: "View Network", | ||||||
|  | 			Annotations: map[string]string{ | ||||||
|  | 				"builtin":    "true", | ||||||
|  | 				"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
|  | 			}, | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				LabelManageKey:   "ekko", | ||||||
|  | 				LabelRoleTypeKey: RoleTypeNamespace}, | ||||||
|  | 		}, | ||||||
|  | 		Rules: []rbacV1.PolicyRule{ | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{""}, | ||||||
|  | 				Resources: []string{"services", "endpoints"}, | ||||||
|  | 				Verbs:     []string{"list", "get", "watch"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{"networking.k8s.io"}, | ||||||
|  | 				Resources: []string{"ingresses", "networkpolicies"}, | ||||||
|  | 				Verbs:     []string{"list", "get", "watch"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name: "Manage Config", | ||||||
|  | 			Annotations: map[string]string{ | ||||||
|  | 				"builtin":    "true", | ||||||
|  | 				"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
|  | 			}, | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				LabelManageKey:   "ekko", | ||||||
|  | 				LabelRoleTypeKey: RoleTypeNamespace}, | ||||||
|  | 		}, | ||||||
|  | 		Rules: []rbacV1.PolicyRule{ | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{""}, | ||||||
|  | 				Resources: []string{"configmaps,secrets,resourcequotas,limitranges"}, | ||||||
|  | 				Verbs:     []string{"*"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{"autoscaling"}, | ||||||
|  | 				Resources: []string{"horizontalpodautoscalers"}, | ||||||
|  | 				Verbs:     []string{"*"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name: "View Config", | ||||||
|  | 			Annotations: map[string]string{ | ||||||
|  | 				"builtin":    "true", | ||||||
|  | 				"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
|  | 			}, | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				LabelManageKey:   "ekko", | ||||||
|  | 				LabelRoleTypeKey: RoleTypeNamespace}, | ||||||
|  | 		}, | ||||||
|  | 		Rules: []rbacV1.PolicyRule{ | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{""}, | ||||||
|  | 				Resources: []string{"configmaps,secrets,resourcequotas,limitranges"}, | ||||||
|  | 				Verbs:     []string{"list", "get", "watch"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{"autoscaling"}, | ||||||
|  | 				Resources: []string{"horizontalpodautoscalers"}, | ||||||
|  | 				Verbs:     []string{"list", "get", "watch"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name: "Manage Storage", | ||||||
|  | 			Annotations: map[string]string{ | ||||||
|  | 				"builtin":    "true", | ||||||
|  | 				"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
|  | 			}, | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				LabelManageKey:   "ekko", | ||||||
|  | 				LabelRoleTypeKey: RoleTypeNamespace, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		Rules: []rbacV1.PolicyRule{ | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{""}, | ||||||
|  | 				Resources: []string{"persistentvolumes", "persistentvolumeclaims"}, | ||||||
|  | 				Verbs:     []string{"*"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{"storage.k8s.io"}, | ||||||
|  | 				Resources: []string{"storageclasses"}, | ||||||
|  | 				Verbs:     []string{"*"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name: "View Storage", | ||||||
|  | 			Annotations: map[string]string{ | ||||||
|  | 				"builtin":    "true", | ||||||
|  | 				"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
|  | 			}, | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				LabelManageKey:   "ekko", | ||||||
|  | 				LabelRoleTypeKey: RoleTypeNamespace, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		Rules: []rbacV1.PolicyRule{ | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{""}, | ||||||
|  | 				Resources: []string{"persistentvolumes", "persistentvolumeclaims"}, | ||||||
|  | 				Verbs:     []string{"list", "get", "watch"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{"storage.k8s.io"}, | ||||||
|  | 				Resources: []string{"storageclasses"}, | ||||||
|  | 				Verbs:     []string{"list", "get", "watch"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name: "Manage Workload", | ||||||
|  | 			Annotations: map[string]string{ | ||||||
|  | 				"builtin":    "true", | ||||||
|  | 				"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
|  | 			}, | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				LabelManageKey:   "ekko", | ||||||
|  | 				LabelRoleTypeKey: RoleTypeNamespace, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		Rules: []rbacV1.PolicyRule{ | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{""}, | ||||||
|  | 				Resources: []string{"pods", "containers"}, | ||||||
|  | 				Verbs:     []string{"*"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{"apps"}, | ||||||
|  | 				Resources: []string{"deployments", "daemonsets", "replicasets", "statefulsets"}, | ||||||
|  | 				Verbs:     []string{"*"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{"batch"}, | ||||||
|  | 				Resources: []string{"jobs", "cronjobs"}, | ||||||
|  | 				Verbs:     []string{"*"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name: "View Workload", | ||||||
|  | 			Annotations: map[string]string{ | ||||||
|  | 				"builtin":    "true", | ||||||
|  | 				"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
|  | 			}, | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				LabelManageKey:   "ekko", | ||||||
|  | 				LabelRoleTypeKey: RoleTypeNamespace, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		Rules: []rbacV1.PolicyRule{ | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{""}, | ||||||
|  | 				Resources: []string{"pods", "containers"}, | ||||||
|  | 				Verbs:     []string{"list", "get", "watch"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{"apps"}, | ||||||
|  | 				Resources: []string{"deployments", "daemonsets", "replicasets", "statefulsets"}, | ||||||
|  | 				Verbs:     []string{"list", "get", "watch"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{"batch"}, | ||||||
|  | 				Resources: []string{"jobs", "cronjobs"}, | ||||||
|  | 				Verbs:     []string{"list", "get", "watch"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name: "Mange RBAC", | ||||||
|  | 			Annotations: map[string]string{ | ||||||
|  | 				"builtin":    "true", | ||||||
|  | 				"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
|  | 			}, | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				LabelManageKey:   "ekko", | ||||||
|  | 				LabelRoleTypeKey: RoleTypeNamespace, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		Rules: []rbacV1.PolicyRule{ | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{"rbac.authorization.k8s.io"}, | ||||||
|  | 				Resources: []string{"clusterroles", "clusterrolebindings", "roles", "rolebindings"}, | ||||||
|  | 				Verbs:     []string{"*"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{""}, | ||||||
|  | 				Resources: []string{"serviceaccounts"}, | ||||||
|  | 				Verbs:     []string{"*"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{"policy"}, | ||||||
|  | 				Resources: []string{"podsecuritypolicies"}, | ||||||
|  | 				Verbs:     []string{"*"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name: "Mange RBAC", | ||||||
|  | 			Annotations: map[string]string{ | ||||||
|  | 				"builtin":    "true", | ||||||
|  | 				"created-at": time.Now().Format("2006-01-02 15:04:05"), | ||||||
|  | 			}, | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				LabelManageKey:   "ekko", | ||||||
|  | 				LabelRoleTypeKey: RoleTypeNamespace, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		Rules: []rbacV1.PolicyRule{ | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{"rbac.authorization.k8s.io"}, | ||||||
|  | 				Resources: []string{"clusterroles", "clusterrolebindings", "roles", "rolebindings"}, | ||||||
|  | 				Verbs:     []string{"list", "get", "watch"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{""}, | ||||||
|  | 				Resources: []string{"serviceaccounts"}, | ||||||
|  | 				Verbs:     []string{"list", "get", "watch"}, | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				APIGroups: []string{"policy"}, | ||||||
|  | 				Resources: []string{"podsecuritypolicies"}, | ||||||
|  | 				Verbs:     []string{"list", "get", "watch"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | } | ||||||
| @@ -8,7 +8,6 @@ import ( | |||||||
| 	"github.com/KubeOperator/ekko/pkg/certificate" | 	"github.com/KubeOperator/ekko/pkg/certificate" | ||||||
| 	v1 "k8s.io/api/authorization/v1" | 	v1 "k8s.io/api/authorization/v1" | ||||||
| 	certv1 "k8s.io/api/certificates/v1" | 	certv1 "k8s.io/api/certificates/v1" | ||||||
| 	rbacV1 "k8s.io/api/rbac/v1" |  | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/version" | 	"k8s.io/apimachinery/pkg/version" | ||||||
| 	"k8s.io/client-go/kubernetes" | 	"k8s.io/client-go/kubernetes" | ||||||
| @@ -40,69 +39,20 @@ func NewKubernetes(cluster v1Cluster.Cluster) Interface { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (k *Kubernetes) CreateDefaultClusterRoles() error { | func (k *Kubernetes) CreateDefaultClusterRoles() error { | ||||||
| 	defaultRoles := []rbacV1.ClusterRole{ |  | ||||||
| 		{ |  | ||||||
| 			ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 				Name: "ekko-admin", |  | ||||||
| 				Annotations: map[string]string{ |  | ||||||
| 					"ekko-i18n":  "cluster_administrator", |  | ||||||
| 					"created-by": "system", |  | ||||||
| 					"created-at": time.Now().Format("2006-01-02 15:04:05"), |  | ||||||
| 				}, |  | ||||||
| 				Labels: map[string]string{ |  | ||||||
| 					"manage": "ekko", |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			Rules: []rbacV1.PolicyRule{ |  | ||||||
| 				{ |  | ||||||
| 					APIGroups: []string{"*"}, |  | ||||||
| 					Resources: []string{"*"}, |  | ||||||
| 					Verbs:     []string{"*"}, |  | ||||||
| 				}, |  | ||||||
| 				{ |  | ||||||
| 					NonResourceURLs: []string{"*"}, |  | ||||||
| 					Verbs:           []string{"*"}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 				Name: "ekko-viewer", |  | ||||||
| 				Annotations: map[string]string{ |  | ||||||
| 					"ekko-i18n":  "cluster_viewer", |  | ||||||
| 					"created-by": "system", |  | ||||||
| 					"created-at": time.Now().Format("2006-01-02 15:04:05"), |  | ||||||
| 				}, |  | ||||||
| 				Labels: map[string]string{ |  | ||||||
| 					"manage": "ekko", |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			Rules: []rbacV1.PolicyRule{ |  | ||||||
| 				{ |  | ||||||
| 					APIGroups: []string{"*"}, |  | ||||||
| 					Resources: []string{"*"}, |  | ||||||
| 					Verbs:     []string{"list", "get"}, |  | ||||||
| 				}, |  | ||||||
| 				{ |  | ||||||
| 					NonResourceURLs: []string{"*"}, |  | ||||||
| 					Verbs:           []string{"list", "get"}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	client, err := k.Client() | 	client, err := k.Client() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	for i := range defaultRoles { | 	for i := range initClusterRoles { | ||||||
| 		instance, err := client.RbacV1().ClusterRoles().Get(context.TODO(), defaultRoles[i].Name, metav1.GetOptions{}) | 		instance, err := client.RbacV1().ClusterRoles().Get(context.TODO(), initClusterRoles[i].Name, metav1.GetOptions{}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			if !strings.Contains(strings.ToLower(err.Error()), "not found") { | 			if !strings.Contains(strings.ToLower(err.Error()), "not found") { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		if instance == nil { | 		if instance == nil || instance.Name == "" { | ||||||
| 			_, err = client.RbacV1().ClusterRoles().Create(context.TODO(), &defaultRoles[i], metav1.CreateOptions{}) | 			_, err = client.RbacV1().ClusterRoles().Create(context.TODO(), &initClusterRoles[i], metav1.CreateOptions{}) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -14,6 +14,15 @@ export function getCurrentUser() { | |||||||
|     return get(authUrl) |     return get(authUrl) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export function getCurrentClusterUser(clusterName) { | ||||||
|  |     return get(`${authUrl}/${clusterName}`) | ||||||
|  | } | ||||||
|  |  | ||||||
| export function isLogin() { | export function isLogin() { | ||||||
|     return get(`${authUrl}/status`) |     return get(`${authUrl}/status`) | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | export function getNamespaces(clusterName) { | ||||||
|  |     return get(`${authUrl}/${clusterName}/namespaces`) | ||||||
|  | } | ||||||
| @@ -1,19 +1,7 @@ | |||||||
| import {post, get, del} from "@/plugins/request" | import {get} from "@/plugins/request" | ||||||
|  |  | ||||||
| const authUrl = "/api/v1/clusters" | const authUrl = "/api/v1/clusters" | ||||||
|  |  | ||||||
| export function create(data) { | export function getCluster(name) { | ||||||
|   return post(authUrl, data) |     return get(`${authUrl}/${name}`) | ||||||
| } |  | ||||||
|  |  | ||||||
| export function listAll(){ |  | ||||||
|   return get(authUrl) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function deleteBy(name){ |  | ||||||
|   return del(`${authUrl}/${name}`) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function searchCluster(page,size) { |  | ||||||
|   return post(`${authUrl}/search?pageNum=${page}&pageSize=${size}`) |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,56 @@ | |||||||
|  | <template> | ||||||
|  |   <el-dropdown trigger="click" @command="handleProjectSwitch"> | ||||||
|  |     <span class="el-dropdown-link"> | ||||||
|  |               <i class="iconfont iconnamesapce" style="color: #3884c5;margin-right: 3px" :icon="['fas', 'globe']"/> | ||||||
|  |       <span>{{ getProjectName }}</span> | ||||||
|  |       <i class="el-icon-arrow-down el-icon--right"></i> | ||||||
|  |     </span> | ||||||
|  |     <el-dropdown-menu slot="dropdown"> | ||||||
|  |       <el-dropdown-item command="">全部</el-dropdown-item> | ||||||
|  |       <el-dropdown-item disabled divided></el-dropdown-item> | ||||||
|  |       <el-dropdown-item v-for="(value,key) in namespaceOptions" :key="key" :command="value">{{ value }} | ||||||
|  |       </el-dropdown-item> | ||||||
|  |     </el-dropdown-menu> | ||||||
|  |   </el-dropdown> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  |  | ||||||
|  | import {getNamespaces} from '@/api/auth' | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   name: "ProjectSwitch", | ||||||
|  |   data() { | ||||||
|  |     return { | ||||||
|  |       namespaceOptions: [], | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   computed: { | ||||||
|  |     getProjectName() { | ||||||
|  |       const p = sessionStorage.getItem("project") | ||||||
|  |       return (p === null) ? "Global" : p | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     handleProjectSwitch(command) { | ||||||
|  |       if (!command) { | ||||||
|  |         sessionStorage.removeItem("project") | ||||||
|  |       } else { | ||||||
|  |         sessionStorage.setItem("project", command) | ||||||
|  |       } | ||||||
|  |       location.reload() | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   created() { | ||||||
|  |     const cluster = this.$store.getters.cluster | ||||||
|  |     getNamespaces(cluster).then((data) => { | ||||||
|  |       this.namespaceOptions = data.data | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style scoped> | ||||||
|  |  | ||||||
|  | </style> | ||||||
| @@ -2,7 +2,7 @@ | |||||||
|     <el-dropdown trigger="click" @command="handleCommand"> |     <el-dropdown trigger="click" @command="handleCommand"> | ||||||
|     <span class="el-dropdown-link"> |     <span class="el-dropdown-link"> | ||||||
|         <i class="el-icon-user-solid" style="margin-right: 3px"></i> |         <i class="el-icon-user-solid" style="margin-right: 3px"></i> | ||||||
|         <span>{{ name }}</span> |         <span>{{ nickName }}</span> | ||||||
|         <i class="el-icon-arrow-down el-icon--right"></i> |         <i class="el-icon-arrow-down el-icon--right"></i> | ||||||
|     </span> |     </span> | ||||||
|         <el-dropdown-menu slot="dropdown"> |         <el-dropdown-menu slot="dropdown"> | ||||||
| @@ -22,7 +22,7 @@ | |||||||
|         name: "UserSetting", |         name: "UserSetting", | ||||||
|         computed: { |         computed: { | ||||||
|             ...mapGetters([ |             ...mapGetters([ | ||||||
|                 "name" |                 "nickName" | ||||||
|             ]) |             ]) | ||||||
|         }, |         }, | ||||||
|         methods: { |         methods: { | ||||||
|   | |||||||
| @@ -5,7 +5,8 @@ | |||||||
|       <home></home> |       <home></home> | ||||||
|     </div> |     </div> | ||||||
|     <div class="header-right"> |     <div class="header-right"> | ||||||
|       <user-setting></user-setting> |       <project-switch></project-switch> | ||||||
|  |       <user-setting style="margin-left: 20px"></user-setting> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| @@ -14,41 +15,43 @@ | |||||||
| import SidebarToggleButton from "@/components/layout/sidebar/SidebarToggleButton" | import SidebarToggleButton from "@/components/layout/sidebar/SidebarToggleButton" | ||||||
| import UserSetting from "@/business/app-layout/header-components/UserSetting" | import UserSetting from "@/business/app-layout/header-components/UserSetting" | ||||||
| import Home from "@/business/app-layout/header-components/Home" | import Home from "@/business/app-layout/header-components/Home" | ||||||
|  | import ProjectSwitch from "@/business/app-layout/header-components/ProjectSwitch"; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   name: "HorizontalHeader", |   name: "HorizontalHeader", | ||||||
|   components: { Home, UserSetting, SidebarToggleButton } |   components: {ProjectSwitch, Home, UserSetting, SidebarToggleButton} | ||||||
| } | } | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
|   @import "~@/styles/common/mixins"; | @import "~@/styles/common/mixins"; | ||||||
|  |  | ||||||
|   .horizontal-header { | .horizontal-header { | ||||||
|  |   @include flex-row(flex-start, center); | ||||||
|  |   position: relative; | ||||||
|  |   height: 100%; | ||||||
|  |  | ||||||
|  |   .header-left { | ||||||
|     @include flex-row(flex-start, center); |     @include flex-row(flex-start, center); | ||||||
|     position: relative; |     position: relative; | ||||||
|     height: 100%; |     height: 100%; | ||||||
|  |   } | ||||||
|  |  | ||||||
|     .header-left { |   .header-right { | ||||||
|       @include flex-row(flex-start, center); |     @include flex-row(flex-end, center); | ||||||
|       position: relative; |     flex: auto; | ||||||
|       height: 100%; |     height: 100%; | ||||||
|  |  | ||||||
|  |     .navbar-item { | ||||||
|  |       color: #2E2E2E; | ||||||
|  |       line-height: 50px; | ||||||
|  |       display: inline-block; | ||||||
|  |       padding-right: 20px; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     .header-right { |     .navbar-item + .navbar-item { | ||||||
|       @include flex-row(flex-end, center); |       margin-left: 20px; | ||||||
|       flex: auto; |  | ||||||
|       height: 100%; |  | ||||||
|  |  | ||||||
|       .navbar-item { |  | ||||||
|         color: #2E2E2E; |  | ||||||
|         line-height: 50px; |  | ||||||
|         display: inline-block; |  | ||||||
|         padding-right: 20px; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       .navbar-item + .navbar-item { |  | ||||||
|         margin-left: 20px; |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | } | ||||||
| </style> | </style> | ||||||
| @@ -3,10 +3,10 @@ | |||||||
|     <complex-table :selects.sync="selects" :data="data" :pagination-config="page" @search="search()"> |     <complex-table :selects.sync="selects" :data="data" :pagination-config="page" @search="search()"> | ||||||
|       <template #header> |       <template #header> | ||||||
|         <el-button-group> |         <el-button-group> | ||||||
|           <el-button type="primary" size="small" @click="onCreate"> |           <el-button v-has-permissions="{apiGroup:'',resource:'namespaces',verb:'create'}" type="primary" size="small" @click="onCreate"> | ||||||
|             {{ $t("commons.button.create") }} |             {{ $t("commons.button.create") }} | ||||||
|           </el-button> |           </el-button> | ||||||
|           <el-button type="primary" size="small" :disabled="selects.length===0" @click="onDelete()"> |           <el-button v-has-permissions="{apiGroup:'',resource:'namespaces',verb:'delete'}" type="primary" size="small" :disabled="selects.length===0" @click="onDelete()"> | ||||||
|             {{ $t("commons.button.delete") }} |             {{ $t("commons.button.delete") }} | ||||||
|           </el-button> |           </el-button> | ||||||
|         </el-button-group> |         </el-button-group> | ||||||
| @@ -58,6 +58,8 @@ import ComplexTable from "@/components/complex-table" | |||||||
| import {listNamespace, deleteNamespace} from "@/api/namespaces" | import {listNamespace, deleteNamespace} from "@/api/namespaces" | ||||||
| import KoTableOperations from "@/components/ko-table-operations" | import KoTableOperations from "@/components/ko-table-operations" | ||||||
| import {downloadYaml} from "@/utils/actions" | import {downloadYaml} from "@/utils/actions" | ||||||
|  | import {checkPermissions} from "@/utils/permission" | ||||||
|  |  | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   name: "NamespaceList", |   name: "NamespaceList", | ||||||
| @@ -73,21 +75,31 @@ export default { | |||||||
|               path: "/namespaces/edit/"+row.metadata.name , |               path: "/namespaces/edit/"+row.metadata.name , | ||||||
|               query: { yamlShow: false } |               query: { yamlShow: false } | ||||||
|             }) |             }) | ||||||
|  |           }, | ||||||
|  |           disabled:()=>{ | ||||||
|  |             return !checkPermissions({apiGroup:"",resource:"namespaces",verb:"update"}) | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           label: this.$t("commons.button.edit_yaml"), |           label: this.$t("commons.button.edit_yaml"), | ||||||
|           icon: "el-icon-edit", |           icon: "el-icon-edit", | ||||||
|  |           disabled:()=>{ | ||||||
|  |             return !checkPermissions({apiGroup:"",resource:"namespaces",verb:"update"}) | ||||||
|  |           }, | ||||||
|           click: (row) => { |           click: (row) => { | ||||||
|             this.$router.push({ |             this.$router.push({ | ||||||
|               path: "/namespaces/edit/"+row.metadata.name , |               path: "/namespaces/edit/"+row.metadata.name , | ||||||
|               query: { yamlShow: true } |               query: { yamlShow: true } | ||||||
|             }) |             }) | ||||||
|  |  | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           label: this.$t("commons.button.download_yaml"), |           label: this.$t("commons.button.download_yaml"), | ||||||
|           icon: "el-icon-download", |           icon: "el-icon-download", | ||||||
|  |           disabled:()=>{ | ||||||
|  |             return !checkPermissions({apiGroup:"",resource:"namespaces",verb:"get"}) | ||||||
|  |           }, | ||||||
|           click: (row) => { |           click: (row) => { | ||||||
|             downloadYaml(row.metadata.name + ".yml", row) |             downloadYaml(row.metadata.name + ".yml", row) | ||||||
|           } |           } | ||||||
| @@ -95,6 +107,9 @@ export default { | |||||||
|         { |         { | ||||||
|           label: this.$t("commons.button.delete"), |           label: this.$t("commons.button.delete"), | ||||||
|           icon: "el-icon-delete", |           icon: "el-icon-delete", | ||||||
|  |           disabled:()=>{ | ||||||
|  |             return !checkPermissions({apiGroup:"",resource:"namespaces",verb:"delete"}) | ||||||
|  |           }, | ||||||
|           click: (row) => { |           click: (row) => { | ||||||
|             this.onDelete(row) |             this.onDelete(row) | ||||||
|           } |           } | ||||||
|   | |||||||
| @@ -1,253 +1,289 @@ | |||||||
| <template> | <template> | ||||||
|   <layout-content :header="$t('business.dashboard.dashboard')"> |     <layout-content :header="$t('business.dashboard.dashboard')"> | ||||||
|     <el-row :gutter="24"> |         <el-row :gutter="24"> | ||||||
|       <el-col :span="24"> |             <el-col :span="24"> | ||||||
|         <el-card shadow="always" style="background-color: #243441;height: 120px"> |                 <el-card v-if="cluster" shadow="always" style="background-color: #243441;height: 120px"> | ||||||
|           <el-row :gutter="24"> |                     <el-row :gutter="24"> | ||||||
|             <el-col :span="6"> |                         <el-col :span="8"> | ||||||
|               <span class="title">{{ $t("commons.table.name") }}</span> |                             <span class="title">{{ $t("commons.table.name") }}</span> | ||||||
|               <div style="text-align: center"> |                             <div style="text-align: center"> | ||||||
|                 <h1>测试</h1> |                                 <h1>{{cluster.name}}</h1> | ||||||
|               </div> |                             </div> | ||||||
|  |                         </el-col> | ||||||
|  |                         <el-col :span="8"> | ||||||
|  |                             <span class="title">{{ $t("business.cluster.version") }}</span> | ||||||
|  |                             <div class="line"></div> | ||||||
|  |                             <div style="text-align: center"> | ||||||
|  |                                 <h1>{{cluster.status.version}}</h1> | ||||||
|  |                             </div> | ||||||
|  |                         </el-col> | ||||||
|  |                         <el-col :span="8"> | ||||||
|  |                             <span class="title">{{ $t("commons.table.created_time") }}</span> | ||||||
|  |                             <div class="line"></div> | ||||||
|  |                             <div style="text-align: center"> | ||||||
|  |                                 <h1>{{fromNow(cluster.createAt)}} Days</h1> | ||||||
|  |                             </div> | ||||||
|  |                         </el-col> | ||||||
|  |                     </el-row> | ||||||
|  |                 </el-card> | ||||||
|             </el-col> |             </el-col> | ||||||
|             <el-col :span="6"> |         </el-row> | ||||||
|               <span class="title">{{ $t("business.cluster.version") }}</span> |         <br> | ||||||
|               <div class="line"></div> |         <el-row :gutter="24" class="resources"> | ||||||
|               <div style="text-align: center"> |             <el-col :span="4" v-for="resource in resources" v-bind:key="resource.name"> | ||||||
|                 <h1>v1.18.0</h1> |                 <el-card :body-style="{padding: '0px'}" class="d-card"> | ||||||
|               </div> |                     <el-row :gutter="24"> | ||||||
|  |                         <el-col :span="10"> | ||||||
|  |                             <div> | ||||||
|  |                                 <ko-charts :chart-data="resource" :key="resource.name"></ko-charts> | ||||||
|  |                             </div> | ||||||
|  |                         </el-col> | ||||||
|  |                         <el-col :span="14"> | ||||||
|  |                             <div class="card-content"> | ||||||
|  |                                 <span>{{ resource.name }}</span> | ||||||
|  |                                 <h1>{{ resource.count }}</h1> | ||||||
|  |                             </div> | ||||||
|  |                         </el-col> | ||||||
|  |                     </el-row> | ||||||
|  |                 </el-card> | ||||||
|             </el-col> |             </el-col> | ||||||
|             <el-col :span="6"> |         </el-row> | ||||||
|               <span class="title">{{ $t("business.cluster.nodes") }}</span> |             <el-row :gutter="24" v-has-permissions="{apiGroup:'',resource:'events',verb:'list'}"> | ||||||
|               <div class="line"></div> | <!--        <el-row :gutter="24">--> | ||||||
|               <div style="text-align: center"> |  | ||||||
|                 <h1>2</h1> |             <h3>Events</h3> | ||||||
|               </div> |             <complex-table :pagination-config="page" :data="events" @search="search()" v-loading="loading"> | ||||||
|             </el-col> |                 <el-table-column label="Reason" prop="reason" fix max-width="50px"> | ||||||
|             <el-col :span="6"> |                     <template v-slot:default="{row}"> | ||||||
|               <span class="title">{{ $t("commons.table.created_time") }}</span> |                         {{ row.reason }} | ||||||
|               <div class="line"></div> |                     </template> | ||||||
|               <div style="text-align: center"> |                 </el-table-column> | ||||||
|                 <h1>16 days ago</h1> |                 <el-table-column label="Namespace" prop="namespace" fix max-width="50px"> | ||||||
|               </div> |                     <template v-slot:default="{row}"> | ||||||
|             </el-col> |                         {{ row.metadata.namespace }} | ||||||
|           </el-row> |                     </template> | ||||||
|         </el-card> |                 </el-table-column> | ||||||
|       </el-col> |                 <el-table-column label="Message" prop="resource" fix min-width="200px" show-overflow-tooltip> | ||||||
|     </el-row> |                     <template v-slot:default="{row}"> | ||||||
|     <br> |                         {{ row.message }} | ||||||
|     <el-row :gutter="24" class="resources"> |                     </template> | ||||||
|       <el-col :span="4" v-for="resource in resources" v-bind:key="resource.name"> |                 </el-table-column> | ||||||
|         <el-card :body-style="{padding: '0px'}" class="d-card"> |                 <el-table-column label="Resource" prop="resource" fix min-width="200px" show-overflow-tooltip> | ||||||
|           <el-row :gutter="24"> |                     <template v-slot:default="{row}"> | ||||||
|             <el-col :span="10"> |                         <el-link>{{ row.involvedObject.kind }} / {{ row.involvedObject.name }}</el-link> | ||||||
|               <div> |                     </template> | ||||||
|                 <ko-charts :chart-data="resource" :key="resource.name"></ko-charts> |                 </el-table-column> | ||||||
|               </div> |                 <el-table-column :label="$t('commons.table.time')" prop="metadata.creationTimestamp" fix> | ||||||
|             </el-col> |                     <template v-slot:default="{row}"> | ||||||
|             <el-col :span="14"> |                         {{ row.eventTime | age }} | ||||||
|               <div class="card-content"> |                     </template> | ||||||
|                 <span>{{ resource.name }}</span> |                 </el-table-column> | ||||||
|                 <h1>{{ resource.count }}</h1> |             </complex-table> | ||||||
|               </div> |         </el-row> | ||||||
|             </el-col> |     </layout-content> | ||||||
|           </el-row> |  | ||||||
|         </el-card> |  | ||||||
|       </el-col> |  | ||||||
|     </el-row> |  | ||||||
|     <el-row :gutter="24"> |  | ||||||
|       <h3>Events</h3> |  | ||||||
|       <complex-table :pagination-config="page" :data="events" @search="search()" v-loading="loading"> |  | ||||||
|         <el-table-column label="Reason" prop="reason" fix max-width="50px"> |  | ||||||
|           <template v-slot:default="{row}"> |  | ||||||
|             {{ row.reason }} |  | ||||||
|           </template> |  | ||||||
|         </el-table-column> |  | ||||||
|         <el-table-column label="Namespace" prop="namespace" fix max-width="50px"> |  | ||||||
|           <template v-slot:default="{row}"> |  | ||||||
|             {{ row.metadata.namespace }} |  | ||||||
|           </template> |  | ||||||
|         </el-table-column> |  | ||||||
|         <el-table-column label="Message" prop="resource" fix min-width="200px" show-overflow-tooltip> |  | ||||||
|           <template v-slot:default="{row}"> |  | ||||||
|             {{ row.message }} |  | ||||||
|           </template> |  | ||||||
|         </el-table-column> |  | ||||||
|         <el-table-column label="Resource" prop="resource" fix min-width="200px" show-overflow-tooltip> |  | ||||||
|           <template v-slot:default="{row}"> |  | ||||||
|             <el-link>{{ row.involvedObject.kind }} / {{ row.involvedObject.name }}</el-link> |  | ||||||
|           </template> |  | ||||||
|         </el-table-column> |  | ||||||
|         <el-table-column :label="$t('commons.table.time')" prop="metadata.creationTimestamp" fix> |  | ||||||
|           <template v-slot:default="{row}"> |  | ||||||
|             {{ row.eventTime | age }} |  | ||||||
|           </template> |  | ||||||
|         </el-table-column> |  | ||||||
|       </complex-table> |  | ||||||
|     </el-row> |  | ||||||
|   </layout-content> |  | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import LayoutContent from "@/components/layout/LayoutContent" |     import LayoutContent from "@/components/layout/LayoutContent" | ||||||
| import KoCharts from "@/components/ko-charts" |     import KoCharts from "@/components/ko-charts" | ||||||
| import {listNamespace} from "@/api/namespaces" |     import {listNamespace} from "@/api/namespaces" | ||||||
| import {listIngresses} from "@/api/ingress" |     import {listIngresses} from "@/api/ingress" | ||||||
| import {listPvs} from "@/api/pv" |     import {listPvs} from "@/api/pv" | ||||||
| import {listDeployments} from "@/api/deployments" |     import {listDeployments} from "@/api/deployments" | ||||||
| import {listStatefulSets} from "@/api/statefulsets" |     import {listStatefulSets} from "@/api/statefulsets" | ||||||
| import {listJobs} from "@/api/jobs" |     import {listJobs} from "@/api/jobs" | ||||||
| import {listDaemonSets} from "@/api/daemonsets" |     import {listDaemonSets} from "@/api/daemonsets" | ||||||
| import {listServices} from "@/api/services" |     import {listServices} from "@/api/services" | ||||||
| import ComplexTable from "@/components/complex-table" |     import {listNodes} from "@/api/nodes" | ||||||
| import {listEvents} from "@/api/events" |     import ComplexTable from "@/components/complex-table" | ||||||
|  |     import {listEvents} from "@/api/events" | ||||||
|  |     import {getCluster} from "@/api/clusters" | ||||||
|  |     import {checkPermissions} from "@/utils/permission" | ||||||
|  |  | ||||||
| export default { |     export default { | ||||||
|   name: "Dashboard", |         name: "Dashboard", | ||||||
|   components: { ComplexTable, KoCharts, LayoutContent }, |         components: {ComplexTable, KoCharts, LayoutContent}, | ||||||
|   data () { |         data() { | ||||||
|     return { |             return { | ||||||
|       clusterName: "", |                 cluster: null, | ||||||
|       resources: [], |                 clusterName: "", | ||||||
|       page: { |                 resources: [], | ||||||
|         pageSize: 10, |                 page: { | ||||||
|         nextToken: "" |                     pageSize: 10, | ||||||
|       }, |                     nextToken: "" | ||||||
|       events: [], |                 }, | ||||||
|       loading: false |                 events: [], | ||||||
|     } |                 loading: false | ||||||
|   }, |  | ||||||
|   methods: { |  | ||||||
|     listResources () { |  | ||||||
|       listNamespace(this.clusterName).then(res => { |  | ||||||
|         const namespaces = { |  | ||||||
|           name: "Namespaces", |  | ||||||
|           count: res.items.length, |  | ||||||
|           data: this.getData(res.items, "status.phase") |  | ||||||
|         } |  | ||||||
|         this.resources.push(namespaces) |  | ||||||
|       }) |  | ||||||
|       listIngresses(this.clusterName).then(res => { |  | ||||||
|         const ingresses = { |  | ||||||
|           name: "Ingresses", |  | ||||||
|           count: res.items.length, |  | ||||||
|           data: [{ |  | ||||||
|             value: res.items.length, |  | ||||||
|             name: "" |  | ||||||
|           }] |  | ||||||
|         } |  | ||||||
|         this.resources.push(ingresses) |  | ||||||
|       }) |  | ||||||
|       listPvs(this.clusterName).then(res => { |  | ||||||
|         const persistentVolumes = { |  | ||||||
|           name: "PersistentVolumes", |  | ||||||
|           count: res.items.length, |  | ||||||
|           data: this.getData(res.items, "status.phase") |  | ||||||
|         } |  | ||||||
|         this.resources.push(persistentVolumes) |  | ||||||
|       }) |  | ||||||
|       listDeployments(this.clusterName).then(res => { |  | ||||||
|         const deployments = { |  | ||||||
|           name: "Deployments", |  | ||||||
|           count: res.items.length, |  | ||||||
|           data: this.getData(res.items, "status.conditions.type") |  | ||||||
|         } |  | ||||||
|         this.resources.push(deployments) |  | ||||||
|       }) |  | ||||||
|       listStatefulSets(this.clusterName).then(res => { |  | ||||||
|         const statefulSets = { |  | ||||||
|           name: "StatefulSets", |  | ||||||
|           count: res.items.length, |  | ||||||
|           data: this.getData(res.items, "status.replicas") |  | ||||||
|         } |  | ||||||
|         this.resources.push(statefulSets) |  | ||||||
|       }) |  | ||||||
|       listJobs(this.clusterName).then(res => { |  | ||||||
|         const jobs = { |  | ||||||
|           name: "Jobs", |  | ||||||
|           count: res.items.length, |  | ||||||
|           data: this.getData(res.items, "metadata.status.active") |  | ||||||
|         } |  | ||||||
|         this.resources.push(jobs) |  | ||||||
|       }) |  | ||||||
|       listDaemonSets(this.clusterName).then(res => { |  | ||||||
|         const daemonSets = { |  | ||||||
|           name: "DaemonSets", |  | ||||||
|           count: res.items.length, |  | ||||||
|           data: this.getData(res.items, "status.numberUnavailable") |  | ||||||
|         } |  | ||||||
|         this.resources.push(daemonSets) |  | ||||||
|       }) |  | ||||||
|       listServices(this.clusterName).then(res => { |  | ||||||
|         const services = { |  | ||||||
|           name: "Services", |  | ||||||
|           count: res.items.length, |  | ||||||
|           data: this.getData(res.items, "status.loadBalancer") |  | ||||||
|         } |  | ||||||
|         this.resources.push(services) |  | ||||||
|       }) |  | ||||||
|       this.search() |  | ||||||
|     }, |  | ||||||
|     search () { |  | ||||||
|       this.loading = true |  | ||||||
|       listEvents(this.clusterName, this.page.pageSize, this.page.nextToken).then(res => { |  | ||||||
|         this.loading = false |  | ||||||
|         this.events = res.items |  | ||||||
|         this.page.nextToken = res.metadata["continue"] ? res.metadata["continue"] : "" |  | ||||||
|       }) |  | ||||||
|     }, |  | ||||||
|     getData (items, keys) { |  | ||||||
|       let key = [] |  | ||||||
|       let result = [] |  | ||||||
|       for (const item of items) { |  | ||||||
|         const name = this.traverse(item, keys) |  | ||||||
|         if (key.indexOf(name) === -1) { |  | ||||||
|           key.push(name) |  | ||||||
|           const d = { |  | ||||||
|             value: 1, |  | ||||||
|             name: name |  | ||||||
|           } |  | ||||||
|           result.push(d) |  | ||||||
|         } else { |  | ||||||
|           for (let i = 0; i < result.length; i++) { |  | ||||||
|             if (result[i].name === name) { |  | ||||||
|               result[i].value++ |  | ||||||
|               break |  | ||||||
|             } |             } | ||||||
|           } |         }, | ||||||
|  |         methods: { | ||||||
|  |  | ||||||
|  |             fromNow(date) { | ||||||
|  |                 const a = new Date(date) | ||||||
|  |                 const b = new Date() | ||||||
|  |                 return parseInt(Math.abs(b - a) / 1000 / 60 / 60 / 24) | ||||||
|  |             }, | ||||||
|  |             listResources() { | ||||||
|  |                 getCluster(this.clusterName).then(res => { | ||||||
|  |                     this.cluster = res.data; | ||||||
|  |                 }) | ||||||
|  |                 if (checkPermissions({apiGroup: "", resource: "nodes", verb: "list"})) { | ||||||
|  |                     listNodes(this.clusterName).then(res => { | ||||||
|  |                         const nodes = { | ||||||
|  |                             name: "Nodes", | ||||||
|  |                             count: res.items.length, | ||||||
|  |                             data: this.getData(res.items, "status.phase") | ||||||
|  |                         } | ||||||
|  |                         this.resources.push(nodes) | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |                 if (checkPermissions({apiGroup: "", resource: "namespaces", verb: "list"})) { | ||||||
|  |                     listNamespace(this.clusterName).then(res => { | ||||||
|  |                         const namespaces = { | ||||||
|  |                             name: "Namespaces", | ||||||
|  |                             count: res.items.length, | ||||||
|  |                             data: this.getData(res.items, "status.phase") | ||||||
|  |                         } | ||||||
|  |                         this.resources.push(namespaces) | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |                 if (checkPermissions({apiGroup: "networking.k8s.io", resource: "ingresses", verb: "list"})) { | ||||||
|  |                     listIngresses(this.clusterName).then(res => { | ||||||
|  |                         const ingresses = { | ||||||
|  |                             name: "Ingresses", | ||||||
|  |                             count: res.items.length, | ||||||
|  |                             data: [{ | ||||||
|  |                                 value: res.items.length, | ||||||
|  |                                 name: "" | ||||||
|  |                             }] | ||||||
|  |                         } | ||||||
|  |                         this.resources.push(ingresses) | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |                 if (checkPermissions({apiGroup: "", resource: "persistentvolumes", verb: "list"})) { | ||||||
|  |                     listPvs(this.clusterName).then(res => { | ||||||
|  |                         const persistentVolumes = { | ||||||
|  |                             name: "PersistentVolumes", | ||||||
|  |                             count: res.items.length, | ||||||
|  |                             data: this.getData(res.items, "status.phase") | ||||||
|  |                         } | ||||||
|  |                         this.resources.push(persistentVolumes) | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |                 if (checkPermissions({apiGroup: "apps", resource: "deployments", verb: "list"})) { | ||||||
|  |                     listDeployments(this.clusterName).then(res => { | ||||||
|  |                         const deployments = { | ||||||
|  |                             name: "Deployments", | ||||||
|  |                             count: res.items.length, | ||||||
|  |                             data: this.getData(res.items, "status.conditions.type") | ||||||
|  |                         } | ||||||
|  |                         this.resources.push(deployments) | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |                 if (checkPermissions({apiGroup: "apps", resource: "statefulsets", verb: "list"})) { | ||||||
|  |                     listStatefulSets(this.clusterName).then(res => { | ||||||
|  |                         const statefulSets = { | ||||||
|  |                             name: "StatefulSets", | ||||||
|  |                             count: res.items.length, | ||||||
|  |                             data: this.getData(res.items, "status.replicas") | ||||||
|  |                         } | ||||||
|  |                         this.resources.push(statefulSets) | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |                 if (checkPermissions({apiGroup: "batch", resource: "jobs", verb: "list"})) { | ||||||
|  |                     listJobs(this.clusterName).then(res => { | ||||||
|  |                         const jobs = { | ||||||
|  |                             name: "Jobs", | ||||||
|  |                             count: res.items.length, | ||||||
|  |                             data: this.getData(res.items, "metadata.status.active") | ||||||
|  |                         } | ||||||
|  |                         this.resources.push(jobs) | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |                 if (checkPermissions({apiGroup: "apps", resource: "daemonsets", verb: "list"})) { | ||||||
|  |                     listDaemonSets(this.clusterName).then(res => { | ||||||
|  |                         const daemonSets = { | ||||||
|  |                             name: "DaemonSets", | ||||||
|  |                             count: res.items.length, | ||||||
|  |                             data: this.getData(res.items, "status.numberUnavailable") | ||||||
|  |                         } | ||||||
|  |                         this.resources.push(daemonSets) | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |                 if (checkPermissions({apiGroup: "", resource: "services", verb: "list"})) { | ||||||
|  |                     listServices(this.clusterName).then(res => { | ||||||
|  |                         const services = { | ||||||
|  |                             name: "Services", | ||||||
|  |                             count: res.items.length, | ||||||
|  |                             data: this.getData(res.items, "status.loadBalancer") | ||||||
|  |                         } | ||||||
|  |                         this.resources.push(services) | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |                 this.search() | ||||||
|  |             }, | ||||||
|  |             search() { | ||||||
|  |                 this.loading = true | ||||||
|  |                 if (checkPermissions({apiGroup: "", resource: "events", verb: "list"})) { | ||||||
|  |                     listEvents(this.clusterName, this.page.pageSize, this.page.nextToken).then(res => { | ||||||
|  |                         this.loading = false | ||||||
|  |                         this.events = res.items | ||||||
|  |                         this.page.nextToken = res.metadata["continue"] ? res.metadata["continue"] : "" | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             getData(items, keys) { | ||||||
|  |                 let key = [] | ||||||
|  |                 let result = [] | ||||||
|  |                 for (const item of items) { | ||||||
|  |                     const name = this.traverse(item, keys) | ||||||
|  |                     if (key.indexOf(name) === -1) { | ||||||
|  |                         key.push(name) | ||||||
|  |                         const d = { | ||||||
|  |                             value: 1, | ||||||
|  |                             name: name | ||||||
|  |                         } | ||||||
|  |                         result.push(d) | ||||||
|  |                     } else { | ||||||
|  |                         for (let i = 0; i < result.length; i++) { | ||||||
|  |                             if (result[i].name === name) { | ||||||
|  |                                 result[i].value++ | ||||||
|  |                                 break | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 return result | ||||||
|  |             }, | ||||||
|  |             traverse(obj, keys) { | ||||||
|  |                 if (keys === "status.conditions.type") { | ||||||
|  |                     if (obj.status.conditions[0].type && obj.status.conditions[0].status === "True") { | ||||||
|  |                         return obj.status.conditions[0].type | ||||||
|  |                     } else { | ||||||
|  |                         return "Error" | ||||||
|  |                     } | ||||||
|  |                 } else if (keys === "status.replicas") { | ||||||
|  |                     return "In Progress" | ||||||
|  |                 } else if (keys === "metadata.status.active" || keys === "status.loadBalancer") { | ||||||
|  |                     return "Active" | ||||||
|  |                 } else if (keys === "status.numberUnavailable") { | ||||||
|  |                     if (obj.status.numberUnavailable > 0) { | ||||||
|  |                         return "Error" | ||||||
|  |                     } else { | ||||||
|  |                         return "Active" | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     return keys.split(".").reduce(function (cur, key) { | ||||||
|  |                         return cur[key] | ||||||
|  |                     }, obj) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         created() { | ||||||
|  |             this.clusterName = this.$route.query.cluster | ||||||
|  |             this.listResources() | ||||||
|         } |         } | ||||||
|       } |  | ||||||
|       return result |  | ||||||
|     }, |  | ||||||
|     traverse (obj, keys) { |  | ||||||
|       if (keys === "status.conditions.type") { |  | ||||||
|         if (obj.status.conditions[0].type && obj.status.conditions[0].status === "True") { |  | ||||||
|           return obj.status.conditions[0].type |  | ||||||
|         } else { |  | ||||||
|           return "Error" |  | ||||||
|         } |  | ||||||
|       } else if (keys === "status.replicas") { |  | ||||||
|         return "In Progress" |  | ||||||
|       } else if (keys === "metadata.status.active" || keys === "status.loadBalancer") { |  | ||||||
|         return "Active" |  | ||||||
|       } else if (keys === "status.numberUnavailable") { |  | ||||||
|         if (obj.status.numberUnavailable > 0) { |  | ||||||
|           return "Error" |  | ||||||
|         } else { |  | ||||||
|           return "Active" |  | ||||||
|         } |  | ||||||
|       } else { |  | ||||||
|         return keys.split(".").reduce(function (cur, key) { |  | ||||||
|           return cur[key] |  | ||||||
|         }, obj) |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|   }, |  | ||||||
|   created () { |  | ||||||
|     this.clusterName = this.$route.query.cluster |  | ||||||
|     this.listResources() |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style scoped> | <style scoped> | ||||||
|   | |||||||
| @@ -101,7 +101,6 @@ | |||||||
|                     if (valid) { |                     if (valid) { | ||||||
|                         this.loading = true |                         this.loading = true | ||||||
|                         this.$store.dispatch("user/login", this.form).then(() => { |                         this.$store.dispatch("user/login", this.form).then(() => { | ||||||
|                             console.log("123") |  | ||||||
|                             this.$router.push({path: this.redirect || "/", query: this.otherQuery}) |                             this.$router.push({path: this.redirect || "/", query: this.otherQuery}) | ||||||
|                             this.loading = false |                             this.loading = false | ||||||
|                         }).catch(() => { |                         }).catch(() => { | ||||||
|   | |||||||
| @@ -3,24 +3,24 @@ | |||||||
|        :class="{'collapse':collapse}"> |        :class="{'collapse':collapse}"> | ||||||
|     <transition name="sidebar-logo-fade" |     <transition name="sidebar-logo-fade" | ||||||
|                 mode="out-in"> |                 mode="out-in"> | ||||||
|       <router-link v-if="collapse" |       <div v-if="collapse" | ||||||
|                    key="collapse" |            key="collapse" | ||||||
|                    class="sidebar-logo-link" |            class="sidebar-logo-link" | ||||||
|                    to="/"> |            to="/"> | ||||||
|         <img v-if="collapseLogo" |         <img v-if="collapseLogo" | ||||||
|              :src="collapseLogo" |              :src="collapseLogo" | ||||||
|              class="sidebar-logo" |              class="sidebar-logo" | ||||||
|              alt="Sidebar Logo"> |              alt="Sidebar Logo"> | ||||||
|       </router-link> |       </div> | ||||||
|       <router-link v-else |       <div v-else | ||||||
|                    key="expand" |            key="expand" | ||||||
|                    class="sidebar-logo-link" |            class="sidebar-logo-link" | ||||||
|                    to="/"> |            to="/"> | ||||||
|         <img v-if="logo" |         <img v-if="logo" | ||||||
|              :src="logo" |              :src="logo" | ||||||
|              class="sidebar-logo" |              class="sidebar-logo" | ||||||
|              alt="Sidebar Logo"> |              alt="Sidebar Logo"> | ||||||
|       </router-link> |       </div> | ||||||
|     </transition> |     </transition> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| @@ -48,59 +48,59 @@ export default { | |||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss"> | <style lang="scss"> | ||||||
|   @import "~@/styles/common/variables"; | @import "~@/styles/common/variables"; | ||||||
|  |  | ||||||
|   .sidebar-logo-container { | .sidebar-logo-container { | ||||||
|     position: relative; |   position: relative; | ||||||
|     height: $header-height; |   height: $header-height; | ||||||
|     line-height: $header-height; |   line-height: $header-height; | ||||||
|     overflow: hidden; |   overflow: hidden; | ||||||
|  |  | ||||||
|     &:after { |   &:after { | ||||||
|       content: ""; |     content: ""; | ||||||
|       position: absolute; |     position: absolute; | ||||||
|       bottom: 0; |     bottom: 0; | ||||||
|       right: #{$sidebar-close-width / 4}; |     right: #{$sidebar-close-width / 4}; | ||||||
|       height: 1px; |     height: 1px; | ||||||
|       width: calc(100% - #{$sidebar-close-width / 2}); |     width: calc(100% - #{$sidebar-close-width / 2}); | ||||||
|       background-color: hsla(0, 0%, 100%, .5); |     background-color: hsla(0, 0%, 100%, .5); | ||||||
|     } |   } | ||||||
|  |  | ||||||
|     & .sidebar-logo-link { |   & .sidebar-logo-link { | ||||||
|       padding: 0 20px; |     padding: 0 20px; | ||||||
|       display: flex; |     display: flex; | ||||||
|       align-items: center; |     align-items: center; | ||||||
|       height: 100%; |     height: 100%; | ||||||
|       width: auto; |     width: auto; | ||||||
|  |  | ||||||
|       & .sidebar-logo { |     & .sidebar-logo { | ||||||
|         height: $logo-height; |       height: $logo-height; | ||||||
|         vertical-align: middle; |       vertical-align: middle; | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     &.collapse { |  | ||||||
|       .sidebar-logo-link { |  | ||||||
|         padding: 0 10px; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       .sidebar-logo { |  | ||||||
|         margin: auto; |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   .sidebar-logo-fade-enter-active { |   &.collapse { | ||||||
|     transition: opacity 0.1s; |     .sidebar-logo-link { | ||||||
|     transition-delay: 0.1s; |       padding: 0 10px; | ||||||
|   } |     } | ||||||
|  |  | ||||||
|   .sidebar-logo-fade-leave-active { |     .sidebar-logo { | ||||||
|     opacity: 0; |       margin: auto; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|   .sidebar-logo-fade-enter, | .sidebar-logo-fade-enter-active { | ||||||
|   .sidebar-logo-fade-leave-to { |   transition: opacity 0.1s; | ||||||
|     opacity: 0; |   transition-delay: 0.1s; | ||||||
|   } | } | ||||||
|  |  | ||||||
|  | .sidebar-logo-fade-leave-active { | ||||||
|  |   opacity: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .sidebar-logo-fade-enter, | ||||||
|  | .sidebar-logo-fade-leave-to { | ||||||
|  |   opacity: 0; | ||||||
|  | } | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -60,6 +60,9 @@ export default { | |||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     hasOneShowingChild(children = [], parent) { |     hasOneShowingChild(children = [], parent) { | ||||||
|  |       if (parent.parent){ | ||||||
|  |         return false | ||||||
|  |       } | ||||||
|       const showingChildren = children.filter(item => { |       const showingChildren = children.filter(item => { | ||||||
|         if (item.hidden) { |         if (item.hidden) { | ||||||
|           return false |           return false | ||||||
|   | |||||||
| @@ -48,7 +48,6 @@ | |||||||
|             }, |             }, | ||||||
|         }, |         }, | ||||||
|         created() { |         created() { | ||||||
|             console.log(this.permission_routes) |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| </script> | </script> | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								web/dashboard/src/directive/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								web/dashboard/src/directive/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | import Permission from "./permission"; | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |     install(Vue) { | ||||||
|  |         Vue.directive('has-permissions', Permission.hasPermissions); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										26
									
								
								web/dashboard/src/directive/permission/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								web/dashboard/src/directive/permission/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | import {checkPermissions} from '@/utils/permission' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | function hasPermissions(el, binding) { | ||||||
|  |     const {value, modifiers} = binding | ||||||
|  |     const hasPermissions = checkPermissions(value) | ||||||
|  |     if (!hasPermissions) { | ||||||
|  |         if (modifiers && modifiers.disable) { | ||||||
|  |             el.setAttribute("disabled", true) | ||||||
|  |         } else { | ||||||
|  |             el.parentNode && el.parentNode.removeChild(el) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |     hasPermissions: { | ||||||
|  |         inserted(el, binding) { | ||||||
|  |             hasPermissions(el, binding) | ||||||
|  |         }, | ||||||
|  |         update(el, binding) { | ||||||
|  |             hasPermissions(el, binding) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -19,6 +19,8 @@ import "./permission" | |||||||
| import "@/styles/common/ekko.css" | import "@/styles/common/ekko.css" | ||||||
| import filters from "./filters"; | import filters from "./filters"; | ||||||
| import JsonViewer from 'vue-json-viewer' | import JsonViewer from 'vue-json-viewer' | ||||||
|  | import directives from "./directive"; | ||||||
|  |  | ||||||
|  |  | ||||||
| Vue.config.productionTip = false | Vue.config.productionTip = false | ||||||
|  |  | ||||||
| @@ -34,6 +36,7 @@ library.add(fas, far, fab) | |||||||
|  |  | ||||||
| Vue.use(icons); | Vue.use(icons); | ||||||
| Vue.use(filters); | Vue.use(filters); | ||||||
|  | Vue.use(directives) | ||||||
| Vue.use(JsonViewer) | Vue.use(JsonViewer) | ||||||
|  |  | ||||||
| new Vue({ | new Vue({ | ||||||
|   | |||||||
| @@ -2,60 +2,72 @@ import router from "./router" | |||||||
| import NProgress from "nprogress" | import NProgress from "nprogress" | ||||||
| import "nprogress/nprogress.css" | import "nprogress/nprogress.css" | ||||||
| import store from "./store" | import store from "./store" | ||||||
|  | import Layout from "@/business/app-layout/horizontal-layout" | ||||||
|  |  | ||||||
| NProgress.configure({ showSpinner: false }) // NProgress Configuration |  | ||||||
|  | NProgress.configure({showSpinner: false}) // NProgress Configuration | ||||||
|  |  | ||||||
| const whiteList = ["/login"] // no redirect whitelist | const whiteList = ["/login"] // no redirect whitelist | ||||||
|  |  | ||||||
| const generateRoutes = async (to, from, next) => { | const generateRoutes = async (to, from, next) => { | ||||||
|   const hasRoles = store.getters.roles && store.getters.roles.length > 0 |     const hasRoles = store.getters.clusterRoles && store.getters.clusterRoles.length > 0 | ||||||
|   if (hasRoles) { |     if (hasRoles) { | ||||||
|     next() |         next() | ||||||
|   } else { |     } else { | ||||||
|     try { |         try { | ||||||
|       const user = await store.dispatch("user/getCurrentUser") |             const {clusterRoles} = await store.dispatch("user/getCurrentUser") | ||||||
|       const accessRoutes = await store.dispatch("permission/generateRoutes", user) |             const accessRoutes = await store.dispatch("permission/generateRoutes", clusterRoles) | ||||||
|       router.addRoutes(accessRoutes) |             if (accessRoutes.length > 0) { | ||||||
|       next({ ...to, replace: true }) |                 const root = { | ||||||
|     } catch (error) { |                     path: "/", | ||||||
|       NProgress.done() |                     component: Layout, | ||||||
|  |                     redirect: accessRoutes[0].path, | ||||||
|  |                 } | ||||||
|  |                 router.addRoute(root) | ||||||
|  |             } | ||||||
|  |             router.addRoutes(accessRoutes) | ||||||
|  |             next({...to, replace: true}) | ||||||
|  |         } catch (error) { | ||||||
|  |             NProgress.done() | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| //路由前置钩子,根据实际需求修改 | //路由前置钩子,根据实际需求修改 | ||||||
| router.beforeEach(async (to, from, next) => { | router.beforeEach(async (to, from, next) => { | ||||||
|   NProgress.start() |     NProgress.start() | ||||||
|   if (!to.query["cluster"]) { |     const isLogin = await store.dispatch("user/isLogin") | ||||||
|     if (from.query["cluster"]) { |     if (isLogin) { | ||||||
|       const q = to.query |         if (to.path === "/login") { | ||||||
|       q["cluster"] = from.query["cluster"] |             next({path: "/"}) | ||||||
|       next({ path: to.path, query: q }) |             NProgress.done() | ||||||
|       NProgress.done() |         } | ||||||
|  |         if (!to.query["cluster"]) { | ||||||
|  |             if (from.query["cluster"]) { | ||||||
|  |                 await store.dispatch("user/setCurrentCluster", from.query["cluster"]) | ||||||
|  |                 const q = to.query | ||||||
|  |                 q["cluster"] = from.query["cluster"] | ||||||
|  |                 next({path: to.path, query: q}) | ||||||
|  |             } else { | ||||||
|  |                 console.log("no cluster") | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             await store.dispatch("user/setCurrentCluster", to.query["cluster"]) | ||||||
|  |         } | ||||||
|  |         await generateRoutes(to, from, next) | ||||||
|     } else { |     } else { | ||||||
|       console.log("no cluster") |         /* has not login*/ | ||||||
|  |         if (whiteList.indexOf(to.path) !== -1) { | ||||||
|  |             // in the free login whitelist, go directly | ||||||
|  |             next() | ||||||
|  |         } else { | ||||||
|  |             // other pages that do not have permission to access are redirected to the login page. | ||||||
|  |             next(`/login?redirect=${to.path}`) | ||||||
|  |             NProgress.done() | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|   } |  | ||||||
|   const isLogin = await store.dispatch("user/isLogin") |  | ||||||
|   if (isLogin) { |  | ||||||
|     if (to.path === "/login") { |  | ||||||
|       next({ path: "/" }) |  | ||||||
|       NProgress.done() |  | ||||||
|     } |  | ||||||
|     await generateRoutes(to, from, next) |  | ||||||
|   } else { |  | ||||||
|     /* has not login*/ |  | ||||||
|     if (whiteList.indexOf(to.path) !== -1) { |  | ||||||
|       // in the free login whitelist, go directly |  | ||||||
|       next() |  | ||||||
|     } else { |  | ||||||
|       // other pages that do not have permission to access are redirected to the login page. |  | ||||||
|       next(`/login?redirect=${to.path}`) |  | ||||||
|       NProgress.done() |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| }) | }) | ||||||
|  |  | ||||||
| router.afterEach(() => { | router.afterEach(() => { | ||||||
|   NProgress.done() |     NProgress.done() | ||||||
| }) | }) | ||||||
|   | |||||||
| @@ -76,11 +76,6 @@ const promise = (request, loading = {}) => { | |||||||
|   return new Promise((resolve, reject) => { |   return new Promise((resolve, reject) => { | ||||||
|     loading.status = true |     loading.status = true | ||||||
|     request.then(response => { |     request.then(response => { | ||||||
|       // if (response.data.success) { |  | ||||||
|       //     resolve(response.data) |  | ||||||
|       // } else { |  | ||||||
|       //     reject(response.message) |  | ||||||
|       // } |  | ||||||
|       resolve(response.data) |       resolve(response.data) | ||||||
|       loading.status = false |       loading.status = false | ||||||
|     }).catch(error => { |     }).catch(error => { | ||||||
|   | |||||||
| @@ -7,62 +7,62 @@ const modules = require.context("./modules", true, /\.js$/) | |||||||
|  |  | ||||||
| // 修复路由变更后报错的问题 | // 修复路由变更后报错的问题 | ||||||
| const routerPush = Router.prototype.push | const routerPush = Router.prototype.push | ||||||
| Router.prototype.push = function push (location) { | Router.prototype.push = function push(location) { | ||||||
|   return routerPush.call(this, location).catch(error => error) |     return routerPush.call(this, location).catch(error => error) | ||||||
| } | } | ||||||
|  |  | ||||||
| Vue.use(Router) | Vue.use(Router) | ||||||
| export const constantRoutes = [ | export const constantRoutes = [ | ||||||
|   { |     { | ||||||
|     path: "/redirect", |         path: "/redirect", | ||||||
|     component: Layout, |         component: Layout, | ||||||
|     hidden: true, |         hidden: true, | ||||||
|     name: "redirect", |         name: "redirect", | ||||||
|     children: [ |         children: [ | ||||||
|       { |             { | ||||||
|         path: "/redirect/:path(.*)", |                 path: "/redirect/:path(.*)", | ||||||
|         component: () => import("@/components/redirect") |                 component: () => import("@/components/redirect") | ||||||
|       } |             } | ||||||
|     ] |         ] | ||||||
|   }, |     }, | ||||||
|   { |     { | ||||||
|     path: "/login", |         path: "/login", | ||||||
|     component: () => import("@/business/login"), |         component: () => import("@/business/login"), | ||||||
|     hidden: true |         hidden: true | ||||||
|   }, |     }, | ||||||
|   { |     { | ||||||
|     path: "/", |         path: "/", | ||||||
|     component: Layout, |         component: Layout, | ||||||
|     redirect: "/dashboard", |         redirect: "/dashboard", | ||||||
|   } |     } | ||||||
| ] | ] | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 用户登录后根据角色加载的路由 |  * 用户登录后根据角色加载的路由 | ||||||
|  */ |  */ | ||||||
| export const rolesRoutes = [ | export const rolesRoutes = [ | ||||||
|   // 先按sort排序 |     // 先按sort排序 | ||||||
|   ...modules.keys().map(key => modules(key).default).sort((r1, r2) => { |     ...modules.keys().map(key => modules(key).default).sort((r1, r2) => { | ||||||
|     r1.sort ??= Number.MAX_VALUE |         r1.sort ??= Number.MAX_VALUE | ||||||
|     r2.sort ??= Number.MAX_VALUE |         r2.sort ??= Number.MAX_VALUE | ||||||
|     return r1.sort - r2.sort |         return r1.sort - r2.sort | ||||||
|   }), |     }), | ||||||
|   { path: "*", redirect: "/", hidden: true } |     {path: "*", redirect: "/", hidden: true} | ||||||
| ] | ] | ||||||
|  |  | ||||||
| const createRouter = () => new Router({ | const createRouter = () => new Router({ | ||||||
|   mode: 'history', |     mode: 'history', | ||||||
|   scrollBehavior: () => ({ y: 0 }), |     scrollBehavior: () => ({y: 0}), | ||||||
|   routes: constantRoutes, |     routes: constantRoutes, | ||||||
|   base: "dashboard" |     base: "dashboard" | ||||||
| }) | }) | ||||||
|  |  | ||||||
| const router = createRouter() | const router = createRouter() | ||||||
|  |  | ||||||
|  |  | ||||||
| export function resetRouter () { | export function resetRouter() { | ||||||
|   const newRouter = createRouter() |     const newRouter = createRouter() | ||||||
|   router.matcher = newRouter.matcher // reset router |     router.matcher = newRouter.matcher // reset router | ||||||
| } | } | ||||||
|  |  | ||||||
| export default router | export default router | ||||||
|   | |||||||
| @@ -1,53 +1,74 @@ | |||||||
| import Layout from "@/business/app-layout/horizontal-layout" | import Layout from "@/business/app-layout/horizontal-layout" | ||||||
|  |  | ||||||
| const AccessControl = { | const AccessControl = { | ||||||
|   path: "/accesscontrol", |     path: "/accesscontrol", | ||||||
|   sort: 5, |     parent: true, | ||||||
|   component: Layout, |     sort: 5, | ||||||
|   name: "Access Control", |     component: Layout, | ||||||
|   meta: { |     name: "Access Control", | ||||||
|     title: "business.access_control.access_control", |     meta: { | ||||||
|     icon: "iconfont iconaccesscontrol", |         title: "business.access_control.access_control", | ||||||
|     global: false |         icon: "iconfont iconaccesscontrol", | ||||||
|   }, |  | ||||||
|   children: [ |  | ||||||
|     { |  | ||||||
|       path: "/serviceaccounts", |  | ||||||
|       component: () => import("@/business/access-control/service-accounts"), |  | ||||||
|       name: "ServiceAccounts", |  | ||||||
|       meta: { |  | ||||||
|         title: "Service Accounts", |  | ||||||
|         global: false |         global: false | ||||||
|       } |  | ||||||
|     }, |     }, | ||||||
|     { |     children: [ | ||||||
|       path: "/rolebindings", |         { | ||||||
|       component: () => import("@/business/access-control/role-bindings"), |             path: "/serviceaccounts", | ||||||
|       name: "RoleBindings", |             requirePermission:{ | ||||||
|       meta: { |                 apiGroup:"", | ||||||
|         title: "Role Bindings", |                 resource:"serviceaccounts", | ||||||
|         global: false |                 verb:"list", | ||||||
|       } |             }, | ||||||
|     }, |             component: () => import("@/business/access-control/service-accounts"), | ||||||
|     { |             name: "ServiceAccounts", | ||||||
|       path: "/roles", |             meta: { | ||||||
|       component: () => import("@/business/access-control/roles"), |                 title: "Service Accounts", | ||||||
|       name: "Roles", |                 global: false | ||||||
|       meta: { |             } | ||||||
|         title: "Roles", |         }, | ||||||
|         global: false |         { | ||||||
|       } |             path: "/rolebindings", | ||||||
|     }, |             requirePermission:{ | ||||||
|     { |                 apiGroup:"rbac.authorization.k8s.io", | ||||||
|       path: "/podsecuritypolicy", |                 resource:"rolebindings", | ||||||
|       component: () => import("@/business/access-control/pod-security-policies"), |                 verb:"list", | ||||||
|       name: "PodSecurityPolicy", |             }, | ||||||
|       meta: { |             component: () => import("@/business/access-control/role-bindings"), | ||||||
|         title: "Pod Security Policy", |             name: "RoleBindings", | ||||||
|         global: false |             meta: { | ||||||
|       } |                 title: "Role Bindings", | ||||||
|     } |                 global: false | ||||||
|   ] |             } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             path: "/roles", | ||||||
|  |             requirePermission:{ | ||||||
|  |                 apiGroup:"rbac.authorization.k8s.io", | ||||||
|  |                 resource:"roles", | ||||||
|  |                 verb:"list", | ||||||
|  |             }, | ||||||
|  |             component: () => import("@/business/access-control/roles"), | ||||||
|  |             name: "Roles", | ||||||
|  |             meta: { | ||||||
|  |                 title: "Roles", | ||||||
|  |                 global: false | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             path: "/podsecuritypolicy", | ||||||
|  |             requirePermission:{ | ||||||
|  |                 apiGroup:"policy", | ||||||
|  |                 resource:"podsecuritypolicies", | ||||||
|  |                 verb:"list", | ||||||
|  |             }, | ||||||
|  |             component: () => import("@/business/access-control/pod-security-policies"), | ||||||
|  |             name: "PodSecurityPolicy", | ||||||
|  |             meta: { | ||||||
|  |                 title: "Pod Security Policy", | ||||||
|  |                 global: false | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     ] | ||||||
| } | } | ||||||
|  |  | ||||||
| export default AccessControl | export default AccessControl | ||||||
|   | |||||||
| @@ -1,79 +1,96 @@ | |||||||
| import Layout from "@/business/app-layout/horizontal-layout" | import Layout from "@/business/app-layout/horizontal-layout" | ||||||
|  |  | ||||||
| const Clusters = { | const Clusters = { | ||||||
|   path: "/cluster", |     path: "/cluster", | ||||||
|   sort: 1, |     parent: true, | ||||||
|   component: Layout, |     global: true, | ||||||
|   name: "Cluster", |     sort: 1, | ||||||
|   meta: { |     component: Layout, | ||||||
|     title: "business.cluster.cluster", |     name: "Cluster", | ||||||
|     icon: "iconfont iconkubernets", |     meta: { | ||||||
|   }, |         title: "business.cluster.cluster", | ||||||
|   children: [ |         icon: "iconfont iconkubernets", | ||||||
|     { |  | ||||||
|       path: "/nodes", |  | ||||||
|       component: () => import("@/business/cluster/nodes"), |  | ||||||
|       name: "Nodes", |  | ||||||
|       meta: { |  | ||||||
|         title: "Nodes", |  | ||||||
|       } |  | ||||||
|     }, |     }, | ||||||
|     { |     children: [ | ||||||
|       path: "/nodes/detail/:name", |         { | ||||||
|       component: () => import("@/business/cluster/nodes/detail"), |             path: "/nodes", | ||||||
|       name: "NodeDetail", |             requirePermission: { | ||||||
|       props: true, |                 apiGroup: "", | ||||||
|       hidden: true, |                 resource: "nodes", | ||||||
|       meta: { |                 verb: "list", | ||||||
|         activeMenu: "/nodes", |             }, | ||||||
|       } |             component: () => import("@/business/cluster/nodes"), | ||||||
|     }, |             name: "Nodes", | ||||||
|     { |             meta: { | ||||||
|       path: "/namespaces", |                 title: "Nodes", | ||||||
|       component: () => import("@/business/cluster/namespaces"), |             } | ||||||
|       name: "Namespaces", |         }, | ||||||
|       meta: { |         { | ||||||
|         title: "Namespaces" |             path: "/nodes/detail/:name", | ||||||
|       } |             component: () => import("@/business/cluster/nodes/detail"), | ||||||
|     }, |             name: "NodeDetail", | ||||||
|     { |             props: true, | ||||||
|       path: "/namespaces/create", |             hidden: true, | ||||||
|       component: () => import("@/business/cluster/namespaces/create"), |             meta: { | ||||||
|       name: "NamespaceCreate", |                 activeMenu: "/nodes", | ||||||
|       hidden: true, |             } | ||||||
|       meta: { |         }, | ||||||
|         activeMenu: "/namespaces" |         { | ||||||
|       } |             path: "/namespaces", | ||||||
|     }, |             component: () => import("@/business/cluster/namespaces"), | ||||||
|     { |             name: "Namespaces", | ||||||
|       path: "/namespaces/detail/:name", |             requirePermission: { | ||||||
|       props: true, |                 apiGroup: "", | ||||||
|       component: () => import("@/business/cluster/namespaces/detail"), |                 resource: "namespaces", | ||||||
|       name: "NamespaceDetail", |                 verb: "list", | ||||||
|       hidden: true, |             }, | ||||||
|       meta: { |             meta: { | ||||||
|         activeMenu: "/namespaces" |                 title: "Namespaces" | ||||||
|       } |             } | ||||||
|     }, |         }, | ||||||
|     { |         { | ||||||
|       path: "/namespaces/edit/:name", |             path: "/namespaces/create", | ||||||
|       props: true, |             component: () => import("@/business/cluster/namespaces/create"), | ||||||
|       component: () => import("@/business/cluster/namespaces/edit"), |             name: "NamespaceCreate", | ||||||
|       name: "NamespaceEdit", |             hidden: true, | ||||||
|       hidden: true, |             meta: { | ||||||
|       meta: { |                 activeMenu: "/namespaces" | ||||||
|         activeMenu: "/namespaces" |             } | ||||||
|       } |         }, | ||||||
|     }, |         { | ||||||
|     { |             path: "/namespaces/detail/:name", | ||||||
|       path: "/events", |             props: true, | ||||||
|       component: () => import("@/business/cluster/events"), |             component: () => import("@/business/cluster/namespaces/detail"), | ||||||
|       name: "events", |             name: "NamespaceDetail", | ||||||
|       meta: { |             hidden: true, | ||||||
|         title: "Events", |             meta: { | ||||||
|       } |                 activeMenu: "/namespaces" | ||||||
|     } |             } | ||||||
|   ] |         }, | ||||||
|  |         { | ||||||
|  |             path: "/namespaces/edit/:name", | ||||||
|  |             props: true, | ||||||
|  |             component: () => import("@/business/cluster/namespaces/edit"), | ||||||
|  |             name: "NamespaceEdit", | ||||||
|  |             hidden: true, | ||||||
|  |             meta: { | ||||||
|  |                 activeMenu: "/namespaces" | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             path: "/events", | ||||||
|  |             component: () => import("@/business/cluster/events"), | ||||||
|  |             name: "events", | ||||||
|  |             requirePermission: { | ||||||
|  |                 apiGroup: "", | ||||||
|  |                 resource: "events", | ||||||
|  |                 verb: "list", | ||||||
|  |             }, | ||||||
|  |             meta: { | ||||||
|  |                 title: "Events", | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     ] | ||||||
| } | } | ||||||
|  |  | ||||||
| export default Clusters | export default Clusters | ||||||
|   | |||||||
| @@ -1,204 +1,205 @@ | |||||||
| import Layout from "@/business/app-layout/horizontal-layout" | import Layout from "@/business/app-layout/horizontal-layout" | ||||||
|  |  | ||||||
| const Configuration = { | const Configuration = { | ||||||
|   path: "/configuration", |     path: "/configuration", | ||||||
|   sort: 2, |     parent: true, | ||||||
|   component: Layout, |     sort: 2, | ||||||
|   name: "Configuration", |     component: Layout, | ||||||
|   meta: { |     name: "Configuration", | ||||||
|     title: "business.configuration.configuration", |     meta: { | ||||||
|     icon: "iconfont iconconfiguration" |         title: "business.configuration.configuration", | ||||||
|   }, |         icon: "iconfont iconconfiguration" | ||||||
|   children: [ |  | ||||||
|     { |  | ||||||
|       path: "/configmaps", |  | ||||||
|       component: () => import("@/business/configuration/config-maps"), |  | ||||||
|       name: "ConfigMaps", |  | ||||||
|       meta: { |  | ||||||
|         title: "Config Maps", |  | ||||||
|       } |  | ||||||
|     }, |     }, | ||||||
|     { |     children: [ | ||||||
|       path: "/configmaps/create", |         { | ||||||
|       component: () => import("@/business/configuration/config-maps/create"), |             path: "/configmaps", | ||||||
|       name: "ConfigMapCreate", |             component: () => import("@/business/configuration/config-maps"), | ||||||
|       props: true, |             name: "ConfigMaps", | ||||||
|       hidden: true, |             meta: { | ||||||
|       meta: { |                 title: "Config Maps", | ||||||
|         activeMenu: "/configmaps" |             } | ||||||
|       } |         }, | ||||||
|     }, |         { | ||||||
|     { |             path: "/configmaps/create", | ||||||
|       path: "/:namespace/configmaps/detail/:name", |             component: () => import("@/business/configuration/config-maps/create"), | ||||||
|       component: () => import("@/business/configuration/config-maps/detail"), |             name: "ConfigMapCreate", | ||||||
|       name: "ConfigMapDetail", |             props: true, | ||||||
|       props: true, |             hidden: true, | ||||||
|       hidden: true, |             meta: { | ||||||
|       meta: { |                 activeMenu: "/configmaps" | ||||||
|         activeMenu: "/configmaps" |             } | ||||||
|       } |         }, | ||||||
|     }, |         { | ||||||
|     { |             path: "/:namespace/configmaps/detail/:name", | ||||||
|       path: "/configmaps/edit/:namespace/:name", |             component: () => import("@/business/configuration/config-maps/detail"), | ||||||
|       component: () => import("@/business/configuration/config-maps/edit"), |             name: "ConfigMapDetail", | ||||||
|       name: "ConfigMapEdit", |             props: true, | ||||||
|       props: true, |             hidden: true, | ||||||
|       hidden: true, |             meta: { | ||||||
|       meta: { |                 activeMenu: "/configmaps" | ||||||
|         activeMenu: "/configmaps" |             } | ||||||
|       } |         }, | ||||||
|     }, |         { | ||||||
|     { |             path: "/configmaps/edit/:namespace/:name", | ||||||
|       path: "/secrets", |             component: () => import("@/business/configuration/config-maps/edit"), | ||||||
|       component: () => import("@/business/configuration/secrets"), |             name: "ConfigMapEdit", | ||||||
|       name: "Secrets", |             props: true, | ||||||
|       meta: { |             hidden: true, | ||||||
|         title: "Secrets", |             meta: { | ||||||
|       } |                 activeMenu: "/configmaps" | ||||||
|     }, |             } | ||||||
|     { |         }, | ||||||
|       path: "/secrets/detail/:namespace/:name", |         { | ||||||
|       component: () => import("@/business/configuration/secrets/detail"), |             path: "/secrets", | ||||||
|       name: "SecretDetail", |             component: () => import("@/business/configuration/secrets"), | ||||||
|       props: true, |             name: "Secrets", | ||||||
|       hidden: true, |             meta: { | ||||||
|       meta: { |                 title: "Secrets", | ||||||
|         activeMenu: "/secrets" |             } | ||||||
|       } |         }, | ||||||
|     }, |         { | ||||||
|     { |             path: "/secrets/detail/:namespace/:name", | ||||||
|       path: "/secrets/create", |             component: () => import("@/business/configuration/secrets/detail"), | ||||||
|       component: () => import("@/business/configuration/secrets/create"), |             name: "SecretDetail", | ||||||
|       name: "SecretCreate", |             props: true, | ||||||
|       props: true, |             hidden: true, | ||||||
|       hidden: true, |             meta: { | ||||||
|       meta: { |                 activeMenu: "/secrets" | ||||||
|         activeMenu: "/secrets" |             } | ||||||
|       } |         }, | ||||||
|     }, |         { | ||||||
|     { |             path: "/secrets/create", | ||||||
|       path: "/:namespace/secrets/edit/:name", |             component: () => import("@/business/configuration/secrets/create"), | ||||||
|       component: () => import("@/business/configuration/secrets/edit"), |             name: "SecretCreate", | ||||||
|       name: "SecretEdit", |             props: true, | ||||||
|       props: true, |             hidden: true, | ||||||
|       hidden: true, |             meta: { | ||||||
|       meta: { |                 activeMenu: "/secrets" | ||||||
|         activeMenu: "/secrets" |             } | ||||||
|       } |         }, | ||||||
|     }, |         { | ||||||
|     { |             path: "/:namespace/secrets/edit/:name", | ||||||
|       path: "/resourcequotas", |             component: () => import("@/business/configuration/secrets/edit"), | ||||||
|       component: () => import("@/business/configuration/resource-quotas"), |             name: "SecretEdit", | ||||||
|       name: "ResourceQuotas", |             props: true, | ||||||
|       meta: { |             hidden: true, | ||||||
|         title: "Resource Quotas", |             meta: { | ||||||
|       } |                 activeMenu: "/secrets" | ||||||
|     }, |             } | ||||||
|     { |         }, | ||||||
|       path: "/:namespace/resourcequotas/detail/:name", |         { | ||||||
|       component: () => import("@/business/configuration/resource-quotas/detail"), |             path: "/resourcequotas", | ||||||
|       name: "ResourceQuotaDetail", |             component: () => import("@/business/configuration/resource-quotas"), | ||||||
|       props: true, |             name: "ResourceQuotas", | ||||||
|       hidden: true, |             meta: { | ||||||
|       meta: { |                 title: "Resource Quotas", | ||||||
|         activeMenu: "/resourcequotas" |             } | ||||||
|       } |         }, | ||||||
|     }, |         { | ||||||
|     { |             path: "/:namespace/resourcequotas/detail/:name", | ||||||
|       path: "/resourcequotas/create", |             component: () => import("@/business/configuration/resource-quotas/detail"), | ||||||
|       component: () => import("@/business/configuration/resource-quotas/create"), |             name: "ResourceQuotaDetail", | ||||||
|       name: "ResourceQuotaCreate", |             props: true, | ||||||
|       props: true, |             hidden: true, | ||||||
|       hidden: true, |             meta: { | ||||||
|       meta: { |                 activeMenu: "/resourcequotas" | ||||||
|         activeMenu: "/resourcequotas" |             } | ||||||
|       } |         }, | ||||||
|     }, |         { | ||||||
|     { |             path: "/resourcequotas/create", | ||||||
|       path: "/resourcequotas/edit/:namespace/:name", |             component: () => import("@/business/configuration/resource-quotas/create"), | ||||||
|       component: () => import("@/business/configuration/resource-quotas/edit"), |             name: "ResourceQuotaCreate", | ||||||
|       name: "ResourceQuotaEdit", |             props: true, | ||||||
|       props: true, |             hidden: true, | ||||||
|       hidden: true, |             meta: { | ||||||
|       meta: { |                 activeMenu: "/resourcequotas" | ||||||
|         activeMenu: "/resourcequotas" |             } | ||||||
|       } |         }, | ||||||
|     }, |         { | ||||||
|     { |             path: "/resourcequotas/edit/:namespace/:name", | ||||||
|       path: "/limitranges", |             component: () => import("@/business/configuration/resource-quotas/edit"), | ||||||
|       component: () => import("@/business/configuration/limit-ranges"), |             name: "ResourceQuotaEdit", | ||||||
|       name: "LimitRanges", |             props: true, | ||||||
|       meta: { |             hidden: true, | ||||||
|         title: "Limit Ranges", |             meta: { | ||||||
|       } |                 activeMenu: "/resourcequotas" | ||||||
|     }, |             } | ||||||
|     { |         }, | ||||||
|       path: "/limitranges/create", |         { | ||||||
|       component: () => import("@/business/configuration/limit-ranges/create"), |             path: "/limitranges", | ||||||
|       name: "LimitRangeCreate", |             component: () => import("@/business/configuration/limit-ranges"), | ||||||
|       hidden: true, |             name: "LimitRanges", | ||||||
|       meta: { |             meta: { | ||||||
|         activeMenu: "/limitranges" |                 title: "Limit Ranges", | ||||||
|       } |             } | ||||||
|     }, |         }, | ||||||
|     { |         { | ||||||
|       path: "/limitranges/detail/:namespace/:name", |             path: "/limitranges/create", | ||||||
|       component: () => import("@/business/configuration/limit-ranges/detail"), |             component: () => import("@/business/configuration/limit-ranges/create"), | ||||||
|       name: "LimitRangeDetail", |             name: "LimitRangeCreate", | ||||||
|       props: true, |             hidden: true, | ||||||
|       hidden: true, |             meta: { | ||||||
|       meta: { |                 activeMenu: "/limitranges" | ||||||
|         activeMenu: "/limitranges" |             } | ||||||
|       } |         }, | ||||||
|     }, |         { | ||||||
|     { |             path: "/limitranges/detail/:namespace/:name", | ||||||
|       path: "/limitranges/edit/:namespace/:name", |             component: () => import("@/business/configuration/limit-ranges/detail"), | ||||||
|       component: () => import("@/business/configuration/limit-ranges/edit"), |             name: "LimitRangeDetail", | ||||||
|       name: "LimitRangeEdit", |             props: true, | ||||||
|       props: true, |             hidden: true, | ||||||
|       hidden: true, |             meta: { | ||||||
|       meta: { |                 activeMenu: "/limitranges" | ||||||
|         activeMenu: "/limitranges" |             } | ||||||
|       } |         }, | ||||||
|     }, |         { | ||||||
|     { |             path: "/limitranges/edit/:namespace/:name", | ||||||
|       path: "/horizontalpodautoscalers", |             component: () => import("@/business/configuration/limit-ranges/edit"), | ||||||
|       component: () => import("@/business/configuration/hpa"), |             name: "LimitRangeEdit", | ||||||
|       name: "HPA", |             props: true, | ||||||
|       meta: { |             hidden: true, | ||||||
|         title: "Horizontal Pod Autoscaler", |             meta: { | ||||||
|       } |                 activeMenu: "/limitranges" | ||||||
|     }, |             } | ||||||
|     { |         }, | ||||||
|       path: "/horizontalpodautoscalers/:namespace/:name/detail", |         { | ||||||
|       component: () => import("@/business/configuration/hpa/detail"), |             path: "/horizontalpodautoscalers", | ||||||
|       name: "HPADetail", |             component: () => import("@/business/configuration/hpa"), | ||||||
|       props: true, |             name: "HPA", | ||||||
|       hidden: true, |             meta: { | ||||||
|       meta: { |                 title: "Horizontal Pod Autoscaler", | ||||||
|         activeMenu: "/horizontalpodautoscalers" |             } | ||||||
|       } |         }, | ||||||
|     }, |         { | ||||||
|     { |             path: "/horizontalpodautoscalers/:namespace/:name/detail", | ||||||
|       path: "/horizontalpodautoscalers/create", |             component: () => import("@/business/configuration/hpa/detail"), | ||||||
|       component: () => import("@/business/configuration/hpa/create"), |             name: "HPADetail", | ||||||
|       name: "HPACreate", |             props: true, | ||||||
|       hidden: true, |             hidden: true, | ||||||
|       meta: { |             meta: { | ||||||
|         activeMenu: "/horizontalpodautoscalers" |                 activeMenu: "/horizontalpodautoscalers" | ||||||
|       } |             } | ||||||
|     }, |         }, | ||||||
|     { |         { | ||||||
|       path: "/horizontalpodautoscalers/:namespace/:name/edit", |             path: "/horizontalpodautoscalers/create", | ||||||
|       component: () => import("@/business/configuration/hpa/edit"), |             component: () => import("@/business/configuration/hpa/create"), | ||||||
|       name: "HPAEdit", |             name: "HPACreate", | ||||||
|       hidden: true, |             hidden: true, | ||||||
|       props: true, |             meta: { | ||||||
|       meta: { |                 activeMenu: "/horizontalpodautoscalers" | ||||||
|         activeMenu: "/horizontalpodautoscalers" |             } | ||||||
|       } |         }, | ||||||
|     } |         { | ||||||
|   ] |             path: "/horizontalpodautoscalers/:namespace/:name/edit", | ||||||
|  |             component: () => import("@/business/configuration/hpa/edit"), | ||||||
|  |             name: "HPAEdit", | ||||||
|  |             hidden: true, | ||||||
|  |             props: true, | ||||||
|  |             meta: { | ||||||
|  |                 activeMenu: "/horizontalpodautoscalers" | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     ] | ||||||
| } | } | ||||||
|  |  | ||||||
| export default Configuration | export default Configuration | ||||||
|   | |||||||
| @@ -1,22 +1,23 @@ | |||||||
| import Layout from "@/business/app-layout/horizontal-layout" | import Layout from "@/business/app-layout/horizontal-layout" | ||||||
|  |  | ||||||
| const Dashboard = { | const Dashboard = { | ||||||
|   path: "/dashboard", |     path: "/dashboard", | ||||||
|   sort: 0, |     sort: 0, | ||||||
|   component: Layout, |     global: true, | ||||||
|   name: "Dashboard", |     component: Layout, | ||||||
|   children: [ |     name: "Dashboard", | ||||||
|     { |     children: [ | ||||||
|       path: "/dashboard", |         { | ||||||
|       component: () => import("@/business/dashboard"), |             path: "/dashboard", | ||||||
|       name: "Dashboard", |             component: () => import("@/business/dashboard"), | ||||||
|       meta: { |             name: "Dashboard", | ||||||
|         title: "business.dashboard.dashboard", |             meta: { | ||||||
|         icon: "el-icon-data-line", |                 title: "business.dashboard.dashboard", | ||||||
|         roles: ['ADMIN'] |                 icon: "el-icon-data-line", | ||||||
|       }, |                 roles: ['ADMIN'] | ||||||
|     }, |             }, | ||||||
|   ] |         }, | ||||||
|  |     ] | ||||||
| } | } | ||||||
|  |  | ||||||
| export default Dashboard | export default Dashboard | ||||||
|   | |||||||
| @@ -12,6 +12,11 @@ const Network = { | |||||||
|   children: [ |   children: [ | ||||||
|     { |     { | ||||||
|       path: "/services", |       path: "/services", | ||||||
|  |       requirePermission: { | ||||||
|  |         apiGroup: "", | ||||||
|  |         resource: "services", | ||||||
|  |         verb: "list", | ||||||
|  |       }, | ||||||
|       component: () => import("@/business/network/services"), |       component: () => import("@/business/network/services"), | ||||||
|       name: "Services", |       name: "Services", | ||||||
|       meta: { |       meta: { | ||||||
| @@ -49,6 +54,11 @@ const Network = { | |||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       path: "/ingresses", |       path: "/ingresses", | ||||||
|  |       requirePermission: { | ||||||
|  |         apiGroup: "networking.k8s.io", | ||||||
|  |         resource: "ingresses", | ||||||
|  |         verb: "list", | ||||||
|  |       }, | ||||||
|       component: () => import("@/business/network/ingresses"), |       component: () => import("@/business/network/ingresses"), | ||||||
|       name: "Ingresses", |       name: "Ingresses", | ||||||
|       meta: { |       meta: { | ||||||
| @@ -67,6 +77,11 @@ const Network = { | |||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       path: "/endpoints", |       path: "/endpoints", | ||||||
|  |       requirePermission: { | ||||||
|  |         apiGroup: "", | ||||||
|  |         resource: "endpoints", | ||||||
|  |         verb: "list", | ||||||
|  |       }, | ||||||
|       component: () => import("@/business/network/endpoints"), |       component: () => import("@/business/network/endpoints"), | ||||||
|       name: "Endpoints", |       name: "Endpoints", | ||||||
|       meta: { |       meta: { | ||||||
| @@ -75,6 +90,11 @@ const Network = { | |||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       path: "/networkpolicies", |       path: "/networkpolicies", | ||||||
|  |       requirePermission: { | ||||||
|  |         apiGroup: "", | ||||||
|  |         resource: "networkpolicies", | ||||||
|  |         verb: "list", | ||||||
|  |       }, | ||||||
|       component: () => import("@/business/network/network-policies"), |       component: () => import("@/business/network/network-policies"), | ||||||
|       name: "NetworkPolicies", |       name: "NetworkPolicies", | ||||||
|       meta: { |       meta: { | ||||||
|   | |||||||
| @@ -1,40 +1,56 @@ | |||||||
| import Layout from "@/business/app-layout/horizontal-layout" | import Layout from "@/business/app-layout/horizontal-layout" | ||||||
|  |  | ||||||
| const Storage = { | const Storage = { | ||||||
|   path: "/storage", |     path: "/storage", | ||||||
|   sort: 3, |     sort: 3, | ||||||
|   component: Layout, |     parent: true, | ||||||
|   name: "Storage", |     component: Layout, | ||||||
|   meta: { |     name: "Storage", | ||||||
|     title: "business.storage.storage", |     meta: { | ||||||
|     icon: "iconfont iconstorage" |         title: "business.storage.storage", | ||||||
|   }, |         icon: "iconfont iconstorage" | ||||||
|   children: [ |  | ||||||
|     { |  | ||||||
|       path: "/pv", |  | ||||||
|       component: () => import("@/business/storage/pvs"), |  | ||||||
|       name: "Pvs", |  | ||||||
|       meta: { |  | ||||||
|         title: "Persistent Volume", |  | ||||||
|       } |  | ||||||
|     }, |     }, | ||||||
|     { |     children: [ | ||||||
|       path: "/pvcs", |         { | ||||||
|       component: () => import("@/business/storage/pvcs"), |             path: "/persistentvolumes", | ||||||
|       name: "Pvcs", |             requirePermission: { | ||||||
|       meta: { |                 apiGroup: "", | ||||||
|         title: "Persistent Volume Claims", |                 resource: "persistentvolumes", | ||||||
|       } |                 verb: "list", | ||||||
|     }, |             }, | ||||||
|     { |             component: () => import("@/business/storage/pvs"), | ||||||
|       path: "/storageclasses", |             name: "Pvs", | ||||||
|       component: () => import("@/business/storage/storage-classes"), |             meta: { | ||||||
|       name: "StorageClasses", |                 title: "Persistent Volume", | ||||||
|       meta: { |             } | ||||||
|         title: "Storage Classes", |         }, | ||||||
|       } |         { | ||||||
|     } |             path: "/persistentvolumeclaims", | ||||||
|   ] |             requirePermission: { | ||||||
|  |                 apiGroup: "", | ||||||
|  |                 resource: "persistentvolumeclaims", | ||||||
|  |                 verb: "list", | ||||||
|  |             }, | ||||||
|  |             component: () => import("@/business/storage/pvcs"), | ||||||
|  |             name: "Pvcs", | ||||||
|  |             meta: { | ||||||
|  |                 title: "Persistent Volume Claims", | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             path: "/storageclasses", | ||||||
|  |             requirePermission: { | ||||||
|  |                 apiGroup: "storage.k8s.io", | ||||||
|  |                 resource: "storageclasses", | ||||||
|  |                 verb: "list", | ||||||
|  |             }, | ||||||
|  |             component: () => import("@/business/storage/storage-classes"), | ||||||
|  |             name: "StorageClasses", | ||||||
|  |             meta: { | ||||||
|  |                 title: "Storage Classes", | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     ] | ||||||
| } | } | ||||||
|  |  | ||||||
| export default Storage | export default Storage | ||||||
|   | |||||||
| @@ -1,213 +1,245 @@ | |||||||
| import Layout from "@/business/app-layout/horizontal-layout"; | import Layout from "@/business/app-layout/horizontal-layout"; | ||||||
|  |  | ||||||
| const Workloads = { | const Workloads = { | ||||||
|   path: "/workloads", |     path: "/workloads", | ||||||
|   sort: 4, |     parent: true, | ||||||
|   component: Layout, |     sort: 4, | ||||||
|   name: "Workloads", |     component: Layout, | ||||||
|   meta: { |     name: "Workloads", | ||||||
|     title: "business.workload.workload", |     meta: { | ||||||
|     icon: "iconfont iconworkload", |         title: "business.workload.workload", | ||||||
|   }, |         icon: "iconfont iconworkload", | ||||||
|   children: [ |  | ||||||
|     { |  | ||||||
|       path: "/deployments", |  | ||||||
|       component: () => import("@/business/workloads/deployments"), |  | ||||||
|       name: "Deployments", |  | ||||||
|       meta: { |  | ||||||
|         title: "Deployments", |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       path: "/deployments/detail/:namespace/:name", |  | ||||||
|       name: "DeploymentDetail", |  | ||||||
|       hidden: true, |  | ||||||
|       component: () => import("@/business/workloads/deployments/detail"), |  | ||||||
|       meta: { |  | ||||||
|         activeMenu: "/deployments", |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       path: "/deployments/create", |  | ||||||
|       name: "DeploymentCreate", |  | ||||||
|       hidden: true, |  | ||||||
|       component: () => import("@/business/workloads/deployments/create"), |  | ||||||
|       meta: { |  | ||||||
|         activeMenu: "/deployments", |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       path: "/deployments/edit/:namespace/:name", |  | ||||||
|       name: "DeploymentEdit", |  | ||||||
|       hidden: true, |  | ||||||
|       component: () => import("@/business/workloads/deployments/edit"), |  | ||||||
|       meta: { |  | ||||||
|         activeMenu: "/deployments", |  | ||||||
|       }, |  | ||||||
|     }, |     }, | ||||||
|  |     children: [ | ||||||
|  |         { | ||||||
|  |             path: "/deployments", | ||||||
|  |             requirePermission: { | ||||||
|  |                 apiGroup: "apps", | ||||||
|  |                 resource: "deployments", | ||||||
|  |                 verb: "list", | ||||||
|  |             }, | ||||||
|  |             component: () => import("@/business/workloads/deployments"), | ||||||
|  |             name: "Deployments", | ||||||
|  |             meta: { | ||||||
|  |                 title: "Deployments", | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             path: "/deployments/detail/:namespace/:name", | ||||||
|  |             name: "DeploymentDetail", | ||||||
|  |             hidden: true, | ||||||
|  |             component: () => import("@/business/workloads/deployments/detail"), | ||||||
|  |             meta: { | ||||||
|  |                 activeMenu: "/deployments", | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             path: "/deployments/create", | ||||||
|  |             name: "DeploymentCreate", | ||||||
|  |             hidden: true, | ||||||
|  |             component: () => import("@/business/workloads/deployments/create"), | ||||||
|  |             meta: { | ||||||
|  |                 activeMenu: "/deployments", | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             path: "/deployments/edit/:namespace/:name", | ||||||
|  |             name: "DeploymentEdit", | ||||||
|  |             hidden: true, | ||||||
|  |             component: () => import("@/business/workloads/deployments/edit"), | ||||||
|  |             meta: { | ||||||
|  |                 activeMenu: "/deployments", | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |  | ||||||
|     { |         { | ||||||
|       path: "/cronjobs", |             path: "/daemonsets", | ||||||
|       component: () => import("@/business/workloads/cronjobs"), |             requirePermission: { | ||||||
|       name: "CronJobs", |                 apiGroup: "apps", | ||||||
|       meta: { |                 resource: "daemonsets", | ||||||
|         title: "CronJobs", |                 verb: "list", | ||||||
|       }, |             }, | ||||||
|     }, |             component: () => import("@/business/workloads/daemonsets"), | ||||||
|     { |             name: "DaemonSets", | ||||||
|       path: "/cronjobs/detail/:namespace/:name", |             meta: { | ||||||
|       name: "CronJobDetail", |                 title: "DaemonSets", | ||||||
|       hidden: true, |             }, | ||||||
|       component: () => import("@/business/workloads/cronjobs/detail"), |         }, | ||||||
|       meta: { |         { | ||||||
|         activeMenu: "/cronjobs", |             path: "/daemonsets/detail/:namespace/:name", | ||||||
|       }, |             name: "DaemonSetDetail", | ||||||
|     }, |             hidden: true, | ||||||
|     { |             component: () => import("@/business/workloads/daemonsets/detail"), | ||||||
|       path: "/cronjobs/create", |             meta: { | ||||||
|       name: "CronJobCreate", |                 activeMenu: "/daemonsets", | ||||||
|       hidden: true, |             }, | ||||||
|       component: () => import("@/business/workloads/cronjobs/create"), |         }, | ||||||
|       meta: { |         { | ||||||
|         activeMenu: "/cronjobs", |             path: "/daemonsets/create", | ||||||
|       }, |             name: "DaemonSetCreate", | ||||||
|     }, |             hidden: true, | ||||||
|     { |             component: () => import("@/business/workloads/daemonsets/create"), | ||||||
|       path: "/cronjobs/edit/:namespace/:name", |             meta: { | ||||||
|       name: "CronJobEdit", |                 activeMenu: "/daemonsets", | ||||||
|       hidden: true, |             }, | ||||||
|       component: () => import("@/business/workloads/cronjobs/edit"), |         }, | ||||||
|       meta: { |         { | ||||||
|         activeMenu: "/cronjobs", |             path: "/daemonsets/edit/:namespace/:name", | ||||||
|       }, |             name: "DaemonSetEdit", | ||||||
|     }, |             hidden: true, | ||||||
|  |             component: () => import("@/business/workloads/daemonsets/edit"), | ||||||
|  |             meta: { | ||||||
|  |                 activeMenu: "/daemonsets", | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |  | ||||||
|     { |         { | ||||||
|       path: "/jobs", |             path: "/statefulsets", | ||||||
|       component: () => import("@/business/workloads/jobs"), |             requirePermission: { | ||||||
|       name: "Jobs", |                 apiGroup: "apps", | ||||||
|       meta: { |                 resource: "statefulsets", | ||||||
|         title: "Jobs", |                 verb: "list", | ||||||
|       }, |             }, | ||||||
|     }, |             component: () => import("@/business/workloads/statefulsets"), | ||||||
|     { |             name: "StatefulSets", | ||||||
|       path: "/jobs/detail/:namespace/:name", |             meta: { | ||||||
|       name: "JobDetail", |                 title: "StatefulSets", | ||||||
|       hidden: true, |             }, | ||||||
|       component: () => import("@/business/workloads/jobs/detail"), |         }, | ||||||
|       meta: { |         { | ||||||
|         activeMenu: "/jobs", |             path: "/statefulsets/detail/:namespace/:name", | ||||||
|       }, |             name: "StatefulSetDetail", | ||||||
|     }, |             hidden: true, | ||||||
|     { |             component: () => import("@/business/workloads/statefulsets/detail"), | ||||||
|       path: "/jobs/create", |             meta: { | ||||||
|       name: "JobCreate", |                 activeMenu: "/statefulsets", | ||||||
|       hidden: true, |             }, | ||||||
|       component: () => import("@/business/workloads/jobs/create"), |         }, | ||||||
|       meta: { |         { | ||||||
|         activeMenu: "/jobs", |             path: "/statefulsets/create", | ||||||
|       }, |             name: "StatefulSetCreate", | ||||||
|     }, |             hidden: true, | ||||||
|     { |             component: () => import("@/business/workloads/statefulsets/create"), | ||||||
|       path: "/jobs/edit/:namespace/:name", |             meta: { | ||||||
|       name: "JobEdit", |                 activeMenu: "/statefulsets", | ||||||
|       hidden: true, |             }, | ||||||
|       component: () => import("@/business/workloads/jobs/edit"), |         }, | ||||||
|       meta: { |         { | ||||||
|         activeMenu: "/jobs", |             path: "/statefulsets/edit/:namespace/:name", | ||||||
|       }, |             name: "StatefulSetEdit", | ||||||
|     }, |             hidden: true, | ||||||
|  |             component: () => import("@/business/workloads/statefulsets/edit"), | ||||||
|  |             meta: { | ||||||
|  |                 activeMenu: "/statefulsets", | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |  | ||||||
|     { |         { | ||||||
|       path: "/daemonsets", |             path: "/cronjobs", | ||||||
|       component: () => import("@/business/workloads/daemonsets"), |             requirePermission: { | ||||||
|       name: "DaemonSets", |                 apiGroup: "batch", | ||||||
|       meta: { |                 resource: "cronjobs", | ||||||
|         title: "DaemonSets", |                 verb: "list", | ||||||
|       }, |             }, | ||||||
|     }, |             component: () => import("@/business/workloads/cronjobs"), | ||||||
|     { |             name: "CronJobs", | ||||||
|       path: "/daemonsets/detail/:namespace/:name", |             meta: { | ||||||
|       name: "DaemonSetDetail", |                 title: "CronJobs", | ||||||
|       hidden: true, |             }, | ||||||
|       component: () => import("@/business/workloads/daemonsets/detail"), |         }, | ||||||
|       meta: { |         { | ||||||
|         activeMenu: "/daemonsets", |             path: "/cronjobs/detail/:namespace/:name", | ||||||
|       }, |             name: "CronJobDetail", | ||||||
|     }, |             hidden: true, | ||||||
|     { |             component: () => import("@/business/workloads/cronjobs/detail"), | ||||||
|       path: "/daemonsets/create", |             meta: { | ||||||
|       name: "DaemonSetCreate", |                 activeMenu: "/cronjobs", | ||||||
|       hidden: true, |             }, | ||||||
|       component: () => import("@/business/workloads/daemonsets/create"), |         }, | ||||||
|       meta: { |         { | ||||||
|         activeMenu: "/daemonsets", |             path: "/cronjobs/create", | ||||||
|       }, |             name: "CronJobCreate", | ||||||
|     }, |             hidden: true, | ||||||
|     { |             component: () => import("@/business/workloads/cronjobs/create"), | ||||||
|       path: "/daemonsets/edit/:namespace/:name", |             meta: { | ||||||
|       name: "DaemonSetEdit", |                 activeMenu: "/cronjobs", | ||||||
|       hidden: true, |             }, | ||||||
|       component: () => import("@/business/workloads/daemonsets/edit"), |         }, | ||||||
|       meta: { |         { | ||||||
|         activeMenu: "/daemonsets", |             path: "/cronjobs/edit/:namespace/:name", | ||||||
|       }, |             name: "CronJobEdit", | ||||||
|     }, |             hidden: true, | ||||||
|  |             component: () => import("@/business/workloads/cronjobs/edit"), | ||||||
|  |             meta: { | ||||||
|  |                 activeMenu: "/cronjobs", | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |  | ||||||
|     { |         { | ||||||
|       path: "/statefulsets", |             path: "/jobs", | ||||||
|       component: () => import("@/business/workloads/statefulsets"), |             requirePermission: { | ||||||
|       name: "StatefulSets", |                 apiGroup: "batch", | ||||||
|       meta: { |                 resource: "jobs", | ||||||
|         title: "StatefulSets", |                 verb: "list", | ||||||
|       }, |             }, | ||||||
|     }, |             component: () => import("@/business/workloads/jobs"), | ||||||
|     { |             name: "Jobs", | ||||||
|       path: "/statefulsets/detail/:namespace/:name", |             meta: { | ||||||
|       name: "StatefulSetDetail", |                 title: "Jobs", | ||||||
|       hidden: true, |             }, | ||||||
|       component: () => import("@/business/workloads/statefulsets/detail"), |         }, | ||||||
|       meta: { |         { | ||||||
|         activeMenu: "/statefulsets", |             path: "/jobs/detail/:namespace/:name", | ||||||
|       }, |             name: "JobDetail", | ||||||
|     }, |             hidden: true, | ||||||
|     { |             component: () => import("@/business/workloads/jobs/detail"), | ||||||
|       path: "/statefulsets/create", |             meta: { | ||||||
|       name: "StatefulSetCreate", |                 activeMenu: "/jobs", | ||||||
|       hidden: true, |             }, | ||||||
|       component: () => import("@/business/workloads/statefulsets/create"), |         }, | ||||||
|       meta: { |         { | ||||||
|         activeMenu: "/statefulsets", |             path: "/jobs/create", | ||||||
|       }, |             name: "JobCreate", | ||||||
|     }, |             hidden: true, | ||||||
|     { |             component: () => import("@/business/workloads/jobs/create"), | ||||||
|       path: "/statefulsets/edit/:namespace/:name", |             meta: { | ||||||
|       name: "StatefulSetEdit", |                 activeMenu: "/jobs", | ||||||
|       hidden: true, |             }, | ||||||
|       component: () => import("@/business/workloads/statefulsets/edit"), |         }, | ||||||
|       meta: { |         { | ||||||
|         activeMenu: "/statefulsets", |             path: "/jobs/edit/:namespace/:name", | ||||||
|       }, |             name: "JobEdit", | ||||||
|     }, |             hidden: true, | ||||||
|  |             component: () => import("@/business/workloads/jobs/edit"), | ||||||
|  |             meta: { | ||||||
|  |                 activeMenu: "/jobs", | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |  | ||||||
|     { |  | ||||||
|       path: "/pods", |         { | ||||||
|       component: () => import("@/business/workloads/pods"), |             path: "/pods", | ||||||
|       name: "Pods", |             requirePermission: { | ||||||
|       meta: { |                 apiGroup: "", | ||||||
|         title: "Pods", |                 resource: "pods", | ||||||
|       }, |                 verb: "list", | ||||||
|     }, |             }, | ||||||
|     { |             component: () => import("@/business/workloads/pods"), | ||||||
|       path: "/pods/detail/:namespace/:name", |             name: "Pods", | ||||||
|       name: "PodDetail", |             meta: { | ||||||
|       hidden: true, |                 title: "Pods", | ||||||
|       component: () => import("@/business/workloads/pods/detail"), |             }, | ||||||
|       meta: { |         }, | ||||||
|         activeMenu: "/pods", |         { | ||||||
|       }, |             path: "/pods/detail/:namespace/:name", | ||||||
|     }, |             name: "PodDetail", | ||||||
|   ], |             hidden: true, | ||||||
|  |             component: () => import("@/business/workloads/pods/detail"), | ||||||
|  |             meta: { | ||||||
|  |                 activeMenu: "/pods", | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |     ], | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export default Workloads; | export default Workloads; | ||||||
|   | |||||||
| @@ -1,10 +1,12 @@ | |||||||
| // 根据实际需要修改 | // 根据实际需要修改 | ||||||
| const getters = { | const getters = { | ||||||
|   sidebar: state => state.app.sidebar, |     sidebar: state => state.app.sidebar, | ||||||
|   name: state => state.user.name, |     name: state => state.user.name, | ||||||
|   // language: state => state.user.language, |     nickName: state => state.user.nickName, | ||||||
|   permission_routes: state => state.permission.routes, |     // language: state => state.user.language, | ||||||
|   menu: state => state.user.menu, |     permission_routes: state => state.permission.routes, | ||||||
|   roles: state => state.user.roles, |     roles: state => state.user.roles, | ||||||
|  |     cluster: state => state.user.cluster, | ||||||
|  |     clusterRoles: state => state.user.clusterRoles, | ||||||
| } | } | ||||||
| export default getters | export default getters | ||||||
|   | |||||||
| @@ -1,59 +1,88 @@ | |||||||
| import {rolesRoutes, constantRoutes} from "@/router" | import {rolesRoutes, constantRoutes} from "@/router" | ||||||
|  |  | ||||||
| const state = { | const state = { | ||||||
|   routes: [], |     routes: [], | ||||||
|   addRoutes: [] |     addRoutes: [] | ||||||
| } | } | ||||||
|  |  | ||||||
| // function hasPermission(user, route) { | function hasPermission(clusterRoles, route) { | ||||||
| //   if (user && route){ |     if (route.requirePermission) { | ||||||
| //     console.log("") |         for (const clusterRole of clusterRoles) { | ||||||
| //   } |             if (clusterRole.rules.length > 0) { | ||||||
| //   return true |                 for (const rule of clusterRole.rules) { | ||||||
| // } |                     if (rule.apiGroups.includes("*") || rule.apiGroups.includes(route.requirePermission.apiGroup)) { | ||||||
|  |                         if (rule.resources.includes("*") || rule.resources.includes(route.requirePermission.resource)) { | ||||||
|  |                             if (rule.verbs.includes("*") || rule.verbs.includes(route.requirePermission.verb)) { | ||||||
| export function filterRolesRoutes (routes, user) { |                                 return true | ||||||
|   const res = [] |                             } | ||||||
|   routes.forEach(route => { |                         } | ||||||
|     const tmp = { ...route } |                     } | ||||||
|     // if (hasPermission(user, tmp)) { |                 } | ||||||
|     if (tmp.children) { |             } | ||||||
|       tmp.children = filterRolesRoutes(tmp.children, user) |         } | ||||||
|  |         return false | ||||||
|     } |     } | ||||||
|     res.push(tmp) |     return true | ||||||
|     // } | } | ||||||
|   }) |  | ||||||
|  |  | ||||||
|   return res |  | ||||||
|  | export function filterRolesRoutes(routes, clusterRoles) { | ||||||
|  |     const res = [] | ||||||
|  |     routes.forEach(route => { | ||||||
|  |         const tmp = {...route} | ||||||
|  |         if (hasPermission(clusterRoles, tmp)) { | ||||||
|  |             if (tmp.children) { | ||||||
|  |                 tmp.children = filterRolesRoutes(tmp.children, clusterRoles) | ||||||
|  |             } | ||||||
|  |             res.push(tmp) | ||||||
|  |         } | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     return res | ||||||
| } | } | ||||||
|  |  | ||||||
| const mutations = { | const mutations = { | ||||||
|   SET_ROUTES: (state, routes) => { |     SET_ROUTES: (state, routes) => { | ||||||
|     state.addRoutes = routes |         state.addRoutes = routes | ||||||
|     state.routes = constantRoutes.concat(routes) |         state.routes = constantRoutes.concat(routes) | ||||||
|   } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| const actions = { | const actions = { | ||||||
|   generateRoutes ({ commit }, p) { |     generateRoutes({commit}, p) { | ||||||
|     return new Promise((resolve, reject) => { |         return new Promise((resolve, reject) => { | ||||||
|       const user = p |             const clusterRoles = p | ||||||
|       let accessedRoutes |             let accessedRoutes | ||||||
|       try { |             try { | ||||||
|         accessedRoutes = filterRolesRoutes(rolesRoutes, user) |                 accessedRoutes = filterRolesRoutes(rolesRoutes, clusterRoles) | ||||||
|         commit("SET_ROUTES", accessedRoutes) |                 for (const route of accessedRoutes) { | ||||||
|         resolve(accessedRoutes) |                     if (route.parent) { | ||||||
|       } catch (error) { |                         let hidden = true | ||||||
|         reject(error) |                         if (route.children.length > 0) { | ||||||
|       } |                             for (const childRoute of route.children) { | ||||||
|     }) |                                 hidden = hidden && childRoute.hidden | ||||||
|   } |                             } | ||||||
|  |                             if (hidden) { | ||||||
|  |                                 route.hidden = true | ||||||
|  |                             } | ||||||
|  |                         } else { | ||||||
|  |                             route.hidden = true | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 commit("SET_ROUTES", accessedRoutes) | ||||||
|  |                 resolve(accessedRoutes) | ||||||
|  |             } catch (error) { | ||||||
|  |                 reject(error) | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   namespaced: true, |     namespaced: true, | ||||||
|   state, |     state, | ||||||
|   mutations, |     mutations, | ||||||
|   actions, |     actions, | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,98 +1,108 @@ | |||||||
| import {login, isLogin, logout, getCurrentUser} from "@/api/auth" | import {login, isLogin, logout, getCurrentClusterUser} from "@/api/auth" | ||||||
| import {resetRouter} from "@/router" | import {resetRouter} from "@/router" | ||||||
| import {getLanguage, setLanguage} from "@/i18n" | import {getLanguage, setLanguage} from "@/i18n" | ||||||
|  | import store from "../../store" | ||||||
|  |  | ||||||
| const state = { | const state = { | ||||||
|   login: false, |     login: false, | ||||||
|   name: "", |     name: "", | ||||||
|   currentProject: "", |     language: getLanguage(), | ||||||
|   language: getLanguage(), |     roles: [], | ||||||
|   roles: [], |     cluster: "", | ||||||
|   cluster: "" |     clusterRoles: [], | ||||||
| } | } | ||||||
|  |  | ||||||
| const mutations = { | const mutations = { | ||||||
|   LOGIN: (state) => { |     LOGIN: (state) => { | ||||||
|     state.login = true |         state.login = true | ||||||
|   }, |     }, | ||||||
|   LOGOUT: (state) => { |     LOGOUT: (state) => { | ||||||
|     state.login = false |         state.login = false | ||||||
|   }, |     }, | ||||||
|   SET_NAME: (state, name) => { |     SET_NAME: (state, name) => { | ||||||
|     state.name = name |         state.name = name | ||||||
|   }, |     }, | ||||||
|   SET_LANGUAGE: (state, language) => { |     SET_NICK_NAME: (state, nickName) => { | ||||||
|     state.language = language |         state.nickName = nickName | ||||||
|     setLanguage(language) |     }, | ||||||
|   }, |     SET_LANGUAGE: (state, language) => { | ||||||
|   SET_ROLES: (state, roles) => { |         state.language = language | ||||||
|     state.roles = roles |         setLanguage(language) | ||||||
|   }, |     }, | ||||||
|   SET_CURRENT_MENU: (state, menu) => { |     SET_ROLES: (state, roles) => { | ||||||
|     state.menu = menu |         state.roles = roles | ||||||
|   }, |     }, | ||||||
|   SET_CURRENT_CLUSTER: (state, name) => { |     SET_CURRENT_CLUSTER: (state, name) => { | ||||||
|     state.cluster = name |         state.cluster = name | ||||||
|   } |     }, | ||||||
|  |     SET_CLUSTER_ROLES: (state, clusterRoles) => { | ||||||
|  |         state.clusterRoles = clusterRoles | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| const actions = { | const actions = { | ||||||
|   login ({ commit }, userInfo) { |     setCurrentCluster({commit}, clusterName) { | ||||||
|     const { username, password } = userInfo |         commit("SET_CURRENT_CLUSTER", clusterName) | ||||||
|     return new Promise((resolve, reject) => { |     }, | ||||||
|       commit("LOGIN") |     login({commit}, userInfo) { | ||||||
|       login({ username: username.trim(), password: password }).then(response => { |         const {username, password} = userInfo | ||||||
|         commit("LOGIN") |         return new Promise((resolve, reject) => { | ||||||
|         resolve(response) |             commit("LOGIN") | ||||||
|       }).catch(error => { |             login({username: username.trim(), password: password}).then(response => { | ||||||
|         reject(error) |                 commit("LOGIN") | ||||||
|       }) |                 resolve(response) | ||||||
|     }) |             }).catch(error => { | ||||||
|   }, |                 reject(error) | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |     }, | ||||||
|  |  | ||||||
|   isLogin ({ commit }) { |     isLogin({commit}) { | ||||||
|     return new Promise((resolve) => { |         return new Promise((resolve) => { | ||||||
|       if (state.login) { |             if (state.login) { | ||||||
|         resolve(true) |                 resolve(true) | ||||||
|       } |             } | ||||||
|       isLogin().then((data) => { |             isLogin().then((data) => { | ||||||
|         if (data.data) { |                 if (data.data) { | ||||||
|           commit("LOGIN") |                     commit("LOGIN") | ||||||
|           resolve(true) |                     resolve(true) | ||||||
|         } else { |                 } else { | ||||||
|           resolve(false) |                     resolve(false) | ||||||
|         } |                 } | ||||||
|       }).catch(() => { |             }).catch(() => { | ||||||
|         resolve(false) |                 resolve(false) | ||||||
|       }) |             }) | ||||||
|     }) |         }) | ||||||
|   }, |     }, | ||||||
|   getCurrentUser ({ commit }) { |     getCurrentUser({commit}) { | ||||||
|     return new Promise((resolve, reject) => { |         return new Promise((resolve, reject) => { | ||||||
|       getCurrentUser().then(data => { |             const clusterName = store.getters.cluster | ||||||
|         const user = data.data |             getCurrentClusterUser(clusterName).then(data => { | ||||||
|         user["roles"] = ["ADMIN"] |                 const user = data.data | ||||||
|         const { name, roles } = user |                 user["roles"] = ["ADMIN"] | ||||||
|         commit("SET_NAME", name) |                 const {name, nickName, roles, clusterRoles} = user | ||||||
|         commit("SET_ROLES", roles) |                 commit("SET_NAME", name) | ||||||
|         commit("SET_LANGUAGE", "zh-CN") |                 commit("SET_ROLES", roles) | ||||||
|         resolve(user) |                 commit("SET_LANGUAGE", "zh-CN") | ||||||
|       }).catch(error => { |                 commit("SET_CLUSTER_ROLES", clusterRoles) | ||||||
|         reject(error) |                 commit("SET_NICK_NAME", nickName) | ||||||
|       }) |                 resolve(user) | ||||||
|     }) |             }).catch(error => { | ||||||
|   }, |                 reject(error) | ||||||
|   logout ({ commit }) { |             }) | ||||||
|     logout().then(() => { |         }) | ||||||
|       commit("LOGOUT") |     }, | ||||||
|       commit("SET_ROLES", []) |     logout({commit}) { | ||||||
|       resetRouter() |         logout().then(() => { | ||||||
|     }) |             commit("LOGOUT") | ||||||
|   }, |             commit("SET_ROLES", []) | ||||||
|  |             resetRouter() | ||||||
|  |         }) | ||||||
|  |     }, | ||||||
| } | } | ||||||
| export default { | export default { | ||||||
|   namespaced: true, |     namespaced: true, | ||||||
|   state, |     state, | ||||||
|   mutations, |     mutations, | ||||||
|   actions |     actions | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								web/dashboard/src/utils/permission.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								web/dashboard/src/utils/permission.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | import store from '@/store' | ||||||
|  |  | ||||||
|  | export const checkPermissions = function (p) { | ||||||
|  |     const userClusterRoles = store.getters && store.getters.clusterRoles | ||||||
|  |     for (const clusterRole of userClusterRoles) { | ||||||
|  |         if (clusterRole.rules.length > 0) { | ||||||
|  |             for (const rule of clusterRole.rules) { | ||||||
|  |                 if (rule.apiGroups.includes("*") || rule.apiGroups.includes(p.apiGroup)) { | ||||||
|  |                     if (rule.resources.includes("*") || rule.resources.includes(p.resource)) { | ||||||
|  |                         if (rule.verbs.includes("*") || rule.verbs.includes(p.verb)) { | ||||||
|  |                             return true | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return false | ||||||
|  | } | ||||||
| @@ -39,8 +39,8 @@ export function updateClusterMember(name, memberName, member) { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| export function getClusterMember(name, memberName, kind) { | export function getClusterMember(name, memberName) { | ||||||
|     return get(`${baseUrl}/${name}/members/${memberName}?kind=${kind}`) |     return get(`${baseUrl}/${name}/members/${memberName}`) | ||||||
| } | } | ||||||
|  |  | ||||||
| export function deleteClusterMember(name, memberName, kind) { | export function deleteClusterMember(name, memberName, kind) { | ||||||
| @@ -70,3 +70,15 @@ export function listClusterResourceByGroupVersion(name, groupVersion) { | |||||||
| export function deleteClusterRole(name, clusterRoleName) { | export function deleteClusterRole(name, clusterRoleName) { | ||||||
|     return del(`${baseUrl}/${name}/clusterroles/${clusterRoleName}`) |     return del(`${baseUrl}/${name}/clusterroles/${clusterRoleName}`) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export function listNamespaces(name) { | ||||||
|  |     return get(`${baseUrl}/${name}/namespaces`) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function listProjects(name) { | ||||||
|  |     return get(`${baseUrl}/${name}/projects`) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function createProject(name, project) { | ||||||
|  |     return post(`${baseUrl}/${name}/projects`, project) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,421 +1,456 @@ | |||||||
| <template> | <template> | ||||||
|     <layout-content> |   <layout-content> | ||||||
|         <complex-table :data="data"> |     <complex-table :data="data"> | ||||||
|             <template #header> |       <template #header> | ||||||
|                 <el-button-group> |         <el-button-group> | ||||||
|                     <el-button type="primary" size="small" @click="onCreate"> |           <el-button type="primary" size="small" @click="onCreate"> | ||||||
|                         {{ $t("commons.button.create") }} |             {{ $t("commons.button.create") }} | ||||||
|                     </el-button> |           </el-button> | ||||||
|                 </el-button-group> |         </el-button-group> | ||||||
|                 <br/> |         <br/> | ||||||
|             </template> |       </template> | ||||||
|             <el-table-column :label="$t('commons.table.name')" min-width="100" fix> |       <el-table-column :label="$t('commons.table.name')" min-width="100" fix> | ||||||
|                 <template v-slot:default="{row}"> |         <template v-slot:default="{row}"> | ||||||
|                     {{ row.metadata.name }} |           {{ row.metadata.name }} | ||||||
|                 </template> |         </template> | ||||||
|             </el-table-column> |       </el-table-column> | ||||||
|             <el-table-column :label="$t('commons.table.description')" min-width="100" fix> |  | ||||||
|                 <template v-slot:default="{row}"> |  | ||||||
|                     {{$t("business.cluster_role."+row.metadata.annotations["ekko-i18n"])}} |  | ||||||
|                 </template> |  | ||||||
|             </el-table-column> |  | ||||||
|             <el-table-column :label="$t('commons.table.creat_by')" min-width="100" fix> |  | ||||||
|                 <template v-slot:default="{row}"> |  | ||||||
|                     {{ row.metadata.annotations["created-by"] }} |  | ||||||
|                 </template> |  | ||||||
|             </el-table-column> |  | ||||||
|             <el-table-column :label="$t('commons.table.created_time')" min-width="100" fix> |  | ||||||
|                 <template v-slot:default="{row}"> |  | ||||||
|                     {{ row.metadata.annotations["created-at"] }} |  | ||||||
|                 </template> |  | ||||||
|             </el-table-column> |  | ||||||
|             <fu-table-operations :buttons="buttons" :label="$t('commons.table.action')" fix/> |  | ||||||
|         </complex-table> |  | ||||||
|  |  | ||||||
|         <el-dialog |       <el-table-column :label="$t('business.cluster.scope')" min-width="100" fix> | ||||||
|                 title="创建规则" |         <template v-slot:default="{row}"> | ||||||
|                 :visible.sync="ruleDialogOpened" |           {{ $t('business.cluster.' + row.metadata.labels["kubeoperator.io/role-type"]) }} | ||||||
|                 width="40%" |         </template> | ||||||
|                 center z-index="20"> |       </el-table-column> | ||||||
|             <el-form :model="ruleForm" label-position="top" label- width="60px"> |  | ||||||
|                 <el-form-item label="API Group"> |  | ||||||
|                     <el-select v-model="ruleForm.groupVersion" style="width:100%" @change="onAPIGroupChange"> |  | ||||||
|                         <el-option v-for="(item,index) in apiGroupsOptions" :key="index" |  | ||||||
|                                    :value="item.preferredVersion.groupVersion"> |  | ||||||
|                             {{item.preferredVersion.groupVersion}} |  | ||||||
|                         </el-option> |  | ||||||
|                     </el-select> |  | ||||||
|                 </el-form-item> |  | ||||||
|  |  | ||||||
|                 <el-form-item label="API Resource"> |       <el-table-column :label="$t('commons.table.built_in')" min-width="100" fix> | ||||||
|                     <el-select multiple v-model="ruleForm.resources" style="width:100%" :disabled="resourcesDisable" |         <template v-slot:default="{row}"> | ||||||
|                                @change="onResourcesChange"> |           {{ $t('commons.bool.' + row.metadata.annotations["builtin"]) }} | ||||||
|                         <el-option v-for="(item,index) in apiResourceOptions" |         </template> | ||||||
|                                    :key="index" |       </el-table-column> | ||||||
|                                    :value="item.name"> |       <el-table-column :label="$t('commons.table.created_time')" min-width="100" fix> | ||||||
|                             {{item.name}} |         <template v-slot:default="{row}"> | ||||||
|                         </el-option> |           {{ row.metadata.annotations["created-at"] }} | ||||||
|                     </el-select> |         </template> | ||||||
|                 </el-form-item> |       </el-table-column> | ||||||
|  |       <fu-table-operations :buttons="buttons" :label="$t('commons.table.action')" fix/> | ||||||
|  |     </complex-table> | ||||||
|  |  | ||||||
|                 <el-form-item label="Verbs"> |     <el-dialog | ||||||
|                     <el-select multiple v-model="ruleForm.verbs" style="width:100%" :disabled="verbsDisable" |         title="创建规则" | ||||||
|                                @change="onVerbsChange"> |         :visible.sync="ruleDialogOpened" | ||||||
|                         <el-option v-for="(item,index) in verbOptions" |         width="40%" | ||||||
|                                    :key="index" |         center z-index="20"> | ||||||
|                                    :value="item"> |       <el-form :model="ruleForm" label-position="top" label- width="60px"> | ||||||
|                             {{item}} |         <el-form-item label="API Group"> | ||||||
|                         </el-option> |           <el-select v-model="ruleForm.apiGroup" style="width:100%" @change="onAPIGroupChange"> | ||||||
|                     </el-select> |             <el-option v-for="(item,index) in apiGroupsOptions" :key="index" | ||||||
|                 </el-form-item> |                        :value="item.name"> | ||||||
|             </el-form> |                             <span v-if="item.name"> | ||||||
|  |                                 {{ item.name }} | ||||||
|  |                             </span> | ||||||
|  |             </el-option> | ||||||
|  |           </el-select> | ||||||
|  |         </el-form-item> | ||||||
|  |  | ||||||
|  |         <el-form-item label="API Resource"> | ||||||
|  |           <el-select multiple v-model="ruleForm.resources" style="width:100%" :disabled="resourcesDisable" | ||||||
|  |                      @change="onResourcesChange"> | ||||||
|  |             <el-option v-for="(item,index) in apiResourceOptions" | ||||||
|  |                        :key="index" | ||||||
|  |                        :value="item"> | ||||||
|  |               {{ item }} | ||||||
|  |             </el-option> | ||||||
|  |           </el-select> | ||||||
|  |         </el-form-item> | ||||||
|  |  | ||||||
|  |         <el-form-item label="Verbs"> | ||||||
|  |           <el-select multiple v-model="ruleForm.verbs" style="width:100%" :disabled="verbsDisable" | ||||||
|  |                      @change="onVerbsChange"> | ||||||
|  |             <el-option v-for="(item,index) in verbOptions" | ||||||
|  |                        :key="index" | ||||||
|  |                        :value="item"> | ||||||
|  |               {{ item }} | ||||||
|  |             </el-option> | ||||||
|  |           </el-select> | ||||||
|  |         </el-form-item> | ||||||
|  |       </el-form> | ||||||
|  |  | ||||||
|  |  | ||||||
|             <span slot="footer" class="dialog-footer"> |       <span slot="footer" class="dialog-footer"> | ||||||
|                 <el-button @click="ruleDialogOpened=false">取 消</el-button> |                 <el-button @click="ruleDialogOpened=false">取 消</el-button> | ||||||
|                 <el-button type="primary" @click="onRuleConfirm">确 定</el-button> |                 <el-button type="primary" @click="onRuleConfirm">确 定</el-button> | ||||||
|             </span> |             </span> | ||||||
|         </el-dialog> |     </el-dialog> | ||||||
|  |  | ||||||
|         <el-dialog |     <el-dialog | ||||||
|                 title="创建角色" |         title="创建角色" | ||||||
|                 :visible.sync="createDialogOpened" |         :visible.sync="createDialogOpened" | ||||||
|                 width="60%" |         width="60%" | ||||||
|                 center z-index="10"> |         center z-index="10"> | ||||||
|  |  | ||||||
|             <el-form :model="clusterRoleForm" label-position="left" label- width="60px"> |       <el-form :model="clusterRoleForm" label-position="left" label-width="144px"> | ||||||
|                 <el-form-item label="名称"> |         <el-form-item :label="$t('commons.table.name')"> | ||||||
|                     <el-input v-model="clusterRoleForm.name"></el-input> |           <el-input v-model="clusterRoleForm.name" style="width: 80%"></el-input> | ||||||
|                 </el-form-item> |         </el-form-item> | ||||||
|                 <el-form-item label="规则"> |         <el-form-item :label="$t('business.cluster.scope')"> | ||||||
|                     <el-button @click="onRuleCreate"><i class="el-icon-plus "></i></el-button> |           <el-radio v-model="clusterRoleForm.type" label="cluster">{{ $t('business.cluster.cluster') }}</el-radio> | ||||||
|                     <el-table |           <el-radio v-model="clusterRoleForm.type" label="namespace">{{ $t('business.cluster.namespace') }}</el-radio> | ||||||
|                             :data="clusterRoleForm.rules" |         </el-form-item> | ||||||
|                             border |  | ||||||
|                             style="width: 100%"> |         <el-form-item label="规则" label-width="144px"> | ||||||
|                         <el-table-column |           <el-button @click="onRuleCreate"><i class="el-icon-plus "></i></el-button> | ||||||
|                                 prop="apiGroup" |           <el-table | ||||||
|                                 label="API Group" |               :data="clusterRoleForm.rules" | ||||||
|                         > |               border | ||||||
|                         </el-table-column> |               style="width: 80%"> | ||||||
|                         <el-table-column |             <el-table-column | ||||||
|                                 prop="resource" |                 prop="apiGroup" | ||||||
|                                 label="API Resource" |                 label="API Group" | ||||||
|                         > |             > | ||||||
|                             <template v-slot:default="{row}"> |             </el-table-column> | ||||||
|                                 <span v-for="v in row.resources" :key="v">{{v}}<br/></span> |             <el-table-column | ||||||
|                             </template> |                 prop="resource" | ||||||
|                         </el-table-column> |                 label="API Resource" | ||||||
|                         <el-table-column |             > | ||||||
|                                 prop="verbs" |               <template v-slot:default="{row}"> | ||||||
|                                 label="Verbs" |                 <span v-for="v in row.resources" :key="v">{{ v }}<br/></span> | ||||||
|                         > |               </template> | ||||||
|                             <template v-slot:default="{row}"> |             </el-table-column> | ||||||
|                                 <span v-for="v in row.verbs" :key="v">{{v}}<br/></span> |             <el-table-column | ||||||
|                             </template> |                 prop="verbs" | ||||||
|                         </el-table-column> |                 label="Verbs" | ||||||
|                         <el-table-column> |             > | ||||||
|                             <template v-slot:default="{row}"> |               <template v-slot:default="{row}"> | ||||||
|                                 <el-button |                 <span v-for="v in row.verbs" :key="v">{{ v }}<br/></span> | ||||||
|                                         size="mini" circle @click="onRuleDelete(row)"> |               </template> | ||||||
|                                     <i class="el-icon-delete"></i> |             </el-table-column> | ||||||
|                                 </el-button> |             <el-table-column> | ||||||
|                             </template> |               <template v-slot:default="{row}"> | ||||||
|                         </el-table-column> |                 <el-button | ||||||
|                     </el-table> |                     size="mini" circle @click="onRuleDelete(row)"> | ||||||
|                 </el-form-item> |                   <i class="el-icon-delete"></i> | ||||||
|             </el-form> |                 </el-button> | ||||||
|  |               </template> | ||||||
|  |             </el-table-column> | ||||||
|  |           </el-table> | ||||||
|  |         </el-form-item> | ||||||
|  |       </el-form> | ||||||
|  |  | ||||||
|  |  | ||||||
|             <span slot="footer" class="dialog-footer"> |       <span slot="footer" class="dialog-footer"> | ||||||
|                 <el-button @click="createDialogOpened = false">取 消</el-button> |                 <el-button @click="createDialogOpened = false">取 消</el-button> | ||||||
|                 <el-button type="primary" @click="onConfirm">确 定</el-button> |                 <el-button type="primary" @click="onConfirm">确 定</el-button> | ||||||
|             </span> |             </span> | ||||||
|         </el-dialog> |     </el-dialog> | ||||||
|  |  | ||||||
|         <el-dialog |     <el-dialog | ||||||
|                 title="提示" |         title="提示" | ||||||
|                 :visible.sync="editDialogOpened" |         :visible.sync="editDialogOpened" | ||||||
|                 width="60%" |         width="60%" | ||||||
|                 center z-index="10"> |         center z-index="10"> | ||||||
|  |  | ||||||
|             <el-form :model="editForm" label-position="left" label- width="60px"> |       <el-form :model="editForm" label-position="left" label- width="60px"> | ||||||
|                 <el-form-item label="名称"> |         <el-form-item label="名称"> | ||||||
|                     <el-input v-model="editForm.name" disabled></el-input> |           <el-input v-model="editForm.name" disabled></el-input> | ||||||
|                 </el-form-item> |         </el-form-item> | ||||||
|                 <el-form-item label="规则"> |         <el-form-item label="规则"> | ||||||
|                     <el-button @click="onRuleCreate"><i class="el-icon-plus "></i></el-button> |           <el-button @click="onRuleCreate"><i class="el-icon-plus "></i></el-button> | ||||||
|                     <el-table |           <el-table | ||||||
|                             :data="editForm.rules" |               :data="editForm.rules" | ||||||
|                             border |               border | ||||||
|                             style="width: 100%"> |               style="width: 100%"> | ||||||
|                         <el-table-column |             <el-table-column | ||||||
|                                 prop="apiGroup" |                 prop="apiGroup" | ||||||
|                                 label="API Group" |                 label="API Group" | ||||||
|                         > |             > | ||||||
|                         </el-table-column> |             </el-table-column> | ||||||
|                         <el-table-column |             <el-table-column | ||||||
|                                 prop="resource" |                 prop="resource" | ||||||
|                                 label="API Resource" |                 label="API Resource" | ||||||
|                         > |             > | ||||||
|                             <template v-slot:default="{row}"> |               <template v-slot:default="{row}"> | ||||||
|                                 <span v-for="v in row.resources" :key="v">{{v}}<br/></span> |                 <span v-for="v in row.resources" :key="v">{{ v }}<br/></span> | ||||||
|                             </template> |               </template> | ||||||
|                         </el-table-column> |             </el-table-column> | ||||||
|                         <el-table-column |             <el-table-column | ||||||
|                                 prop="verbs" |                 prop="verbs" | ||||||
|                                 label="Verbs" |                 label="Verbs" | ||||||
|                         > |             > | ||||||
|                             <template v-slot:default="{row}"> |               <template v-slot:default="{row}"> | ||||||
|                                 <span v-for="v in row.verbs" :key="v">{{v}}<br/></span> |                 <span v-for="v in row.verbs" :key="v">{{ v }}<br/></span> | ||||||
|                             </template> |               </template> | ||||||
|                         </el-table-column> |             </el-table-column> | ||||||
|                         <el-table-column> |             <el-table-column> | ||||||
|                             <template v-slot:default="{row}"> |               <template v-slot:default="{row}"> | ||||||
|                                 <el-button |                 <el-button | ||||||
|                                         size="mini" circle @click="onRuleDelete(row)"> |                     size="mini" circle @click="onRuleDelete(row)"> | ||||||
|                                     <i class="el-icon-delete"></i> |                   <i class="el-icon-delete"></i> | ||||||
|                                 </el-button> |                 </el-button> | ||||||
|                             </template> |               </template> | ||||||
|                         </el-table-column> |             </el-table-column> | ||||||
|                     </el-table> |           </el-table> | ||||||
|                 </el-form-item> |         </el-form-item> | ||||||
|             </el-form> |       </el-form> | ||||||
|  |  | ||||||
|  |  | ||||||
|             <span slot="footer" class="dialog-footer"> |       <span slot="footer" class="dialog-footer"> | ||||||
|                 <el-button @click="editDialogOpened = false">取 消</el-button> |                 <el-button @click="editDialogOpened = false">取 消</el-button> | ||||||
|                 <el-button type="primary" @click="onEditConfirm">确 定</el-button> |                 <el-button type="primary" @click="onEditConfirm">确 定</el-button> | ||||||
|             </span> |             </span> | ||||||
|         </el-dialog> |     </el-dialog> | ||||||
|  |  | ||||||
|     </layout-content> |   </layout-content> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
|     import LayoutContent from "@/components/layout/LayoutContent" | import LayoutContent from "@/components/layout/LayoutContent" | ||||||
|     import ComplexTable from "@/components/complex-table" | import ComplexTable from "@/components/complex-table" | ||||||
|     import { | import { | ||||||
|         listClusterRoles, |   listClusterRoles, | ||||||
|         listClusterApiGroups, |   listClusterApiGroups, | ||||||
|         listClusterResourceByGroupVersion, |   listClusterResourceByGroupVersion, | ||||||
|         createClusterRole, |   createClusterRole, | ||||||
|         deleteClusterRole, |   deleteClusterRole, | ||||||
|         updateClusterRole |   updateClusterRole | ||||||
|     } from "@/api/clusters"; | } from "@/api/clusters"; | ||||||
|  |  | ||||||
|  |  | ||||||
|     export default { | export default { | ||||||
|         name: "ClusterRoles", |   name: "ClusterRoles", | ||||||
|         props: ["name"], |   props: ["name"], | ||||||
|         components: {LayoutContent, ComplexTable}, |   components: {LayoutContent, ComplexTable}, | ||||||
|         data() { |   data() { | ||||||
|             return { |     return { | ||||||
|                 createDialogOpened: false, |       createDialogOpened: false, | ||||||
|                 editDialogOpened: false, |       editDialogOpened: false, | ||||||
|                 ruleDialogOpened: false, |       ruleDialogOpened: false, | ||||||
|                 apiGroupsOptions: [], |       apiGroupsOptions: [], | ||||||
|                 apiResourceOptions: [], |       apiResourceOptions: [], | ||||||
|                 operation: "", |       operation: "", | ||||||
|                 verbOptions: ["*", "create", "delete", "deletecollection", "get", "list", "patch", "update", "watch"], |       verbOptions: ["*", "create", "delete", "deletecollection", "get", "list", "patch", "update", "watch"], | ||||||
|                 memberOptions: [], |       memberOptions: [], | ||||||
|                 clusterRoleForm: { |       clusterRoleForm: { | ||||||
|                     name: "", |         name: "", | ||||||
|                     rules: [], |         type: "cluster", | ||||||
|                 }, |         rules: [], | ||||||
|                 editForm: { |       }, | ||||||
|                     name: "", |       editForm: { | ||||||
|                     rules: [], |         name: "", | ||||||
|                 }, |         rules: [], | ||||||
|                 resourcesDisable: false, |       }, | ||||||
|                 verbsDisable: false, |       resourcesDisable: false, | ||||||
|                 ruleForm: { |       verbsDisable: false, | ||||||
|                     groupVersion: "", |       ruleForm: { | ||||||
|                     resources: [], |         apiGroup: "", | ||||||
|                     verbs: [] |         resources: [], | ||||||
|                 }, |         verbs: [] | ||||||
|                 buttons: [ |       }, | ||||||
|                     { |       buttons: [ | ||||||
|                         label: this.$t("commons.button.edit"), |         { | ||||||
|                         icon: "el-icon-edit", |           label: this.$t("commons.button.edit"), | ||||||
|                         click: (row) => { |           icon: "el-icon-edit", | ||||||
|                             this.onEdit(row) |           click: (row) => { | ||||||
|                         } |             this.onEdit(row) | ||||||
|                     }, |           }, | ||||||
|                     { |           disabled: (row) => { | ||||||
|                         label: this.$t("commons.button.delete"), |             return row.metadata.annotations['builtin'] === 'true' | ||||||
|                         icon: "el-icon-delete", |           } | ||||||
|                         click: (row) => { |  | ||||||
|                             this.onDelete(row) |  | ||||||
|                         } |  | ||||||
|                     }, |  | ||||||
|                 ], |  | ||||||
|                 data: [], |  | ||||||
|             } |  | ||||||
|         }, |         }, | ||||||
|         methods: { |         { | ||||||
|             list() { |           label: this.$t("commons.button.delete"), | ||||||
|                 this.loading = false |           icon: "el-icon-delete", | ||||||
|                 listClusterRoles(this.name).then(data => { |           click: (row) => { | ||||||
|                     this.loading = false |             this.onDelete(row) | ||||||
|                     this.data = data.data |           }, | ||||||
|                 }) |           disabled: (row) => { | ||||||
|             }, |             return row.metadata.annotations['builtin'] === 'true' | ||||||
|             onCreate() { |           } | ||||||
|                 this.operation = "create" |  | ||||||
|                 this.clusterRoleForm = { |  | ||||||
|                     name: "", |  | ||||||
|                     rules: [], |  | ||||||
|                 } |  | ||||||
|                 this.createDialogOpened = true |  | ||||||
|                 listClusterApiGroups(this.name).then(data => { |  | ||||||
|                     this.apiGroupsOptions.push({preferredVersion: {groupVersion: "*"}}) |  | ||||||
|                     this.apiGroupsOptions = this.apiGroupsOptions.concat(data.data) |  | ||||||
|                 }) |  | ||||||
|             }, |  | ||||||
|  |  | ||||||
|             onEdit(row) { |  | ||||||
|                 this.operation = "update" |  | ||||||
|                 this.editForm.name = row.metadata.name |  | ||||||
|                 this.editForm.rules = [] |  | ||||||
|                 for (const rule of row.rules) { |  | ||||||
|                     if (rule.apiGroups) { |  | ||||||
|                         this.editForm.rules.push({ |  | ||||||
|                             apiGroup: rule.apiGroups[0], |  | ||||||
|                             resources: rule.resources, |  | ||||||
|                             verbs: rule.verbs, |  | ||||||
|                         }) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 this.editDialogOpened = true |  | ||||||
|                 listClusterApiGroups(this.name).then(data => { |  | ||||||
|                     this.apiGroupsOptions.push({preferredVersion: {groupVersion: "*"}}) |  | ||||||
|                     this.apiGroupsOptions = this.apiGroupsOptions.concat(data.data) |  | ||||||
|                 }) |  | ||||||
|             }, |  | ||||||
|             onRuleCreate() { |  | ||||||
|                 this.ruleForm = { |  | ||||||
|                     groupVersion: "", |  | ||||||
|                     resources: [], |  | ||||||
|                     verbs: [], |  | ||||||
|                 } |  | ||||||
|                 this.ruleDialogOpened = true |  | ||||||
|                 this.resourcesDisable = false |  | ||||||
|                 this.verbsDisable = false |  | ||||||
|             }, |  | ||||||
|             onAPIGroupChange() { |  | ||||||
|                 this.apiResourceOptions = [] |  | ||||||
|                 this.ruleForm.resources = [] |  | ||||||
|                 this.ruleForm.verbs = [] |  | ||||||
|                 this.resourcesDisable = false |  | ||||||
|                 this.verbsDisable = false |  | ||||||
|                 if (this.ruleForm.groupVersion === "*") { |  | ||||||
|                     this.apiResourceOptions = [{name: "*"}] |  | ||||||
|                     this.ruleForm.resources = ["*"] |  | ||||||
|                     this.resourcesDisable = true |  | ||||||
|                     return |  | ||||||
|                 } |  | ||||||
|                 listClusterResourceByGroupVersion(this.name, this.ruleForm.groupVersion).then(data => { |  | ||||||
|                     this.apiResourceOptions.push({name: "*"}) |  | ||||||
|                     this.apiResourceOptions = this.apiResourceOptions.concat(data.data); |  | ||||||
|                 }) |  | ||||||
|             }, |  | ||||||
|             onResourcesChange() { |  | ||||||
|                 if (this.ruleForm.resources.indexOf("*") > -1) { |  | ||||||
|                     this.ruleForm.resources = ["*"] |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|             onVerbsChange() { |  | ||||||
|                 if (this.ruleForm.verbs.indexOf("*") > -1) { |  | ||||||
|                     this.ruleForm.verbs = ["*"] |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|             onRuleConfirm() { |  | ||||||
|                 switch (this.operation) { |  | ||||||
|                     case "create": |  | ||||||
|                         this.clusterRoleForm.rules.push({ |  | ||||||
|                             apiGroup: this.ruleForm.groupVersion, |  | ||||||
|                             resources: this.ruleForm.resources, |  | ||||||
|                             verbs: this.ruleForm.verbs |  | ||||||
|                         }) |  | ||||||
|                         break |  | ||||||
|                     case "update": |  | ||||||
|                         this.editForm.rules.push({ |  | ||||||
|                             apiGroup: this.ruleForm.groupVersion, |  | ||||||
|                             resources: this.ruleForm.resources, |  | ||||||
|                             verbs: this.ruleForm.verbs |  | ||||||
|                         }) |  | ||||||
|                         break |  | ||||||
|                 } |  | ||||||
|                 this.ruleDialogOpened = false |  | ||||||
|             }, |  | ||||||
|  |  | ||||||
|             onRuleDelete(row) { |  | ||||||
|                 switch (this.operation) { |  | ||||||
|                     case "create": |  | ||||||
|                         this.clusterRoleForm.rules.splice(this.clusterRoleForm.rules.indexOf(row), 1) |  | ||||||
|                         break |  | ||||||
|                     case "update": |  | ||||||
|                         this.editForm.rules.splice(this.clusterRoleForm.rules.indexOf(row), 1) |  | ||||||
|                         break |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|             onDelete(row) { |  | ||||||
|                 this.$confirm(this.$t("commons.confirm_message.delete"), this.$t("commons.message_box.alert"), { |  | ||||||
|                     confirmButtonText: this.$t("commons.button.confirm"), |  | ||||||
|                     cancelButtonText: this.$t("commons.button.cancel"), |  | ||||||
|                     type: 'warning' |  | ||||||
|                 }).then(() => { |  | ||||||
|                     deleteClusterRole(this.name, row.metadata.name).then(() => { |  | ||||||
|                         this.list() |  | ||||||
|                     }) |  | ||||||
|                 }); |  | ||||||
|             }, |  | ||||||
|  |  | ||||||
|             onConfirm() { |  | ||||||
|                 const req = { |  | ||||||
|                     metadata: { |  | ||||||
|                         name: this.clusterRoleForm.name, |  | ||||||
|                     }, |  | ||||||
|                     rules: [] |  | ||||||
|                 } |  | ||||||
|                 for (const rule of this.clusterRoleForm.rules) { |  | ||||||
|                     req.rules.push({ |  | ||||||
|                         apiGroups: [rule.apiGroup], |  | ||||||
|                         resources: rule.resources, |  | ||||||
|                         verbs: rule.verbs, |  | ||||||
|                     }) |  | ||||||
|                 } |  | ||||||
|                 createClusterRole(this.name, req).then(() => { |  | ||||||
|                     this.list() |  | ||||||
|                     this.createDialogOpened = false |  | ||||||
|                 }) |  | ||||||
|             }, |  | ||||||
|             onEditConfirm() { |  | ||||||
|                 const req = { |  | ||||||
|                     metadata: { |  | ||||||
|                         name: this.editForm.name, |  | ||||||
|                     }, |  | ||||||
|                     rules: [] |  | ||||||
|                 } |  | ||||||
|                 for (const rule of this.editForm.rules) { |  | ||||||
|                     req.rules.push({ |  | ||||||
|                         apiGroups: [rule.apiGroup], |  | ||||||
|                         resources: rule.resources, |  | ||||||
|                         verbs: rule.verbs, |  | ||||||
|                     }) |  | ||||||
|                 } |  | ||||||
|                 console.log(123) |  | ||||||
|                 updateClusterRole(this.name, req.metadata.name, req).then(() => { |  | ||||||
|                     this.list() |  | ||||||
|                     this.editDialogOpened = false |  | ||||||
|                 }) |  | ||||||
|             } |  | ||||||
|         }, |         }, | ||||||
|         created() { |       ], | ||||||
|             this.list() |       data: [], | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     list() { | ||||||
|  |       this.loading = false | ||||||
|  |       listClusterRoles(this.name).then(data => { | ||||||
|  |         this.loading = false | ||||||
|  |         this.data = data.data | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |     onCreate() { | ||||||
|  |       this.operation = "create" | ||||||
|  |       this.clusterRoleForm = { | ||||||
|  |         name: "", | ||||||
|  |         rules: [], | ||||||
|  |         type: "cluster", | ||||||
|  |       } | ||||||
|  |       this.createDialogOpened = true | ||||||
|  |       listClusterApiGroups(this.name).then(data => { | ||||||
|  |         this.apiGroupsOptions.push({name: "*"}) | ||||||
|  |         this.apiGroupsOptions = this.apiGroupsOptions.concat(data.data) | ||||||
|  |         for (const group of this.apiGroupsOptions) { | ||||||
|  |           if (group.name === "") { | ||||||
|  |             group.name = "core" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     onEdit(row) { | ||||||
|  |       this.operation = "update" | ||||||
|  |       this.editForm.name = row.metadata.name | ||||||
|  |       this.editForm.rules = [] | ||||||
|  |       for (const rule of row.rules) { | ||||||
|  |         if (rule.apiGroups) { | ||||||
|  |           const r = { | ||||||
|  |             apiGroup: [], | ||||||
|  |             resources: rule.resources, | ||||||
|  |             verbs: rule.verbs, | ||||||
|  |           } | ||||||
|  |           for (const g of rule.apiGroups) { | ||||||
|  |             if (g === "") { | ||||||
|  |               r.apiGroup = "core" | ||||||
|  |             } else { | ||||||
|  |               r.apiGroup = g | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |           this.editForm.rules.push(r) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       this.editDialogOpened = true | ||||||
|  |       listClusterApiGroups(this.name).then(data => { | ||||||
|  |         this.apiGroupsOptions.push({name: "*"}) | ||||||
|  |         this.apiGroupsOptions = this.apiGroupsOptions.concat(data.data) | ||||||
|  |         for (const group of this.apiGroupsOptions) { | ||||||
|  |           if (group.name === "") { | ||||||
|  |             group.name = "core" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |     onRuleCreate() { | ||||||
|  |       this.ruleForm = { | ||||||
|  |         apiGroup: "", | ||||||
|  |         resources: [], | ||||||
|  |         verbs: [], | ||||||
|  |       } | ||||||
|  |       this.ruleDialogOpened = true | ||||||
|  |       this.resourcesDisable = false | ||||||
|  |       this.verbsDisable = false | ||||||
|  |     }, | ||||||
|  |     onAPIGroupChange() { | ||||||
|  |       this.apiResourceOptions = [] | ||||||
|  |       this.ruleForm.resources = [] | ||||||
|  |       this.ruleForm.verbs = [] | ||||||
|  |       this.resourcesDisable = false | ||||||
|  |       this.verbsDisable = false | ||||||
|  |       if (this.ruleForm.apiGroup === "*") { | ||||||
|  |         this.apiResourceOptions = ["*"] | ||||||
|  |         this.ruleForm.resources = ["*"] | ||||||
|  |         this.resourcesDisable = true | ||||||
|  |         return | ||||||
|  |       } | ||||||
|  |       listClusterResourceByGroupVersion(this.name, this.ruleForm.apiGroup).then(data => { | ||||||
|  |         this.apiResourceOptions.push("*") | ||||||
|  |         this.apiResourceOptions = this.apiResourceOptions.concat(data.data); | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |     onResourcesChange() { | ||||||
|  |       if (this.ruleForm.resources.indexOf("*") > -1) { | ||||||
|  |         this.ruleForm.resources = ["*"] | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     onVerbsChange() { | ||||||
|  |       if (this.ruleForm.verbs.indexOf("*") > -1) { | ||||||
|  |         this.ruleForm.verbs = ["*"] | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     onRuleConfirm() { | ||||||
|  |       const rule = { | ||||||
|  |         apiGroup: this.ruleForm.apiGroup, | ||||||
|  |         resources: this.ruleForm.resources, | ||||||
|  |         verbs: this.ruleForm.verbs | ||||||
|  |       } | ||||||
|  |       switch (this.operation) { | ||||||
|  |  | ||||||
|  |         case "create": | ||||||
|  |           this.clusterRoleForm.rules.push(rule) | ||||||
|  |           break | ||||||
|  |         case "update": | ||||||
|  |           this.editForm.rules.push(rule) | ||||||
|  |           break | ||||||
|  |       } | ||||||
|  |       this.ruleDialogOpened = false | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     onRuleDelete(row) { | ||||||
|  |       switch (this.operation) { | ||||||
|  |         case "create": | ||||||
|  |           this.clusterRoleForm.rules.splice(this.clusterRoleForm.rules.indexOf(row), 1) | ||||||
|  |           break | ||||||
|  |         case "update": | ||||||
|  |           this.editForm.rules.splice(this.clusterRoleForm.rules.indexOf(row), 1) | ||||||
|  |           break | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     onDelete(row) { | ||||||
|  |       this.$confirm(this.$t("commons.confirm_message.delete"), this.$t("commons.message_box.alert"), { | ||||||
|  |         confirmButtonText: this.$t("commons.button.confirm"), | ||||||
|  |         cancelButtonText: this.$t("commons.button.cancel"), | ||||||
|  |         type: 'warning' | ||||||
|  |       }).then(() => { | ||||||
|  |         deleteClusterRole(this.name, row.metadata.name).then(() => { | ||||||
|  |           this.list() | ||||||
|  |         }) | ||||||
|  |       }); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     onConfirm() { | ||||||
|  |       const req = { | ||||||
|  |         metadata: { | ||||||
|  |           name: this.clusterRoleForm.name, | ||||||
|  |           labels: { | ||||||
|  |             "kubeoperator.io/role-type": this.clusterRoleForm.type | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         rules: [] | ||||||
|  |       } | ||||||
|  |       for (const rule of this.clusterRoleForm.rules) { | ||||||
|  |         req.rules.push({ | ||||||
|  |           apiGroups: [rule.apiGroup], | ||||||
|  |           resources: rule.resources, | ||||||
|  |           verbs: rule.verbs, | ||||||
|  |         }) | ||||||
|  |       } | ||||||
|  |       createClusterRole(this.name, req).then(() => { | ||||||
|  |         this.list() | ||||||
|  |         this.createDialogOpened = false | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |     onEditConfirm() { | ||||||
|  |       const req = { | ||||||
|  |         metadata: { | ||||||
|  |           name: this.editForm.name, | ||||||
|  |         }, | ||||||
|  |         rules: [] | ||||||
|  |       } | ||||||
|  |       for (const rule of this.editForm.rules) { | ||||||
|  |         req.rules.push({ | ||||||
|  |           apiGroups: [rule.apiGroup], | ||||||
|  |           resources: rule.resources, | ||||||
|  |           verbs: rule.verbs, | ||||||
|  |         }) | ||||||
|  |       } | ||||||
|  |       updateClusterRole(this.name, req.metadata.name, req).then(() => { | ||||||
|  |         this.list() | ||||||
|  |         this.editDialogOpened = false | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   created() { | ||||||
|  |     this.list() | ||||||
|  |   } | ||||||
|  | } | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style scoped> | <style scoped> | ||||||
|   | |||||||
| @@ -1,41 +1,41 @@ | |||||||
| <template> | <template> | ||||||
|     <layout-content :header="$t('business.cluster.cluster_detail')" :back-to="{ name: 'Clusters' }"> |   <layout-content :header="$t('business.cluster.cluster_detail')" :back-to="{ name: 'Clusters' }"> | ||||||
|         <el-menu class="menuClass" router :default-active="$route.path" mode="horizontal"> |     <el-menu class="menuClass" router :default-active="$route.path" mode="horizontal"> | ||||||
|             <el-menu-item :index="'/clusters/detail/'+name+'/members'">成员管理</el-menu-item> |       <el-menu-item :index="'/clusters/detail/'+name+'/members'">成员</el-menu-item> | ||||||
|             <el-menu-item :index="'/clusters/detail/'+name+'/clusterroles'">角色管理</el-menu-item> |       <el-menu-item :index="'/clusters/detail/'+name+'/clusterroles'">角色</el-menu-item> | ||||||
|         </el-menu> |     </el-menu> | ||||||
|         <br/> |     <br/> | ||||||
|         <div class="detailClass"> |     <div class="detailClass"> | ||||||
|             <router-view></router-view> |       <router-view></router-view> | ||||||
|         </div> |     </div> | ||||||
|     </layout-content> |   </layout-content> | ||||||
|  |  | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
|     import LayoutContent from "@/components/layout/LayoutContent" | import LayoutContent from "@/components/layout/LayoutContent" | ||||||
|  |  | ||||||
|     export default { | export default { | ||||||
|         name: "ClusterEdit", |   name: "ClusterEdit", | ||||||
|         props: ["name"], |   props: ["name"], | ||||||
|         components: {LayoutContent}, |   components: {LayoutContent}, | ||||||
|         data() { |   data() { | ||||||
|             return {} |     return {} | ||||||
|         }, |   }, | ||||||
|         methods: {}, |   methods: {}, | ||||||
|     } | } | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style scoped> | <style scoped> | ||||||
|     .menuClass { | .menuClass { | ||||||
|         position: fixed; |   position: fixed; | ||||||
|         z-index: 1; |   z-index: 1; | ||||||
|         width: calc(100vw - 330px); |   width: calc(100vw - 330px); | ||||||
|     } | } | ||||||
|  |  | ||||||
|     .detailClass { | .detailClass { | ||||||
|         margin-top: 60px; |   margin-top: 60px; | ||||||
|         height: calc(100% - 140px); |   height: calc(100% - 140px); | ||||||
|         overflow: auto; |   overflow: auto; | ||||||
|     } | } | ||||||
| </style> | </style> | ||||||
| @@ -1,247 +1,363 @@ | |||||||
| <template> | <template> | ||||||
|     <layout-content> |   <layout-content> | ||||||
|         <complex-table :data="data"> |     <complex-table :data="data"> | ||||||
|             <template #header> |       <template #header> | ||||||
|                 <el-button-group> |         <el-button-group> | ||||||
|                     <el-button type="primary" size="small" @click="onCreate"> |           <el-button type="primary" size="small" @click="onCreate"> | ||||||
|                         {{ $t("commons.button.create") }} |             {{ $t("commons.button.create") }} | ||||||
|                     </el-button> |           </el-button> | ||||||
|                 </el-button-group> |         </el-button-group> | ||||||
|                 <br/> |         <br/> | ||||||
|             </template> |       </template> | ||||||
|             <el-table-column :label="$t('commons.table.name')" min-width="100" fix> |       <el-table-column :label="$t('commons.table.name')" min-width="100" fix> | ||||||
|  |         <template v-slot:default="{row}"> | ||||||
|  |           {{ row.name }} | ||||||
|  |         </template> | ||||||
|  |       </el-table-column> | ||||||
|  |       <el-table-column :label="$t('commons.table.created_time')" min-width="100" fix> | ||||||
|  |         <template v-slot:default="{row}"> | ||||||
|  |           {{ row.createAt }} | ||||||
|  |         </template> | ||||||
|  |       </el-table-column> | ||||||
|  |       <fu-table-operations :buttons="buttons" :label="$t('commons.table.action')" fix/> | ||||||
|  |     </complex-table> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     <el-dialog | ||||||
|  |         title="创建命名空间角色" | ||||||
|  |         :visible.sync="namespaceRoleDialogOpened" | ||||||
|  |         width="40%" | ||||||
|  |         center z-index="20"> | ||||||
|  |       <el-form label-position="left" label-width="144px" :model="namespaceRoleForm"> | ||||||
|  |         <el-form-item label="命名空间"> | ||||||
|  |           <el-select v-model="namespaceRoleForm.namespace" style="width:100%"> | ||||||
|  |             <el-option v-for="(item,index) in getNamespaceOptions" | ||||||
|  |                        :key="index" | ||||||
|  |                        :value="item.metadata.name"> | ||||||
|  |               {{ item.metadata.name }} | ||||||
|  |             </el-option> | ||||||
|  |           </el-select> | ||||||
|  |         </el-form-item> | ||||||
|  |  | ||||||
|  |         <el-form-item label="角色"> | ||||||
|  |           <el-select multiple v-model="namespaceRoleForm.roles" style="width:100%"> | ||||||
|  |             <el-option v-for="(item,index) in namespaceRoleOptions" | ||||||
|  |                        :key="index" | ||||||
|  |                        :value="item.metadata.name"> | ||||||
|  |               {{ item.metadata.name }} | ||||||
|  |             </el-option> | ||||||
|  |           </el-select> | ||||||
|  |         </el-form-item> | ||||||
|  |       </el-form> | ||||||
|  |       <span slot="footer" class="dialog-footer"> | ||||||
|  |                 <el-button @click="namespaceRoleDialogOpened=false">取 消</el-button> | ||||||
|  |                 <el-button type="primary" @click="onNamespaceRoleConfirm">确 定</el-button> | ||||||
|  |         </span> | ||||||
|  |     </el-dialog> | ||||||
|  |  | ||||||
|  |     <el-dialog | ||||||
|  |         title="添加成员" | ||||||
|  |         :visible.sync="createDialogOpened" | ||||||
|  |         z-index="10" | ||||||
|  |         width="60%" | ||||||
|  |         center> | ||||||
|  |       <el-form :model="memberForm" label-position="left" label-width="144px"> | ||||||
|  |         <el-form-item label="用户名称"> | ||||||
|  |           <el-select v-model="memberForm.userName" style="width: 80%"> | ||||||
|  |             <el-option v-for="(item, index) in userOptions" :key="index" :value="item.name"> | ||||||
|  |               {{ item.name }} | ||||||
|  |             </el-option> | ||||||
|  |           </el-select> | ||||||
|  |         </el-form-item> | ||||||
|  |         <el-form-item label="集群角色"> | ||||||
|  |           <el-radio-group v-model="memberForm.roleType"> | ||||||
|  |             <el-radio label="admin">管理者</el-radio> | ||||||
|  |             <el-radio label="viewer">只读者</el-radio> | ||||||
|  |             <el-radio label="custom">自定义</el-radio> | ||||||
|  |           </el-radio-group> | ||||||
|  |         </el-form-item> | ||||||
|  |         <div v-if="memberForm.roleType==='custom'"> | ||||||
|  |           <el-form-item> | ||||||
|  |             <el-select v-model="memberForm.customClusterRoles" multiple style="width: 80%" | ||||||
|  |                        placeholder="请选择"> | ||||||
|  |               <el-option | ||||||
|  |                   v-for="(item,index) in clusterRolesOptions" | ||||||
|  |                   :key="index" | ||||||
|  |                   :value="item.metadata.name"> | ||||||
|  |                 {{ item.metadata.name }} | ||||||
|  |               </el-option> | ||||||
|  |             </el-select> | ||||||
|  |           </el-form-item> | ||||||
|  |  | ||||||
|  |           <el-form-item label="命名空间角色"> | ||||||
|  |             <el-button @click="onNamespaceRoleCreate"><i class="el-icon-plus "></i></el-button> | ||||||
|  |             <el-table | ||||||
|  |                 :data="memberForm.namespaceRoles" | ||||||
|  |                 border | ||||||
|  |                 style="width: 80%"> | ||||||
|  |               <el-table-column | ||||||
|  |                   prop="namespace" | ||||||
|  |                   label="命名空间" | ||||||
|  |               > | ||||||
|  |               </el-table-column> | ||||||
|  |               <el-table-column | ||||||
|  |                   label="角色" | ||||||
|  |               > | ||||||
|                 <template v-slot:default="{row}"> |                 <template v-slot:default="{row}"> | ||||||
|                     {{ row.name }} |                   <span v-for="v in row.roles" :key="v">{{ v }}<br/></span> | ||||||
|                 </template> |                 </template> | ||||||
|             </el-table-column> |               </el-table-column> | ||||||
|             <el-table-column :label="$t('commons.table.kind')" min-width="100" fix> |               <el-table-column width="256x"> | ||||||
|                 <template v-slot:default="{row}"> |                 <template v-slot:default="{row}"> | ||||||
|                     {{ row.kind}} |                   <el-button | ||||||
|  |                       size="mini" circle @click="onNamespaceRoleDelete(row)"> | ||||||
|  |                     <i class="el-icon-delete"></i> | ||||||
|  |                   </el-button> | ||||||
|                 </template> |                 </template> | ||||||
|             </el-table-column> |               </el-table-column> | ||||||
|             <el-table-column :label="$t('commons.table.created_time')" min-width="100" fix> |             </el-table> | ||||||
|                 <template v-slot:default="{row}"> |           </el-form-item> | ||||||
|                     {{ row.createAt }} |         </div> | ||||||
|                 </template> |       </el-form> | ||||||
|             </el-table-column> |       <span slot="footer" class="dialog-footer"> | ||||||
|             <fu-table-operations :buttons="buttons" :label="$t('commons.table.action')" fix/> |                 <el-button @click="createDialogOpened = false">取 消</el-button> | ||||||
|         </complex-table> |                 <el-button type="primary" @click="onConfirm">确 定</el-button> | ||||||
|  |             </span> | ||||||
|  |     </el-dialog> | ||||||
|  |  | ||||||
|  |     <el-dialog | ||||||
|         <el-dialog |         title="编辑成员" | ||||||
|                 title="提示" |         :visible.sync="editDialogOpened" | ||||||
|                 :visible.sync="createDialogOpened" |         width="60%" | ||||||
|                 width="20%" |         center> | ||||||
|                 center> |       <el-form :model="editForm" label-position="left" label-width="144px"> | ||||||
|             <el-form :model="memberForm" label-position="left" label- width="60px"> |         <el-form-item label="用户名称"> | ||||||
|                 <el-form-item label="主体类型"> |           <el-select v-model="editForm.userName" style="width: 80%" disabled="disabled"> | ||||||
|                     <el-select v-model="memberForm.subjectKind" @change="onSubjectKindChange"> |             <el-option v-for="(item, index) in userOptions" :key="index" :value="item.name"> | ||||||
|                         <el-option value="User"> |               {{ item.name }} | ||||||
|                             用户 |             </el-option> | ||||||
|                         </el-option> |           </el-select> | ||||||
|                         <el-option value="Group"> |         </el-form-item> | ||||||
|                             用户组 |         <el-form-item label="集群角色"> | ||||||
|                         </el-option> |           <el-radio-group v-model="editForm.roleType"> | ||||||
|                     </el-select> |             <el-radio label="admin">管理者</el-radio> | ||||||
|                 </el-form-item> |             <el-radio label="viewer">只读者</el-radio> | ||||||
|                 <el-form-item label="主体名称"> |             <el-radio label="custom">自定义</el-radio> | ||||||
|                     <el-select v-model="memberForm.subjectName"> |           </el-radio-group> | ||||||
|                         <el-option v-for="(item, index) in memberOptions" :key="index" :value="item.name"> |         </el-form-item> | ||||||
|                             {{item.name}} |         <div v-if="editForm.roleType==='custom'"> | ||||||
|                         </el-option> |           <el-form-item> | ||||||
|                     </el-select> |             <el-select v-model="editForm.customClusterRoles" multiple style="width: 80%" | ||||||
|                 </el-form-item> |                        placeholder="请选择"> | ||||||
|                 <el-form-item label="集群角色"> |               <el-option | ||||||
|                     <el-select v-model="memberForm.clusterRoles" multiple placeholder="请选择"> |                   v-for="(item,index) in clusterRolesOptions" | ||||||
|                         <el-option |                   :key="index" | ||||||
|                                 v-for="(item,index) in clusterRolesOptions" |                   :value="item.metadata.name"> | ||||||
|                                 :key="index" |                 {{ item.metadata.name }} | ||||||
|                                 :value="item.metadata.name"> |               </el-option> | ||||||
|                             {{item.metadata.name}} |             </el-select> | ||||||
|                         </el-option> |           </el-form-item> | ||||||
|                     </el-select> |         </div> | ||||||
|                 </el-form-item> |       </el-form> | ||||||
|             </el-form> |       <span slot="footer" class="dialog-footer"> | ||||||
|             <span slot="footer" class="dialog-footer"> |                 <el-button @click="editDialogOpened = false">取 消</el-button> | ||||||
|     <el-button @click="createDialogOpened = false">取 消</el-button> |                 <el-button type="primary" @click="onEditConfirm">确 定</el-button> | ||||||
|     <el-button type="primary" @click="onConfirm">确 定</el-button> |             </span> | ||||||
|   </span> |     </el-dialog> | ||||||
|         </el-dialog> |   </layout-content> | ||||||
|  |  | ||||||
|  |  | ||||||
|         <el-dialog |  | ||||||
|                 title="提示" |  | ||||||
|                 :visible.sync="editDialogOpened" |  | ||||||
|                 width="20%" |  | ||||||
|                 center> |  | ||||||
|             <el-form :model="editForm" label-position="left" label- width="60px"> |  | ||||||
|                 <el-form-item label="主体类型"> |  | ||||||
|                     <el-select v-model="editForm.subjectKind" disabled @change="onSubjectKindChange"> |  | ||||||
|                         <el-option value="User"> |  | ||||||
|                             用户 |  | ||||||
|                         </el-option> |  | ||||||
|                         <el-option value="Group"> |  | ||||||
|                             用户组 |  | ||||||
|                         </el-option> |  | ||||||
|                     </el-select> |  | ||||||
|                 </el-form-item> |  | ||||||
|                 <el-form-item label="主体名称"> |  | ||||||
|                     <el-select v-model="editForm.subjectName" disabled> |  | ||||||
|                         <el-option v-for="(item, index) in memberOptions" :key="index" :value="item.name"> |  | ||||||
|                             {{item.name}} |  | ||||||
|                         </el-option> |  | ||||||
|                     </el-select> |  | ||||||
|                 </el-form-item> |  | ||||||
|                 <el-form-item label="集群角色"> |  | ||||||
|                     <el-select v-model="editForm.clusterRoles" multiple placeholder="请选择"> |  | ||||||
|                         <el-option |  | ||||||
|                                 v-for="(item,index) in clusterRolesOptions" |  | ||||||
|                                 :key="index" |  | ||||||
|                                 :value="item.metadata.name"> |  | ||||||
|                             {{item.metadata.name}} |  | ||||||
|                         </el-option> |  | ||||||
|                     </el-select> |  | ||||||
|                 </el-form-item> |  | ||||||
|             </el-form> |  | ||||||
|             <span slot="footer" class="dialog-footer"> |  | ||||||
|     <el-button @click="editDialogOpened = false">取 消</el-button> |  | ||||||
|     <el-button type="primary" @click="onEditConfirm">确 定</el-button> |  | ||||||
|   </span> |  | ||||||
|         </el-dialog> |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     </layout-content> |  | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
|     import LayoutContent from "@/components/layout/LayoutContent" | import LayoutContent from "@/components/layout/LayoutContent" | ||||||
|     import ComplexTable from "@/components/complex-table" | import ComplexTable from "@/components/complex-table" | ||||||
|     import {createClusterMember, listClusterMembers} from "@/api/clusters" | import {createClusterMember, listClusterMembers, listNamespaces} from "@/api/clusters" | ||||||
|     import {listUsers} from "@/api/users" | import {listUsers} from "@/api/users" | ||||||
|     import {listGroups} from "@/api/groups" | import {listClusterRoles, deleteClusterMember, getClusterMember, updateClusterMember} from "@/api/clusters"; | ||||||
|     import {listClusterRoles, deleteClusterMember, getClusterMember, updateClusterMember} from "@/api/clusters"; |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     export default { | export default { | ||||||
|         name: "ClusterMembers", |   name: "ClusterMembers", | ||||||
|         props: ["name"], |   props: ["name"], | ||||||
|         components: {LayoutContent, ComplexTable}, |   components: {LayoutContent, ComplexTable}, | ||||||
|         data() { |   data() { | ||||||
|             return { |     return { | ||||||
|                 createDialogOpened: false, |       createDialogOpened: false, | ||||||
|                 editDialogOpened: false, |       editDialogOpened: false, | ||||||
|                 memberOptions: [], |       namespaceRoleDialogOpened: false, | ||||||
|                 clusterRolesOptions: [], |       userOptions: [], | ||||||
|                 memberForm: { |       clusterRolesOptions: [], | ||||||
|                     subjectKind: "", |       namespaceRoleOptions: [], | ||||||
|                     subjectName: "", |       namespaceOptions: [], | ||||||
|                     clusterRoles: [] |       namespaceRoleForm: { | ||||||
|                 }, |         namespace: "", | ||||||
|                 editForm: { |         roles: [], | ||||||
|                     subjectKind: "", |       }, | ||||||
|                     subjectName: "", |       memberForm: { | ||||||
|                     clusterRoles: [] |         userName: "", | ||||||
|                 }, |         customClusterRoles: [], | ||||||
|                 buttons: [ |         namespaceRoles: [], | ||||||
|                     { |         roleType: "admin" | ||||||
|                         label: this.$t("commons.button.edit"), |       }, | ||||||
|                         icon: "el-icon-edit", |       editForm: { | ||||||
|                         click: (row) => { |         userName: "", | ||||||
|                             this.onEdit(row) |         customClusterRoles: [], | ||||||
|                         } |         roleType: "" | ||||||
|                     }, |       }, | ||||||
|                     { |       buttons: [ | ||||||
|                         label: this.$t("commons.button.delete"), |         { | ||||||
|                         icon: "el-icon-delete", |           label: this.$t("commons.button.edit"), | ||||||
|                         click: (row) => { |           icon: "el-icon-edit", | ||||||
|                             this.onDelete(row) |           click: (row) => { | ||||||
|                         } |             this.onEdit(row) | ||||||
|                     }, |           } | ||||||
|                 ], |  | ||||||
|                 data: [], |  | ||||||
|             } |  | ||||||
|         }, |         }, | ||||||
|         methods: { |         { | ||||||
|             list() { |           label: this.$t("commons.button.delete"), | ||||||
|                 this.loading = false |           icon: "el-icon-delete", | ||||||
|                 listClusterMembers(this.name).then(data => { |           click: (row) => { | ||||||
|                     this.loading = false |             this.onDelete(row) | ||||||
|                     this.data = data.data |           } | ||||||
|                 }) |  | ||||||
|             }, |  | ||||||
|             onCreate() { |  | ||||||
|                 this.memberForm.subjectKind = "" |  | ||||||
|                 this.memberForm.clusterRoles = [] |  | ||||||
|                 this.createDialogOpened = true |  | ||||||
|                 listClusterRoles(this.name).then(data => { |  | ||||||
|                     console.log(data) |  | ||||||
|                     this.clusterRolesOptions = data.data |  | ||||||
|                 }) |  | ||||||
|                 this.onSubjectKindChange() |  | ||||||
|             }, |  | ||||||
|  |  | ||||||
|             onEdit(row) { |  | ||||||
|                 listClusterRoles(this.name).then(data => { |  | ||||||
|                     this.clusterRolesOptions = data.data |  | ||||||
|                     getClusterMember(this.name, row.name, row.kind).then(data => { |  | ||||||
|                         this.editForm.subjectName = data.data.name |  | ||||||
|                         this.editForm.subjectKind = data.data.kind |  | ||||||
|                         this.editForm.clusterRoles = data.data.clusterRoles |  | ||||||
|                     }) |  | ||||||
|                 }) |  | ||||||
|                 this.editDialogOpened = true |  | ||||||
|             }, |  | ||||||
|             onDelete(raw) { |  | ||||||
|                 this.$confirm(this.$t("commons.confirm_message.delete"), this.$t("commons.message_box.alert"), { |  | ||||||
|                     confirmButtonText: this.$t("commons.button.confirm"), |  | ||||||
|                     cancelButtonText: this.$t("commons.button.cancel"), |  | ||||||
|                     type: 'warning' |  | ||||||
|                 }).then(() => { |  | ||||||
|                     deleteClusterMember(this.name, raw.name, raw.kind).then(() => { |  | ||||||
|                         this.$message.success("删除成功") |  | ||||||
|                         this.list() |  | ||||||
|                     }) |  | ||||||
|                 }); |  | ||||||
|             }, |  | ||||||
|             onSubjectKindChange() { |  | ||||||
|                 this.memberForm.subjectName = "" |  | ||||||
|                 if (this.memberForm.subjectKind === 'Group') { |  | ||||||
|                     listGroups().then((data) => { |  | ||||||
|                         this.memberOptions = data.data; |  | ||||||
|                     }) |  | ||||||
|                 } |  | ||||||
|                 if (this.memberForm.subjectKind === 'User') { |  | ||||||
|                     listUsers().then((data) => { |  | ||||||
|                         this.memberOptions = data.data; |  | ||||||
|                     }) |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|             onEditConfirm() { |  | ||||||
|                 updateClusterMember(this.name, this.editForm.subjectName, { |  | ||||||
|                     name: this.editForm.subjectName, |  | ||||||
|                     kind: this.editForm.subjectKind, |  | ||||||
|                     clusterRoles: this.editForm.clusterRoles |  | ||||||
|                 }).then(() => { |  | ||||||
|                     this.editDialogOpened = false |  | ||||||
|                     this.list() |  | ||||||
|                 }) |  | ||||||
|             }, |  | ||||||
|  |  | ||||||
|             onConfirm() { |  | ||||||
|                 createClusterMember(this.name, { |  | ||||||
|                     kind: this.memberForm.subjectKind, |  | ||||||
|                     name: this.memberForm.subjectName, |  | ||||||
|                     clusterRoles: this.memberForm.clusterRoles |  | ||||||
|                 }).then(() => { |  | ||||||
|                     this.createDialogOpened = false |  | ||||||
|                     this.list() |  | ||||||
|                 }) |  | ||||||
|             } |  | ||||||
|         }, |         }, | ||||||
|         created() { |       ], | ||||||
|             this.list() |       data: [], | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |   }, | ||||||
|  |   computed: { | ||||||
|  |     getNamespaceOptions() { | ||||||
|  |       return this.namespaceOptions.filter((n) => { | ||||||
|  |         let exists = false | ||||||
|  |         for (const nrs of this.memberForm.namespaceRoles) { | ||||||
|  |           if (nrs.namespace === n.metadata.name) { | ||||||
|  |             exists = true | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         return !exists | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     list() { | ||||||
|  |       this.loading = false | ||||||
|  |       listClusterMembers(this.name).then(data => { | ||||||
|  |         this.loading = false | ||||||
|  |         this.data = data.data | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |     onCreate() { | ||||||
|  |       this.createDialogOpened = true | ||||||
|  |       listClusterRoles(this.name).then(data => { | ||||||
|  |         this.clusterRolesOptions = data.data.filter((r) => { | ||||||
|  |           return r.metadata["labels"]["kubeoperator.io/role-type"] === "cluster" | ||||||
|  |         }) | ||||||
|  |         this.namespaceRoleOptions = data.data.filter((r) => { | ||||||
|  |           return r.metadata["labels"]["kubeoperator.io/role-type"] === "namespace" | ||||||
|  |         }) | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       listUsers().then(data => { | ||||||
|  |         this.userOptions = data.data; | ||||||
|  |       }) | ||||||
|  |       listNamespaces(this.name).then(data => { | ||||||
|  |         this.namespaceOptions = data.data; | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     onEdit(row) { | ||||||
|  |       listClusterRoles(this.name).then(data => { | ||||||
|  |         this.clusterRolesOptions = data.data | ||||||
|  |         getClusterMember(this.name, row.name).then(data => { | ||||||
|  |           this.editForm.userName = data.data.name | ||||||
|  |           if (data.data.clusterRoles.length === 1) { | ||||||
|  |             if (data.data.clusterRoles[0] === 'Admin Cluster') { | ||||||
|  |               this.editForm.roleType = 'admin' | ||||||
|  |             } else if (data.data.clusterRoles[0] === 'View Cluster') { | ||||||
|  |               this.editForm.roleType = 'viewer' | ||||||
|  |             } else { | ||||||
|  |               this.editForm.roleType = 'custom' | ||||||
|  |               this.editForm.customClusterRoles = data.data.clusterRoles | ||||||
|  |             } | ||||||
|  |           } else { | ||||||
|  |             this.editForm.roleType = 'custom' | ||||||
|  |             this.editForm.customClusterRoles = data.data.clusterRoles | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|  |       }) | ||||||
|  |       this.editDialogOpened = true | ||||||
|  |     }, | ||||||
|  |     onDelete(raw) { | ||||||
|  |       this.$confirm(this.$t("commons.confirm_message.delete"), this.$t("commons.message_box.alert"), { | ||||||
|  |         confirmButtonText: this.$t("commons.button.confirm"), | ||||||
|  |         cancelButtonText: this.$t("commons.button.cancel"), | ||||||
|  |         type: 'warning' | ||||||
|  |       }).then(() => { | ||||||
|  |         deleteClusterMember(this.name, raw.name, raw.kind).then(() => { | ||||||
|  |           this.$message.success("删除成功") | ||||||
|  |           this.list() | ||||||
|  |         }) | ||||||
|  |       }); | ||||||
|  |     }, | ||||||
|  |     onNamespaceRoleCreate() { | ||||||
|  |       this.namespaceRoleForm.namespaceRoles = [] | ||||||
|  |       this.namespaceRoleDialogOpened = true | ||||||
|  |       this.namespaceRoleForm.namespace = "" | ||||||
|  |     }, | ||||||
|  |     onEditConfirm() { | ||||||
|  |       let req = { | ||||||
|  |         name: this.editForm.userName, | ||||||
|  |       } | ||||||
|  |       switch (this.editForm.roleType) { | ||||||
|  |         case "admin": | ||||||
|  |           req.clusterRoles = ["Admin Cluster"] | ||||||
|  |           break | ||||||
|  |         case "viewer": | ||||||
|  |           req.clusterRoles = ["View Cluster"] | ||||||
|  |           break | ||||||
|  |         case "custom": | ||||||
|  |           req.clusterRoles = this.editForm.customClusterRoles | ||||||
|  |           break | ||||||
|  |       } | ||||||
|  |       updateClusterMember(this.name, this.editForm.userName, req).then(() => { | ||||||
|  |         this.editDialogOpened = false | ||||||
|  |         this.list() | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |     onNamespaceRoleConfirm() { | ||||||
|  |       this.memberForm.namespaceRoles.push({ | ||||||
|  |         namespace: this.namespaceRoleForm.namespace, | ||||||
|  |         roles: this.namespaceRoleForm.roles, | ||||||
|  |       }) | ||||||
|  |       this.namespaceRoleDialogOpened = false | ||||||
|  |     }, | ||||||
|  |     onNamespaceRoleDelete(row) { | ||||||
|  |       this.memberForm.namespaceRoles.splice(this.memberForm.namespaceRoles.indexOf(row), 1) | ||||||
|  |  | ||||||
|  |     }, | ||||||
|  |     onConfirm() { | ||||||
|  |       let req = { | ||||||
|  |         name: this.memberForm.userName, | ||||||
|  |         namespaceRoles: this.memberForm.namespaceRoles, | ||||||
|  |       } | ||||||
|  |       switch (this.memberForm.roleType) { | ||||||
|  |         case "admin": | ||||||
|  |           req.clusterRoles = ["Admin Cluster"] | ||||||
|  |           break | ||||||
|  |         case "viewer": | ||||||
|  |           req.clusterRoles = ["View Cluster"] | ||||||
|  |           break | ||||||
|  |         case "custom": | ||||||
|  |           req.clusterRoles = this.memberForm.customClusterRoles | ||||||
|  |           break | ||||||
|  |       } | ||||||
|  |       createClusterMember(this.name, req).then(() => { | ||||||
|  |         this.createDialogOpened = false | ||||||
|  |         this.list() | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   created() { | ||||||
|  |     this.list() | ||||||
|  |   } | ||||||
|  | } | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style scoped> | <style scoped> | ||||||
|   | |||||||
| @@ -1,13 +0,0 @@ | |||||||
| <template> |  | ||||||
|     <span>2</span> |  | ||||||
| </template> |  | ||||||
|  |  | ||||||
| <script> |  | ||||||
|     export default { |  | ||||||
|         name: "ClusterOverview" |  | ||||||
|     } |  | ||||||
| </script> |  | ||||||
|  |  | ||||||
| <style scoped> |  | ||||||
|  |  | ||||||
| </style> |  | ||||||
| @@ -86,13 +86,13 @@ const message = { | |||||||
|         }, |         }, | ||||||
|         cluster: { |         cluster: { | ||||||
|             cluster: "集群", |             cluster: "集群", | ||||||
|  |             namespace: "命名空间", | ||||||
|  |             scope: "作用域", | ||||||
|             version: "版本", |             version: "版本", | ||||||
|             list: "集群列表", |             list: "集群列表", | ||||||
|             import: "导入集群", |             import: "导入集群", | ||||||
|             edit: "编辑", |             edit: "编辑", | ||||||
|             nodes: "节点", |             nodes: "节点", | ||||||
|             api_server_help: "例如: https://172.16.10.100:8443", |  | ||||||
|             router_help: "装有 kube-proxy 的任意节点的且可以被访问到的 IP 地址", |  | ||||||
|             label: "标签", |             label: "标签", | ||||||
|             description: "描述", |             description: "描述", | ||||||
|             cluster_detail: "集群详情", |             cluster_detail: "集群详情", | ||||||
| @@ -106,7 +106,7 @@ const message = { | |||||||
|             expect: "敬请期待", |             expect: "敬请期待", | ||||||
|             management: "管理", |             management: "管理", | ||||||
|             open_dashboard: "控制台", |             open_dashboard: "控制台", | ||||||
|             cluster_version: "版本" |             cluster_version: "版本", | ||||||
|  |  | ||||||
|         }, |         }, | ||||||
|         namespace: { |         namespace: { | ||||||
|   | |||||||
| @@ -61,9 +61,8 @@ const Clusters = { | |||||||
|                     meta: { |                     meta: { | ||||||
|                         activeMenu: "/clusters", |                         activeMenu: "/clusters", | ||||||
|                     }, |                     }, | ||||||
|                 } |                 }, | ||||||
|             ] |             ] | ||||||
|  |  | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,8 +5,6 @@ const getters = { | |||||||
|     nickName: state => state.user.nickName, |     nickName: state => state.user.nickName, | ||||||
|     // language: state => state.user.language, |     // language: state => state.user.language, | ||||||
|     permission_routes: state => state.permission.routes, |     permission_routes: state => state.permission.routes, | ||||||
|     menu: state => state.user.menu, |  | ||||||
|     roles: state => state.user.roles, |  | ||||||
|     permissions: state => state.user.permissions |     permissions: state => state.user.permissions | ||||||
| } | } | ||||||
| export default getters | export default getters | ||||||
|   | |||||||
| @@ -5,9 +5,7 @@ import {getLanguage, setLanguage} from "@/i18n" | |||||||
| const state = { | const state = { | ||||||
|     login: false, |     login: false, | ||||||
|     name: "", |     name: "", | ||||||
|     currentProject: "", |  | ||||||
|     language: getLanguage(), |     language: getLanguage(), | ||||||
|     roles: [], |  | ||||||
|     permissions: {} |     permissions: {} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 chenyang
					chenyang