mirror of
https://github.com/1Panel-dev/KubePi.git
synced 2025-11-02 21:24:01 +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,11 +185,24 @@ func (h *Handler) CreateCluster() iris.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
roleBindingName := "ekko:admin-cluster"
|
||||||
|
obj, err := kc.RbacV1().ClusterRoleBindings().Get(goContext.TODO(), roleBindingName, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
if !strings.Contains(strings.ToLower(err.Error()), "not found") {
|
||||||
|
_ = 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{
|
if _, err := kc.RbacV1().ClusterRoleBindings().Create(goContext.TODO(), &rbacV1.ClusterRoleBinding{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: fmt.Sprintf("ekko:cluster-admin-%s", profile.Name),
|
Name: roleBindingName,
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"created-by": profile.Name,
|
"builtin": "true",
|
||||||
|
},
|
||||||
|
Labels: map[string]string{
|
||||||
|
"user-name": profile.Name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Subjects: []rbacV1.Subject{
|
Subjects: []rbacV1.Subject{
|
||||||
@@ -200,7 +212,7 @@ func (h *Handler) CreateCluster() iris.Handler {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
RoleRef: rbacV1.RoleRef{
|
RoleRef: rbacV1.RoleRef{
|
||||||
Name: "cluster-admin",
|
Name: "Admin Cluster",
|
||||||
Kind: "ClusterRole",
|
Kind: "ClusterRole",
|
||||||
},
|
},
|
||||||
}, metav1.CreateOptions{}); err != nil {
|
}, metav1.CreateOptions{}); err != nil {
|
||||||
@@ -209,8 +221,8 @@ func (h *Handler) CreateCluster() iris.Handler {
|
|||||||
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,7 +227,6 @@ 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)
|
||||||
@@ -272,7 +234,6 @@ func (h *Handler) CreateClusterMember() iris.Handler {
|
|||||||
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 NamespaceRoles struct {
|
||||||
|
Namespace string `json:"namespace"`
|
||||||
|
Roles []string `json:"roles"`
|
||||||
|
}
|
||||||
|
|
||||||
type Member struct {
|
type Member struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Kind string `json:"kind"`
|
|
||||||
ClusterRoles []string `json:"clusterRoles"`
|
ClusterRoles []string `json:"clusterRoles"`
|
||||||
CreateAt time.Time `json:"createAt"`
|
|
||||||
BindingName string `json:"bindingName"`
|
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,30 +1,37 @@
|
|||||||
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
|
||||||
|
clusterService cluster.Service
|
||||||
rolebindingService rolebinding.Service
|
rolebindingService rolebinding.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandler() *Handler {
|
func NewHandler() *Handler {
|
||||||
return &Handler{
|
return &Handler{
|
||||||
|
clusterService: cluster.NewService(),
|
||||||
userService: user.NewService(),
|
userService: user.NewService(),
|
||||||
roleService: role.NewService(),
|
roleService: role.NewService(),
|
||||||
rolebindingService: rolebinding.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,16 +15,18 @@
|
|||||||
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);
|
@include flex-row(flex-start, center);
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -50,5 +53,5 @@ export default {
|
|||||||
margin-left: 20px;
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,33 +2,26 @@
|
|||||||
<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>
|
||||||
<el-col :span="6">
|
<el-col :span="8">
|
||||||
<span class="title">{{ $t("business.cluster.version") }}</span>
|
<span class="title">{{ $t("business.cluster.version") }}</span>
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
<div style="text-align: center">
|
<div style="text-align: center">
|
||||||
<h1>v1.18.0</h1>
|
<h1>{{cluster.status.version}}</h1>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6">
|
<el-col :span="8">
|
||||||
<span class="title">{{ $t("business.cluster.nodes") }}</span>
|
|
||||||
<div class="line"></div>
|
|
||||||
<div style="text-align: center">
|
|
||||||
<h1>2</h1>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="6">
|
|
||||||
<span class="title">{{ $t("commons.table.created_time") }}</span>
|
<span class="title">{{ $t("commons.table.created_time") }}</span>
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
<div style="text-align: center">
|
<div style="text-align: center">
|
||||||
<h1>16 days ago</h1>
|
<h1>{{fromNow(cluster.createAt)}} Days</h1>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -55,7 +48,9 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row :gutter="24">
|
<el-row :gutter="24" v-has-permissions="{apiGroup:'',resource:'events',verb:'list'}">
|
||||||
|
<!-- <el-row :gutter="24">-->
|
||||||
|
|
||||||
<h3>Events</h3>
|
<h3>Events</h3>
|
||||||
<complex-table :pagination-config="page" :data="events" @search="search()" v-loading="loading">
|
<complex-table :pagination-config="page" :data="events" @search="search()" v-loading="loading">
|
||||||
<el-table-column label="Reason" prop="reason" fix max-width="50px">
|
<el-table-column label="Reason" prop="reason" fix max-width="50px">
|
||||||
@@ -89,24 +84,28 @@
|
|||||||
</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 {
|
||||||
|
cluster: null,
|
||||||
clusterName: "",
|
clusterName: "",
|
||||||
resources: [],
|
resources: [],
|
||||||
page: {
|
page: {
|
||||||
@@ -118,7 +117,27 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
listResources () {
|
|
||||||
|
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 => {
|
listNamespace(this.clusterName).then(res => {
|
||||||
const namespaces = {
|
const namespaces = {
|
||||||
name: "Namespaces",
|
name: "Namespaces",
|
||||||
@@ -127,6 +146,8 @@ export default {
|
|||||||
}
|
}
|
||||||
this.resources.push(namespaces)
|
this.resources.push(namespaces)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
if (checkPermissions({apiGroup: "networking.k8s.io", resource: "ingresses", verb: "list"})) {
|
||||||
listIngresses(this.clusterName).then(res => {
|
listIngresses(this.clusterName).then(res => {
|
||||||
const ingresses = {
|
const ingresses = {
|
||||||
name: "Ingresses",
|
name: "Ingresses",
|
||||||
@@ -138,6 +159,8 @@ export default {
|
|||||||
}
|
}
|
||||||
this.resources.push(ingresses)
|
this.resources.push(ingresses)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
if (checkPermissions({apiGroup: "", resource: "persistentvolumes", verb: "list"})) {
|
||||||
listPvs(this.clusterName).then(res => {
|
listPvs(this.clusterName).then(res => {
|
||||||
const persistentVolumes = {
|
const persistentVolumes = {
|
||||||
name: "PersistentVolumes",
|
name: "PersistentVolumes",
|
||||||
@@ -146,6 +169,8 @@ export default {
|
|||||||
}
|
}
|
||||||
this.resources.push(persistentVolumes)
|
this.resources.push(persistentVolumes)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
if (checkPermissions({apiGroup: "apps", resource: "deployments", verb: "list"})) {
|
||||||
listDeployments(this.clusterName).then(res => {
|
listDeployments(this.clusterName).then(res => {
|
||||||
const deployments = {
|
const deployments = {
|
||||||
name: "Deployments",
|
name: "Deployments",
|
||||||
@@ -154,6 +179,8 @@ export default {
|
|||||||
}
|
}
|
||||||
this.resources.push(deployments)
|
this.resources.push(deployments)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
if (checkPermissions({apiGroup: "apps", resource: "statefulsets", verb: "list"})) {
|
||||||
listStatefulSets(this.clusterName).then(res => {
|
listStatefulSets(this.clusterName).then(res => {
|
||||||
const statefulSets = {
|
const statefulSets = {
|
||||||
name: "StatefulSets",
|
name: "StatefulSets",
|
||||||
@@ -162,6 +189,8 @@ export default {
|
|||||||
}
|
}
|
||||||
this.resources.push(statefulSets)
|
this.resources.push(statefulSets)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
if (checkPermissions({apiGroup: "batch", resource: "jobs", verb: "list"})) {
|
||||||
listJobs(this.clusterName).then(res => {
|
listJobs(this.clusterName).then(res => {
|
||||||
const jobs = {
|
const jobs = {
|
||||||
name: "Jobs",
|
name: "Jobs",
|
||||||
@@ -170,6 +199,8 @@ export default {
|
|||||||
}
|
}
|
||||||
this.resources.push(jobs)
|
this.resources.push(jobs)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
if (checkPermissions({apiGroup: "apps", resource: "daemonsets", verb: "list"})) {
|
||||||
listDaemonSets(this.clusterName).then(res => {
|
listDaemonSets(this.clusterName).then(res => {
|
||||||
const daemonSets = {
|
const daemonSets = {
|
||||||
name: "DaemonSets",
|
name: "DaemonSets",
|
||||||
@@ -178,6 +209,8 @@ export default {
|
|||||||
}
|
}
|
||||||
this.resources.push(daemonSets)
|
this.resources.push(daemonSets)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
if (checkPermissions({apiGroup: "", resource: "services", verb: "list"})) {
|
||||||
listServices(this.clusterName).then(res => {
|
listServices(this.clusterName).then(res => {
|
||||||
const services = {
|
const services = {
|
||||||
name: "Services",
|
name: "Services",
|
||||||
@@ -186,17 +219,20 @@ export default {
|
|||||||
}
|
}
|
||||||
this.resources.push(services)
|
this.resources.push(services)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
this.search()
|
this.search()
|
||||||
},
|
},
|
||||||
search () {
|
search() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
if (checkPermissions({apiGroup: "", resource: "events", verb: "list"})) {
|
||||||
listEvents(this.clusterName, this.page.pageSize, this.page.nextToken).then(res => {
|
listEvents(this.clusterName, this.page.pageSize, this.page.nextToken).then(res => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
this.events = res.items
|
this.events = res.items
|
||||||
this.page.nextToken = res.metadata["continue"] ? res.metadata["continue"] : ""
|
this.page.nextToken = res.metadata["continue"] ? res.metadata["continue"] : ""
|
||||||
})
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getData (items, keys) {
|
getData(items, keys) {
|
||||||
let key = []
|
let key = []
|
||||||
let result = []
|
let result = []
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
@@ -219,7 +255,7 @@ export default {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
traverse (obj, keys) {
|
traverse(obj, keys) {
|
||||||
if (keys === "status.conditions.type") {
|
if (keys === "status.conditions.type") {
|
||||||
if (obj.status.conditions[0].type && obj.status.conditions[0].status === "True") {
|
if (obj.status.conditions[0].type && obj.status.conditions[0].status === "True") {
|
||||||
return obj.status.conditions[0].type
|
return obj.status.conditions[0].type
|
||||||
@@ -243,11 +279,11 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
this.clusterName = this.$route.query.cluster
|
this.clusterName = this.$route.query.cluster
|
||||||
this.listResources()
|
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,7 +3,7 @@
|
|||||||
: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="/">
|
||||||
@@ -11,8 +11,8 @@
|
|||||||
: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="/">
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
: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,9 +48,9 @@ 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;
|
||||||
@@ -88,19 +88,19 @@ export default {
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-logo-fade-enter-active {
|
.sidebar-logo-fade-enter-active {
|
||||||
transition: opacity 0.1s;
|
transition: opacity 0.1s;
|
||||||
transition-delay: 0.1s;
|
transition-delay: 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-logo-fade-leave-active {
|
.sidebar-logo-fade-leave-active {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-logo-fade-enter,
|
.sidebar-logo-fade-enter,
|
||||||
.sidebar-logo-fade-leave-to {
|
.sidebar-logo-fade-leave-to {
|
||||||
opacity: 0;
|
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,21 +2,31 @@ 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)
|
||||||
|
if (accessRoutes.length > 0) {
|
||||||
|
const root = {
|
||||||
|
path: "/",
|
||||||
|
component: Layout,
|
||||||
|
redirect: accessRoutes[0].path,
|
||||||
|
}
|
||||||
|
router.addRoute(root)
|
||||||
|
}
|
||||||
router.addRoutes(accessRoutes)
|
router.addRoutes(accessRoutes)
|
||||||
next({ ...to, replace: true })
|
next({...to, replace: true})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
NProgress.done()
|
NProgress.done()
|
||||||
}
|
}
|
||||||
@@ -26,22 +36,24 @@ const generateRoutes = async (to, from, next) => {
|
|||||||
//路由前置钩子,根据实际需求修改
|
//路由前置钩子,根据实际需求修改
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
NProgress.start()
|
NProgress.start()
|
||||||
if (!to.query["cluster"]) {
|
|
||||||
if (from.query["cluster"]) {
|
|
||||||
const q = to.query
|
|
||||||
q["cluster"] = from.query["cluster"]
|
|
||||||
next({ path: to.path, query: q })
|
|
||||||
NProgress.done()
|
|
||||||
} else {
|
|
||||||
console.log("no cluster")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const isLogin = await store.dispatch("user/isLogin")
|
const isLogin = await store.dispatch("user/isLogin")
|
||||||
if (isLogin) {
|
if (isLogin) {
|
||||||
if (to.path === "/login") {
|
if (to.path === "/login") {
|
||||||
next({ path: "/" })
|
next({path: "/"})
|
||||||
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)
|
await generateRoutes(to, from, next)
|
||||||
} else {
|
} else {
|
||||||
/* has not login*/
|
/* has not login*/
|
||||||
|
|||||||
@@ -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,7 +7,7 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,12 +47,12 @@ export const rolesRoutes = [
|
|||||||
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"
|
||||||
})
|
})
|
||||||
@@ -60,7 +60,7 @@ const createRouter = () => new Router({
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Layout from "@/business/app-layout/horizontal-layout"
|
|||||||
|
|
||||||
const AccessControl = {
|
const AccessControl = {
|
||||||
path: "/accesscontrol",
|
path: "/accesscontrol",
|
||||||
|
parent: true,
|
||||||
sort: 5,
|
sort: 5,
|
||||||
component: Layout,
|
component: Layout,
|
||||||
name: "Access Control",
|
name: "Access Control",
|
||||||
@@ -13,6 +14,11 @@ const AccessControl = {
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "/serviceaccounts",
|
path: "/serviceaccounts",
|
||||||
|
requirePermission:{
|
||||||
|
apiGroup:"",
|
||||||
|
resource:"serviceaccounts",
|
||||||
|
verb:"list",
|
||||||
|
},
|
||||||
component: () => import("@/business/access-control/service-accounts"),
|
component: () => import("@/business/access-control/service-accounts"),
|
||||||
name: "ServiceAccounts",
|
name: "ServiceAccounts",
|
||||||
meta: {
|
meta: {
|
||||||
@@ -22,6 +28,11 @@ const AccessControl = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/rolebindings",
|
path: "/rolebindings",
|
||||||
|
requirePermission:{
|
||||||
|
apiGroup:"rbac.authorization.k8s.io",
|
||||||
|
resource:"rolebindings",
|
||||||
|
verb:"list",
|
||||||
|
},
|
||||||
component: () => import("@/business/access-control/role-bindings"),
|
component: () => import("@/business/access-control/role-bindings"),
|
||||||
name: "RoleBindings",
|
name: "RoleBindings",
|
||||||
meta: {
|
meta: {
|
||||||
@@ -31,6 +42,11 @@ const AccessControl = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/roles",
|
path: "/roles",
|
||||||
|
requirePermission:{
|
||||||
|
apiGroup:"rbac.authorization.k8s.io",
|
||||||
|
resource:"roles",
|
||||||
|
verb:"list",
|
||||||
|
},
|
||||||
component: () => import("@/business/access-control/roles"),
|
component: () => import("@/business/access-control/roles"),
|
||||||
name: "Roles",
|
name: "Roles",
|
||||||
meta: {
|
meta: {
|
||||||
@@ -40,6 +56,11 @@ const AccessControl = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/podsecuritypolicy",
|
path: "/podsecuritypolicy",
|
||||||
|
requirePermission:{
|
||||||
|
apiGroup:"policy",
|
||||||
|
resource:"podsecuritypolicies",
|
||||||
|
verb:"list",
|
||||||
|
},
|
||||||
component: () => import("@/business/access-control/pod-security-policies"),
|
component: () => import("@/business/access-control/pod-security-policies"),
|
||||||
name: "PodSecurityPolicy",
|
name: "PodSecurityPolicy",
|
||||||
meta: {
|
meta: {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import Layout from "@/business/app-layout/horizontal-layout"
|
|||||||
|
|
||||||
const Clusters = {
|
const Clusters = {
|
||||||
path: "/cluster",
|
path: "/cluster",
|
||||||
|
parent: true,
|
||||||
|
global: true,
|
||||||
sort: 1,
|
sort: 1,
|
||||||
component: Layout,
|
component: Layout,
|
||||||
name: "Cluster",
|
name: "Cluster",
|
||||||
@@ -12,6 +14,11 @@ const Clusters = {
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "/nodes",
|
path: "/nodes",
|
||||||
|
requirePermission: {
|
||||||
|
apiGroup: "",
|
||||||
|
resource: "nodes",
|
||||||
|
verb: "list",
|
||||||
|
},
|
||||||
component: () => import("@/business/cluster/nodes"),
|
component: () => import("@/business/cluster/nodes"),
|
||||||
name: "Nodes",
|
name: "Nodes",
|
||||||
meta: {
|
meta: {
|
||||||
@@ -32,6 +39,11 @@ const Clusters = {
|
|||||||
path: "/namespaces",
|
path: "/namespaces",
|
||||||
component: () => import("@/business/cluster/namespaces"),
|
component: () => import("@/business/cluster/namespaces"),
|
||||||
name: "Namespaces",
|
name: "Namespaces",
|
||||||
|
requirePermission: {
|
||||||
|
apiGroup: "",
|
||||||
|
resource: "namespaces",
|
||||||
|
verb: "list",
|
||||||
|
},
|
||||||
meta: {
|
meta: {
|
||||||
title: "Namespaces"
|
title: "Namespaces"
|
||||||
}
|
}
|
||||||
@@ -69,6 +81,11 @@ const Clusters = {
|
|||||||
path: "/events",
|
path: "/events",
|
||||||
component: () => import("@/business/cluster/events"),
|
component: () => import("@/business/cluster/events"),
|
||||||
name: "events",
|
name: "events",
|
||||||
|
requirePermission: {
|
||||||
|
apiGroup: "",
|
||||||
|
resource: "events",
|
||||||
|
verb: "list",
|
||||||
|
},
|
||||||
meta: {
|
meta: {
|
||||||
title: "Events",
|
title: "Events",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Layout from "@/business/app-layout/horizontal-layout"
|
|||||||
|
|
||||||
const Configuration = {
|
const Configuration = {
|
||||||
path: "/configuration",
|
path: "/configuration",
|
||||||
|
parent: true,
|
||||||
sort: 2,
|
sort: 2,
|
||||||
component: Layout,
|
component: Layout,
|
||||||
name: "Configuration",
|
name: "Configuration",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import Layout from "@/business/app-layout/horizontal-layout"
|
|||||||
const Dashboard = {
|
const Dashboard = {
|
||||||
path: "/dashboard",
|
path: "/dashboard",
|
||||||
sort: 0,
|
sort: 0,
|
||||||
|
global: true,
|
||||||
component: Layout,
|
component: Layout,
|
||||||
name: "Dashboard",
|
name: "Dashboard",
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import Layout from "@/business/app-layout/horizontal-layout"
|
|||||||
const Storage = {
|
const Storage = {
|
||||||
path: "/storage",
|
path: "/storage",
|
||||||
sort: 3,
|
sort: 3,
|
||||||
|
parent: true,
|
||||||
component: Layout,
|
component: Layout,
|
||||||
name: "Storage",
|
name: "Storage",
|
||||||
meta: {
|
meta: {
|
||||||
@@ -11,7 +12,12 @@ const Storage = {
|
|||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "/pv",
|
path: "/persistentvolumes",
|
||||||
|
requirePermission: {
|
||||||
|
apiGroup: "",
|
||||||
|
resource: "persistentvolumes",
|
||||||
|
verb: "list",
|
||||||
|
},
|
||||||
component: () => import("@/business/storage/pvs"),
|
component: () => import("@/business/storage/pvs"),
|
||||||
name: "Pvs",
|
name: "Pvs",
|
||||||
meta: {
|
meta: {
|
||||||
@@ -19,7 +25,12 @@ const Storage = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/pvcs",
|
path: "/persistentvolumeclaims",
|
||||||
|
requirePermission: {
|
||||||
|
apiGroup: "",
|
||||||
|
resource: "persistentvolumeclaims",
|
||||||
|
verb: "list",
|
||||||
|
},
|
||||||
component: () => import("@/business/storage/pvcs"),
|
component: () => import("@/business/storage/pvcs"),
|
||||||
name: "Pvcs",
|
name: "Pvcs",
|
||||||
meta: {
|
meta: {
|
||||||
@@ -28,6 +39,11 @@ const Storage = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/storageclasses",
|
path: "/storageclasses",
|
||||||
|
requirePermission: {
|
||||||
|
apiGroup: "storage.k8s.io",
|
||||||
|
resource: "storageclasses",
|
||||||
|
verb: "list",
|
||||||
|
},
|
||||||
component: () => import("@/business/storage/storage-classes"),
|
component: () => import("@/business/storage/storage-classes"),
|
||||||
name: "StorageClasses",
|
name: "StorageClasses",
|
||||||
meta: {
|
meta: {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Layout from "@/business/app-layout/horizontal-layout";
|
|||||||
|
|
||||||
const Workloads = {
|
const Workloads = {
|
||||||
path: "/workloads",
|
path: "/workloads",
|
||||||
|
parent: true,
|
||||||
sort: 4,
|
sort: 4,
|
||||||
component: Layout,
|
component: Layout,
|
||||||
name: "Workloads",
|
name: "Workloads",
|
||||||
@@ -12,6 +13,11 @@ const Workloads = {
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "/deployments",
|
path: "/deployments",
|
||||||
|
requirePermission: {
|
||||||
|
apiGroup: "apps",
|
||||||
|
resource: "deployments",
|
||||||
|
verb: "list",
|
||||||
|
},
|
||||||
component: () => import("@/business/workloads/deployments"),
|
component: () => import("@/business/workloads/deployments"),
|
||||||
name: "Deployments",
|
name: "Deployments",
|
||||||
meta: {
|
meta: {
|
||||||
@@ -46,80 +52,13 @@ const Workloads = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
path: "/cronjobs",
|
|
||||||
component: () => import("@/business/workloads/cronjobs"),
|
|
||||||
name: "CronJobs",
|
|
||||||
meta: {
|
|
||||||
title: "CronJobs",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/cronjobs/detail/:namespace/:name",
|
|
||||||
name: "CronJobDetail",
|
|
||||||
hidden: true,
|
|
||||||
component: () => import("@/business/workloads/cronjobs/detail"),
|
|
||||||
meta: {
|
|
||||||
activeMenu: "/cronjobs",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/cronjobs/create",
|
|
||||||
name: "CronJobCreate",
|
|
||||||
hidden: true,
|
|
||||||
component: () => import("@/business/workloads/cronjobs/create"),
|
|
||||||
meta: {
|
|
||||||
activeMenu: "/cronjobs",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/cronjobs/edit/:namespace/:name",
|
|
||||||
name: "CronJobEdit",
|
|
||||||
hidden: true,
|
|
||||||
component: () => import("@/business/workloads/cronjobs/edit"),
|
|
||||||
meta: {
|
|
||||||
activeMenu: "/cronjobs",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
path: "/jobs",
|
|
||||||
component: () => import("@/business/workloads/jobs"),
|
|
||||||
name: "Jobs",
|
|
||||||
meta: {
|
|
||||||
title: "Jobs",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/jobs/detail/:namespace/:name",
|
|
||||||
name: "JobDetail",
|
|
||||||
hidden: true,
|
|
||||||
component: () => import("@/business/workloads/jobs/detail"),
|
|
||||||
meta: {
|
|
||||||
activeMenu: "/jobs",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/jobs/create",
|
|
||||||
name: "JobCreate",
|
|
||||||
hidden: true,
|
|
||||||
component: () => import("@/business/workloads/jobs/create"),
|
|
||||||
meta: {
|
|
||||||
activeMenu: "/jobs",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/jobs/edit/:namespace/:name",
|
|
||||||
name: "JobEdit",
|
|
||||||
hidden: true,
|
|
||||||
component: () => import("@/business/workloads/jobs/edit"),
|
|
||||||
meta: {
|
|
||||||
activeMenu: "/jobs",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
path: "/daemonsets",
|
path: "/daemonsets",
|
||||||
|
requirePermission: {
|
||||||
|
apiGroup: "apps",
|
||||||
|
resource: "daemonsets",
|
||||||
|
verb: "list",
|
||||||
|
},
|
||||||
component: () => import("@/business/workloads/daemonsets"),
|
component: () => import("@/business/workloads/daemonsets"),
|
||||||
name: "DaemonSets",
|
name: "DaemonSets",
|
||||||
meta: {
|
meta: {
|
||||||
@@ -156,6 +95,11 @@ const Workloads = {
|
|||||||
|
|
||||||
{
|
{
|
||||||
path: "/statefulsets",
|
path: "/statefulsets",
|
||||||
|
requirePermission: {
|
||||||
|
apiGroup: "apps",
|
||||||
|
resource: "statefulsets",
|
||||||
|
verb: "list",
|
||||||
|
},
|
||||||
component: () => import("@/business/workloads/statefulsets"),
|
component: () => import("@/business/workloads/statefulsets"),
|
||||||
name: "StatefulSets",
|
name: "StatefulSets",
|
||||||
meta: {
|
meta: {
|
||||||
@@ -190,8 +134,96 @@ const Workloads = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: "/cronjobs",
|
||||||
|
requirePermission: {
|
||||||
|
apiGroup: "batch",
|
||||||
|
resource: "cronjobs",
|
||||||
|
verb: "list",
|
||||||
|
},
|
||||||
|
component: () => import("@/business/workloads/cronjobs"),
|
||||||
|
name: "CronJobs",
|
||||||
|
meta: {
|
||||||
|
title: "CronJobs",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/cronjobs/detail/:namespace/:name",
|
||||||
|
name: "CronJobDetail",
|
||||||
|
hidden: true,
|
||||||
|
component: () => import("@/business/workloads/cronjobs/detail"),
|
||||||
|
meta: {
|
||||||
|
activeMenu: "/cronjobs",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/cronjobs/create",
|
||||||
|
name: "CronJobCreate",
|
||||||
|
hidden: true,
|
||||||
|
component: () => import("@/business/workloads/cronjobs/create"),
|
||||||
|
meta: {
|
||||||
|
activeMenu: "/cronjobs",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/cronjobs/edit/:namespace/:name",
|
||||||
|
name: "CronJobEdit",
|
||||||
|
hidden: true,
|
||||||
|
component: () => import("@/business/workloads/cronjobs/edit"),
|
||||||
|
meta: {
|
||||||
|
activeMenu: "/cronjobs",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: "/jobs",
|
||||||
|
requirePermission: {
|
||||||
|
apiGroup: "batch",
|
||||||
|
resource: "jobs",
|
||||||
|
verb: "list",
|
||||||
|
},
|
||||||
|
component: () => import("@/business/workloads/jobs"),
|
||||||
|
name: "Jobs",
|
||||||
|
meta: {
|
||||||
|
title: "Jobs",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/jobs/detail/:namespace/:name",
|
||||||
|
name: "JobDetail",
|
||||||
|
hidden: true,
|
||||||
|
component: () => import("@/business/workloads/jobs/detail"),
|
||||||
|
meta: {
|
||||||
|
activeMenu: "/jobs",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/jobs/create",
|
||||||
|
name: "JobCreate",
|
||||||
|
hidden: true,
|
||||||
|
component: () => import("@/business/workloads/jobs/create"),
|
||||||
|
meta: {
|
||||||
|
activeMenu: "/jobs",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/jobs/edit/:namespace/:name",
|
||||||
|
name: "JobEdit",
|
||||||
|
hidden: true,
|
||||||
|
component: () => import("@/business/workloads/jobs/edit"),
|
||||||
|
meta: {
|
||||||
|
activeMenu: "/jobs",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
path: "/pods",
|
path: "/pods",
|
||||||
|
requirePermission: {
|
||||||
|
apiGroup: "",
|
||||||
|
resource: "pods",
|
||||||
|
verb: "list",
|
||||||
|
},
|
||||||
component: () => import("@/business/workloads/pods"),
|
component: () => import("@/business/workloads/pods"),
|
||||||
name: "Pods",
|
name: "Pods",
|
||||||
meta: {
|
meta: {
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
const getters = {
|
const getters = {
|
||||||
sidebar: state => state.app.sidebar,
|
sidebar: state => state.app.sidebar,
|
||||||
name: state => state.user.name,
|
name: state => state.user.name,
|
||||||
|
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,
|
roles: state => state.user.roles,
|
||||||
|
cluster: state => state.user.cluster,
|
||||||
|
clusterRoles: state => state.user.clusterRoles,
|
||||||
}
|
}
|
||||||
export default getters
|
export default getters
|
||||||
|
|||||||
@@ -5,24 +5,37 @@ const state = {
|
|||||||
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)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function filterRolesRoutes (routes, user) {
|
export function filterRolesRoutes(routes, clusterRoles) {
|
||||||
const res = []
|
const res = []
|
||||||
routes.forEach(route => {
|
routes.forEach(route => {
|
||||||
const tmp = { ...route }
|
const tmp = {...route}
|
||||||
// if (hasPermission(user, tmp)) {
|
if (hasPermission(clusterRoles, tmp)) {
|
||||||
if (tmp.children) {
|
if (tmp.children) {
|
||||||
tmp.children = filterRolesRoutes(tmp.children, user)
|
tmp.children = filterRolesRoutes(tmp.children, clusterRoles)
|
||||||
}
|
}
|
||||||
res.push(tmp)
|
res.push(tmp)
|
||||||
// }
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return res
|
return res
|
||||||
@@ -36,12 +49,28 @@ const mutations = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
for (const route of accessedRoutes) {
|
||||||
|
if (route.parent) {
|
||||||
|
let hidden = true
|
||||||
|
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)
|
commit("SET_ROUTES", accessedRoutes)
|
||||||
resolve(accessedRoutes)
|
resolve(accessedRoutes)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
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 = {
|
||||||
@@ -21,6 +22,9 @@ const mutations = {
|
|||||||
SET_NAME: (state, name) => {
|
SET_NAME: (state, name) => {
|
||||||
state.name = name
|
state.name = name
|
||||||
},
|
},
|
||||||
|
SET_NICK_NAME: (state, nickName) => {
|
||||||
|
state.nickName = nickName
|
||||||
|
},
|
||||||
SET_LANGUAGE: (state, language) => {
|
SET_LANGUAGE: (state, language) => {
|
||||||
state.language = language
|
state.language = language
|
||||||
setLanguage(language)
|
setLanguage(language)
|
||||||
@@ -28,20 +32,23 @@ const mutations = {
|
|||||||
SET_ROLES: (state, roles) => {
|
SET_ROLES: (state, roles) => {
|
||||||
state.roles = roles
|
state.roles = roles
|
||||||
},
|
},
|
||||||
SET_CURRENT_MENU: (state, menu) => {
|
|
||||||
state.menu = menu
|
|
||||||
},
|
|
||||||
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)
|
||||||
|
},
|
||||||
|
login({commit}, userInfo) {
|
||||||
|
const {username, password} = userInfo
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
commit("LOGIN")
|
commit("LOGIN")
|
||||||
login({ username: username.trim(), password: password }).then(response => {
|
login({username: username.trim(), password: password}).then(response => {
|
||||||
commit("LOGIN")
|
commit("LOGIN")
|
||||||
resolve(response)
|
resolve(response)
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
@@ -50,7 +57,7 @@ const actions = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
isLogin ({ commit }) {
|
isLogin({commit}) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (state.login) {
|
if (state.login) {
|
||||||
resolve(true)
|
resolve(true)
|
||||||
@@ -67,22 +74,25 @@ const actions = {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getCurrentUser ({ commit }) {
|
getCurrentUser({commit}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getCurrentUser().then(data => {
|
const clusterName = store.getters.cluster
|
||||||
|
getCurrentClusterUser(clusterName).then(data => {
|
||||||
const user = data.data
|
const user = data.data
|
||||||
user["roles"] = ["ADMIN"]
|
user["roles"] = ["ADMIN"]
|
||||||
const { name, roles } = user
|
const {name, nickName, roles, clusterRoles} = user
|
||||||
commit("SET_NAME", name)
|
commit("SET_NAME", name)
|
||||||
commit("SET_ROLES", roles)
|
commit("SET_ROLES", roles)
|
||||||
commit("SET_LANGUAGE", "zh-CN")
|
commit("SET_LANGUAGE", "zh-CN")
|
||||||
|
commit("SET_CLUSTER_ROLES", clusterRoles)
|
||||||
|
commit("SET_NICK_NAME", nickName)
|
||||||
resolve(user)
|
resolve(user)
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
reject(error)
|
reject(error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
logout ({ commit }) {
|
logout({commit}) {
|
||||||
logout().then(() => {
|
logout().then(() => {
|
||||||
commit("LOGOUT")
|
commit("LOGOUT")
|
||||||
commit("SET_ROLES", [])
|
commit("SET_ROLES", [])
|
||||||
|
|||||||
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,14 +14,16 @@
|
|||||||
{{ 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>
|
|
||||||
|
<el-table-column :label="$t('business.cluster.scope')" min-width="100" fix>
|
||||||
<template v-slot:default="{row}">
|
<template v-slot:default="{row}">
|
||||||
{{$t("business.cluster_role."+row.metadata.annotations["ekko-i18n"])}}
|
{{ $t('business.cluster.' + row.metadata.labels["kubeoperator.io/role-type"]) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('commons.table.creat_by')" min-width="100" fix>
|
|
||||||
|
<el-table-column :label="$t('commons.table.built_in')" min-width="100" fix>
|
||||||
<template v-slot:default="{row}">
|
<template v-slot:default="{row}">
|
||||||
{{ row.metadata.annotations["created-by"] }}
|
{{ $t('commons.bool.' + row.metadata.annotations["builtin"]) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('commons.table.created_time')" min-width="100" fix>
|
<el-table-column :label="$t('commons.table.created_time')" min-width="100" fix>
|
||||||
@@ -39,10 +41,12 @@
|
|||||||
center z-index="20">
|
center z-index="20">
|
||||||
<el-form :model="ruleForm" label-position="top" label- width="60px">
|
<el-form :model="ruleForm" label-position="top" label- width="60px">
|
||||||
<el-form-item label="API Group">
|
<el-form-item label="API Group">
|
||||||
<el-select v-model="ruleForm.groupVersion" style="width:100%" @change="onAPIGroupChange">
|
<el-select v-model="ruleForm.apiGroup" style="width:100%" @change="onAPIGroupChange">
|
||||||
<el-option v-for="(item,index) in apiGroupsOptions" :key="index"
|
<el-option v-for="(item,index) in apiGroupsOptions" :key="index"
|
||||||
:value="item.preferredVersion.groupVersion">
|
:value="item.name">
|
||||||
{{item.preferredVersion.groupVersion}}
|
<span v-if="item.name">
|
||||||
|
{{ item.name }}
|
||||||
|
</span>
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -52,8 +56,8 @@
|
|||||||
@change="onResourcesChange">
|
@change="onResourcesChange">
|
||||||
<el-option v-for="(item,index) in apiResourceOptions"
|
<el-option v-for="(item,index) in apiResourceOptions"
|
||||||
:key="index"
|
:key="index"
|
||||||
:value="item.name">
|
:value="item">
|
||||||
{{item.name}}
|
{{ item }}
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -64,7 +68,7 @@
|
|||||||
<el-option v-for="(item,index) in verbOptions"
|
<el-option v-for="(item,index) in verbOptions"
|
||||||
:key="index"
|
:key="index"
|
||||||
:value="item">
|
:value="item">
|
||||||
{{item}}
|
{{ item }}
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -83,16 +87,21 @@
|
|||||||
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-radio v-model="clusterRoleForm.type" label="cluster">{{ $t('business.cluster.cluster') }}</el-radio>
|
||||||
|
<el-radio v-model="clusterRoleForm.type" label="namespace">{{ $t('business.cluster.namespace') }}</el-radio>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="规则" label-width="144px">
|
||||||
<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="clusterRoleForm.rules"
|
:data="clusterRoleForm.rules"
|
||||||
border
|
border
|
||||||
style="width: 100%">
|
style="width: 80%">
|
||||||
<el-table-column
|
<el-table-column
|
||||||
prop="apiGroup"
|
prop="apiGroup"
|
||||||
label="API Group"
|
label="API Group"
|
||||||
@@ -103,7 +112,7 @@
|
|||||||
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
|
||||||
@@ -111,7 +120,7 @@
|
|||||||
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>
|
||||||
@@ -159,7 +168,7 @@
|
|||||||
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
|
||||||
@@ -167,7 +176,7 @@
|
|||||||
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>
|
||||||
@@ -193,19 +202,19 @@
|
|||||||
</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},
|
||||||
@@ -221,6 +230,7 @@
|
|||||||
memberOptions: [],
|
memberOptions: [],
|
||||||
clusterRoleForm: {
|
clusterRoleForm: {
|
||||||
name: "",
|
name: "",
|
||||||
|
type: "cluster",
|
||||||
rules: [],
|
rules: [],
|
||||||
},
|
},
|
||||||
editForm: {
|
editForm: {
|
||||||
@@ -230,7 +240,7 @@
|
|||||||
resourcesDisable: false,
|
resourcesDisable: false,
|
||||||
verbsDisable: false,
|
verbsDisable: false,
|
||||||
ruleForm: {
|
ruleForm: {
|
||||||
groupVersion: "",
|
apiGroup: "",
|
||||||
resources: [],
|
resources: [],
|
||||||
verbs: []
|
verbs: []
|
||||||
},
|
},
|
||||||
@@ -240,6 +250,9 @@
|
|||||||
icon: "el-icon-edit",
|
icon: "el-icon-edit",
|
||||||
click: (row) => {
|
click: (row) => {
|
||||||
this.onEdit(row)
|
this.onEdit(row)
|
||||||
|
},
|
||||||
|
disabled: (row) => {
|
||||||
|
return row.metadata.annotations['builtin'] === 'true'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -247,6 +260,9 @@
|
|||||||
icon: "el-icon-delete",
|
icon: "el-icon-delete",
|
||||||
click: (row) => {
|
click: (row) => {
|
||||||
this.onDelete(row)
|
this.onDelete(row)
|
||||||
|
},
|
||||||
|
disabled: (row) => {
|
||||||
|
return row.metadata.annotations['builtin'] === 'true'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -266,11 +282,17 @@
|
|||||||
this.clusterRoleForm = {
|
this.clusterRoleForm = {
|
||||||
name: "",
|
name: "",
|
||||||
rules: [],
|
rules: [],
|
||||||
|
type: "cluster",
|
||||||
}
|
}
|
||||||
this.createDialogOpened = true
|
this.createDialogOpened = true
|
||||||
listClusterApiGroups(this.name).then(data => {
|
listClusterApiGroups(this.name).then(data => {
|
||||||
this.apiGroupsOptions.push({preferredVersion: {groupVersion: "*"}})
|
this.apiGroupsOptions.push({name: "*"})
|
||||||
this.apiGroupsOptions = this.apiGroupsOptions.concat(data.data)
|
this.apiGroupsOptions = this.apiGroupsOptions.concat(data.data)
|
||||||
|
for (const group of this.apiGroupsOptions) {
|
||||||
|
if (group.name === "") {
|
||||||
|
group.name = "core"
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -280,22 +302,35 @@
|
|||||||
this.editForm.rules = []
|
this.editForm.rules = []
|
||||||
for (const rule of row.rules) {
|
for (const rule of row.rules) {
|
||||||
if (rule.apiGroups) {
|
if (rule.apiGroups) {
|
||||||
this.editForm.rules.push({
|
const r = {
|
||||||
apiGroup: rule.apiGroups[0],
|
apiGroup: [],
|
||||||
resources: rule.resources,
|
resources: rule.resources,
|
||||||
verbs: rule.verbs,
|
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
|
this.editDialogOpened = true
|
||||||
listClusterApiGroups(this.name).then(data => {
|
listClusterApiGroups(this.name).then(data => {
|
||||||
this.apiGroupsOptions.push({preferredVersion: {groupVersion: "*"}})
|
this.apiGroupsOptions.push({name: "*"})
|
||||||
this.apiGroupsOptions = this.apiGroupsOptions.concat(data.data)
|
this.apiGroupsOptions = this.apiGroupsOptions.concat(data.data)
|
||||||
|
for (const group of this.apiGroupsOptions) {
|
||||||
|
if (group.name === "") {
|
||||||
|
group.name = "core"
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onRuleCreate() {
|
onRuleCreate() {
|
||||||
this.ruleForm = {
|
this.ruleForm = {
|
||||||
groupVersion: "",
|
apiGroup: "",
|
||||||
resources: [],
|
resources: [],
|
||||||
verbs: [],
|
verbs: [],
|
||||||
}
|
}
|
||||||
@@ -309,14 +344,14 @@
|
|||||||
this.ruleForm.verbs = []
|
this.ruleForm.verbs = []
|
||||||
this.resourcesDisable = false
|
this.resourcesDisable = false
|
||||||
this.verbsDisable = false
|
this.verbsDisable = false
|
||||||
if (this.ruleForm.groupVersion === "*") {
|
if (this.ruleForm.apiGroup === "*") {
|
||||||
this.apiResourceOptions = [{name: "*"}]
|
this.apiResourceOptions = ["*"]
|
||||||
this.ruleForm.resources = ["*"]
|
this.ruleForm.resources = ["*"]
|
||||||
this.resourcesDisable = true
|
this.resourcesDisable = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
listClusterResourceByGroupVersion(this.name, this.ruleForm.groupVersion).then(data => {
|
listClusterResourceByGroupVersion(this.name, this.ruleForm.apiGroup).then(data => {
|
||||||
this.apiResourceOptions.push({name: "*"})
|
this.apiResourceOptions.push("*")
|
||||||
this.apiResourceOptions = this.apiResourceOptions.concat(data.data);
|
this.apiResourceOptions = this.apiResourceOptions.concat(data.data);
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -331,20 +366,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRuleConfirm() {
|
onRuleConfirm() {
|
||||||
switch (this.operation) {
|
const rule = {
|
||||||
case "create":
|
apiGroup: this.ruleForm.apiGroup,
|
||||||
this.clusterRoleForm.rules.push({
|
|
||||||
apiGroup: this.ruleForm.groupVersion,
|
|
||||||
resources: this.ruleForm.resources,
|
resources: this.ruleForm.resources,
|
||||||
verbs: this.ruleForm.verbs
|
verbs: this.ruleForm.verbs
|
||||||
})
|
}
|
||||||
|
switch (this.operation) {
|
||||||
|
|
||||||
|
case "create":
|
||||||
|
this.clusterRoleForm.rules.push(rule)
|
||||||
break
|
break
|
||||||
case "update":
|
case "update":
|
||||||
this.editForm.rules.push({
|
this.editForm.rules.push(rule)
|
||||||
apiGroup: this.ruleForm.groupVersion,
|
|
||||||
resources: this.ruleForm.resources,
|
|
||||||
verbs: this.ruleForm.verbs
|
|
||||||
})
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
this.ruleDialogOpened = false
|
this.ruleDialogOpened = false
|
||||||
@@ -376,6 +409,9 @@
|
|||||||
const req = {
|
const req = {
|
||||||
metadata: {
|
metadata: {
|
||||||
name: this.clusterRoleForm.name,
|
name: this.clusterRoleForm.name,
|
||||||
|
labels: {
|
||||||
|
"kubeoperator.io/role-type": this.clusterRoleForm.type
|
||||||
|
}
|
||||||
},
|
},
|
||||||
rules: []
|
rules: []
|
||||||
}
|
}
|
||||||
@@ -405,7 +441,6 @@
|
|||||||
verbs: rule.verbs,
|
verbs: rule.verbs,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
console.log(123)
|
|
||||||
updateClusterRole(this.name, req.metadata.name, req).then(() => {
|
updateClusterRole(this.name, req.metadata.name, req).then(() => {
|
||||||
this.list()
|
this.list()
|
||||||
this.editDialogOpened = false
|
this.editDialogOpened = false
|
||||||
@@ -415,7 +450,7 @@
|
|||||||
created() {
|
created() {
|
||||||
this.list()
|
this.list()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<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">
|
||||||
@@ -13,9 +13,9 @@
|
|||||||
</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},
|
||||||
@@ -23,19 +23,19 @@
|
|||||||
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>
|
||||||
@@ -14,11 +14,6 @@
|
|||||||
{{ row.name }}
|
{{ row.name }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('commons.table.kind')" min-width="100" fix>
|
|
||||||
<template v-slot:default="{row}">
|
|
||||||
{{ row.kind}}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column :label="$t('commons.table.created_time')" min-width="100" fix>
|
<el-table-column :label="$t('commons.table.created_time')" min-width="100" fix>
|
||||||
<template v-slot:default="{row}">
|
<template v-slot:default="{row}">
|
||||||
{{ row.createAt }}
|
{{ row.createAt }}
|
||||||
@@ -29,38 +24,100 @@
|
|||||||
|
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
title="提示"
|
title="创建命名空间角色"
|
||||||
:visible.sync="createDialogOpened"
|
:visible.sync="namespaceRoleDialogOpened"
|
||||||
width="20%"
|
width="40%"
|
||||||
center>
|
center z-index="20">
|
||||||
<el-form :model="memberForm" label-position="left" label- width="60px">
|
<el-form label-position="left" label-width="144px" :model="namespaceRoleForm">
|
||||||
<el-form-item label="主体类型">
|
<el-form-item label="命名空间">
|
||||||
<el-select v-model="memberForm.subjectKind" @change="onSubjectKindChange">
|
<el-select v-model="namespaceRoleForm.namespace" style="width:100%">
|
||||||
<el-option value="User">
|
<el-option v-for="(item,index) in getNamespaceOptions"
|
||||||
用户
|
:key="index"
|
||||||
</el-option>
|
:value="item.metadata.name">
|
||||||
<el-option value="Group">
|
{{ item.metadata.name }}
|
||||||
用户组
|
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="主体名称">
|
|
||||||
<el-select v-model="memberForm.subjectName">
|
<el-form-item label="角色">
|
||||||
<el-option v-for="(item, index) in memberOptions" :key="index" :value="item.name">
|
<el-select multiple v-model="namespaceRoleForm.roles" style="width:100%">
|
||||||
{{item.name}}
|
<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-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="集群角色">
|
<el-form-item label="集群角色">
|
||||||
<el-select v-model="memberForm.clusterRoles" multiple placeholder="请选择">
|
<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
|
<el-option
|
||||||
v-for="(item,index) in clusterRolesOptions"
|
v-for="(item,index) in clusterRolesOptions"
|
||||||
:key="index"
|
:key="index"
|
||||||
:value="item.metadata.name">
|
:value="item.metadata.name">
|
||||||
{{item.metadata.name}}
|
{{ item.metadata.name }}
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</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}">
|
||||||
|
<span v-for="v in row.roles" :key="v">{{ v }}<br/></span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column width="256x">
|
||||||
|
<template v-slot:default="{row}">
|
||||||
|
<el-button
|
||||||
|
size="mini" circle @click="onNamespaceRoleDelete(row)">
|
||||||
|
<i class="el-icon-delete"></i>
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
</el-form>
|
</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>
|
||||||
@@ -68,61 +125,57 @@
|
|||||||
</span>
|
</span>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
title="提示"
|
title="编辑成员"
|
||||||
:visible.sync="editDialogOpened"
|
:visible.sync="editDialogOpened"
|
||||||
width="20%"
|
width="60%"
|
||||||
center>
|
center>
|
||||||
<el-form :model="editForm" label-position="left" label- width="60px">
|
<el-form :model="editForm" label-position="left" label-width="144px">
|
||||||
<el-form-item label="主体类型">
|
<el-form-item label="用户名称">
|
||||||
<el-select v-model="editForm.subjectKind" disabled @change="onSubjectKindChange">
|
<el-select v-model="editForm.userName" style="width: 80%" disabled="disabled">
|
||||||
<el-option value="User">
|
<el-option v-for="(item, index) in userOptions" :key="index" :value="item.name">
|
||||||
用户
|
{{ item.name }}
|
||||||
</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-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="集群角色">
|
<el-form-item label="集群角色">
|
||||||
<el-select v-model="editForm.clusterRoles" multiple placeholder="请选择">
|
<el-radio-group v-model="editForm.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="editForm.roleType==='custom'">
|
||||||
|
<el-form-item>
|
||||||
|
<el-select v-model="editForm.customClusterRoles" multiple style="width: 80%"
|
||||||
|
placeholder="请选择">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="(item,index) in clusterRolesOptions"
|
v-for="(item,index) in clusterRolesOptions"
|
||||||
:key="index"
|
:key="index"
|
||||||
:value="item.metadata.name">
|
:value="item.metadata.name">
|
||||||
{{item.metadata.name}}
|
{{ item.metadata.name }}
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
</div>
|
||||||
</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 {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},
|
||||||
@@ -130,17 +183,25 @@
|
|||||||
return {
|
return {
|
||||||
createDialogOpened: false,
|
createDialogOpened: false,
|
||||||
editDialogOpened: false,
|
editDialogOpened: false,
|
||||||
memberOptions: [],
|
namespaceRoleDialogOpened: false,
|
||||||
|
userOptions: [],
|
||||||
clusterRolesOptions: [],
|
clusterRolesOptions: [],
|
||||||
|
namespaceRoleOptions: [],
|
||||||
|
namespaceOptions: [],
|
||||||
|
namespaceRoleForm: {
|
||||||
|
namespace: "",
|
||||||
|
roles: [],
|
||||||
|
},
|
||||||
memberForm: {
|
memberForm: {
|
||||||
subjectKind: "",
|
userName: "",
|
||||||
subjectName: "",
|
customClusterRoles: [],
|
||||||
clusterRoles: []
|
namespaceRoles: [],
|
||||||
|
roleType: "admin"
|
||||||
},
|
},
|
||||||
editForm: {
|
editForm: {
|
||||||
subjectKind: "",
|
userName: "",
|
||||||
subjectName: "",
|
customClusterRoles: [],
|
||||||
clusterRoles: []
|
roleType: ""
|
||||||
},
|
},
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
@@ -161,6 +222,19 @@
|
|||||||
data: [],
|
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: {
|
methods: {
|
||||||
list() {
|
list() {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
@@ -170,23 +244,42 @@
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
onCreate() {
|
onCreate() {
|
||||||
this.memberForm.subjectKind = ""
|
|
||||||
this.memberForm.clusterRoles = []
|
|
||||||
this.createDialogOpened = true
|
this.createDialogOpened = true
|
||||||
listClusterRoles(this.name).then(data => {
|
listClusterRoles(this.name).then(data => {
|
||||||
console.log(data)
|
this.clusterRolesOptions = data.data.filter((r) => {
|
||||||
this.clusterRolesOptions = data.data
|
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;
|
||||||
})
|
})
|
||||||
this.onSubjectKindChange()
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onEdit(row) {
|
onEdit(row) {
|
||||||
listClusterRoles(this.name).then(data => {
|
listClusterRoles(this.name).then(data => {
|
||||||
this.clusterRolesOptions = data.data
|
this.clusterRolesOptions = data.data
|
||||||
getClusterMember(this.name, row.name, row.kind).then(data => {
|
getClusterMember(this.name, row.name).then(data => {
|
||||||
this.editForm.subjectName = data.data.name
|
this.editForm.userName = data.data.name
|
||||||
this.editForm.subjectKind = data.data.kind
|
if (data.data.clusterRoles.length === 1) {
|
||||||
this.editForm.clusterRoles = data.data.clusterRoles
|
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
|
this.editDialogOpened = true
|
||||||
@@ -203,45 +296,68 @@
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSubjectKindChange() {
|
onNamespaceRoleCreate() {
|
||||||
this.memberForm.subjectName = ""
|
this.namespaceRoleForm.namespaceRoles = []
|
||||||
if (this.memberForm.subjectKind === 'Group') {
|
this.namespaceRoleDialogOpened = true
|
||||||
listGroups().then((data) => {
|
this.namespaceRoleForm.namespace = ""
|
||||||
this.memberOptions = data.data;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (this.memberForm.subjectKind === 'User') {
|
|
||||||
listUsers().then((data) => {
|
|
||||||
this.memberOptions = data.data;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onEditConfirm() {
|
onEditConfirm() {
|
||||||
updateClusterMember(this.name, this.editForm.subjectName, {
|
let req = {
|
||||||
name: this.editForm.subjectName,
|
name: this.editForm.userName,
|
||||||
kind: this.editForm.subjectKind,
|
}
|
||||||
clusterRoles: this.editForm.clusterRoles
|
switch (this.editForm.roleType) {
|
||||||
}).then(() => {
|
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.editDialogOpened = false
|
||||||
this.list()
|
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() {
|
onConfirm() {
|
||||||
createClusterMember(this.name, {
|
let req = {
|
||||||
kind: this.memberForm.subjectKind,
|
name: this.memberForm.userName,
|
||||||
name: this.memberForm.subjectName,
|
namespaceRoles: this.memberForm.namespaceRoles,
|
||||||
clusterRoles: this.memberForm.clusterRoles
|
}
|
||||||
}).then(() => {
|
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.createDialogOpened = false
|
||||||
this.list()
|
this.list()
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.list()
|
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