feat(rbac): 集群级别rbac重构

This commit is contained in:
chenyang
2021-07-23 17:05:53 +08:00
parent 6928299a0a
commit 44247f934b
54 changed files with 3177 additions and 2020 deletions

View File

@@ -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)
} }
} }

View File

@@ -10,6 +10,7 @@ import (
"github.com/KubeOperator/ekko/internal/service/v1/cluster" "github.com/KubeOperator/ekko/internal/service/v1/cluster"
"github.com/KubeOperator/ekko/internal/service/v1/clusterbinding" "github.com/KubeOperator/ekko/internal/service/v1/clusterbinding"
"github.com/KubeOperator/ekko/internal/service/v1/common" "github.com/KubeOperator/ekko/internal/service/v1/common"
"github.com/KubeOperator/ekko/internal/service/v1/project"
pkgV1 "github.com/KubeOperator/ekko/pkg/api/v1" pkgV1 "github.com/KubeOperator/ekko/pkg/api/v1"
"github.com/KubeOperator/ekko/pkg/certificate" "github.com/KubeOperator/ekko/pkg/certificate"
"github.com/KubeOperator/ekko/pkg/kubernetes" "github.com/KubeOperator/ekko/pkg/kubernetes"
@@ -26,12 +27,14 @@ import (
type Handler struct { type Handler struct {
clusterService cluster.Service clusterService cluster.Service
clusterBindingService clusterbinding.Service clusterBindingService clusterbinding.Service
projectService project.Service
} }
func NewHandler() *Handler { func NewHandler() *Handler {
return &Handler{ return &Handler{
clusterService: cluster.NewService(), clusterService: cluster.NewService(),
clusterBindingService: clusterbinding.NewService(), clusterBindingService: clusterbinding.NewService(),
projectService: project.NewService(),
} }
} }
@@ -131,7 +134,6 @@ func (h *Handler) CreateCluster() iris.Handler {
txOptions := common.DBOptions{DB: tx} txOptions := common.DBOptions{DB: tx}
req.CreatedBy = profile.Name req.CreatedBy = profile.Name
if err := client.CreateDefaultClusterRoles(); err != nil { if err := client.CreateDefaultClusterRoles(); err != nil {
_ = tx.Rollback() _ = tx.Rollback()
ctx.StatusCode(iris.StatusInternalServerError) ctx.StatusCode(iris.StatusInternalServerError)
@@ -152,12 +154,9 @@ func (h *Handler) CreateCluster() iris.Handler {
CreatedBy: profile.Name, CreatedBy: profile.Name,
}, },
Metadata: v1.Metadata{ Metadata: v1.Metadata{
Name: fmt.Sprintf("%s-%s-cluster-binding-", req.Name, profile.Name), Name: fmt.Sprintf("%s-%s-cluster-binding", req.Name, profile.Name),
},
Subject: v1Cluster.Subject{
Name: profile.Name,
Kind: "User",
}, },
UserRef: profile.Name,
ClusterRef: req.Name, ClusterRef: req.Name,
} }
@@ -186,31 +185,44 @@ func (h *Handler) CreateCluster() iris.Handler {
return return
} }
if _, err := kc.RbacV1().ClusterRoleBindings().Create(goContext.TODO(), &rbacV1.ClusterRoleBinding{ roleBindingName := "ekko:admin-cluster"
ObjectMeta: metav1.ObjectMeta{ obj, err := kc.RbacV1().ClusterRoleBindings().Get(goContext.TODO(), roleBindingName, metav1.GetOptions{})
Name: fmt.Sprintf("ekko:cluster-admin-%s", profile.Name), if err != nil {
Annotations: map[string]string{ if !strings.Contains(strings.ToLower(err.Error()), "not found") {
"created-by": profile.Name, _ = tx.Rollback()
ctx.StatusCode(iris.StatusInternalServerError)
ctx.Values().Set("message", err.Error())
}
}
if obj == nil || obj.Name == "" {
if _, err := kc.RbacV1().ClusterRoleBindings().Create(goContext.TODO(), &rbacV1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: roleBindingName,
Annotations: map[string]string{
"builtin": "true",
},
Labels: map[string]string{
"user-name": profile.Name,
},
}, },
}, Subjects: []rbacV1.Subject{
Subjects: []rbacV1.Subject{ {
{ Kind: "User",
Kind: "User", Name: profile.Name,
Name: profile.Name, },
}, },
}, RoleRef: rbacV1.RoleRef{
RoleRef: rbacV1.RoleRef{ Name: "Admin Cluster",
Name: "cluster-admin", Kind: "ClusterRole",
Kind: "ClusterRole", },
}, }, metav1.CreateOptions{}); err != nil {
}, metav1.CreateOptions{}); err != nil { _ = tx.Rollback()
_ = tx.Rollback() ctx.StatusCode(iris.StatusInternalServerError)
ctx.StatusCode(iris.StatusInternalServerError) ctx.Values().Set("message", err.Error())
ctx.Values().Set("message", err.Error()) return
return }
} }
_ = tx.Commit() _ = tx.Commit()
ctx.Values().Set("data", &req) ctx.Values().Set("data", &req)
} }
} }
@@ -236,6 +248,18 @@ func (h *Handler) SearchClusters() iris.Handler {
ctx.Values().Set("data", pkgV1.Page{Items: clusters, Total: total}) ctx.Values().Set("data", pkgV1.Page{Items: clusters, Total: total})
} }
} }
func (h *Handler) GetCluster() iris.Handler {
return func(ctx *context.Context) {
name := ctx.Params().GetString("name")
c, err := h.clusterService.Get(name, common.DBOptions{})
if err != nil {
ctx.StatusCode(iris.StatusBadRequest)
ctx.Values().Set("message", fmt.Sprintf("get clusters failed: %s", err.Error()))
return
}
ctx.Values().Set("data", c)
}
}
func (h *Handler) ListClusters() iris.Handler { func (h *Handler) ListClusters() iris.Handler {
return func(ctx *context.Context) { return func(ctx *context.Context) {
@@ -266,7 +290,7 @@ func (h *Handler) ListClusters() iris.Handler {
return return
} }
for j := range mbs { for j := range mbs {
if mbs[j].Subject.Kind == "User" && mbs[j].Subject.Name == profile.Name { if mbs[j].UserRef == profile.Name {
resultClusters = append(resultClusters, clusters[i]) resultClusters = append(resultClusters, clusters[i])
} }
} }
@@ -323,18 +347,19 @@ func Install(parent iris.Party) {
sp := parent.Party("/clusters") sp := parent.Party("/clusters")
sp.Post("", handler.CreateCluster()) sp.Post("", handler.CreateCluster())
sp.Get("", handler.ListClusters()) sp.Get("", handler.ListClusters())
sp.Get("/:name", handler.GetCluster())
sp.Delete("/:name", handler.DeleteCluster()) sp.Delete("/:name", handler.DeleteCluster())
sp.Post("/search", handler.SearchClusters()) sp.Post("/search", handler.SearchClusters())
sp.Get("/:name/members", handler.GetClusterMembers()) sp.Get("/:name/members", handler.ListClusterMembers())
sp.Post("/:name/members", handler.CreateClusterMember()) sp.Post("/:name/members", handler.CreateClusterMember())
sp.Delete("/:name/members/:member", handler.DeleteClusterMember()) sp.Delete("/:name/members/:member", handler.DeleteClusterMember())
sp.Put("/:name/members/:member", handler.UpdateClusterMember()) sp.Put("/:name/members/:member", handler.UpdateClusterMember())
sp.Get("/:name/members/:member", handler.GetClusterMember()) sp.Get("/:name/members/:member", handler.GetClusterMember())
sp.Get("/:name/clusterroles", handler.GetClusterRoles()) sp.Get("/:name/clusterroles", handler.ListClusterRoles())
sp.Post("/:name/clusterroles", handler.CreateClusterRole()) sp.Post("/:name/clusterroles", handler.CreateClusterRole())
sp.Put("/:name/clusterroles/:clusterrole", handler.UpdateClusterRole()) sp.Put("/:name/clusterroles/:clusterrole", handler.UpdateClusterRole())
sp.Delete("/:name/clusterroles/:clusterrole", handler.DeleteClusterRole()) sp.Delete("/:name/clusterroles/:clusterrole", handler.DeleteClusterRole())
sp.Get("/:name/apigroups", handler.ListApiGroups()) sp.Get("/:name/apigroups", handler.ListApiGroups())
sp.Get("/:name/apigroups/{group:path}", handler.ListApiGroupResources()) sp.Get("/:name/apigroups/{group:path}", handler.ListApiGroupResources())
sp.Get("/:name/namespaces", handler.ListNamespace())
} }

View File

@@ -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()))

View File

@@ -16,7 +16,6 @@ import (
"github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/context"
rbacV1 "k8s.io/api/rbac/v1" rbacV1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"strings"
"time" "time"
) )
@@ -46,10 +45,7 @@ func (h *Handler) UpdateClusterMember() iris.Handler {
return return
} }
binding, err := h.clusterBindingService.GetBindingByClusterNameAndSubject(name, v1Cluster.Subject{ binding, err := h.clusterBindingService.GetBindingByClusterNameAndUserName(name, req.Name, common.DBOptions{})
Kind: req.Kind,
Name: req.Name,
}, common.DBOptions{})
if err != nil { if err != nil {
ctx.StatusCode(iris.StatusInternalServerError) ctx.StatusCode(iris.StatusInternalServerError)
ctx.Values().Set("message", fmt.Sprintf("get cluster binding failed: %s", err.Error())) ctx.Values().Set("message", fmt.Sprintf("get cluster binding failed: %s", err.Error()))
@@ -57,7 +53,7 @@ func (h *Handler) UpdateClusterMember() iris.Handler {
} }
clusterRoleBindings, err := client.RbacV1().ClusterRoleBindings().List(goContext.TODO(), metav1.ListOptions{ clusterRoleBindings, err := client.RbacV1().ClusterRoleBindings().List(goContext.TODO(), metav1.ListOptions{
LabelSelector: fmt.Sprintf("subject-name=%s,subject-kind=%s", binding.Subject.Name, binding.Subject.Kind), LabelSelector: fmt.Sprintf("user-name=%s", binding.UserRef),
}) })
if err != nil { if err != nil {
ctx.StatusCode(iris.StatusInternalServerError) ctx.StatusCode(iris.StatusInternalServerError)
@@ -96,25 +92,22 @@ func (h *Handler) UpdateClusterMember() iris.Handler {
return return
} }
} }
u := ctx.Values().Get("profile")
profile := u.(session.UserProfile)
for i := range forCreate { for i := range forCreate {
b := rbacV1.ClusterRoleBinding{ b := rbacV1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-%s-%s", name, req.Name, forCreate[i]), Name: fmt.Sprintf("%s-%s-%s", name, req.Name, forCreate[i]),
Labels: map[string]string{ Labels: map[string]string{
"manage": "ekko", "kubeoperator.io/manage": "ekko",
"subject-name": req.Name, "user-name": req.Name,
"subject-kind": req.Kind,
}, },
Annotations: map[string]string{ Annotations: map[string]string{
"created-by": profile.Name, "builtin": "false",
"created-at": time.Now().Format("2006-01-02 15:04:05"), "created-at": time.Now().Format("2006-01-02 15:04:05"),
}, },
}, },
Subjects: []rbacV1.Subject{ Subjects: []rbacV1.Subject{
{ {
Kind: req.Kind, Kind: "User",
Name: req.Name, Name: req.Name,
}, },
}, },
@@ -140,27 +133,6 @@ func (h *Handler) GetClusterMember() iris.Handler {
name := ctx.Params().GetString("name") name := ctx.Params().GetString("name")
memberName := ctx.Params().Get("member") memberName := ctx.Params().Get("member")
var kind string
if ctx.URLParamExists("kind") {
kind = ctx.URLParam("kind")
}
subject := v1Cluster.Subject{
Name: memberName,
}
if kind != "" {
switch strings.ToLower(kind) {
case "user":
subject.Kind = "User"
case "group":
subject.Kind = "Group"
default:
ctx.StatusCode(iris.StatusInternalServerError)
ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", fmt.Errorf("invalid kind %s", kind)))
return
}
subject.Kind = kind
}
c, err := h.clusterService.Get(name, common.DBOptions{}) c, err := h.clusterService.Get(name, common.DBOptions{})
if err != nil { if err != nil {
ctx.StatusCode(iris.StatusInternalServerError) ctx.StatusCode(iris.StatusInternalServerError)
@@ -175,7 +147,7 @@ func (h *Handler) GetClusterMember() iris.Handler {
return return
} }
binding, err := h.clusterBindingService.GetBindingByClusterNameAndSubject(name, subject, common.DBOptions{}) binding, err := h.clusterBindingService.GetBindingByClusterNameAndUserName(name, memberName, common.DBOptions{})
if err != nil { if err != nil {
ctx.StatusCode(iris.StatusInternalServerError) ctx.StatusCode(iris.StatusInternalServerError)
ctx.Values().Set("message", fmt.Sprintf("get cluster binding failed: %s", err.Error())) ctx.Values().Set("message", fmt.Sprintf("get cluster binding failed: %s", err.Error()))
@@ -183,7 +155,7 @@ func (h *Handler) GetClusterMember() iris.Handler {
} }
clusterRoleBindings, err := client.RbacV1().ClusterRoleBindings().List(goContext.TODO(), metav1.ListOptions{ clusterRoleBindings, err := client.RbacV1().ClusterRoleBindings().List(goContext.TODO(), metav1.ListOptions{
LabelSelector: fmt.Sprintf("subject-name=%s,subject-kind=%s", binding.Subject.Name, binding.Subject.Kind), LabelSelector: fmt.Sprintf("user-name=%s", binding.UserRef),
}) })
if err != nil { if err != nil {
ctx.StatusCode(iris.StatusInternalServerError) ctx.StatusCode(iris.StatusInternalServerError)
@@ -193,8 +165,7 @@ func (h *Handler) GetClusterMember() iris.Handler {
var member Member var member Member
member.ClusterRoles = make([]string, 0) member.ClusterRoles = make([]string, 0)
member.Kind = binding.Subject.Kind member.Name = binding.UserRef
member.Name = binding.Subject.Name
set := collectons.NewStringSet() set := collectons.NewStringSet()
for i := range clusterRoleBindings.Items { for i := range clusterRoleBindings.Items {
set.Add(clusterRoleBindings.Items[i].RoleRef.Name) set.Add(clusterRoleBindings.Items[i].RoleRef.Name)
@@ -205,7 +176,7 @@ func (h *Handler) GetClusterMember() iris.Handler {
} }
func (h *Handler) GetClusterMembers() iris.Handler { func (h *Handler) ListClusterMembers() iris.Handler {
return func(ctx *context.Context) { return func(ctx *context.Context) {
name := ctx.Params().GetString("name") name := ctx.Params().GetString("name")
bindings, err := h.clusterBindingService.GetClusterBindingByClusterName(name, common.DBOptions{}) bindings, err := h.clusterBindingService.GetClusterBindingByClusterName(name, common.DBOptions{})
@@ -214,17 +185,12 @@ func (h *Handler) GetClusterMembers() iris.Handler {
ctx.Values().Set("message", err.Error()) ctx.Values().Set("message", err.Error())
return return
} }
subjectMap := map[v1Cluster.Subject]v1Cluster.Binding{} var members []Member
for i := range bindings { for i := range bindings {
subjectMap[bindings[i].Subject] = bindings[i]
}
members := make([]Member, 0)
for key := range subjectMap {
members = append(members, Member{ members = append(members, Member{
Name: key.Name, Name: bindings[i].UserRef,
Kind: key.Kind, BindingName: bindings[i].Name,
BindingName: subjectMap[key].Name, CreateAt: bindings[i].CreateAt,
CreateAt: subjectMap[key].CreateAt,
}) })
} }
ctx.Values().Set("data", members) ctx.Values().Set("data", members)
@@ -238,7 +204,7 @@ func (h *Handler) CreateClusterMember() iris.Handler {
err := ctx.ReadJSON(&req) err := ctx.ReadJSON(&req)
if err != nil { if err != nil {
ctx.StatusCode(iris.StatusBadRequest) ctx.StatusCode(iris.StatusBadRequest)
ctx.Values().Set("message", fmt.Sprintf("delete cluster failed: %s", err.Error())) ctx.Values().Set("message", fmt.Sprintf("create cluster member failed: %s", err.Error()))
return return
} }
u := ctx.Values().Get("profile") u := ctx.Values().Get("profile")
@@ -249,12 +215,9 @@ func (h *Handler) CreateClusterMember() iris.Handler {
CreatedBy: profile.Name, CreatedBy: profile.Name,
}, },
Metadata: v1.Metadata{ Metadata: v1.Metadata{
Name: fmt.Sprintf("%s-%s-cluster-binding-", name, req.Name), Name: fmt.Sprintf("%s-%s-cluster-binding", name, req.Name),
},
Subject: v1Cluster.Subject{
Name: req.Name,
Kind: req.Kind,
}, },
UserRef: req.Name,
ClusterRef: name, ClusterRef: name,
} }
c, err := h.clusterService.Get(name, common.DBOptions{}) c, err := h.clusterService.Get(name, common.DBOptions{})
@@ -264,15 +227,13 @@ func (h *Handler) CreateClusterMember() iris.Handler {
return return
} }
k := kubernetes.NewKubernetes(*c) k := kubernetes.NewKubernetes(*c)
if req.Kind == "User" { cert, err := k.CreateCommonUser(req.Name)
cert, err := k.CreateCommonUser(req.Name) if err != nil {
if err != nil { ctx.StatusCode(iris.StatusInternalServerError)
ctx.StatusCode(iris.StatusInternalServerError) ctx.Values().Set("message", fmt.Sprintf("create common user failed: %s", err.Error()))
ctx.Values().Set("message", fmt.Sprintf("create common user failed: %s", err.Error())) return
return
}
binding.Certificate = cert
} }
binding.Certificate = cert
if err := h.clusterBindingService.CreateClusterBinding(&binding, common.DBOptions{}); err != nil { if err := h.clusterBindingService.CreateClusterBinding(&binding, common.DBOptions{}); err != nil {
ctx.StatusCode(iris.StatusInternalServerError) ctx.StatusCode(iris.StatusInternalServerError)
ctx.Values().Set("message", err.Error()) ctx.Values().Set("message", err.Error())
@@ -284,25 +245,23 @@ func (h *Handler) CreateClusterMember() iris.Handler {
ctx.StatusCode(iris.StatusInternalServerError) ctx.StatusCode(iris.StatusInternalServerError)
ctx.Values().Set("message", fmt.Sprintf("get k8s client failed: %s", err.Error())) ctx.Values().Set("message", fmt.Sprintf("get k8s client failed: %s", err.Error()))
return return
} }
for i := range req.ClusterRoles { for i := range req.ClusterRoles {
binding := rbacV1.ClusterRoleBinding{ binding := rbacV1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-%s-%s", name, req.Name, req.ClusterRoles[i]), Name: fmt.Sprintf("%s-%s", req.Name, req.ClusterRoles[i]),
Labels: map[string]string{ Labels: map[string]string{
"manage": "ekko", "kubeoperator.io/manage": "ekko",
"subject-name": req.Name, "user-name": req.Name,
"subject-kind": req.Kind,
}, },
Annotations: map[string]string{ Annotations: map[string]string{
"created-by": profile.Name, "builtin": "false",
"created-at": time.Now().Format("2006-01-02 15:04:05"), "created-at": time.Now().Format("2006-01-02 15:04:05"),
}, },
}, },
Subjects: []rbacV1.Subject{ Subjects: []rbacV1.Subject{
{ {
Kind: req.Kind, Kind: "User",
Name: req.Name, Name: req.Name,
}, },
}, },
@@ -318,6 +277,42 @@ func (h *Handler) CreateClusterMember() iris.Handler {
return return
} }
} }
// 创建Rolebinding
for i := range req.NamespaceRoles {
for j := range req.NamespaceRoles[i].Roles {
b := rbacV1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Namespace: req.NamespaceRoles[i].Namespace,
Name: fmt.Sprintf("%s-%s", req.Name, req.NamespaceRoles[i].Roles[j]),
Labels: map[string]string{
"kubeoperator.io/manage": "ekko",
"user-name": req.Name,
},
Annotations: map[string]string{
"builtin": "false",
"created-at": time.Now().Format("2006-01-02 15:04:05"),
},
},
Subjects: []rbacV1.Subject{
{
Kind: "User",
Name: req.Name,
},
},
RoleRef: rbacV1.RoleRef{
Kind: "ClusterRole",
Name: req.NamespaceRoles[i].Roles[j],
},
}
_, err := client.RbacV1().RoleBindings(req.NamespaceRoles[i].Namespace).Create(goContext.TODO(), &b, metav1.CreateOptions{})
if err != nil {
ctx.StatusCode(iris.StatusInternalServerError)
ctx.Values().Set("message", fmt.Sprintf("create role binding failed: %s", err.Error()))
return
}
}
}
ctx.Values().Set("data", req) ctx.Values().Set("data", req)
} }
} }
@@ -325,11 +320,7 @@ func (h *Handler) CreateClusterMember() iris.Handler {
func (h *Handler) DeleteClusterMember() iris.Handler { func (h *Handler) DeleteClusterMember() iris.Handler {
return func(ctx *context.Context) { return func(ctx *context.Context) {
name := ctx.Params().GetString("name") name := ctx.Params().GetString("name")
member := ctx.Params().GetString("member") memberName := ctx.Params().GetString("member")
var kind string
if ctx.URLParamExists("kind") {
kind = ctx.URLParam("kind")
}
u := ctx.Values().Get("profile") u := ctx.Values().Get("profile")
profile := u.(session.UserProfile) profile := u.(session.UserProfile)
c, err := h.clusterService.Get(name, common.DBOptions{}) c, err := h.clusterService.Get(name, common.DBOptions{})
@@ -338,29 +329,13 @@ func (h *Handler) DeleteClusterMember() iris.Handler {
ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error()))
return return
} }
if c.CreatedBy == profile.Name { if c.CreatedBy == memberName {
ctx.StatusCode(iris.StatusBadRequest) ctx.StatusCode(iris.StatusBadRequest)
ctx.Values().Set("message", fmt.Sprintf("can not delete cluster importer %s", profile.Name)) ctx.Values().Set("message", fmt.Sprintf("can not delete cluster importer %s", profile.Name))
return return
} }
subject := v1Cluster.Subject{ binding, err := h.clusterBindingService.GetBindingByClusterNameAndUserName(c.Name, memberName, common.DBOptions{})
Name: member,
}
if kind != "" {
switch strings.ToLower(kind) {
case "user":
subject.Kind = "User"
case "group":
subject.Kind = "Group"
default:
ctx.StatusCode(iris.StatusInternalServerError)
ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", fmt.Errorf("invalid kind %s", kind)))
return
}
subject.Kind = kind
}
binding, err := h.clusterBindingService.GetBindingByClusterNameAndSubject(c.Name, subject, common.DBOptions{})
if err != nil { if err != nil {
ctx.StatusCode(iris.StatusInternalServerError) ctx.StatusCode(iris.StatusInternalServerError)
ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error())) ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error()))
@@ -388,7 +363,7 @@ func (h *Handler) DeleteClusterMember() iris.Handler {
if err = client.RbacV1().ClusterRoleBindings().DeleteCollection(goContext.TODO(), if err = client.RbacV1().ClusterRoleBindings().DeleteCollection(goContext.TODO(),
metav1.DeleteOptions{}, metav1.DeleteOptions{},
metav1.ListOptions{ metav1.ListOptions{
LabelSelector: fmt.Sprintf("subject-name=%s,subject-kind=%s", subject.Name, subject.Kind), LabelSelector: fmt.Sprintf("user-name=%s", memberName),
}); err != nil { }); err != nil {
ctx.StatusCode(iris.StatusInternalServerError) ctx.StatusCode(iris.StatusInternalServerError)
ctx.Values().Set("message", fmt.Sprintf("delete cluster role failed: %s", err.Error())) ctx.Values().Set("message", fmt.Sprintf("delete cluster role failed: %s", err.Error()))

View 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)
}
}

View 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)
}
}

View File

@@ -12,10 +12,15 @@ type Cluster struct {
CaDataStr string `json:"caDataStr"` CaDataStr string `json:"caDataStr"`
} }
type Member struct { type NamespaceRoles struct {
Name string `json:"name"` Namespace string `json:"namespace"`
Kind string `json:"kind"` Roles []string `json:"roles"`
ClusterRoles []string `json:"clusterRoles"` }
CreateAt time.Time `json:"createAt"`
BindingName string `json:"bindingName"` type Member struct {
Name string `json:"name"`
ClusterRoles []string `json:"clusterRoles"`
BindingName string `json:"bindingName"`
CreateAt time.Time `json:"createAt"`
NamespaceRoles []NamespaceRoles `json:"namespaceRoles"`
} }

View File

@@ -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))

View File

@@ -1,33 +1,40 @@
package session package session
import ( import (
goContext "context"
"errors" "errors"
"fmt" "fmt"
v1Role "github.com/KubeOperator/ekko/internal/model/v1/role" v1Role "github.com/KubeOperator/ekko/internal/model/v1/role"
"github.com/KubeOperator/ekko/internal/service/v1/cluster"
"github.com/KubeOperator/ekko/internal/service/v1/common" "github.com/KubeOperator/ekko/internal/service/v1/common"
"github.com/KubeOperator/ekko/internal/service/v1/role" "github.com/KubeOperator/ekko/internal/service/v1/role"
"github.com/KubeOperator/ekko/internal/service/v1/rolebinding" "github.com/KubeOperator/ekko/internal/service/v1/rolebinding"
"github.com/KubeOperator/ekko/internal/service/v1/user" "github.com/KubeOperator/ekko/internal/service/v1/user"
pkgV1 "github.com/KubeOperator/ekko/pkg/api/v1" pkgV1 "github.com/KubeOperator/ekko/pkg/api/v1"
"github.com/KubeOperator/ekko/pkg/collectons" "github.com/KubeOperator/ekko/pkg/collectons"
"github.com/KubeOperator/ekko/pkg/kubernetes"
"github.com/asdine/storm/v3" "github.com/asdine/storm/v3"
"github.com/kataras/iris/v12" "github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/context"
"github.com/kataras/iris/v12/sessions" "github.com/kataras/iris/v12/sessions"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
v1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
type Handler struct { type Handler struct {
userService user.Service userService user.Service
roleService role.Service roleService role.Service
rolebindingService rolebinding.Service clusterService cluster.Service
rolebindingService rolebinding.Service
} }
func NewHandler() *Handler { func NewHandler() *Handler {
return &Handler{ return &Handler{
userService: user.NewService(), clusterService: cluster.NewService(),
roleService: role.NewService(), userService: user.NewService(),
rolebindingService: rolebinding.NewService(), roleService: role.NewService(),
rolebindingService: rolebinding.NewService(),
} }
} }
@@ -184,11 +191,106 @@ func (h *Handler) GetProfile() iris.Handler {
} }
} }
func (h *Handler) ListUserNamespace() iris.Handler {
return func(ctx *context.Context) {
name := ctx.Params().GetString("cluster_name")
c, err := h.clusterService.Get(name, common.DBOptions{})
if err != nil {
ctx.StatusCode(iris.StatusInternalServerError)
ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error()))
return
}
session := sessions.Get(ctx)
u := session.Get("profile")
profile := u.(UserProfile)
k := kubernetes.NewKubernetes(*c)
client, err := k.Client()
if err != nil {
ctx.StatusCode(iris.StatusInternalServerError)
ctx.Values().Set("message", fmt.Sprintf("get k8s client failed: %s", err.Error()))
return
}
rbs, err := client.RbacV1().RoleBindings("").List(goContext.TODO(), metav1.ListOptions{})
if err != nil {
ctx.StatusCode(iris.StatusInternalServerError)
ctx.Values().Set("message", fmt.Sprintf("get k8s client failed: %s", err.Error()))
return
}
namespaceSet := collectons.NewStringSet()
for i := range rbs.Items {
for j := range rbs.Items[i].Subjects {
if rbs.Items[i].Subjects[j].Kind == "User" && rbs.Items[i].Subjects[j].Name == profile.Name {
namespaceSet.Add(rbs.Items[i].Namespace)
}
}
}
ctx.Values().Set("data", namespaceSet.ToSlice())
}
}
func (h *Handler) GetClusterProfile() iris.Handler {
return func(ctx *context.Context) {
session := sessions.Get(ctx)
clusterName := ctx.Params().GetString("cluster_name")
c, err := h.clusterService.Get(clusterName, common.DBOptions{})
if err != nil {
ctx.StatusCode(iris.StatusInternalServerError)
ctx.Values().Set("message", err.Error())
return
}
k := kubernetes.NewKubernetes(*c)
client, err := k.Client()
if err != nil {
ctx.StatusCode(iris.StatusInternalServerError)
ctx.Values().Set("message", fmt.Sprintf("get k8s client failed: %s", err.Error()))
return
}
u := session.Get("profile")
profile := u.(UserProfile)
clusterRoleBindings, err := client.RbacV1().ClusterRoleBindings().List(goContext.TODO(), metav1.ListOptions{
LabelSelector: fmt.Sprintf("user-name=%s", profile.Name),
})
if err != nil {
ctx.StatusCode(iris.StatusInternalServerError)
ctx.Values().Set("message", fmt.Sprintf("get cluster-role-binding failed: %s", err.Error()))
return
}
roleSet := map[string]struct{}{}
for i := range clusterRoleBindings.Items {
for j := range clusterRoleBindings.Items[i].Subjects {
if clusterRoleBindings.Items[i].Subjects[j].Kind == "User" {
roleSet[clusterRoleBindings.Items[i].RoleRef.Name] = struct{}{}
}
}
}
var roles []v1.ClusterRole
for key := range roleSet {
r, err := client.RbacV1().ClusterRoles().Get(goContext.TODO(), key, metav1.GetOptions{})
if err != nil {
ctx.StatusCode(iris.StatusInternalServerError)
ctx.Values().Set("message", fmt.Sprintf("get cluster-role failed: %s", err.Error()))
return
}
roles = append(roles, *r)
}
crp := ClusterUserProfile{
UserProfile: profile,
ClusterRoles: roles,
}
ctx.Values().Set("data", &crp)
}
}
func Install(parent iris.Party) { func Install(parent iris.Party) {
handler := NewHandler() handler := NewHandler()
sp := parent.Party("/sessions") sp := parent.Party("/sessions")
sp.Post("", handler.Login()) sp.Post("", handler.Login())
sp.Delete("", handler.Logout()) sp.Delete("", handler.Logout())
sp.Get("", handler.GetProfile()) sp.Get("", handler.GetProfile())
sp.Get("/:cluster_name", handler.GetClusterProfile())
sp.Get("/status", handler.IsLogin()) sp.Get("/status", handler.IsLogin())
sp.Get("/:cluster_name/namespaces", handler.ListUserNamespace())
} }

View File

@@ -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"`
}

View File

@@ -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)

View File

@@ -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"`
} }

View 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"`
}

View File

@@ -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

View 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
View 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"},
},
},
},
}

View File

@@ -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
} }

View File

@@ -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`)
}

View File

@@ -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}`)
} }

View File

@@ -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>

View File

@@ -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: {

View File

@@ -5,7 +5,8 @@
<home></home> <home></home>
</div> </div>
<div class="header-right"> <div class="header-right">
<user-setting></user-setting> <project-switch></project-switch>
<user-setting style="margin-left: 20px"></user-setting>
</div> </div>
</div> </div>
</template> </template>
@@ -14,41 +15,43 @@
import SidebarToggleButton from "@/components/layout/sidebar/SidebarToggleButton" import SidebarToggleButton from "@/components/layout/sidebar/SidebarToggleButton"
import UserSetting from "@/business/app-layout/header-components/UserSetting" import UserSetting from "@/business/app-layout/header-components/UserSetting"
import Home from "@/business/app-layout/header-components/Home" import Home from "@/business/app-layout/header-components/Home"
import ProjectSwitch from "@/business/app-layout/header-components/ProjectSwitch";
export default { export default {
name: "HorizontalHeader", name: "HorizontalHeader",
components: { Home, UserSetting, SidebarToggleButton } components: {ProjectSwitch, Home, UserSetting, SidebarToggleButton}
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "~@/styles/common/mixins"; @import "~@/styles/common/mixins";
.horizontal-header { .horizontal-header {
@include flex-row(flex-start, center);
position: relative;
height: 100%;
.header-left {
@include flex-row(flex-start, center); @include flex-row(flex-start, center);
position: relative; position: relative;
height: 100%; height: 100%;
}
.header-left { .header-right {
@include flex-row(flex-start, center); @include flex-row(flex-end, center);
position: relative; flex: auto;
height: 100%; height: 100%;
.navbar-item {
color: #2E2E2E;
line-height: 50px;
display: inline-block;
padding-right: 20px;
} }
.header-right { .navbar-item + .navbar-item {
@include flex-row(flex-end, center); margin-left: 20px;
flex: auto;
height: 100%;
.navbar-item {
color: #2E2E2E;
line-height: 50px;
display: inline-block;
padding-right: 20px;
}
.navbar-item + .navbar-item {
margin-left: 20px;
}
} }
} }
}
</style> </style>

View File

@@ -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)
} }

View File

@@ -1,253 +1,289 @@
<template> <template>
<layout-content :header="$t('business.dashboard.dashboard')"> <layout-content :header="$t('business.dashboard.dashboard')">
<el-row :gutter="24"> <el-row :gutter="24">
<el-col :span="24"> <el-col :span="24">
<el-card shadow="always" style="background-color: #243441;height: 120px"> <el-card v-if="cluster" shadow="always" style="background-color: #243441;height: 120px">
<el-row :gutter="24"> <el-row :gutter="24">
<el-col :span="6"> <el-col :span="8">
<span class="title">{{ $t("commons.table.name") }}</span> <span class="title">{{ $t("commons.table.name") }}</span>
<div style="text-align: center"> <div style="text-align: center">
<h1>测试</h1> <h1>{{cluster.name}}</h1>
</div> </div>
</el-col>
<el-col :span="8">
<span class="title">{{ $t("business.cluster.version") }}</span>
<div class="line"></div>
<div style="text-align: center">
<h1>{{cluster.status.version}}</h1>
</div>
</el-col>
<el-col :span="8">
<span class="title">{{ $t("commons.table.created_time") }}</span>
<div class="line"></div>
<div style="text-align: center">
<h1>{{fromNow(cluster.createAt)}} Days</h1>
</div>
</el-col>
</el-row>
</el-card>
</el-col> </el-col>
<el-col :span="6"> </el-row>
<span class="title">{{ $t("business.cluster.version") }}</span> <br>
<div class="line"></div> <el-row :gutter="24" class="resources">
<div style="text-align: center"> <el-col :span="4" v-for="resource in resources" v-bind:key="resource.name">
<h1>v1.18.0</h1> <el-card :body-style="{padding: '0px'}" class="d-card">
</div> <el-row :gutter="24">
<el-col :span="10">
<div>
<ko-charts :chart-data="resource" :key="resource.name"></ko-charts>
</div>
</el-col>
<el-col :span="14">
<div class="card-content">
<span>{{ resource.name }}</span>
<h1>{{ resource.count }}</h1>
</div>
</el-col>
</el-row>
</el-card>
</el-col> </el-col>
<el-col :span="6"> </el-row>
<span class="title">{{ $t("business.cluster.nodes") }}</span> <el-row :gutter="24" v-has-permissions="{apiGroup:'',resource:'events',verb:'list'}">
<div class="line"></div> <!-- <el-row :gutter="24">-->
<div style="text-align: center">
<h1>2</h1> <h3>Events</h3>
</div> <complex-table :pagination-config="page" :data="events" @search="search()" v-loading="loading">
</el-col> <el-table-column label="Reason" prop="reason" fix max-width="50px">
<el-col :span="6"> <template v-slot:default="{row}">
<span class="title">{{ $t("commons.table.created_time") }}</span> {{ row.reason }}
<div class="line"></div> </template>
<div style="text-align: center"> </el-table-column>
<h1>16 days ago</h1> <el-table-column label="Namespace" prop="namespace" fix max-width="50px">
</div> <template v-slot:default="{row}">
</el-col> {{ row.metadata.namespace }}
</el-row> </template>
</el-card> </el-table-column>
</el-col> <el-table-column label="Message" prop="resource" fix min-width="200px" show-overflow-tooltip>
</el-row> <template v-slot:default="{row}">
<br> {{ row.message }}
<el-row :gutter="24" class="resources"> </template>
<el-col :span="4" v-for="resource in resources" v-bind:key="resource.name"> </el-table-column>
<el-card :body-style="{padding: '0px'}" class="d-card"> <el-table-column label="Resource" prop="resource" fix min-width="200px" show-overflow-tooltip>
<el-row :gutter="24"> <template v-slot:default="{row}">
<el-col :span="10"> <el-link>{{ row.involvedObject.kind }} / {{ row.involvedObject.name }}</el-link>
<div> </template>
<ko-charts :chart-data="resource" :key="resource.name"></ko-charts> </el-table-column>
</div> <el-table-column :label="$t('commons.table.time')" prop="metadata.creationTimestamp" fix>
</el-col> <template v-slot:default="{row}">
<el-col :span="14"> {{ row.eventTime | age }}
<div class="card-content"> </template>
<span>{{ resource.name }}</span> </el-table-column>
<h1>{{ resource.count }}</h1> </complex-table>
</div> </el-row>
</el-col> </layout-content>
</el-row>
</el-card>
</el-col>
</el-row>
<el-row :gutter="24">
<h3>Events</h3>
<complex-table :pagination-config="page" :data="events" @search="search()" v-loading="loading">
<el-table-column label="Reason" prop="reason" fix max-width="50px">
<template v-slot:default="{row}">
{{ row.reason }}
</template>
</el-table-column>
<el-table-column label="Namespace" prop="namespace" fix max-width="50px">
<template v-slot:default="{row}">
{{ row.metadata.namespace }}
</template>
</el-table-column>
<el-table-column label="Message" prop="resource" fix min-width="200px" show-overflow-tooltip>
<template v-slot:default="{row}">
{{ row.message }}
</template>
</el-table-column>
<el-table-column label="Resource" prop="resource" fix min-width="200px" show-overflow-tooltip>
<template v-slot:default="{row}">
<el-link>{{ row.involvedObject.kind }} / {{ row.involvedObject.name }}</el-link>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.time')" prop="metadata.creationTimestamp" fix>
<template v-slot:default="{row}">
{{ row.eventTime | age }}
</template>
</el-table-column>
</complex-table>
</el-row>
</layout-content>
</template> </template>
<script> <script>
import LayoutContent from "@/components/layout/LayoutContent" import LayoutContent from "@/components/layout/LayoutContent"
import KoCharts from "@/components/ko-charts" import KoCharts from "@/components/ko-charts"
import {listNamespace} from "@/api/namespaces" import {listNamespace} from "@/api/namespaces"
import {listIngresses} from "@/api/ingress" import {listIngresses} from "@/api/ingress"
import {listPvs} from "@/api/pv" import {listPvs} from "@/api/pv"
import {listDeployments} from "@/api/deployments" import {listDeployments} from "@/api/deployments"
import {listStatefulSets} from "@/api/statefulsets" import {listStatefulSets} from "@/api/statefulsets"
import {listJobs} from "@/api/jobs" import {listJobs} from "@/api/jobs"
import {listDaemonSets} from "@/api/daemonsets" import {listDaemonSets} from "@/api/daemonsets"
import {listServices} from "@/api/services" import {listServices} from "@/api/services"
import ComplexTable from "@/components/complex-table" import {listNodes} from "@/api/nodes"
import {listEvents} from "@/api/events" import ComplexTable from "@/components/complex-table"
import {listEvents} from "@/api/events"
import {getCluster} from "@/api/clusters"
import {checkPermissions} from "@/utils/permission"
export default { export default {
name: "Dashboard", name: "Dashboard",
components: { ComplexTable, KoCharts, LayoutContent }, components: {ComplexTable, KoCharts, LayoutContent},
data () { data() {
return { return {
clusterName: "", cluster: null,
resources: [], clusterName: "",
page: { resources: [],
pageSize: 10, page: {
nextToken: "" pageSize: 10,
}, nextToken: ""
events: [], },
loading: false events: [],
} loading: false
},
methods: {
listResources () {
listNamespace(this.clusterName).then(res => {
const namespaces = {
name: "Namespaces",
count: res.items.length,
data: this.getData(res.items, "status.phase")
}
this.resources.push(namespaces)
})
listIngresses(this.clusterName).then(res => {
const ingresses = {
name: "Ingresses",
count: res.items.length,
data: [{
value: res.items.length,
name: ""
}]
}
this.resources.push(ingresses)
})
listPvs(this.clusterName).then(res => {
const persistentVolumes = {
name: "PersistentVolumes",
count: res.items.length,
data: this.getData(res.items, "status.phase")
}
this.resources.push(persistentVolumes)
})
listDeployments(this.clusterName).then(res => {
const deployments = {
name: "Deployments",
count: res.items.length,
data: this.getData(res.items, "status.conditions.type")
}
this.resources.push(deployments)
})
listStatefulSets(this.clusterName).then(res => {
const statefulSets = {
name: "StatefulSets",
count: res.items.length,
data: this.getData(res.items, "status.replicas")
}
this.resources.push(statefulSets)
})
listJobs(this.clusterName).then(res => {
const jobs = {
name: "Jobs",
count: res.items.length,
data: this.getData(res.items, "metadata.status.active")
}
this.resources.push(jobs)
})
listDaemonSets(this.clusterName).then(res => {
const daemonSets = {
name: "DaemonSets",
count: res.items.length,
data: this.getData(res.items, "status.numberUnavailable")
}
this.resources.push(daemonSets)
})
listServices(this.clusterName).then(res => {
const services = {
name: "Services",
count: res.items.length,
data: this.getData(res.items, "status.loadBalancer")
}
this.resources.push(services)
})
this.search()
},
search () {
this.loading = true
listEvents(this.clusterName, this.page.pageSize, this.page.nextToken).then(res => {
this.loading = false
this.events = res.items
this.page.nextToken = res.metadata["continue"] ? res.metadata["continue"] : ""
})
},
getData (items, keys) {
let key = []
let result = []
for (const item of items) {
const name = this.traverse(item, keys)
if (key.indexOf(name) === -1) {
key.push(name)
const d = {
value: 1,
name: name
}
result.push(d)
} else {
for (let i = 0; i < result.length; i++) {
if (result[i].name === name) {
result[i].value++
break
} }
} },
methods: {
fromNow(date) {
const a = new Date(date)
const b = new Date()
return parseInt(Math.abs(b - a) / 1000 / 60 / 60 / 24)
},
listResources() {
getCluster(this.clusterName).then(res => {
this.cluster = res.data;
})
if (checkPermissions({apiGroup: "", resource: "nodes", verb: "list"})) {
listNodes(this.clusterName).then(res => {
const nodes = {
name: "Nodes",
count: res.items.length,
data: this.getData(res.items, "status.phase")
}
this.resources.push(nodes)
})
}
if (checkPermissions({apiGroup: "", resource: "namespaces", verb: "list"})) {
listNamespace(this.clusterName).then(res => {
const namespaces = {
name: "Namespaces",
count: res.items.length,
data: this.getData(res.items, "status.phase")
}
this.resources.push(namespaces)
})
}
if (checkPermissions({apiGroup: "networking.k8s.io", resource: "ingresses", verb: "list"})) {
listIngresses(this.clusterName).then(res => {
const ingresses = {
name: "Ingresses",
count: res.items.length,
data: [{
value: res.items.length,
name: ""
}]
}
this.resources.push(ingresses)
})
}
if (checkPermissions({apiGroup: "", resource: "persistentvolumes", verb: "list"})) {
listPvs(this.clusterName).then(res => {
const persistentVolumes = {
name: "PersistentVolumes",
count: res.items.length,
data: this.getData(res.items, "status.phase")
}
this.resources.push(persistentVolumes)
})
}
if (checkPermissions({apiGroup: "apps", resource: "deployments", verb: "list"})) {
listDeployments(this.clusterName).then(res => {
const deployments = {
name: "Deployments",
count: res.items.length,
data: this.getData(res.items, "status.conditions.type")
}
this.resources.push(deployments)
})
}
if (checkPermissions({apiGroup: "apps", resource: "statefulsets", verb: "list"})) {
listStatefulSets(this.clusterName).then(res => {
const statefulSets = {
name: "StatefulSets",
count: res.items.length,
data: this.getData(res.items, "status.replicas")
}
this.resources.push(statefulSets)
})
}
if (checkPermissions({apiGroup: "batch", resource: "jobs", verb: "list"})) {
listJobs(this.clusterName).then(res => {
const jobs = {
name: "Jobs",
count: res.items.length,
data: this.getData(res.items, "metadata.status.active")
}
this.resources.push(jobs)
})
}
if (checkPermissions({apiGroup: "apps", resource: "daemonsets", verb: "list"})) {
listDaemonSets(this.clusterName).then(res => {
const daemonSets = {
name: "DaemonSets",
count: res.items.length,
data: this.getData(res.items, "status.numberUnavailable")
}
this.resources.push(daemonSets)
})
}
if (checkPermissions({apiGroup: "", resource: "services", verb: "list"})) {
listServices(this.clusterName).then(res => {
const services = {
name: "Services",
count: res.items.length,
data: this.getData(res.items, "status.loadBalancer")
}
this.resources.push(services)
})
}
this.search()
},
search() {
this.loading = true
if (checkPermissions({apiGroup: "", resource: "events", verb: "list"})) {
listEvents(this.clusterName, this.page.pageSize, this.page.nextToken).then(res => {
this.loading = false
this.events = res.items
this.page.nextToken = res.metadata["continue"] ? res.metadata["continue"] : ""
})
}
},
getData(items, keys) {
let key = []
let result = []
for (const item of items) {
const name = this.traverse(item, keys)
if (key.indexOf(name) === -1) {
key.push(name)
const d = {
value: 1,
name: name
}
result.push(d)
} else {
for (let i = 0; i < result.length; i++) {
if (result[i].name === name) {
result[i].value++
break
}
}
}
}
return result
},
traverse(obj, keys) {
if (keys === "status.conditions.type") {
if (obj.status.conditions[0].type && obj.status.conditions[0].status === "True") {
return obj.status.conditions[0].type
} else {
return "Error"
}
} else if (keys === "status.replicas") {
return "In Progress"
} else if (keys === "metadata.status.active" || keys === "status.loadBalancer") {
return "Active"
} else if (keys === "status.numberUnavailable") {
if (obj.status.numberUnavailable > 0) {
return "Error"
} else {
return "Active"
}
} else {
return keys.split(".").reduce(function (cur, key) {
return cur[key]
}, obj)
}
}
},
created() {
this.clusterName = this.$route.query.cluster
this.listResources()
} }
}
return result
},
traverse (obj, keys) {
if (keys === "status.conditions.type") {
if (obj.status.conditions[0].type && obj.status.conditions[0].status === "True") {
return obj.status.conditions[0].type
} else {
return "Error"
}
} else if (keys === "status.replicas") {
return "In Progress"
} else if (keys === "metadata.status.active" || keys === "status.loadBalancer") {
return "Active"
} else if (keys === "status.numberUnavailable") {
if (obj.status.numberUnavailable > 0) {
return "Error"
} else {
return "Active"
}
} else {
return keys.split(".").reduce(function (cur, key) {
return cur[key]
}, obj)
}
} }
},
created () {
this.clusterName = this.$route.query.cluster
this.listResources()
}
}
</script> </script>
<style scoped> <style scoped>

View File

@@ -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(() => {

View File

@@ -3,24 +3,24 @@
:class="{'collapse':collapse}"> :class="{'collapse':collapse}">
<transition name="sidebar-logo-fade" <transition name="sidebar-logo-fade"
mode="out-in"> mode="out-in">
<router-link v-if="collapse" <div v-if="collapse"
key="collapse" key="collapse"
class="sidebar-logo-link" class="sidebar-logo-link"
to="/"> to="/">
<img v-if="collapseLogo" <img v-if="collapseLogo"
:src="collapseLogo" :src="collapseLogo"
class="sidebar-logo" class="sidebar-logo"
alt="Sidebar Logo"> alt="Sidebar Logo">
</router-link> </div>
<router-link v-else <div v-else
key="expand" key="expand"
class="sidebar-logo-link" class="sidebar-logo-link"
to="/"> to="/">
<img v-if="logo" <img v-if="logo"
:src="logo" :src="logo"
class="sidebar-logo" class="sidebar-logo"
alt="Sidebar Logo"> alt="Sidebar Logo">
</router-link> </div>
</transition> </transition>
</div> </div>
</template> </template>
@@ -48,59 +48,59 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
@import "~@/styles/common/variables"; @import "~@/styles/common/variables";
.sidebar-logo-container { .sidebar-logo-container {
position: relative; position: relative;
height: $header-height; height: $header-height;
line-height: $header-height; line-height: $header-height;
overflow: hidden; overflow: hidden;
&:after { &:after {
content: ""; content: "";
position: absolute; position: absolute;
bottom: 0; bottom: 0;
right: #{$sidebar-close-width / 4}; right: #{$sidebar-close-width / 4};
height: 1px; height: 1px;
width: calc(100% - #{$sidebar-close-width / 2}); width: calc(100% - #{$sidebar-close-width / 2});
background-color: hsla(0, 0%, 100%, .5); background-color: hsla(0, 0%, 100%, .5);
} }
& .sidebar-logo-link { & .sidebar-logo-link {
padding: 0 20px; padding: 0 20px;
display: flex; display: flex;
align-items: center; align-items: center;
height: 100%; height: 100%;
width: auto; width: auto;
& .sidebar-logo { & .sidebar-logo {
height: $logo-height; height: $logo-height;
vertical-align: middle; vertical-align: middle;
}
}
&.collapse {
.sidebar-logo-link {
padding: 0 10px;
}
.sidebar-logo {
margin: auto;
}
} }
} }
.sidebar-logo-fade-enter-active { &.collapse {
transition: opacity 0.1s; .sidebar-logo-link {
transition-delay: 0.1s; padding: 0 10px;
} }
.sidebar-logo-fade-leave-active { .sidebar-logo {
opacity: 0; margin: auto;
}
} }
}
.sidebar-logo-fade-enter, .sidebar-logo-fade-enter-active {
.sidebar-logo-fade-leave-to { transition: opacity 0.1s;
opacity: 0; transition-delay: 0.1s;
} }
.sidebar-logo-fade-leave-active {
opacity: 0;
}
.sidebar-logo-fade-enter,
.sidebar-logo-fade-leave-to {
opacity: 0;
}
</style> </style>

View File

@@ -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

View File

@@ -48,7 +48,6 @@
}, },
}, },
created() { created() {
console.log(this.permission_routes)
} }
} }
</script> </script>

View File

@@ -0,0 +1,8 @@
import Permission from "./permission";
export default {
install(Vue) {
Vue.directive('has-permissions', Permission.hasPermissions);
}
}

View 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)
}
}
}

View File

@@ -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({

View File

@@ -2,60 +2,72 @@ import router from "./router"
import NProgress from "nprogress" import NProgress from "nprogress"
import "nprogress/nprogress.css" import "nprogress/nprogress.css"
import store from "./store" import store from "./store"
import Layout from "@/business/app-layout/horizontal-layout"
NProgress.configure({ showSpinner: false }) // NProgress Configuration
NProgress.configure({showSpinner: false}) // NProgress Configuration
const whiteList = ["/login"] // no redirect whitelist const whiteList = ["/login"] // no redirect whitelist
const generateRoutes = async (to, from, next) => { const generateRoutes = async (to, from, next) => {
const hasRoles = store.getters.roles && store.getters.roles.length > 0 const hasRoles = store.getters.clusterRoles && store.getters.clusterRoles.length > 0
if (hasRoles) { if (hasRoles) {
next() next()
} else { } else {
try { try {
const user = await store.dispatch("user/getCurrentUser") const {clusterRoles} = await store.dispatch("user/getCurrentUser")
const accessRoutes = await store.dispatch("permission/generateRoutes", user) const accessRoutes = await store.dispatch("permission/generateRoutes", clusterRoles)
router.addRoutes(accessRoutes) if (accessRoutes.length > 0) {
next({ ...to, replace: true }) const root = {
} catch (error) { path: "/",
NProgress.done() component: Layout,
redirect: accessRoutes[0].path,
}
router.addRoute(root)
}
router.addRoutes(accessRoutes)
next({...to, replace: true})
} catch (error) {
NProgress.done()
}
} }
}
} }
//路由前置钩子,根据实际需求修改 //路由前置钩子,根据实际需求修改
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
NProgress.start() NProgress.start()
if (!to.query["cluster"]) { const isLogin = await store.dispatch("user/isLogin")
if (from.query["cluster"]) { if (isLogin) {
const q = to.query if (to.path === "/login") {
q["cluster"] = from.query["cluster"] next({path: "/"})
next({ path: to.path, query: q }) NProgress.done()
NProgress.done() }
if (!to.query["cluster"]) {
if (from.query["cluster"]) {
await store.dispatch("user/setCurrentCluster", from.query["cluster"])
const q = to.query
q["cluster"] = from.query["cluster"]
next({path: to.path, query: q})
} else {
console.log("no cluster")
}
} else {
await store.dispatch("user/setCurrentCluster", to.query["cluster"])
}
await generateRoutes(to, from, next)
} else { } else {
console.log("no cluster") /* has not login*/
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login?redirect=${to.path}`)
NProgress.done()
}
} }
}
const isLogin = await store.dispatch("user/isLogin")
if (isLogin) {
if (to.path === "/login") {
next({ path: "/" })
NProgress.done()
}
await generateRoutes(to, from, next)
} else {
/* has not login*/
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}) })
router.afterEach(() => { router.afterEach(() => {
NProgress.done() NProgress.done()
}) })

View File

@@ -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 => {

View File

@@ -7,62 +7,62 @@ const modules = require.context("./modules", true, /\.js$/)
// 修复路由变更后报错的问题 // 修复路由变更后报错的问题
const routerPush = Router.prototype.push const routerPush = Router.prototype.push
Router.prototype.push = function push (location) { Router.prototype.push = function push(location) {
return routerPush.call(this, location).catch(error => error) return routerPush.call(this, location).catch(error => error)
} }
Vue.use(Router) Vue.use(Router)
export const constantRoutes = [ export const constantRoutes = [
{ {
path: "/redirect", path: "/redirect",
component: Layout, component: Layout,
hidden: true, hidden: true,
name: "redirect", name: "redirect",
children: [ children: [
{ {
path: "/redirect/:path(.*)", path: "/redirect/:path(.*)",
component: () => import("@/components/redirect") component: () => import("@/components/redirect")
} }
] ]
}, },
{ {
path: "/login", path: "/login",
component: () => import("@/business/login"), component: () => import("@/business/login"),
hidden: true hidden: true
}, },
{ {
path: "/", path: "/",
component: Layout, component: Layout,
redirect: "/dashboard", redirect: "/dashboard",
} }
] ]
/** /**
* 用户登录后根据角色加载的路由 * 用户登录后根据角色加载的路由
*/ */
export const rolesRoutes = [ export const rolesRoutes = [
// 先按sort排序 // 先按sort排序
...modules.keys().map(key => modules(key).default).sort((r1, r2) => { ...modules.keys().map(key => modules(key).default).sort((r1, r2) => {
r1.sort ??= Number.MAX_VALUE r1.sort ??= Number.MAX_VALUE
r2.sort ??= Number.MAX_VALUE r2.sort ??= Number.MAX_VALUE
return r1.sort - r2.sort return r1.sort - r2.sort
}), }),
{ path: "*", redirect: "/", hidden: true } {path: "*", redirect: "/", hidden: true}
] ]
const createRouter = () => new Router({ const createRouter = () => new Router({
mode: 'history', mode: 'history',
scrollBehavior: () => ({ y: 0 }), scrollBehavior: () => ({y: 0}),
routes: constantRoutes, routes: constantRoutes,
base: "dashboard" base: "dashboard"
}) })
const router = createRouter() const router = createRouter()
export function resetRouter () { export function resetRouter() {
const newRouter = createRouter() const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router router.matcher = newRouter.matcher // reset router
} }
export default router export default router

View File

@@ -1,53 +1,74 @@
import Layout from "@/business/app-layout/horizontal-layout" import Layout from "@/business/app-layout/horizontal-layout"
const AccessControl = { const AccessControl = {
path: "/accesscontrol", path: "/accesscontrol",
sort: 5, parent: true,
component: Layout, sort: 5,
name: "Access Control", component: Layout,
meta: { name: "Access Control",
title: "business.access_control.access_control", meta: {
icon: "iconfont iconaccesscontrol", title: "business.access_control.access_control",
global: false icon: "iconfont iconaccesscontrol",
},
children: [
{
path: "/serviceaccounts",
component: () => import("@/business/access-control/service-accounts"),
name: "ServiceAccounts",
meta: {
title: "Service Accounts",
global: false global: false
}
}, },
{ children: [
path: "/rolebindings", {
component: () => import("@/business/access-control/role-bindings"), path: "/serviceaccounts",
name: "RoleBindings", requirePermission:{
meta: { apiGroup:"",
title: "Role Bindings", resource:"serviceaccounts",
global: false verb:"list",
} },
}, component: () => import("@/business/access-control/service-accounts"),
{ name: "ServiceAccounts",
path: "/roles", meta: {
component: () => import("@/business/access-control/roles"), title: "Service Accounts",
name: "Roles", global: false
meta: { }
title: "Roles", },
global: false {
} path: "/rolebindings",
}, requirePermission:{
{ apiGroup:"rbac.authorization.k8s.io",
path: "/podsecuritypolicy", resource:"rolebindings",
component: () => import("@/business/access-control/pod-security-policies"), verb:"list",
name: "PodSecurityPolicy", },
meta: { component: () => import("@/business/access-control/role-bindings"),
title: "Pod Security Policy", name: "RoleBindings",
global: false meta: {
} title: "Role Bindings",
} global: false
] }
},
{
path: "/roles",
requirePermission:{
apiGroup:"rbac.authorization.k8s.io",
resource:"roles",
verb:"list",
},
component: () => import("@/business/access-control/roles"),
name: "Roles",
meta: {
title: "Roles",
global: false
}
},
{
path: "/podsecuritypolicy",
requirePermission:{
apiGroup:"policy",
resource:"podsecuritypolicies",
verb:"list",
},
component: () => import("@/business/access-control/pod-security-policies"),
name: "PodSecurityPolicy",
meta: {
title: "Pod Security Policy",
global: false
}
}
]
} }
export default AccessControl export default AccessControl

View File

@@ -1,79 +1,96 @@
import Layout from "@/business/app-layout/horizontal-layout" import Layout from "@/business/app-layout/horizontal-layout"
const Clusters = { const Clusters = {
path: "/cluster", path: "/cluster",
sort: 1, parent: true,
component: Layout, global: true,
name: "Cluster", sort: 1,
meta: { component: Layout,
title: "business.cluster.cluster", name: "Cluster",
icon: "iconfont iconkubernets", meta: {
}, title: "business.cluster.cluster",
children: [ icon: "iconfont iconkubernets",
{
path: "/nodes",
component: () => import("@/business/cluster/nodes"),
name: "Nodes",
meta: {
title: "Nodes",
}
}, },
{ children: [
path: "/nodes/detail/:name", {
component: () => import("@/business/cluster/nodes/detail"), path: "/nodes",
name: "NodeDetail", requirePermission: {
props: true, apiGroup: "",
hidden: true, resource: "nodes",
meta: { verb: "list",
activeMenu: "/nodes", },
} component: () => import("@/business/cluster/nodes"),
}, name: "Nodes",
{ meta: {
path: "/namespaces", title: "Nodes",
component: () => import("@/business/cluster/namespaces"), }
name: "Namespaces", },
meta: { {
title: "Namespaces" path: "/nodes/detail/:name",
} component: () => import("@/business/cluster/nodes/detail"),
}, name: "NodeDetail",
{ props: true,
path: "/namespaces/create", hidden: true,
component: () => import("@/business/cluster/namespaces/create"), meta: {
name: "NamespaceCreate", activeMenu: "/nodes",
hidden: true, }
meta: { },
activeMenu: "/namespaces" {
} path: "/namespaces",
}, component: () => import("@/business/cluster/namespaces"),
{ name: "Namespaces",
path: "/namespaces/detail/:name", requirePermission: {
props: true, apiGroup: "",
component: () => import("@/business/cluster/namespaces/detail"), resource: "namespaces",
name: "NamespaceDetail", verb: "list",
hidden: true, },
meta: { meta: {
activeMenu: "/namespaces" title: "Namespaces"
} }
}, },
{ {
path: "/namespaces/edit/:name", path: "/namespaces/create",
props: true, component: () => import("@/business/cluster/namespaces/create"),
component: () => import("@/business/cluster/namespaces/edit"), name: "NamespaceCreate",
name: "NamespaceEdit", hidden: true,
hidden: true, meta: {
meta: { activeMenu: "/namespaces"
activeMenu: "/namespaces" }
} },
}, {
{ path: "/namespaces/detail/:name",
path: "/events", props: true,
component: () => import("@/business/cluster/events"), component: () => import("@/business/cluster/namespaces/detail"),
name: "events", name: "NamespaceDetail",
meta: { hidden: true,
title: "Events", meta: {
} activeMenu: "/namespaces"
} }
] },
{
path: "/namespaces/edit/:name",
props: true,
component: () => import("@/business/cluster/namespaces/edit"),
name: "NamespaceEdit",
hidden: true,
meta: {
activeMenu: "/namespaces"
}
},
{
path: "/events",
component: () => import("@/business/cluster/events"),
name: "events",
requirePermission: {
apiGroup: "",
resource: "events",
verb: "list",
},
meta: {
title: "Events",
}
}
]
} }
export default Clusters export default Clusters

View File

@@ -1,204 +1,205 @@
import Layout from "@/business/app-layout/horizontal-layout" import Layout from "@/business/app-layout/horizontal-layout"
const Configuration = { const Configuration = {
path: "/configuration", path: "/configuration",
sort: 2, parent: true,
component: Layout, sort: 2,
name: "Configuration", component: Layout,
meta: { name: "Configuration",
title: "business.configuration.configuration", meta: {
icon: "iconfont iconconfiguration" title: "business.configuration.configuration",
}, icon: "iconfont iconconfiguration"
children: [
{
path: "/configmaps",
component: () => import("@/business/configuration/config-maps"),
name: "ConfigMaps",
meta: {
title: "Config Maps",
}
}, },
{ children: [
path: "/configmaps/create", {
component: () => import("@/business/configuration/config-maps/create"), path: "/configmaps",
name: "ConfigMapCreate", component: () => import("@/business/configuration/config-maps"),
props: true, name: "ConfigMaps",
hidden: true, meta: {
meta: { title: "Config Maps",
activeMenu: "/configmaps" }
} },
}, {
{ path: "/configmaps/create",
path: "/:namespace/configmaps/detail/:name", component: () => import("@/business/configuration/config-maps/create"),
component: () => import("@/business/configuration/config-maps/detail"), name: "ConfigMapCreate",
name: "ConfigMapDetail", props: true,
props: true, hidden: true,
hidden: true, meta: {
meta: { activeMenu: "/configmaps"
activeMenu: "/configmaps" }
} },
}, {
{ path: "/:namespace/configmaps/detail/:name",
path: "/configmaps/edit/:namespace/:name", component: () => import("@/business/configuration/config-maps/detail"),
component: () => import("@/business/configuration/config-maps/edit"), name: "ConfigMapDetail",
name: "ConfigMapEdit", props: true,
props: true, hidden: true,
hidden: true, meta: {
meta: { activeMenu: "/configmaps"
activeMenu: "/configmaps" }
} },
}, {
{ path: "/configmaps/edit/:namespace/:name",
path: "/secrets", component: () => import("@/business/configuration/config-maps/edit"),
component: () => import("@/business/configuration/secrets"), name: "ConfigMapEdit",
name: "Secrets", props: true,
meta: { hidden: true,
title: "Secrets", meta: {
} activeMenu: "/configmaps"
}, }
{ },
path: "/secrets/detail/:namespace/:name", {
component: () => import("@/business/configuration/secrets/detail"), path: "/secrets",
name: "SecretDetail", component: () => import("@/business/configuration/secrets"),
props: true, name: "Secrets",
hidden: true, meta: {
meta: { title: "Secrets",
activeMenu: "/secrets" }
} },
}, {
{ path: "/secrets/detail/:namespace/:name",
path: "/secrets/create", component: () => import("@/business/configuration/secrets/detail"),
component: () => import("@/business/configuration/secrets/create"), name: "SecretDetail",
name: "SecretCreate", props: true,
props: true, hidden: true,
hidden: true, meta: {
meta: { activeMenu: "/secrets"
activeMenu: "/secrets" }
} },
}, {
{ path: "/secrets/create",
path: "/:namespace/secrets/edit/:name", component: () => import("@/business/configuration/secrets/create"),
component: () => import("@/business/configuration/secrets/edit"), name: "SecretCreate",
name: "SecretEdit", props: true,
props: true, hidden: true,
hidden: true, meta: {
meta: { activeMenu: "/secrets"
activeMenu: "/secrets" }
} },
}, {
{ path: "/:namespace/secrets/edit/:name",
path: "/resourcequotas", component: () => import("@/business/configuration/secrets/edit"),
component: () => import("@/business/configuration/resource-quotas"), name: "SecretEdit",
name: "ResourceQuotas", props: true,
meta: { hidden: true,
title: "Resource Quotas", meta: {
} activeMenu: "/secrets"
}, }
{ },
path: "/:namespace/resourcequotas/detail/:name", {
component: () => import("@/business/configuration/resource-quotas/detail"), path: "/resourcequotas",
name: "ResourceQuotaDetail", component: () => import("@/business/configuration/resource-quotas"),
props: true, name: "ResourceQuotas",
hidden: true, meta: {
meta: { title: "Resource Quotas",
activeMenu: "/resourcequotas" }
} },
}, {
{ path: "/:namespace/resourcequotas/detail/:name",
path: "/resourcequotas/create", component: () => import("@/business/configuration/resource-quotas/detail"),
component: () => import("@/business/configuration/resource-quotas/create"), name: "ResourceQuotaDetail",
name: "ResourceQuotaCreate", props: true,
props: true, hidden: true,
hidden: true, meta: {
meta: { activeMenu: "/resourcequotas"
activeMenu: "/resourcequotas" }
} },
}, {
{ path: "/resourcequotas/create",
path: "/resourcequotas/edit/:namespace/:name", component: () => import("@/business/configuration/resource-quotas/create"),
component: () => import("@/business/configuration/resource-quotas/edit"), name: "ResourceQuotaCreate",
name: "ResourceQuotaEdit", props: true,
props: true, hidden: true,
hidden: true, meta: {
meta: { activeMenu: "/resourcequotas"
activeMenu: "/resourcequotas" }
} },
}, {
{ path: "/resourcequotas/edit/:namespace/:name",
path: "/limitranges", component: () => import("@/business/configuration/resource-quotas/edit"),
component: () => import("@/business/configuration/limit-ranges"), name: "ResourceQuotaEdit",
name: "LimitRanges", props: true,
meta: { hidden: true,
title: "Limit Ranges", meta: {
} activeMenu: "/resourcequotas"
}, }
{ },
path: "/limitranges/create", {
component: () => import("@/business/configuration/limit-ranges/create"), path: "/limitranges",
name: "LimitRangeCreate", component: () => import("@/business/configuration/limit-ranges"),
hidden: true, name: "LimitRanges",
meta: { meta: {
activeMenu: "/limitranges" title: "Limit Ranges",
} }
}, },
{ {
path: "/limitranges/detail/:namespace/:name", path: "/limitranges/create",
component: () => import("@/business/configuration/limit-ranges/detail"), component: () => import("@/business/configuration/limit-ranges/create"),
name: "LimitRangeDetail", name: "LimitRangeCreate",
props: true, hidden: true,
hidden: true, meta: {
meta: { activeMenu: "/limitranges"
activeMenu: "/limitranges" }
} },
}, {
{ path: "/limitranges/detail/:namespace/:name",
path: "/limitranges/edit/:namespace/:name", component: () => import("@/business/configuration/limit-ranges/detail"),
component: () => import("@/business/configuration/limit-ranges/edit"), name: "LimitRangeDetail",
name: "LimitRangeEdit", props: true,
props: true, hidden: true,
hidden: true, meta: {
meta: { activeMenu: "/limitranges"
activeMenu: "/limitranges" }
} },
}, {
{ path: "/limitranges/edit/:namespace/:name",
path: "/horizontalpodautoscalers", component: () => import("@/business/configuration/limit-ranges/edit"),
component: () => import("@/business/configuration/hpa"), name: "LimitRangeEdit",
name: "HPA", props: true,
meta: { hidden: true,
title: "Horizontal Pod Autoscaler", meta: {
} activeMenu: "/limitranges"
}, }
{ },
path: "/horizontalpodautoscalers/:namespace/:name/detail", {
component: () => import("@/business/configuration/hpa/detail"), path: "/horizontalpodautoscalers",
name: "HPADetail", component: () => import("@/business/configuration/hpa"),
props: true, name: "HPA",
hidden: true, meta: {
meta: { title: "Horizontal Pod Autoscaler",
activeMenu: "/horizontalpodautoscalers" }
} },
}, {
{ path: "/horizontalpodautoscalers/:namespace/:name/detail",
path: "/horizontalpodautoscalers/create", component: () => import("@/business/configuration/hpa/detail"),
component: () => import("@/business/configuration/hpa/create"), name: "HPADetail",
name: "HPACreate", props: true,
hidden: true, hidden: true,
meta: { meta: {
activeMenu: "/horizontalpodautoscalers" activeMenu: "/horizontalpodautoscalers"
} }
}, },
{ {
path: "/horizontalpodautoscalers/:namespace/:name/edit", path: "/horizontalpodautoscalers/create",
component: () => import("@/business/configuration/hpa/edit"), component: () => import("@/business/configuration/hpa/create"),
name: "HPAEdit", name: "HPACreate",
hidden: true, hidden: true,
props: true, meta: {
meta: { activeMenu: "/horizontalpodautoscalers"
activeMenu: "/horizontalpodautoscalers" }
} },
} {
] path: "/horizontalpodautoscalers/:namespace/:name/edit",
component: () => import("@/business/configuration/hpa/edit"),
name: "HPAEdit",
hidden: true,
props: true,
meta: {
activeMenu: "/horizontalpodautoscalers"
}
}
]
} }
export default Configuration export default Configuration

View File

@@ -1,22 +1,23 @@
import Layout from "@/business/app-layout/horizontal-layout" import Layout from "@/business/app-layout/horizontal-layout"
const Dashboard = { const Dashboard = {
path: "/dashboard", path: "/dashboard",
sort: 0, sort: 0,
component: Layout, global: true,
name: "Dashboard", component: Layout,
children: [ name: "Dashboard",
{ children: [
path: "/dashboard", {
component: () => import("@/business/dashboard"), path: "/dashboard",
name: "Dashboard", component: () => import("@/business/dashboard"),
meta: { name: "Dashboard",
title: "business.dashboard.dashboard", meta: {
icon: "el-icon-data-line", title: "business.dashboard.dashboard",
roles: ['ADMIN'] icon: "el-icon-data-line",
}, roles: ['ADMIN']
}, },
] },
]
} }
export default Dashboard export default Dashboard

View File

@@ -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: {

View File

@@ -1,40 +1,56 @@
import Layout from "@/business/app-layout/horizontal-layout" import Layout from "@/business/app-layout/horizontal-layout"
const Storage = { const Storage = {
path: "/storage", path: "/storage",
sort: 3, sort: 3,
component: Layout, parent: true,
name: "Storage", component: Layout,
meta: { name: "Storage",
title: "business.storage.storage", meta: {
icon: "iconfont iconstorage" title: "business.storage.storage",
}, icon: "iconfont iconstorage"
children: [
{
path: "/pv",
component: () => import("@/business/storage/pvs"),
name: "Pvs",
meta: {
title: "Persistent Volume",
}
}, },
{ children: [
path: "/pvcs", {
component: () => import("@/business/storage/pvcs"), path: "/persistentvolumes",
name: "Pvcs", requirePermission: {
meta: { apiGroup: "",
title: "Persistent Volume Claims", resource: "persistentvolumes",
} verb: "list",
}, },
{ component: () => import("@/business/storage/pvs"),
path: "/storageclasses", name: "Pvs",
component: () => import("@/business/storage/storage-classes"), meta: {
name: "StorageClasses", title: "Persistent Volume",
meta: { }
title: "Storage Classes", },
} {
} path: "/persistentvolumeclaims",
] requirePermission: {
apiGroup: "",
resource: "persistentvolumeclaims",
verb: "list",
},
component: () => import("@/business/storage/pvcs"),
name: "Pvcs",
meta: {
title: "Persistent Volume Claims",
}
},
{
path: "/storageclasses",
requirePermission: {
apiGroup: "storage.k8s.io",
resource: "storageclasses",
verb: "list",
},
component: () => import("@/business/storage/storage-classes"),
name: "StorageClasses",
meta: {
title: "Storage Classes",
}
}
]
} }
export default Storage export default Storage

View File

@@ -1,213 +1,245 @@
import Layout from "@/business/app-layout/horizontal-layout"; import Layout from "@/business/app-layout/horizontal-layout";
const Workloads = { const Workloads = {
path: "/workloads", path: "/workloads",
sort: 4, parent: true,
component: Layout, sort: 4,
name: "Workloads", component: Layout,
meta: { name: "Workloads",
title: "business.workload.workload", meta: {
icon: "iconfont iconworkload", title: "business.workload.workload",
}, icon: "iconfont iconworkload",
children: [
{
path: "/deployments",
component: () => import("@/business/workloads/deployments"),
name: "Deployments",
meta: {
title: "Deployments",
},
},
{
path: "/deployments/detail/:namespace/:name",
name: "DeploymentDetail",
hidden: true,
component: () => import("@/business/workloads/deployments/detail"),
meta: {
activeMenu: "/deployments",
},
},
{
path: "/deployments/create",
name: "DeploymentCreate",
hidden: true,
component: () => import("@/business/workloads/deployments/create"),
meta: {
activeMenu: "/deployments",
},
},
{
path: "/deployments/edit/:namespace/:name",
name: "DeploymentEdit",
hidden: true,
component: () => import("@/business/workloads/deployments/edit"),
meta: {
activeMenu: "/deployments",
},
}, },
children: [
{
path: "/deployments",
requirePermission: {
apiGroup: "apps",
resource: "deployments",
verb: "list",
},
component: () => import("@/business/workloads/deployments"),
name: "Deployments",
meta: {
title: "Deployments",
},
},
{
path: "/deployments/detail/:namespace/:name",
name: "DeploymentDetail",
hidden: true,
component: () => import("@/business/workloads/deployments/detail"),
meta: {
activeMenu: "/deployments",
},
},
{
path: "/deployments/create",
name: "DeploymentCreate",
hidden: true,
component: () => import("@/business/workloads/deployments/create"),
meta: {
activeMenu: "/deployments",
},
},
{
path: "/deployments/edit/:namespace/:name",
name: "DeploymentEdit",
hidden: true,
component: () => import("@/business/workloads/deployments/edit"),
meta: {
activeMenu: "/deployments",
},
},
{ {
path: "/cronjobs", path: "/daemonsets",
component: () => import("@/business/workloads/cronjobs"), requirePermission: {
name: "CronJobs", apiGroup: "apps",
meta: { resource: "daemonsets",
title: "CronJobs", verb: "list",
}, },
}, component: () => import("@/business/workloads/daemonsets"),
{ name: "DaemonSets",
path: "/cronjobs/detail/:namespace/:name", meta: {
name: "CronJobDetail", title: "DaemonSets",
hidden: true, },
component: () => import("@/business/workloads/cronjobs/detail"), },
meta: { {
activeMenu: "/cronjobs", path: "/daemonsets/detail/:namespace/:name",
}, name: "DaemonSetDetail",
}, hidden: true,
{ component: () => import("@/business/workloads/daemonsets/detail"),
path: "/cronjobs/create", meta: {
name: "CronJobCreate", activeMenu: "/daemonsets",
hidden: true, },
component: () => import("@/business/workloads/cronjobs/create"), },
meta: { {
activeMenu: "/cronjobs", path: "/daemonsets/create",
}, name: "DaemonSetCreate",
}, hidden: true,
{ component: () => import("@/business/workloads/daemonsets/create"),
path: "/cronjobs/edit/:namespace/:name", meta: {
name: "CronJobEdit", activeMenu: "/daemonsets",
hidden: true, },
component: () => import("@/business/workloads/cronjobs/edit"), },
meta: { {
activeMenu: "/cronjobs", path: "/daemonsets/edit/:namespace/:name",
}, name: "DaemonSetEdit",
}, hidden: true,
component: () => import("@/business/workloads/daemonsets/edit"),
meta: {
activeMenu: "/daemonsets",
},
},
{ {
path: "/jobs", path: "/statefulsets",
component: () => import("@/business/workloads/jobs"), requirePermission: {
name: "Jobs", apiGroup: "apps",
meta: { resource: "statefulsets",
title: "Jobs", verb: "list",
}, },
}, component: () => import("@/business/workloads/statefulsets"),
{ name: "StatefulSets",
path: "/jobs/detail/:namespace/:name", meta: {
name: "JobDetail", title: "StatefulSets",
hidden: true, },
component: () => import("@/business/workloads/jobs/detail"), },
meta: { {
activeMenu: "/jobs", path: "/statefulsets/detail/:namespace/:name",
}, name: "StatefulSetDetail",
}, hidden: true,
{ component: () => import("@/business/workloads/statefulsets/detail"),
path: "/jobs/create", meta: {
name: "JobCreate", activeMenu: "/statefulsets",
hidden: true, },
component: () => import("@/business/workloads/jobs/create"), },
meta: { {
activeMenu: "/jobs", path: "/statefulsets/create",
}, name: "StatefulSetCreate",
}, hidden: true,
{ component: () => import("@/business/workloads/statefulsets/create"),
path: "/jobs/edit/:namespace/:name", meta: {
name: "JobEdit", activeMenu: "/statefulsets",
hidden: true, },
component: () => import("@/business/workloads/jobs/edit"), },
meta: { {
activeMenu: "/jobs", path: "/statefulsets/edit/:namespace/:name",
}, name: "StatefulSetEdit",
}, hidden: true,
component: () => import("@/business/workloads/statefulsets/edit"),
meta: {
activeMenu: "/statefulsets",
},
},
{ {
path: "/daemonsets", path: "/cronjobs",
component: () => import("@/business/workloads/daemonsets"), requirePermission: {
name: "DaemonSets", apiGroup: "batch",
meta: { resource: "cronjobs",
title: "DaemonSets", verb: "list",
}, },
}, component: () => import("@/business/workloads/cronjobs"),
{ name: "CronJobs",
path: "/daemonsets/detail/:namespace/:name", meta: {
name: "DaemonSetDetail", title: "CronJobs",
hidden: true, },
component: () => import("@/business/workloads/daemonsets/detail"), },
meta: { {
activeMenu: "/daemonsets", path: "/cronjobs/detail/:namespace/:name",
}, name: "CronJobDetail",
}, hidden: true,
{ component: () => import("@/business/workloads/cronjobs/detail"),
path: "/daemonsets/create", meta: {
name: "DaemonSetCreate", activeMenu: "/cronjobs",
hidden: true, },
component: () => import("@/business/workloads/daemonsets/create"), },
meta: { {
activeMenu: "/daemonsets", path: "/cronjobs/create",
}, name: "CronJobCreate",
}, hidden: true,
{ component: () => import("@/business/workloads/cronjobs/create"),
path: "/daemonsets/edit/:namespace/:name", meta: {
name: "DaemonSetEdit", activeMenu: "/cronjobs",
hidden: true, },
component: () => import("@/business/workloads/daemonsets/edit"), },
meta: { {
activeMenu: "/daemonsets", path: "/cronjobs/edit/:namespace/:name",
}, name: "CronJobEdit",
}, hidden: true,
component: () => import("@/business/workloads/cronjobs/edit"),
meta: {
activeMenu: "/cronjobs",
},
},
{ {
path: "/statefulsets", path: "/jobs",
component: () => import("@/business/workloads/statefulsets"), requirePermission: {
name: "StatefulSets", apiGroup: "batch",
meta: { resource: "jobs",
title: "StatefulSets", verb: "list",
}, },
}, component: () => import("@/business/workloads/jobs"),
{ name: "Jobs",
path: "/statefulsets/detail/:namespace/:name", meta: {
name: "StatefulSetDetail", title: "Jobs",
hidden: true, },
component: () => import("@/business/workloads/statefulsets/detail"), },
meta: { {
activeMenu: "/statefulsets", path: "/jobs/detail/:namespace/:name",
}, name: "JobDetail",
}, hidden: true,
{ component: () => import("@/business/workloads/jobs/detail"),
path: "/statefulsets/create", meta: {
name: "StatefulSetCreate", activeMenu: "/jobs",
hidden: true, },
component: () => import("@/business/workloads/statefulsets/create"), },
meta: { {
activeMenu: "/statefulsets", path: "/jobs/create",
}, name: "JobCreate",
}, hidden: true,
{ component: () => import("@/business/workloads/jobs/create"),
path: "/statefulsets/edit/:namespace/:name", meta: {
name: "StatefulSetEdit", activeMenu: "/jobs",
hidden: true, },
component: () => import("@/business/workloads/statefulsets/edit"), },
meta: { {
activeMenu: "/statefulsets", path: "/jobs/edit/:namespace/:name",
}, name: "JobEdit",
}, hidden: true,
component: () => import("@/business/workloads/jobs/edit"),
meta: {
activeMenu: "/jobs",
},
},
{
path: "/pods", {
component: () => import("@/business/workloads/pods"), path: "/pods",
name: "Pods", requirePermission: {
meta: { apiGroup: "",
title: "Pods", resource: "pods",
}, verb: "list",
}, },
{ component: () => import("@/business/workloads/pods"),
path: "/pods/detail/:namespace/:name", name: "Pods",
name: "PodDetail", meta: {
hidden: true, title: "Pods",
component: () => import("@/business/workloads/pods/detail"), },
meta: { },
activeMenu: "/pods", {
}, path: "/pods/detail/:namespace/:name",
}, name: "PodDetail",
], hidden: true,
component: () => import("@/business/workloads/pods/detail"),
meta: {
activeMenu: "/pods",
},
},
],
}; };
export default Workloads; export default Workloads;

View File

@@ -1,10 +1,12 @@
// 根据实际需要修改 // 根据实际需要修改
const getters = { const getters = {
sidebar: state => state.app.sidebar, sidebar: state => state.app.sidebar,
name: state => state.user.name, name: state => state.user.name,
// language: state => state.user.language, nickName: state => state.user.nickName,
permission_routes: state => state.permission.routes, // language: state => state.user.language,
menu: state => state.user.menu, permission_routes: state => state.permission.routes,
roles: state => state.user.roles, roles: state => state.user.roles,
cluster: state => state.user.cluster,
clusterRoles: state => state.user.clusterRoles,
} }
export default getters export default getters

View File

@@ -1,59 +1,88 @@
import {rolesRoutes, constantRoutes} from "@/router" import {rolesRoutes, constantRoutes} from "@/router"
const state = { const state = {
routes: [], routes: [],
addRoutes: [] addRoutes: []
} }
// function hasPermission(user, route) { function hasPermission(clusterRoles, route) {
// if (user && route){ if (route.requirePermission) {
// console.log("") for (const clusterRole of clusterRoles) {
// } if (clusterRole.rules.length > 0) {
// return true for (const rule of clusterRole.rules) {
// } if (rule.apiGroups.includes("*") || rule.apiGroups.includes(route.requirePermission.apiGroup)) {
if (rule.resources.includes("*") || rule.resources.includes(route.requirePermission.resource)) {
if (rule.verbs.includes("*") || rule.verbs.includes(route.requirePermission.verb)) {
export function filterRolesRoutes (routes, user) { return true
const res = [] }
routes.forEach(route => { }
const tmp = { ...route } }
// if (hasPermission(user, tmp)) { }
if (tmp.children) { }
tmp.children = filterRolesRoutes(tmp.children, user) }
return false
} }
res.push(tmp) return true
// } }
})
return res
export function filterRolesRoutes(routes, clusterRoles) {
const res = []
routes.forEach(route => {
const tmp = {...route}
if (hasPermission(clusterRoles, tmp)) {
if (tmp.children) {
tmp.children = filterRolesRoutes(tmp.children, clusterRoles)
}
res.push(tmp)
}
})
return res
} }
const mutations = { const mutations = {
SET_ROUTES: (state, routes) => { SET_ROUTES: (state, routes) => {
state.addRoutes = routes state.addRoutes = routes
state.routes = constantRoutes.concat(routes) state.routes = constantRoutes.concat(routes)
} }
} }
const actions = { const actions = {
generateRoutes ({ commit }, p) { generateRoutes({commit}, p) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const user = p const clusterRoles = p
let accessedRoutes let accessedRoutes
try { try {
accessedRoutes = filterRolesRoutes(rolesRoutes, user) accessedRoutes = filterRolesRoutes(rolesRoutes, clusterRoles)
commit("SET_ROUTES", accessedRoutes) for (const route of accessedRoutes) {
resolve(accessedRoutes) if (route.parent) {
} catch (error) { let hidden = true
reject(error) if (route.children.length > 0) {
} for (const childRoute of route.children) {
}) hidden = hidden && childRoute.hidden
} }
if (hidden) {
route.hidden = true
}
} else {
route.hidden = true
}
}
}
commit("SET_ROUTES", accessedRoutes)
resolve(accessedRoutes)
} catch (error) {
reject(error)
}
})
}
} }
export default { export default {
namespaced: true, namespaced: true,
state, state,
mutations, mutations,
actions, actions,
} }

View File

@@ -1,98 +1,108 @@
import {login, isLogin, logout, getCurrentUser} from "@/api/auth" import {login, isLogin, logout, getCurrentClusterUser} from "@/api/auth"
import {resetRouter} from "@/router" import {resetRouter} from "@/router"
import {getLanguage, setLanguage} from "@/i18n" import {getLanguage, setLanguage} from "@/i18n"
import store from "../../store"
const state = { const state = {
login: false, login: false,
name: "", name: "",
currentProject: "", language: getLanguage(),
language: getLanguage(), roles: [],
roles: [], cluster: "",
cluster: "" clusterRoles: [],
} }
const mutations = { const mutations = {
LOGIN: (state) => { LOGIN: (state) => {
state.login = true state.login = true
}, },
LOGOUT: (state) => { LOGOUT: (state) => {
state.login = false state.login = false
}, },
SET_NAME: (state, name) => { SET_NAME: (state, name) => {
state.name = name state.name = name
}, },
SET_LANGUAGE: (state, language) => { SET_NICK_NAME: (state, nickName) => {
state.language = language state.nickName = nickName
setLanguage(language) },
}, SET_LANGUAGE: (state, language) => {
SET_ROLES: (state, roles) => { state.language = language
state.roles = roles setLanguage(language)
}, },
SET_CURRENT_MENU: (state, menu) => { SET_ROLES: (state, roles) => {
state.menu = menu state.roles = roles
}, },
SET_CURRENT_CLUSTER: (state, name) => { SET_CURRENT_CLUSTER: (state, name) => {
state.cluster = name state.cluster = name
} },
SET_CLUSTER_ROLES: (state, clusterRoles) => {
state.clusterRoles = clusterRoles
}
} }
const actions = { const actions = {
login ({ commit }, userInfo) { setCurrentCluster({commit}, clusterName) {
const { username, password } = userInfo commit("SET_CURRENT_CLUSTER", clusterName)
return new Promise((resolve, reject) => { },
commit("LOGIN") login({commit}, userInfo) {
login({ username: username.trim(), password: password }).then(response => { const {username, password} = userInfo
commit("LOGIN") return new Promise((resolve, reject) => {
resolve(response) commit("LOGIN")
}).catch(error => { login({username: username.trim(), password: password}).then(response => {
reject(error) commit("LOGIN")
}) resolve(response)
}) }).catch(error => {
}, reject(error)
})
})
},
isLogin ({ commit }) { isLogin({commit}) {
return new Promise((resolve) => { return new Promise((resolve) => {
if (state.login) { if (state.login) {
resolve(true) resolve(true)
} }
isLogin().then((data) => { isLogin().then((data) => {
if (data.data) { if (data.data) {
commit("LOGIN") commit("LOGIN")
resolve(true) resolve(true)
} else { } else {
resolve(false) resolve(false)
} }
}).catch(() => { }).catch(() => {
resolve(false) resolve(false)
}) })
}) })
}, },
getCurrentUser ({ commit }) { getCurrentUser({commit}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getCurrentUser().then(data => { const clusterName = store.getters.cluster
const user = data.data getCurrentClusterUser(clusterName).then(data => {
user["roles"] = ["ADMIN"] const user = data.data
const { name, roles } = user user["roles"] = ["ADMIN"]
commit("SET_NAME", name) const {name, nickName, roles, clusterRoles} = user
commit("SET_ROLES", roles) commit("SET_NAME", name)
commit("SET_LANGUAGE", "zh-CN") commit("SET_ROLES", roles)
resolve(user) commit("SET_LANGUAGE", "zh-CN")
}).catch(error => { commit("SET_CLUSTER_ROLES", clusterRoles)
reject(error) commit("SET_NICK_NAME", nickName)
}) resolve(user)
}) }).catch(error => {
}, reject(error)
logout ({ commit }) { })
logout().then(() => { })
commit("LOGOUT") },
commit("SET_ROLES", []) logout({commit}) {
resetRouter() logout().then(() => {
}) commit("LOGOUT")
}, commit("SET_ROLES", [])
resetRouter()
})
},
} }
export default { export default {
namespaced: true, namespaced: true,
state, state,
mutations, mutations,
actions actions
} }

View 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
}

View File

@@ -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)
}

View File

@@ -1,421 +1,456 @@
<template> <template>
<layout-content> <layout-content>
<complex-table :data="data"> <complex-table :data="data">
<template #header> <template #header>
<el-button-group> <el-button-group>
<el-button type="primary" size="small" @click="onCreate"> <el-button type="primary" size="small" @click="onCreate">
{{ $t("commons.button.create") }} {{ $t("commons.button.create") }}
</el-button> </el-button>
</el-button-group> </el-button-group>
<br/> <br/>
</template> </template>
<el-table-column :label="$t('commons.table.name')" min-width="100" fix> <el-table-column :label="$t('commons.table.name')" min-width="100" fix>
<template v-slot:default="{row}"> <template v-slot:default="{row}">
{{ row.metadata.name }} {{ row.metadata.name }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('commons.table.description')" min-width="100" fix>
<template v-slot:default="{row}">
{{$t("business.cluster_role."+row.metadata.annotations["ekko-i18n"])}}
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.creat_by')" min-width="100" fix>
<template v-slot:default="{row}">
{{ row.metadata.annotations["created-by"] }}
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.created_time')" min-width="100" fix>
<template v-slot:default="{row}">
{{ row.metadata.annotations["created-at"] }}
</template>
</el-table-column>
<fu-table-operations :buttons="buttons" :label="$t('commons.table.action')" fix/>
</complex-table>
<el-dialog <el-table-column :label="$t('business.cluster.scope')" min-width="100" fix>
title="创建规则" <template v-slot:default="{row}">
:visible.sync="ruleDialogOpened" {{ $t('business.cluster.' + row.metadata.labels["kubeoperator.io/role-type"]) }}
width="40%" </template>
center z-index="20"> </el-table-column>
<el-form :model="ruleForm" label-position="top" label- width="60px">
<el-form-item label="API Group">
<el-select v-model="ruleForm.groupVersion" style="width:100%" @change="onAPIGroupChange">
<el-option v-for="(item,index) in apiGroupsOptions" :key="index"
:value="item.preferredVersion.groupVersion">
{{item.preferredVersion.groupVersion}}
</el-option>
</el-select>
</el-form-item>
<el-form-item label="API Resource"> <el-table-column :label="$t('commons.table.built_in')" min-width="100" fix>
<el-select multiple v-model="ruleForm.resources" style="width:100%" :disabled="resourcesDisable" <template v-slot:default="{row}">
@change="onResourcesChange"> {{ $t('commons.bool.' + row.metadata.annotations["builtin"]) }}
<el-option v-for="(item,index) in apiResourceOptions" </template>
:key="index" </el-table-column>
:value="item.name"> <el-table-column :label="$t('commons.table.created_time')" min-width="100" fix>
{{item.name}} <template v-slot:default="{row}">
</el-option> {{ row.metadata.annotations["created-at"] }}
</el-select> </template>
</el-form-item> </el-table-column>
<fu-table-operations :buttons="buttons" :label="$t('commons.table.action')" fix/>
</complex-table>
<el-form-item label="Verbs"> <el-dialog
<el-select multiple v-model="ruleForm.verbs" style="width:100%" :disabled="verbsDisable" title="创建规则"
@change="onVerbsChange"> :visible.sync="ruleDialogOpened"
<el-option v-for="(item,index) in verbOptions" width="40%"
:key="index" center z-index="20">
:value="item"> <el-form :model="ruleForm" label-position="top" label- width="60px">
{{item}} <el-form-item label="API Group">
</el-option> <el-select v-model="ruleForm.apiGroup" style="width:100%" @change="onAPIGroupChange">
</el-select> <el-option v-for="(item,index) in apiGroupsOptions" :key="index"
</el-form-item> :value="item.name">
</el-form> <span v-if="item.name">
{{ item.name }}
</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="API Resource">
<el-select multiple v-model="ruleForm.resources" style="width:100%" :disabled="resourcesDisable"
@change="onResourcesChange">
<el-option v-for="(item,index) in apiResourceOptions"
:key="index"
:value="item">
{{ item }}
</el-option>
</el-select>
</el-form-item>
<el-form-item label="Verbs">
<el-select multiple v-model="ruleForm.verbs" style="width:100%" :disabled="verbsDisable"
@change="onVerbsChange">
<el-option v-for="(item,index) in verbOptions"
:key="index"
:value="item">
{{ item }}
</el-option>
</el-select>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="ruleDialogOpened=false"> </el-button> <el-button @click="ruleDialogOpened=false"> </el-button>
<el-button type="primary" @click="onRuleConfirm"> </el-button> <el-button type="primary" @click="onRuleConfirm"> </el-button>
</span> </span>
</el-dialog> </el-dialog>
<el-dialog <el-dialog
title="创建角色" title="创建角色"
:visible.sync="createDialogOpened" :visible.sync="createDialogOpened"
width="60%" width="60%"
center z-index="10"> center z-index="10">
<el-form :model="clusterRoleForm" label-position="left" label- width="60px"> <el-form :model="clusterRoleForm" label-position="left" label-width="144px">
<el-form-item label="名称"> <el-form-item :label="$t('commons.table.name')">
<el-input v-model="clusterRoleForm.name"></el-input> <el-input v-model="clusterRoleForm.name" style="width: 80%"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="规则"> <el-form-item :label="$t('business.cluster.scope')">
<el-button @click="onRuleCreate"><i class="el-icon-plus "></i></el-button> <el-radio v-model="clusterRoleForm.type" label="cluster">{{ $t('business.cluster.cluster') }}</el-radio>
<el-table <el-radio v-model="clusterRoleForm.type" label="namespace">{{ $t('business.cluster.namespace') }}</el-radio>
:data="clusterRoleForm.rules" </el-form-item>
border
style="width: 100%"> <el-form-item label="规则" label-width="144px">
<el-table-column <el-button @click="onRuleCreate"><i class="el-icon-plus "></i></el-button>
prop="apiGroup" <el-table
label="API Group" :data="clusterRoleForm.rules"
> border
</el-table-column> style="width: 80%">
<el-table-column <el-table-column
prop="resource" prop="apiGroup"
label="API Resource" label="API Group"
> >
<template v-slot:default="{row}"> </el-table-column>
<span v-for="v in row.resources" :key="v">{{v}}<br/></span> <el-table-column
</template> prop="resource"
</el-table-column> label="API Resource"
<el-table-column >
prop="verbs" <template v-slot:default="{row}">
label="Verbs" <span v-for="v in row.resources" :key="v">{{ v }}<br/></span>
> </template>
<template v-slot:default="{row}"> </el-table-column>
<span v-for="v in row.verbs" :key="v">{{v}}<br/></span> <el-table-column
</template> prop="verbs"
</el-table-column> label="Verbs"
<el-table-column> >
<template v-slot:default="{row}"> <template v-slot:default="{row}">
<el-button <span v-for="v in row.verbs" :key="v">{{ v }}<br/></span>
size="mini" circle @click="onRuleDelete(row)"> </template>
<i class="el-icon-delete"></i> </el-table-column>
</el-button> <el-table-column>
</template> <template v-slot:default="{row}">
</el-table-column> <el-button
</el-table> size="mini" circle @click="onRuleDelete(row)">
</el-form-item> <i class="el-icon-delete"></i>
</el-form> </el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="createDialogOpened = false"> </el-button> <el-button @click="createDialogOpened = false"> </el-button>
<el-button type="primary" @click="onConfirm"> </el-button> <el-button type="primary" @click="onConfirm"> </el-button>
</span> </span>
</el-dialog> </el-dialog>
<el-dialog <el-dialog
title="提示" title="提示"
:visible.sync="editDialogOpened" :visible.sync="editDialogOpened"
width="60%" width="60%"
center z-index="10"> center z-index="10">
<el-form :model="editForm" label-position="left" label- width="60px"> <el-form :model="editForm" label-position="left" label- width="60px">
<el-form-item label="名称"> <el-form-item label="名称">
<el-input v-model="editForm.name" disabled></el-input> <el-input v-model="editForm.name" disabled></el-input>
</el-form-item> </el-form-item>
<el-form-item label="规则"> <el-form-item label="规则">
<el-button @click="onRuleCreate"><i class="el-icon-plus "></i></el-button> <el-button @click="onRuleCreate"><i class="el-icon-plus "></i></el-button>
<el-table <el-table
:data="editForm.rules" :data="editForm.rules"
border border
style="width: 100%"> style="width: 100%">
<el-table-column <el-table-column
prop="apiGroup" prop="apiGroup"
label="API Group" label="API Group"
> >
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="resource" prop="resource"
label="API Resource" label="API Resource"
> >
<template v-slot:default="{row}"> <template v-slot:default="{row}">
<span v-for="v in row.resources" :key="v">{{v}}<br/></span> <span v-for="v in row.resources" :key="v">{{ v }}<br/></span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="verbs" prop="verbs"
label="Verbs" label="Verbs"
> >
<template v-slot:default="{row}"> <template v-slot:default="{row}">
<span v-for="v in row.verbs" :key="v">{{v}}<br/></span> <span v-for="v in row.verbs" :key="v">{{ v }}<br/></span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column> <el-table-column>
<template v-slot:default="{row}"> <template v-slot:default="{row}">
<el-button <el-button
size="mini" circle @click="onRuleDelete(row)"> size="mini" circle @click="onRuleDelete(row)">
<i class="el-icon-delete"></i> <i class="el-icon-delete"></i>
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-form-item> </el-form-item>
</el-form> </el-form>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="editDialogOpened = false"> </el-button> <el-button @click="editDialogOpened = false"> </el-button>
<el-button type="primary" @click="onEditConfirm"> </el-button> <el-button type="primary" @click="onEditConfirm"> </el-button>
</span> </span>
</el-dialog> </el-dialog>
</layout-content> </layout-content>
</template> </template>
<script> <script>
import LayoutContent from "@/components/layout/LayoutContent" import LayoutContent from "@/components/layout/LayoutContent"
import ComplexTable from "@/components/complex-table" import ComplexTable from "@/components/complex-table"
import { import {
listClusterRoles, listClusterRoles,
listClusterApiGroups, listClusterApiGroups,
listClusterResourceByGroupVersion, listClusterResourceByGroupVersion,
createClusterRole, createClusterRole,
deleteClusterRole, deleteClusterRole,
updateClusterRole updateClusterRole
} from "@/api/clusters"; } from "@/api/clusters";
export default { export default {
name: "ClusterRoles", name: "ClusterRoles",
props: ["name"], props: ["name"],
components: {LayoutContent, ComplexTable}, components: {LayoutContent, ComplexTable},
data() { data() {
return { return {
createDialogOpened: false, createDialogOpened: false,
editDialogOpened: false, editDialogOpened: false,
ruleDialogOpened: false, ruleDialogOpened: false,
apiGroupsOptions: [], apiGroupsOptions: [],
apiResourceOptions: [], apiResourceOptions: [],
operation: "", operation: "",
verbOptions: ["*", "create", "delete", "deletecollection", "get", "list", "patch", "update", "watch"], verbOptions: ["*", "create", "delete", "deletecollection", "get", "list", "patch", "update", "watch"],
memberOptions: [], memberOptions: [],
clusterRoleForm: { clusterRoleForm: {
name: "", name: "",
rules: [], type: "cluster",
}, rules: [],
editForm: { },
name: "", editForm: {
rules: [], name: "",
}, rules: [],
resourcesDisable: false, },
verbsDisable: false, resourcesDisable: false,
ruleForm: { verbsDisable: false,
groupVersion: "", ruleForm: {
resources: [], apiGroup: "",
verbs: [] resources: [],
}, verbs: []
buttons: [ },
{ buttons: [
label: this.$t("commons.button.edit"), {
icon: "el-icon-edit", label: this.$t("commons.button.edit"),
click: (row) => { icon: "el-icon-edit",
this.onEdit(row) click: (row) => {
} this.onEdit(row)
}, },
{ disabled: (row) => {
label: this.$t("commons.button.delete"), return row.metadata.annotations['builtin'] === 'true'
icon: "el-icon-delete", }
click: (row) => {
this.onDelete(row)
}
},
],
data: [],
}
}, },
methods: { {
list() { label: this.$t("commons.button.delete"),
this.loading = false icon: "el-icon-delete",
listClusterRoles(this.name).then(data => { click: (row) => {
this.loading = false this.onDelete(row)
this.data = data.data },
}) disabled: (row) => {
}, return row.metadata.annotations['builtin'] === 'true'
onCreate() { }
this.operation = "create"
this.clusterRoleForm = {
name: "",
rules: [],
}
this.createDialogOpened = true
listClusterApiGroups(this.name).then(data => {
this.apiGroupsOptions.push({preferredVersion: {groupVersion: "*"}})
this.apiGroupsOptions = this.apiGroupsOptions.concat(data.data)
})
},
onEdit(row) {
this.operation = "update"
this.editForm.name = row.metadata.name
this.editForm.rules = []
for (const rule of row.rules) {
if (rule.apiGroups) {
this.editForm.rules.push({
apiGroup: rule.apiGroups[0],
resources: rule.resources,
verbs: rule.verbs,
})
}
}
this.editDialogOpened = true
listClusterApiGroups(this.name).then(data => {
this.apiGroupsOptions.push({preferredVersion: {groupVersion: "*"}})
this.apiGroupsOptions = this.apiGroupsOptions.concat(data.data)
})
},
onRuleCreate() {
this.ruleForm = {
groupVersion: "",
resources: [],
verbs: [],
}
this.ruleDialogOpened = true
this.resourcesDisable = false
this.verbsDisable = false
},
onAPIGroupChange() {
this.apiResourceOptions = []
this.ruleForm.resources = []
this.ruleForm.verbs = []
this.resourcesDisable = false
this.verbsDisable = false
if (this.ruleForm.groupVersion === "*") {
this.apiResourceOptions = [{name: "*"}]
this.ruleForm.resources = ["*"]
this.resourcesDisable = true
return
}
listClusterResourceByGroupVersion(this.name, this.ruleForm.groupVersion).then(data => {
this.apiResourceOptions.push({name: "*"})
this.apiResourceOptions = this.apiResourceOptions.concat(data.data);
})
},
onResourcesChange() {
if (this.ruleForm.resources.indexOf("*") > -1) {
this.ruleForm.resources = ["*"]
}
},
onVerbsChange() {
if (this.ruleForm.verbs.indexOf("*") > -1) {
this.ruleForm.verbs = ["*"]
}
},
onRuleConfirm() {
switch (this.operation) {
case "create":
this.clusterRoleForm.rules.push({
apiGroup: this.ruleForm.groupVersion,
resources: this.ruleForm.resources,
verbs: this.ruleForm.verbs
})
break
case "update":
this.editForm.rules.push({
apiGroup: this.ruleForm.groupVersion,
resources: this.ruleForm.resources,
verbs: this.ruleForm.verbs
})
break
}
this.ruleDialogOpened = false
},
onRuleDelete(row) {
switch (this.operation) {
case "create":
this.clusterRoleForm.rules.splice(this.clusterRoleForm.rules.indexOf(row), 1)
break
case "update":
this.editForm.rules.splice(this.clusterRoleForm.rules.indexOf(row), 1)
break
}
},
onDelete(row) {
this.$confirm(this.$t("commons.confirm_message.delete"), this.$t("commons.message_box.alert"), {
confirmButtonText: this.$t("commons.button.confirm"),
cancelButtonText: this.$t("commons.button.cancel"),
type: 'warning'
}).then(() => {
deleteClusterRole(this.name, row.metadata.name).then(() => {
this.list()
})
});
},
onConfirm() {
const req = {
metadata: {
name: this.clusterRoleForm.name,
},
rules: []
}
for (const rule of this.clusterRoleForm.rules) {
req.rules.push({
apiGroups: [rule.apiGroup],
resources: rule.resources,
verbs: rule.verbs,
})
}
createClusterRole(this.name, req).then(() => {
this.list()
this.createDialogOpened = false
})
},
onEditConfirm() {
const req = {
metadata: {
name: this.editForm.name,
},
rules: []
}
for (const rule of this.editForm.rules) {
req.rules.push({
apiGroups: [rule.apiGroup],
resources: rule.resources,
verbs: rule.verbs,
})
}
console.log(123)
updateClusterRole(this.name, req.metadata.name, req).then(() => {
this.list()
this.editDialogOpened = false
})
}
}, },
created() { ],
this.list() data: [],
}
} }
},
methods: {
list() {
this.loading = false
listClusterRoles(this.name).then(data => {
this.loading = false
this.data = data.data
})
},
onCreate() {
this.operation = "create"
this.clusterRoleForm = {
name: "",
rules: [],
type: "cluster",
}
this.createDialogOpened = true
listClusterApiGroups(this.name).then(data => {
this.apiGroupsOptions.push({name: "*"})
this.apiGroupsOptions = this.apiGroupsOptions.concat(data.data)
for (const group of this.apiGroupsOptions) {
if (group.name === "") {
group.name = "core"
}
}
})
},
onEdit(row) {
this.operation = "update"
this.editForm.name = row.metadata.name
this.editForm.rules = []
for (const rule of row.rules) {
if (rule.apiGroups) {
const r = {
apiGroup: [],
resources: rule.resources,
verbs: rule.verbs,
}
for (const g of rule.apiGroups) {
if (g === "") {
r.apiGroup = "core"
} else {
r.apiGroup = g
}
}
this.editForm.rules.push(r)
}
}
this.editDialogOpened = true
listClusterApiGroups(this.name).then(data => {
this.apiGroupsOptions.push({name: "*"})
this.apiGroupsOptions = this.apiGroupsOptions.concat(data.data)
for (const group of this.apiGroupsOptions) {
if (group.name === "") {
group.name = "core"
}
}
})
},
onRuleCreate() {
this.ruleForm = {
apiGroup: "",
resources: [],
verbs: [],
}
this.ruleDialogOpened = true
this.resourcesDisable = false
this.verbsDisable = false
},
onAPIGroupChange() {
this.apiResourceOptions = []
this.ruleForm.resources = []
this.ruleForm.verbs = []
this.resourcesDisable = false
this.verbsDisable = false
if (this.ruleForm.apiGroup === "*") {
this.apiResourceOptions = ["*"]
this.ruleForm.resources = ["*"]
this.resourcesDisable = true
return
}
listClusterResourceByGroupVersion(this.name, this.ruleForm.apiGroup).then(data => {
this.apiResourceOptions.push("*")
this.apiResourceOptions = this.apiResourceOptions.concat(data.data);
})
},
onResourcesChange() {
if (this.ruleForm.resources.indexOf("*") > -1) {
this.ruleForm.resources = ["*"]
}
},
onVerbsChange() {
if (this.ruleForm.verbs.indexOf("*") > -1) {
this.ruleForm.verbs = ["*"]
}
},
onRuleConfirm() {
const rule = {
apiGroup: this.ruleForm.apiGroup,
resources: this.ruleForm.resources,
verbs: this.ruleForm.verbs
}
switch (this.operation) {
case "create":
this.clusterRoleForm.rules.push(rule)
break
case "update":
this.editForm.rules.push(rule)
break
}
this.ruleDialogOpened = false
},
onRuleDelete(row) {
switch (this.operation) {
case "create":
this.clusterRoleForm.rules.splice(this.clusterRoleForm.rules.indexOf(row), 1)
break
case "update":
this.editForm.rules.splice(this.clusterRoleForm.rules.indexOf(row), 1)
break
}
},
onDelete(row) {
this.$confirm(this.$t("commons.confirm_message.delete"), this.$t("commons.message_box.alert"), {
confirmButtonText: this.$t("commons.button.confirm"),
cancelButtonText: this.$t("commons.button.cancel"),
type: 'warning'
}).then(() => {
deleteClusterRole(this.name, row.metadata.name).then(() => {
this.list()
})
});
},
onConfirm() {
const req = {
metadata: {
name: this.clusterRoleForm.name,
labels: {
"kubeoperator.io/role-type": this.clusterRoleForm.type
}
},
rules: []
}
for (const rule of this.clusterRoleForm.rules) {
req.rules.push({
apiGroups: [rule.apiGroup],
resources: rule.resources,
verbs: rule.verbs,
})
}
createClusterRole(this.name, req).then(() => {
this.list()
this.createDialogOpened = false
})
},
onEditConfirm() {
const req = {
metadata: {
name: this.editForm.name,
},
rules: []
}
for (const rule of this.editForm.rules) {
req.rules.push({
apiGroups: [rule.apiGroup],
resources: rule.resources,
verbs: rule.verbs,
})
}
updateClusterRole(this.name, req.metadata.name, req).then(() => {
this.list()
this.editDialogOpened = false
})
}
},
created() {
this.list()
}
}
</script> </script>
<style scoped> <style scoped>

View File

@@ -1,41 +1,41 @@
<template> <template>
<layout-content :header="$t('business.cluster.cluster_detail')" :back-to="{ name: 'Clusters' }"> <layout-content :header="$t('business.cluster.cluster_detail')" :back-to="{ name: 'Clusters' }">
<el-menu class="menuClass" router :default-active="$route.path" mode="horizontal"> <el-menu class="menuClass" router :default-active="$route.path" mode="horizontal">
<el-menu-item :index="'/clusters/detail/'+name+'/members'">成员管理</el-menu-item> <el-menu-item :index="'/clusters/detail/'+name+'/members'">成员</el-menu-item>
<el-menu-item :index="'/clusters/detail/'+name+'/clusterroles'">角色管理</el-menu-item> <el-menu-item :index="'/clusters/detail/'+name+'/clusterroles'">角色</el-menu-item>
</el-menu> </el-menu>
<br/> <br/>
<div class="detailClass"> <div class="detailClass">
<router-view></router-view> <router-view></router-view>
</div> </div>
</layout-content> </layout-content>
</template> </template>
<script> <script>
import LayoutContent from "@/components/layout/LayoutContent" import LayoutContent from "@/components/layout/LayoutContent"
export default { export default {
name: "ClusterEdit", name: "ClusterEdit",
props: ["name"], props: ["name"],
components: {LayoutContent}, components: {LayoutContent},
data() { data() {
return {} return {}
}, },
methods: {}, methods: {},
} }
</script> </script>
<style scoped> <style scoped>
.menuClass { .menuClass {
position: fixed; position: fixed;
z-index: 1; z-index: 1;
width: calc(100vw - 330px); width: calc(100vw - 330px);
} }
.detailClass { .detailClass {
margin-top: 60px; margin-top: 60px;
height: calc(100% - 140px); height: calc(100% - 140px);
overflow: auto; overflow: auto;
} }
</style> </style>

View File

@@ -1,247 +1,363 @@
<template> <template>
<layout-content> <layout-content>
<complex-table :data="data"> <complex-table :data="data">
<template #header> <template #header>
<el-button-group> <el-button-group>
<el-button type="primary" size="small" @click="onCreate"> <el-button type="primary" size="small" @click="onCreate">
{{ $t("commons.button.create") }} {{ $t("commons.button.create") }}
</el-button> </el-button>
</el-button-group> </el-button-group>
<br/> <br/>
</template> </template>
<el-table-column :label="$t('commons.table.name')" min-width="100" fix> <el-table-column :label="$t('commons.table.name')" min-width="100" fix>
<template v-slot:default="{row}">
{{ row.name }}
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.created_time')" min-width="100" fix>
<template v-slot:default="{row}">
{{ row.createAt }}
</template>
</el-table-column>
<fu-table-operations :buttons="buttons" :label="$t('commons.table.action')" fix/>
</complex-table>
<el-dialog
title="创建命名空间角色"
:visible.sync="namespaceRoleDialogOpened"
width="40%"
center z-index="20">
<el-form label-position="left" label-width="144px" :model="namespaceRoleForm">
<el-form-item label="命名空间">
<el-select v-model="namespaceRoleForm.namespace" style="width:100%">
<el-option v-for="(item,index) in getNamespaceOptions"
:key="index"
:value="item.metadata.name">
{{ item.metadata.name }}
</el-option>
</el-select>
</el-form-item>
<el-form-item label="角色">
<el-select multiple v-model="namespaceRoleForm.roles" style="width:100%">
<el-option v-for="(item,index) in namespaceRoleOptions"
:key="index"
:value="item.metadata.name">
{{ item.metadata.name }}
</el-option>
</el-select>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="namespaceRoleDialogOpened=false"> </el-button>
<el-button type="primary" @click="onNamespaceRoleConfirm"> </el-button>
</span>
</el-dialog>
<el-dialog
title="添加成员"
:visible.sync="createDialogOpened"
z-index="10"
width="60%"
center>
<el-form :model="memberForm" label-position="left" label-width="144px">
<el-form-item label="用户名称">
<el-select v-model="memberForm.userName" style="width: 80%">
<el-option v-for="(item, index) in userOptions" :key="index" :value="item.name">
{{ item.name }}
</el-option>
</el-select>
</el-form-item>
<el-form-item label="集群角色">
<el-radio-group v-model="memberForm.roleType">
<el-radio label="admin">管理者</el-radio>
<el-radio label="viewer">只读者</el-radio>
<el-radio label="custom">自定义</el-radio>
</el-radio-group>
</el-form-item>
<div v-if="memberForm.roleType==='custom'">
<el-form-item>
<el-select v-model="memberForm.customClusterRoles" multiple style="width: 80%"
placeholder="请选择">
<el-option
v-for="(item,index) in clusterRolesOptions"
:key="index"
:value="item.metadata.name">
{{ item.metadata.name }}
</el-option>
</el-select>
</el-form-item>
<el-form-item label="命名空间角色">
<el-button @click="onNamespaceRoleCreate"><i class="el-icon-plus "></i></el-button>
<el-table
:data="memberForm.namespaceRoles"
border
style="width: 80%">
<el-table-column
prop="namespace"
label="命名空间"
>
</el-table-column>
<el-table-column
label="角色"
>
<template v-slot:default="{row}"> <template v-slot:default="{row}">
{{ row.name }} <span v-for="v in row.roles" :key="v">{{ v }}<br/></span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('commons.table.kind')" min-width="100" fix> <el-table-column width="256x">
<template v-slot:default="{row}"> <template v-slot:default="{row}">
{{ row.kind}} <el-button
size="mini" circle @click="onNamespaceRoleDelete(row)">
<i class="el-icon-delete"></i>
</el-button>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('commons.table.created_time')" min-width="100" fix> </el-table>
<template v-slot:default="{row}"> </el-form-item>
{{ row.createAt }} </div>
</template> </el-form>
</el-table-column> <span slot="footer" class="dialog-footer">
<fu-table-operations :buttons="buttons" :label="$t('commons.table.action')" fix/> <el-button @click="createDialogOpened = false"> </el-button>
</complex-table> <el-button type="primary" @click="onConfirm"> </el-button>
</span>
</el-dialog>
<el-dialog
<el-dialog title="编辑成员"
title="提示" :visible.sync="editDialogOpened"
:visible.sync="createDialogOpened" width="60%"
width="20%" center>
center> <el-form :model="editForm" label-position="left" label-width="144px">
<el-form :model="memberForm" label-position="left" label- width="60px"> <el-form-item label="用户名称">
<el-form-item label="主体类型"> <el-select v-model="editForm.userName" style="width: 80%" disabled="disabled">
<el-select v-model="memberForm.subjectKind" @change="onSubjectKindChange"> <el-option v-for="(item, index) in userOptions" :key="index" :value="item.name">
<el-option value="User"> {{ item.name }}
用户 </el-option>
</el-option> </el-select>
<el-option value="Group"> </el-form-item>
用户组 <el-form-item label="集群角色">
</el-option> <el-radio-group v-model="editForm.roleType">
</el-select> <el-radio label="admin">管理者</el-radio>
</el-form-item> <el-radio label="viewer">只读者</el-radio>
<el-form-item label="主体名称"> <el-radio label="custom">自定义</el-radio>
<el-select v-model="memberForm.subjectName"> </el-radio-group>
<el-option v-for="(item, index) in memberOptions" :key="index" :value="item.name"> </el-form-item>
{{item.name}} <div v-if="editForm.roleType==='custom'">
</el-option> <el-form-item>
</el-select> <el-select v-model="editForm.customClusterRoles" multiple style="width: 80%"
</el-form-item> placeholder="请选择">
<el-form-item label="集群角色"> <el-option
<el-select v-model="memberForm.clusterRoles" multiple placeholder="请选择"> v-for="(item,index) in clusterRolesOptions"
<el-option :key="index"
v-for="(item,index) in clusterRolesOptions" :value="item.metadata.name">
:key="index" {{ item.metadata.name }}
:value="item.metadata.name"> </el-option>
{{item.metadata.name}} </el-select>
</el-option> </el-form-item>
</el-select> </div>
</el-form-item> </el-form>
</el-form> <span slot="footer" class="dialog-footer">
<span slot="footer" class="dialog-footer"> <el-button @click="editDialogOpened = false"> </el-button>
<el-button @click="createDialogOpened = false"> </el-button> <el-button type="primary" @click="onEditConfirm"> </el-button>
<el-button type="primary" @click="onConfirm"> </el-button> </span>
</span> </el-dialog>
</el-dialog> </layout-content>
<el-dialog
title="提示"
:visible.sync="editDialogOpened"
width="20%"
center>
<el-form :model="editForm" label-position="left" label- width="60px">
<el-form-item label="主体类型">
<el-select v-model="editForm.subjectKind" disabled @change="onSubjectKindChange">
<el-option value="User">
用户
</el-option>
<el-option value="Group">
用户组
</el-option>
</el-select>
</el-form-item>
<el-form-item label="主体名称">
<el-select v-model="editForm.subjectName" disabled>
<el-option v-for="(item, index) in memberOptions" :key="index" :value="item.name">
{{item.name}}
</el-option>
</el-select>
</el-form-item>
<el-form-item label="集群角色">
<el-select v-model="editForm.clusterRoles" multiple placeholder="请选择">
<el-option
v-for="(item,index) in clusterRolesOptions"
:key="index"
:value="item.metadata.name">
{{item.metadata.name}}
</el-option>
</el-select>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editDialogOpened = false"> </el-button>
<el-button type="primary" @click="onEditConfirm"> </el-button>
</span>
</el-dialog>
</layout-content>
</template> </template>
<script> <script>
import LayoutContent from "@/components/layout/LayoutContent" import LayoutContent from "@/components/layout/LayoutContent"
import ComplexTable from "@/components/complex-table" import ComplexTable from "@/components/complex-table"
import {createClusterMember, listClusterMembers} from "@/api/clusters" import {createClusterMember, listClusterMembers, listNamespaces} from "@/api/clusters"
import {listUsers} from "@/api/users" import {listUsers} from "@/api/users"
import {listGroups} from "@/api/groups" import {listClusterRoles, deleteClusterMember, getClusterMember, updateClusterMember} from "@/api/clusters";
import {listClusterRoles, deleteClusterMember, getClusterMember, updateClusterMember} from "@/api/clusters";
export default { export default {
name: "ClusterMembers", name: "ClusterMembers",
props: ["name"], props: ["name"],
components: {LayoutContent, ComplexTable}, components: {LayoutContent, ComplexTable},
data() { data() {
return { return {
createDialogOpened: false, createDialogOpened: false,
editDialogOpened: false, editDialogOpened: false,
memberOptions: [], namespaceRoleDialogOpened: false,
clusterRolesOptions: [], userOptions: [],
memberForm: { clusterRolesOptions: [],
subjectKind: "", namespaceRoleOptions: [],
subjectName: "", namespaceOptions: [],
clusterRoles: [] namespaceRoleForm: {
}, namespace: "",
editForm: { roles: [],
subjectKind: "", },
subjectName: "", memberForm: {
clusterRoles: [] userName: "",
}, customClusterRoles: [],
buttons: [ namespaceRoles: [],
{ roleType: "admin"
label: this.$t("commons.button.edit"), },
icon: "el-icon-edit", editForm: {
click: (row) => { userName: "",
this.onEdit(row) customClusterRoles: [],
} roleType: ""
}, },
{ buttons: [
label: this.$t("commons.button.delete"), {
icon: "el-icon-delete", label: this.$t("commons.button.edit"),
click: (row) => { icon: "el-icon-edit",
this.onDelete(row) click: (row) => {
} this.onEdit(row)
}, }
],
data: [],
}
}, },
methods: { {
list() { label: this.$t("commons.button.delete"),
this.loading = false icon: "el-icon-delete",
listClusterMembers(this.name).then(data => { click: (row) => {
this.loading = false this.onDelete(row)
this.data = data.data }
})
},
onCreate() {
this.memberForm.subjectKind = ""
this.memberForm.clusterRoles = []
this.createDialogOpened = true
listClusterRoles(this.name).then(data => {
console.log(data)
this.clusterRolesOptions = data.data
})
this.onSubjectKindChange()
},
onEdit(row) {
listClusterRoles(this.name).then(data => {
this.clusterRolesOptions = data.data
getClusterMember(this.name, row.name, row.kind).then(data => {
this.editForm.subjectName = data.data.name
this.editForm.subjectKind = data.data.kind
this.editForm.clusterRoles = data.data.clusterRoles
})
})
this.editDialogOpened = true
},
onDelete(raw) {
this.$confirm(this.$t("commons.confirm_message.delete"), this.$t("commons.message_box.alert"), {
confirmButtonText: this.$t("commons.button.confirm"),
cancelButtonText: this.$t("commons.button.cancel"),
type: 'warning'
}).then(() => {
deleteClusterMember(this.name, raw.name, raw.kind).then(() => {
this.$message.success("删除成功")
this.list()
})
});
},
onSubjectKindChange() {
this.memberForm.subjectName = ""
if (this.memberForm.subjectKind === 'Group') {
listGroups().then((data) => {
this.memberOptions = data.data;
})
}
if (this.memberForm.subjectKind === 'User') {
listUsers().then((data) => {
this.memberOptions = data.data;
})
}
},
onEditConfirm() {
updateClusterMember(this.name, this.editForm.subjectName, {
name: this.editForm.subjectName,
kind: this.editForm.subjectKind,
clusterRoles: this.editForm.clusterRoles
}).then(() => {
this.editDialogOpened = false
this.list()
})
},
onConfirm() {
createClusterMember(this.name, {
kind: this.memberForm.subjectKind,
name: this.memberForm.subjectName,
clusterRoles: this.memberForm.clusterRoles
}).then(() => {
this.createDialogOpened = false
this.list()
})
}
}, },
created() { ],
this.list() data: [],
}
} }
},
computed: {
getNamespaceOptions() {
return this.namespaceOptions.filter((n) => {
let exists = false
for (const nrs of this.memberForm.namespaceRoles) {
if (nrs.namespace === n.metadata.name) {
exists = true
}
}
return !exists
})
}
},
methods: {
list() {
this.loading = false
listClusterMembers(this.name).then(data => {
this.loading = false
this.data = data.data
})
},
onCreate() {
this.createDialogOpened = true
listClusterRoles(this.name).then(data => {
this.clusterRolesOptions = data.data.filter((r) => {
return r.metadata["labels"]["kubeoperator.io/role-type"] === "cluster"
})
this.namespaceRoleOptions = data.data.filter((r) => {
return r.metadata["labels"]["kubeoperator.io/role-type"] === "namespace"
})
})
listUsers().then(data => {
this.userOptions = data.data;
})
listNamespaces(this.name).then(data => {
this.namespaceOptions = data.data;
})
},
onEdit(row) {
listClusterRoles(this.name).then(data => {
this.clusterRolesOptions = data.data
getClusterMember(this.name, row.name).then(data => {
this.editForm.userName = data.data.name
if (data.data.clusterRoles.length === 1) {
if (data.data.clusterRoles[0] === 'Admin Cluster') {
this.editForm.roleType = 'admin'
} else if (data.data.clusterRoles[0] === 'View Cluster') {
this.editForm.roleType = 'viewer'
} else {
this.editForm.roleType = 'custom'
this.editForm.customClusterRoles = data.data.clusterRoles
}
} else {
this.editForm.roleType = 'custom'
this.editForm.customClusterRoles = data.data.clusterRoles
}
})
})
this.editDialogOpened = true
},
onDelete(raw) {
this.$confirm(this.$t("commons.confirm_message.delete"), this.$t("commons.message_box.alert"), {
confirmButtonText: this.$t("commons.button.confirm"),
cancelButtonText: this.$t("commons.button.cancel"),
type: 'warning'
}).then(() => {
deleteClusterMember(this.name, raw.name, raw.kind).then(() => {
this.$message.success("删除成功")
this.list()
})
});
},
onNamespaceRoleCreate() {
this.namespaceRoleForm.namespaceRoles = []
this.namespaceRoleDialogOpened = true
this.namespaceRoleForm.namespace = ""
},
onEditConfirm() {
let req = {
name: this.editForm.userName,
}
switch (this.editForm.roleType) {
case "admin":
req.clusterRoles = ["Admin Cluster"]
break
case "viewer":
req.clusterRoles = ["View Cluster"]
break
case "custom":
req.clusterRoles = this.editForm.customClusterRoles
break
}
updateClusterMember(this.name, this.editForm.userName, req).then(() => {
this.editDialogOpened = false
this.list()
})
},
onNamespaceRoleConfirm() {
this.memberForm.namespaceRoles.push({
namespace: this.namespaceRoleForm.namespace,
roles: this.namespaceRoleForm.roles,
})
this.namespaceRoleDialogOpened = false
},
onNamespaceRoleDelete(row) {
this.memberForm.namespaceRoles.splice(this.memberForm.namespaceRoles.indexOf(row), 1)
},
onConfirm() {
let req = {
name: this.memberForm.userName,
namespaceRoles: this.memberForm.namespaceRoles,
}
switch (this.memberForm.roleType) {
case "admin":
req.clusterRoles = ["Admin Cluster"]
break
case "viewer":
req.clusterRoles = ["View Cluster"]
break
case "custom":
req.clusterRoles = this.memberForm.customClusterRoles
break
}
createClusterMember(this.name, req).then(() => {
this.createDialogOpened = false
this.list()
})
},
},
created() {
this.list()
}
}
</script> </script>
<style scoped> <style scoped>

View File

@@ -1,13 +0,0 @@
<template>
<span>2</span>
</template>
<script>
export default {
name: "ClusterOverview"
}
</script>
<style scoped>
</style>

View File

@@ -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: {

View File

@@ -61,9 +61,8 @@ const Clusters = {
meta: { meta: {
activeMenu: "/clusters", activeMenu: "/clusters",
}, },
} },
] ]
} }
] ]
} }

View File

@@ -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

View File

@@ -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: {}
} }