mirror of
https://github.com/1Panel-dev/KubePi.git
synced 2025-11-01 11:02:39 +08:00
feat(rbac): 集群级别rbac重构
This commit is contained in:
@@ -6,12 +6,14 @@ import (
|
||||
"github.com/KubeOperator/ekko/pkg/kubernetes"
|
||||
"github.com/kataras/iris/v12"
|
||||
"github.com/kataras/iris/v12/context"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (h *Handler) ListApiGroups() iris.Handler {
|
||||
return func(ctx *context.Context) {
|
||||
name := ctx.Params().GetString("name")
|
||||
c, err := h.clusterService.Get(name,common.DBOptions{})
|
||||
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()))
|
||||
@@ -37,8 +39,8 @@ func (h *Handler) ListApiGroups() iris.Handler {
|
||||
func (h *Handler) ListApiGroupResources() iris.Handler {
|
||||
return func(ctx *context.Context) {
|
||||
name := ctx.Params().GetString("name")
|
||||
groupVersion := ctx.Params().GetString("group")
|
||||
c, err := h.clusterService.Get(name,common.DBOptions{})
|
||||
groupName := ctx.Params().GetString("group")
|
||||
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()))
|
||||
@@ -51,13 +53,39 @@ func (h *Handler) ListApiGroupResources() iris.Handler {
|
||||
ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
apiResources, err := client.ServerResourcesForGroupVersion(groupVersion)
|
||||
gss, rss, err := client.ServerGroupsAndResources()
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
ctx.Values().Set("data", apiResources.APIResources)
|
||||
var groupVersions []v1.GroupVersionForDiscovery
|
||||
resourceSet := map[string]struct{}{}
|
||||
|
||||
if groupName == "core" {
|
||||
groupName = ""
|
||||
}
|
||||
for i := range gss {
|
||||
if gss[i].Name == groupName {
|
||||
groupVersions = append(groupVersions, gss[i].Versions...)
|
||||
}
|
||||
}
|
||||
for i := range rss {
|
||||
for j := range groupVersions {
|
||||
if groupVersions[j].GroupVersion == rss[i].GroupVersion {
|
||||
for k := range rss[i].APIResources {
|
||||
if !strings.Contains(rss[i].APIResources[k].Name, "/") {
|
||||
resourceSet[rss[i].APIResources[k].Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var resources []string
|
||||
for k := range resourceSet {
|
||||
resources = append(resources, k)
|
||||
}
|
||||
ctx.Values().Set("data", resources)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/KubeOperator/ekko/internal/service/v1/cluster"
|
||||
"github.com/KubeOperator/ekko/internal/service/v1/clusterbinding"
|
||||
"github.com/KubeOperator/ekko/internal/service/v1/common"
|
||||
"github.com/KubeOperator/ekko/internal/service/v1/project"
|
||||
pkgV1 "github.com/KubeOperator/ekko/pkg/api/v1"
|
||||
"github.com/KubeOperator/ekko/pkg/certificate"
|
||||
"github.com/KubeOperator/ekko/pkg/kubernetes"
|
||||
@@ -26,12 +27,14 @@ import (
|
||||
type Handler struct {
|
||||
clusterService cluster.Service
|
||||
clusterBindingService clusterbinding.Service
|
||||
projectService project.Service
|
||||
}
|
||||
|
||||
func NewHandler() *Handler {
|
||||
return &Handler{
|
||||
clusterService: cluster.NewService(),
|
||||
clusterBindingService: clusterbinding.NewService(),
|
||||
projectService: project.NewService(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +134,6 @@ func (h *Handler) CreateCluster() iris.Handler {
|
||||
txOptions := common.DBOptions{DB: tx}
|
||||
req.CreatedBy = profile.Name
|
||||
|
||||
|
||||
if err := client.CreateDefaultClusterRoles(); err != nil {
|
||||
_ = tx.Rollback()
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
@@ -152,12 +154,9 @@ func (h *Handler) CreateCluster() iris.Handler {
|
||||
CreatedBy: profile.Name,
|
||||
},
|
||||
Metadata: v1.Metadata{
|
||||
Name: fmt.Sprintf("%s-%s-cluster-binding-", req.Name, profile.Name),
|
||||
},
|
||||
Subject: v1Cluster.Subject{
|
||||
Name: profile.Name,
|
||||
Kind: "User",
|
||||
Name: fmt.Sprintf("%s-%s-cluster-binding", req.Name, profile.Name),
|
||||
},
|
||||
UserRef: profile.Name,
|
||||
ClusterRef: req.Name,
|
||||
}
|
||||
|
||||
@@ -186,11 +185,24 @@ func (h *Handler) CreateCluster() iris.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
roleBindingName := "ekko:admin-cluster"
|
||||
obj, err := kc.RbacV1().ClusterRoleBindings().Get(goContext.TODO(), roleBindingName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if !strings.Contains(strings.ToLower(err.Error()), "not found") {
|
||||
_ = tx.Rollback()
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.Values().Set("message", err.Error())
|
||||
}
|
||||
}
|
||||
if obj == nil || obj.Name == "" {
|
||||
if _, err := kc.RbacV1().ClusterRoleBindings().Create(goContext.TODO(), &rbacV1.ClusterRoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("ekko:cluster-admin-%s", profile.Name),
|
||||
Name: roleBindingName,
|
||||
Annotations: map[string]string{
|
||||
"created-by": profile.Name,
|
||||
"builtin": "true",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"user-name": profile.Name,
|
||||
},
|
||||
},
|
||||
Subjects: []rbacV1.Subject{
|
||||
@@ -200,7 +212,7 @@ func (h *Handler) CreateCluster() iris.Handler {
|
||||
},
|
||||
},
|
||||
RoleRef: rbacV1.RoleRef{
|
||||
Name: "cluster-admin",
|
||||
Name: "Admin Cluster",
|
||||
Kind: "ClusterRole",
|
||||
},
|
||||
}, metav1.CreateOptions{}); err != nil {
|
||||
@@ -209,8 +221,8 @@ func (h *Handler) CreateCluster() iris.Handler {
|
||||
ctx.Values().Set("message", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
_ = tx.Commit()
|
||||
|
||||
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})
|
||||
}
|
||||
}
|
||||
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 {
|
||||
return func(ctx *context.Context) {
|
||||
@@ -266,7 +290,7 @@ func (h *Handler) ListClusters() iris.Handler {
|
||||
return
|
||||
}
|
||||
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])
|
||||
}
|
||||
}
|
||||
@@ -323,18 +347,19 @@ func Install(parent iris.Party) {
|
||||
sp := parent.Party("/clusters")
|
||||
sp.Post("", handler.CreateCluster())
|
||||
sp.Get("", handler.ListClusters())
|
||||
sp.Get("/:name", handler.GetCluster())
|
||||
sp.Delete("/:name", handler.DeleteCluster())
|
||||
sp.Post("/search", handler.SearchClusters())
|
||||
sp.Get("/:name/members", handler.GetClusterMembers())
|
||||
sp.Get("/:name/members", handler.ListClusterMembers())
|
||||
sp.Post("/:name/members", handler.CreateClusterMember())
|
||||
sp.Delete("/:name/members/:member", handler.DeleteClusterMember())
|
||||
sp.Put("/:name/members/:member", handler.UpdateClusterMember())
|
||||
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.Put("/:name/clusterroles/:clusterrole", handler.UpdateClusterRole())
|
||||
sp.Delete("/:name/clusterroles/:clusterrole", handler.DeleteClusterRole())
|
||||
sp.Get("/:name/apigroups", handler.ListApiGroups())
|
||||
sp.Get("/:name/apigroups/{group:path}", handler.ListApiGroupResources())
|
||||
|
||||
sp.Get("/:name/namespaces", handler.ListNamespace())
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package cluster
|
||||
import (
|
||||
goContext "context"
|
||||
"fmt"
|
||||
"github.com/KubeOperator/ekko/internal/api/v1/session"
|
||||
"github.com/KubeOperator/ekko/internal/service/v1/common"
|
||||
"github.com/KubeOperator/ekko/pkg/kubernetes"
|
||||
"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()))
|
||||
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{})
|
||||
if err != nil {
|
||||
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()))
|
||||
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{})
|
||||
if err != nil {
|
||||
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()))
|
||||
return
|
||||
}
|
||||
u := ctx.Values().Get("profile")
|
||||
profile := u.(session.UserProfile)
|
||||
req.Annotations = map[string]string{
|
||||
"ekko-i18n": "none",
|
||||
"created-by": profile.Name,
|
||||
"builtin": "false",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
req.Labels = map[string]string{
|
||||
"manage": "ekko",
|
||||
}
|
||||
req.Labels[kubernetes.LabelManageKey] = "ekko"
|
||||
resp, err := client.RbacV1().ClusterRoles().Create(goContext.TODO(), &req, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
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) {
|
||||
name := ctx.Params().GetString("name")
|
||||
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()))
|
||||
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 {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.Values().Set("message", fmt.Sprintf("get cluster roles failed: %s", err.Error()))
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"github.com/kataras/iris/v12/context"
|
||||
rbacV1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -46,10 +45,7 @@ func (h *Handler) UpdateClusterMember() iris.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
binding, err := h.clusterBindingService.GetBindingByClusterNameAndSubject(name, v1Cluster.Subject{
|
||||
Kind: req.Kind,
|
||||
Name: req.Name,
|
||||
}, common.DBOptions{})
|
||||
binding, err := h.clusterBindingService.GetBindingByClusterNameAndUserName(name, req.Name, common.DBOptions{})
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
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{
|
||||
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 {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
@@ -96,25 +92,22 @@ func (h *Handler) UpdateClusterMember() iris.Handler {
|
||||
return
|
||||
}
|
||||
}
|
||||
u := ctx.Values().Get("profile")
|
||||
profile := u.(session.UserProfile)
|
||||
for i := range forCreate {
|
||||
b := rbacV1.ClusterRoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%s-%s-%s", name, req.Name, forCreate[i]),
|
||||
Labels: map[string]string{
|
||||
"manage": "ekko",
|
||||
"subject-name": req.Name,
|
||||
"subject-kind": req.Kind,
|
||||
"kubeoperator.io/manage": "ekko",
|
||||
"user-name": req.Name,
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"created-by": profile.Name,
|
||||
"builtin": "false",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
},
|
||||
Subjects: []rbacV1.Subject{
|
||||
{
|
||||
Kind: req.Kind,
|
||||
Kind: "User",
|
||||
Name: req.Name,
|
||||
},
|
||||
},
|
||||
@@ -140,27 +133,6 @@ func (h *Handler) GetClusterMember() iris.Handler {
|
||||
name := ctx.Params().GetString("name")
|
||||
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{})
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
@@ -175,7 +147,7 @@ func (h *Handler) GetClusterMember() iris.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
binding, err := h.clusterBindingService.GetBindingByClusterNameAndSubject(name, subject, common.DBOptions{})
|
||||
binding, err := h.clusterBindingService.GetBindingByClusterNameAndUserName(name, memberName, common.DBOptions{})
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
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{
|
||||
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 {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
@@ -193,8 +165,7 @@ func (h *Handler) GetClusterMember() iris.Handler {
|
||||
|
||||
var member Member
|
||||
member.ClusterRoles = make([]string, 0)
|
||||
member.Kind = binding.Subject.Kind
|
||||
member.Name = binding.Subject.Name
|
||||
member.Name = binding.UserRef
|
||||
set := collectons.NewStringSet()
|
||||
for i := range clusterRoleBindings.Items {
|
||||
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) {
|
||||
name := ctx.Params().GetString("name")
|
||||
bindings, err := h.clusterBindingService.GetClusterBindingByClusterName(name, common.DBOptions{})
|
||||
@@ -214,17 +185,12 @@ func (h *Handler) GetClusterMembers() iris.Handler {
|
||||
ctx.Values().Set("message", err.Error())
|
||||
return
|
||||
}
|
||||
subjectMap := map[v1Cluster.Subject]v1Cluster.Binding{}
|
||||
var members []Member
|
||||
for i := range bindings {
|
||||
subjectMap[bindings[i].Subject] = bindings[i]
|
||||
}
|
||||
members := make([]Member, 0)
|
||||
for key := range subjectMap {
|
||||
members = append(members, Member{
|
||||
Name: key.Name,
|
||||
Kind: key.Kind,
|
||||
BindingName: subjectMap[key].Name,
|
||||
CreateAt: subjectMap[key].CreateAt,
|
||||
Name: bindings[i].UserRef,
|
||||
BindingName: bindings[i].Name,
|
||||
CreateAt: bindings[i].CreateAt,
|
||||
})
|
||||
}
|
||||
ctx.Values().Set("data", members)
|
||||
@@ -238,7 +204,7 @@ func (h *Handler) CreateClusterMember() iris.Handler {
|
||||
err := ctx.ReadJSON(&req)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
u := ctx.Values().Get("profile")
|
||||
@@ -249,12 +215,9 @@ func (h *Handler) CreateClusterMember() iris.Handler {
|
||||
CreatedBy: profile.Name,
|
||||
},
|
||||
Metadata: v1.Metadata{
|
||||
Name: fmt.Sprintf("%s-%s-cluster-binding-", name, req.Name),
|
||||
},
|
||||
Subject: v1Cluster.Subject{
|
||||
Name: req.Name,
|
||||
Kind: req.Kind,
|
||||
Name: fmt.Sprintf("%s-%s-cluster-binding", name, req.Name),
|
||||
},
|
||||
UserRef: req.Name,
|
||||
ClusterRef: name,
|
||||
}
|
||||
c, err := h.clusterService.Get(name, common.DBOptions{})
|
||||
@@ -264,7 +227,6 @@ func (h *Handler) CreateClusterMember() iris.Handler {
|
||||
return
|
||||
}
|
||||
k := kubernetes.NewKubernetes(*c)
|
||||
if req.Kind == "User" {
|
||||
cert, err := k.CreateCommonUser(req.Name)
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
@@ -272,7 +234,6 @@ func (h *Handler) CreateClusterMember() iris.Handler {
|
||||
return
|
||||
}
|
||||
binding.Certificate = cert
|
||||
}
|
||||
if err := h.clusterBindingService.CreateClusterBinding(&binding, common.DBOptions{}); err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.Values().Set("message", err.Error())
|
||||
@@ -284,25 +245,23 @@ func (h *Handler) CreateClusterMember() iris.Handler {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.Values().Set("message", fmt.Sprintf("get k8s client failed: %s", err.Error()))
|
||||
return
|
||||
|
||||
}
|
||||
for i := range req.ClusterRoles {
|
||||
binding := rbacV1.ClusterRoleBinding{
|
||||
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{
|
||||
"manage": "ekko",
|
||||
"subject-name": req.Name,
|
||||
"subject-kind": req.Kind,
|
||||
"kubeoperator.io/manage": "ekko",
|
||||
"user-name": req.Name,
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"created-by": profile.Name,
|
||||
"builtin": "false",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
},
|
||||
Subjects: []rbacV1.Subject{
|
||||
{
|
||||
Kind: req.Kind,
|
||||
Kind: "User",
|
||||
Name: req.Name,
|
||||
},
|
||||
},
|
||||
@@ -318,6 +277,42 @@ func (h *Handler) CreateClusterMember() iris.Handler {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -325,11 +320,7 @@ func (h *Handler) CreateClusterMember() iris.Handler {
|
||||
func (h *Handler) DeleteClusterMember() iris.Handler {
|
||||
return func(ctx *context.Context) {
|
||||
name := ctx.Params().GetString("name")
|
||||
member := ctx.Params().GetString("member")
|
||||
var kind string
|
||||
if ctx.URLParamExists("kind") {
|
||||
kind = ctx.URLParam("kind")
|
||||
}
|
||||
memberName := ctx.Params().GetString("member")
|
||||
u := ctx.Values().Get("profile")
|
||||
profile := u.(session.UserProfile)
|
||||
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()))
|
||||
return
|
||||
}
|
||||
if c.CreatedBy == profile.Name {
|
||||
if c.CreatedBy == memberName {
|
||||
ctx.StatusCode(iris.StatusBadRequest)
|
||||
ctx.Values().Set("message", fmt.Sprintf("can not delete cluster importer %s", profile.Name))
|
||||
return
|
||||
}
|
||||
|
||||
subject := v1Cluster.Subject{
|
||||
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{})
|
||||
binding, err := h.clusterBindingService.GetBindingByClusterNameAndUserName(c.Name, memberName, common.DBOptions{})
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
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(),
|
||||
metav1.DeleteOptions{},
|
||||
metav1.ListOptions{
|
||||
LabelSelector: fmt.Sprintf("subject-name=%s,subject-kind=%s", subject.Name, subject.Kind),
|
||||
LabelSelector: fmt.Sprintf("user-name=%s", memberName),
|
||||
}); err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.Values().Set("message", fmt.Sprintf("delete cluster role failed: %s", err.Error()))
|
||||
|
||||
39
internal/api/v1/cluster/namespace.go
Normal file
39
internal/api/v1/cluster/namespace.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
goContext "context"
|
||||
"fmt"
|
||||
"github.com/KubeOperator/ekko/internal/service/v1/common"
|
||||
"github.com/KubeOperator/ekko/pkg/kubernetes"
|
||||
"github.com/kataras/iris/v12"
|
||||
"github.com/kataras/iris/v12/context"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func (h *Handler) ListNamespace() iris.Handler {
|
||||
return func(ctx *context.Context) {
|
||||
name := ctx.Params().GetString("name")
|
||||
c, err := h.clusterService.Get(name, common.DBOptions{})
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
k := kubernetes.NewKubernetes(*c)
|
||||
client, err := k.Client()
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.Values().Set("message", fmt.Sprintf("get k8s client failed: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
ns, err := client.CoreV1().Namespaces().List(goContext.TODO(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.Values().Set("message", fmt.Sprintf("get k8s client failed: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
ctx.Values().Set("data", ns.Items)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
63
internal/api/v1/cluster/project.go
Normal file
63
internal/api/v1/cluster/project.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
v1Project "github.com/KubeOperator/ekko/internal/model/v1/project"
|
||||
"github.com/KubeOperator/ekko/internal/service/v1/common"
|
||||
"github.com/KubeOperator/ekko/pkg/kubernetes"
|
||||
"github.com/asdine/storm/v3"
|
||||
"github.com/kataras/iris/v12"
|
||||
"github.com/kataras/iris/v12/context"
|
||||
)
|
||||
|
||||
func (h *Handler) CreateProject() iris.Handler {
|
||||
return func(ctx *context.Context) {
|
||||
name := ctx.Params().GetString("name")
|
||||
var req v1Project.Project
|
||||
if err := ctx.ReadJSON(&req); err != nil {
|
||||
ctx.StatusCode(iris.StatusBadRequest)
|
||||
ctx.Values().Set("message", err)
|
||||
return
|
||||
}
|
||||
c, err := h.clusterService.Get(name, common.DBOptions{})
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
k := kubernetes.NewKubernetes(*c)
|
||||
cert, err := k.CreateCommonUser(req.Name)
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.Values().Set("message", err)
|
||||
return
|
||||
}
|
||||
req.Certificate = cert
|
||||
if err := h.projectService.Create(c.Name, &req, common.DBOptions{}); err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.Values().Set("message", err)
|
||||
return
|
||||
}
|
||||
ctx.Values().Set("data", req)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) ListProject() iris.Handler {
|
||||
return func(ctx *context.Context) {
|
||||
name := ctx.Params().GetString("name")
|
||||
c, err := h.clusterService.Get(name, common.DBOptions{})
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.Values().Set("message", fmt.Sprintf("get cluster failed: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
ps, err := h.projectService.List(c.Name, common.DBOptions{})
|
||||
if err != nil && !errors.Is(err, storm.ErrNotFound) {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.Values().Set("message", err)
|
||||
return
|
||||
}
|
||||
ctx.Values().Set("data", ps)
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,15 @@ type Cluster struct {
|
||||
CaDataStr string `json:"caDataStr"`
|
||||
}
|
||||
|
||||
type NamespaceRoles struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Roles []string `json:"roles"`
|
||||
}
|
||||
|
||||
type Member struct {
|
||||
Name string `json:"name"`
|
||||
Kind string `json:"kind"`
|
||||
ClusterRoles []string `json:"clusterRoles"`
|
||||
CreateAt time.Time `json:"createAt"`
|
||||
BindingName string `json:"bindingName"`
|
||||
CreateAt time.Time `json:"createAt"`
|
||||
NamespaceRoles []NamespaceRoles `json:"namespaceRoles"`
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"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/clusterbinding"
|
||||
"github.com/KubeOperator/ekko/internal/service/v1/common"
|
||||
@@ -41,10 +40,7 @@ func (h *Handler) KubernetesAPIProxy() iris.Handler {
|
||||
}
|
||||
u := ctx.Values().Get("profile")
|
||||
profile := u.(session.UserProfile)
|
||||
binding, err := h.clusterBindingService.GetBindingByClusterNameAndSubject(name, v1Cluster.Subject{
|
||||
Kind: "User",
|
||||
Name: profile.Name,
|
||||
}, common.DBOptions{})
|
||||
binding, err := h.clusterBindingService.GetBindingByClusterNameAndUserName(name, profile.Name, common.DBOptions{})
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusForbidden)
|
||||
ctx.Values().Set("message", fmt.Sprintf("user %s not cluster %s member ", profile.Name, name))
|
||||
|
||||
@@ -1,30 +1,37 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
goContext "context"
|
||||
"errors"
|
||||
"fmt"
|
||||
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/role"
|
||||
"github.com/KubeOperator/ekko/internal/service/v1/rolebinding"
|
||||
"github.com/KubeOperator/ekko/internal/service/v1/user"
|
||||
pkgV1 "github.com/KubeOperator/ekko/pkg/api/v1"
|
||||
"github.com/KubeOperator/ekko/pkg/collectons"
|
||||
"github.com/KubeOperator/ekko/pkg/kubernetes"
|
||||
"github.com/asdine/storm/v3"
|
||||
"github.com/kataras/iris/v12"
|
||||
"github.com/kataras/iris/v12/context"
|
||||
"github.com/kataras/iris/v12/sessions"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
v1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
userService user.Service
|
||||
roleService role.Service
|
||||
clusterService cluster.Service
|
||||
rolebindingService rolebinding.Service
|
||||
}
|
||||
|
||||
func NewHandler() *Handler {
|
||||
return &Handler{
|
||||
clusterService: cluster.NewService(),
|
||||
userService: user.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) {
|
||||
handler := NewHandler()
|
||||
sp := parent.Party("/sessions")
|
||||
sp.Post("", handler.Login())
|
||||
sp.Delete("", handler.Logout())
|
||||
sp.Get("", handler.GetProfile())
|
||||
sp.Get("/:cluster_name", handler.GetClusterProfile())
|
||||
sp.Get("/status", handler.IsLogin())
|
||||
sp.Get("/:cluster_name/namespaces", handler.ListUserNamespace())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package session
|
||||
|
||||
import v1 "k8s.io/api/rbac/v1"
|
||||
|
||||
type LoginCredential struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
@@ -12,3 +14,8 @@ type UserProfile struct {
|
||||
Email string `json:"email"`
|
||||
ResourcePermissions map[string][]string `json:"resourcePermissions"`
|
||||
}
|
||||
|
||||
type ClusterUserProfile struct {
|
||||
UserProfile
|
||||
ClusterRoles []v1.ClusterRole `json:"clusterRoles"`
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/KubeOperator/ekko/internal/api/v1/session"
|
||||
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"
|
||||
"github.com/KubeOperator/ekko/internal/server"
|
||||
"github.com/KubeOperator/ekko/internal/service/v1/clusterbinding"
|
||||
@@ -136,10 +135,7 @@ func (h *Handler) DeleteUser() iris.Handler {
|
||||
return
|
||||
}
|
||||
}
|
||||
cbs, err := h.clusterBindingService.GetBindingsBySubject(v1Cluster.Subject{
|
||||
Kind: "User",
|
||||
Name: userName,
|
||||
}, txOptions)
|
||||
cbs, err := h.clusterBindingService.GetBindingsByUserName(userName, txOptions)
|
||||
if err != nil && !errors.As(err, &storm.ErrNotFound) {
|
||||
_ = tx.Rollback()
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
|
||||
@@ -2,15 +2,10 @@ package cluster
|
||||
|
||||
import v1 "github.com/KubeOperator/ekko/internal/model/v1"
|
||||
|
||||
type Subject struct {
|
||||
Kind string `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type Binding struct {
|
||||
v1.BaseModel `storm:"inline"`
|
||||
v1.Metadata `storm:"inline"`
|
||||
Subject Subject `json:"subject" storm:"inline"`
|
||||
UserRef string `json:"UserRef" storm:"inline"`
|
||||
ClusterRef string `json:"clusterRef" storm:"index"`
|
||||
Certificate []byte `json:"certificate"`
|
||||
}
|
||||
|
||||
12
internal/model/v1/project/project.go
Normal file
12
internal/model/v1/project/project.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package project
|
||||
|
||||
import v1 "github.com/KubeOperator/ekko/internal/model/v1"
|
||||
|
||||
type Project struct {
|
||||
v1.BaseModel `storm:"inline"`
|
||||
v1.Metadata `storm:"inline"`
|
||||
Users []string `json:"users"`
|
||||
ClusterRef string `json:"clusterRef" storm:"index"`
|
||||
ProjectName string `json:"projectName"`
|
||||
Certificate []byte `json:"certificate"`
|
||||
}
|
||||
@@ -14,10 +14,9 @@ type Service interface {
|
||||
GetClusterBindingByClusterName(clusterName string, options common.DBOptions) ([]v1Cluster.Binding, error)
|
||||
CreateClusterBinding(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
|
||||
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 {
|
||||
@@ -45,9 +44,9 @@ func (s *service) UpdateClusterBinding(name string, binding *v1Cluster.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)
|
||||
query := db.Select(q.Eq("Subject", subject))
|
||||
query := db.Select(q.Eq("UserRef", userName))
|
||||
var rbs []v1Cluster.Binding
|
||||
if err := query.Find(&rbs); err != nil {
|
||||
return rbs, err
|
||||
@@ -55,9 +54,9 @@ func (s *service) GetBindingsBySubject(subject v1Cluster.Subject, options common
|
||||
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)
|
||||
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
|
||||
if err := query.First(&rb); err != nil {
|
||||
return nil, err
|
||||
|
||||
71
internal/service/v1/project/project.go
Normal file
71
internal/service/v1/project/project.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"errors"
|
||||
v1Project "github.com/KubeOperator/ekko/internal/model/v1/project"
|
||||
"github.com/KubeOperator/ekko/internal/service/v1/common"
|
||||
"github.com/google/uuid"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
common.DBService
|
||||
Create(clusterName string, r *v1Project.Project, options common.DBOptions) error
|
||||
Get(name string, options common.DBOptions) (*v1Project.Project, error)
|
||||
List(clusterName string, options common.DBOptions) ([]v1Project.Project, error)
|
||||
Delete(name string, options common.DBOptions) error
|
||||
Update(name string, role *v1Project.Project, options common.DBOptions) error
|
||||
}
|
||||
|
||||
func NewService() Service {
|
||||
return &service{}
|
||||
}
|
||||
|
||||
type service struct {
|
||||
common.DefaultDBService
|
||||
}
|
||||
|
||||
func (s service) Create(clusterName string, p *v1Project.Project, options common.DBOptions) error {
|
||||
db := s.GetDB(options)
|
||||
p.UUID = uuid.New().String()
|
||||
p.CreateAt = time.Now()
|
||||
p.UpdateAt = time.Now()
|
||||
p.ClusterRef = clusterName
|
||||
return db.Save(p)
|
||||
}
|
||||
|
||||
func (s service) Get(name string, options common.DBOptions) (*v1Project.Project, error) {
|
||||
db := s.GetDB(options)
|
||||
var p v1Project.Project
|
||||
if err := db.One("Name", name, &p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func (s service) List(clusterName string, options common.DBOptions) ([]v1Project.Project, error) {
|
||||
db := s.GetDB(options)
|
||||
|
||||
rs := make([]v1Project.Project, 0)
|
||||
if err := db.Find("ClusterRef", clusterName, &rs); err != nil {
|
||||
return rs, err
|
||||
}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (s service) Delete(name string, options common.DBOptions) error {
|
||||
db := s.GetDB(options)
|
||||
|
||||
item, err := s.Get(name, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if item.BuiltIn {
|
||||
return errors.New("can not delete this resource,because it created by system")
|
||||
}
|
||||
return db.DeleteStruct(item)
|
||||
}
|
||||
|
||||
func (s service) Update(name string, role *v1Project.Project, options common.DBOptions) error {
|
||||
panic("implement me")
|
||||
}
|
||||
419
pkg/kubernetes/constants.go
Normal file
419
pkg/kubernetes/constants.go
Normal file
@@ -0,0 +1,419 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
rbacV1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
LabelManageKey = "kubeoperator.io/manage"
|
||||
LabelRoleTypeKey = "kubeoperator.io/role-type"
|
||||
|
||||
RoleTypeCluster = "cluster"
|
||||
RoleTypeNamespace = "namespace"
|
||||
)
|
||||
|
||||
var initClusterRoles = []rbacV1.ClusterRole{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Admin Cluster",
|
||||
Annotations: map[string]string{
|
||||
"builtin": "true",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Labels: map[string]string{
|
||||
LabelManageKey: "ekko",
|
||||
LabelRoleTypeKey: RoleTypeCluster,
|
||||
},
|
||||
},
|
||||
Rules: []rbacV1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"*"},
|
||||
Verbs: []string{"*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "View Cluster",
|
||||
Annotations: map[string]string{
|
||||
"builtin": "true",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Labels: map[string]string{
|
||||
LabelManageKey: "ekko",
|
||||
LabelRoleTypeKey: RoleTypeCluster},
|
||||
},
|
||||
Rules: []rbacV1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"*"},
|
||||
Verbs: []string{"list", "get", "watch"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Manage Namespaces",
|
||||
Annotations: map[string]string{
|
||||
"builtin": "true",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Labels: map[string]string{
|
||||
LabelManageKey: "ekko",
|
||||
LabelRoleTypeKey: RoleTypeCluster},
|
||||
},
|
||||
Rules: []rbacV1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"namespaces"},
|
||||
Verbs: []string{"*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "View Namespaces",
|
||||
Annotations: map[string]string{
|
||||
"builtin": "true",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Labels: map[string]string{
|
||||
LabelManageKey: "ekko",
|
||||
LabelRoleTypeKey: RoleTypeCluster},
|
||||
},
|
||||
Rules: []rbacV1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"namespaces"},
|
||||
Verbs: []string{"list", "get", "watch"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "View Nodes",
|
||||
Annotations: map[string]string{
|
||||
"builtin": "true",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Labels: map[string]string{
|
||||
LabelManageKey: "ekko",
|
||||
LabelRoleTypeKey: RoleTypeCluster},
|
||||
},
|
||||
Rules: []rbacV1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"nodes"},
|
||||
Verbs: []string{"*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "View Nodes",
|
||||
Annotations: map[string]string{
|
||||
"builtin": "true",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Labels: map[string]string{
|
||||
LabelManageKey: "ekko",
|
||||
LabelRoleTypeKey: RoleTypeCluster},
|
||||
},
|
||||
Rules: []rbacV1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"nodes"},
|
||||
Verbs: []string{"list", "get", "watch"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "View Events",
|
||||
Annotations: map[string]string{
|
||||
"builtin": "true",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Labels: map[string]string{
|
||||
LabelManageKey: "ekko",
|
||||
LabelRoleTypeKey: RoleTypeCluster},
|
||||
},
|
||||
Rules: []rbacV1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"events"},
|
||||
Verbs: []string{"list", "get", "watch"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Manage Network",
|
||||
Annotations: map[string]string{
|
||||
"builtin": "true",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Labels: map[string]string{
|
||||
LabelManageKey: "ekko",
|
||||
LabelRoleTypeKey: RoleTypeNamespace,
|
||||
},
|
||||
},
|
||||
Rules: []rbacV1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"services", "endpoints"},
|
||||
Verbs: []string{"*"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"networking.k8s.io"},
|
||||
Resources: []string{"ingresses", "networkpolicies"},
|
||||
Verbs: []string{"*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "View Network",
|
||||
Annotations: map[string]string{
|
||||
"builtin": "true",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Labels: map[string]string{
|
||||
LabelManageKey: "ekko",
|
||||
LabelRoleTypeKey: RoleTypeNamespace},
|
||||
},
|
||||
Rules: []rbacV1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"services", "endpoints"},
|
||||
Verbs: []string{"list", "get", "watch"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"networking.k8s.io"},
|
||||
Resources: []string{"ingresses", "networkpolicies"},
|
||||
Verbs: []string{"list", "get", "watch"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Manage Config",
|
||||
Annotations: map[string]string{
|
||||
"builtin": "true",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Labels: map[string]string{
|
||||
LabelManageKey: "ekko",
|
||||
LabelRoleTypeKey: RoleTypeNamespace},
|
||||
},
|
||||
Rules: []rbacV1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"configmaps,secrets,resourcequotas,limitranges"},
|
||||
Verbs: []string{"*"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"autoscaling"},
|
||||
Resources: []string{"horizontalpodautoscalers"},
|
||||
Verbs: []string{"*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "View Config",
|
||||
Annotations: map[string]string{
|
||||
"builtin": "true",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Labels: map[string]string{
|
||||
LabelManageKey: "ekko",
|
||||
LabelRoleTypeKey: RoleTypeNamespace},
|
||||
},
|
||||
Rules: []rbacV1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"configmaps,secrets,resourcequotas,limitranges"},
|
||||
Verbs: []string{"list", "get", "watch"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"autoscaling"},
|
||||
Resources: []string{"horizontalpodautoscalers"},
|
||||
Verbs: []string{"list", "get", "watch"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Manage Storage",
|
||||
Annotations: map[string]string{
|
||||
"builtin": "true",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Labels: map[string]string{
|
||||
LabelManageKey: "ekko",
|
||||
LabelRoleTypeKey: RoleTypeNamespace,
|
||||
},
|
||||
},
|
||||
Rules: []rbacV1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"persistentvolumes", "persistentvolumeclaims"},
|
||||
Verbs: []string{"*"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"storage.k8s.io"},
|
||||
Resources: []string{"storageclasses"},
|
||||
Verbs: []string{"*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "View Storage",
|
||||
Annotations: map[string]string{
|
||||
"builtin": "true",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Labels: map[string]string{
|
||||
LabelManageKey: "ekko",
|
||||
LabelRoleTypeKey: RoleTypeNamespace,
|
||||
},
|
||||
},
|
||||
Rules: []rbacV1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"persistentvolumes", "persistentvolumeclaims"},
|
||||
Verbs: []string{"list", "get", "watch"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"storage.k8s.io"},
|
||||
Resources: []string{"storageclasses"},
|
||||
Verbs: []string{"list", "get", "watch"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Manage Workload",
|
||||
Annotations: map[string]string{
|
||||
"builtin": "true",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Labels: map[string]string{
|
||||
LabelManageKey: "ekko",
|
||||
LabelRoleTypeKey: RoleTypeNamespace,
|
||||
},
|
||||
},
|
||||
Rules: []rbacV1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"pods", "containers"},
|
||||
Verbs: []string{"*"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"apps"},
|
||||
Resources: []string{"deployments", "daemonsets", "replicasets", "statefulsets"},
|
||||
Verbs: []string{"*"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"batch"},
|
||||
Resources: []string{"jobs", "cronjobs"},
|
||||
Verbs: []string{"*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "View Workload",
|
||||
Annotations: map[string]string{
|
||||
"builtin": "true",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Labels: map[string]string{
|
||||
LabelManageKey: "ekko",
|
||||
LabelRoleTypeKey: RoleTypeNamespace,
|
||||
},
|
||||
},
|
||||
Rules: []rbacV1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"pods", "containers"},
|
||||
Verbs: []string{"list", "get", "watch"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"apps"},
|
||||
Resources: []string{"deployments", "daemonsets", "replicasets", "statefulsets"},
|
||||
Verbs: []string{"list", "get", "watch"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"batch"},
|
||||
Resources: []string{"jobs", "cronjobs"},
|
||||
Verbs: []string{"list", "get", "watch"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Mange RBAC",
|
||||
Annotations: map[string]string{
|
||||
"builtin": "true",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Labels: map[string]string{
|
||||
LabelManageKey: "ekko",
|
||||
LabelRoleTypeKey: RoleTypeNamespace,
|
||||
},
|
||||
},
|
||||
Rules: []rbacV1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{"rbac.authorization.k8s.io"},
|
||||
Resources: []string{"clusterroles", "clusterrolebindings", "roles", "rolebindings"},
|
||||
Verbs: []string{"*"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"serviceaccounts"},
|
||||
Verbs: []string{"*"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"policy"},
|
||||
Resources: []string{"podsecuritypolicies"},
|
||||
Verbs: []string{"*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Mange RBAC",
|
||||
Annotations: map[string]string{
|
||||
"builtin": "true",
|
||||
"created-at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Labels: map[string]string{
|
||||
LabelManageKey: "ekko",
|
||||
LabelRoleTypeKey: RoleTypeNamespace,
|
||||
},
|
||||
},
|
||||
Rules: []rbacV1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{"rbac.authorization.k8s.io"},
|
||||
Resources: []string{"clusterroles", "clusterrolebindings", "roles", "rolebindings"},
|
||||
Verbs: []string{"list", "get", "watch"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"serviceaccounts"},
|
||||
Verbs: []string{"list", "get", "watch"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"policy"},
|
||||
Resources: []string{"podsecuritypolicies"},
|
||||
Verbs: []string{"list", "get", "watch"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/KubeOperator/ekko/pkg/certificate"
|
||||
v1 "k8s.io/api/authorization/v1"
|
||||
certv1 "k8s.io/api/certificates/v1"
|
||||
rbacV1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
@@ -40,69 +39,20 @@ func NewKubernetes(cluster v1Cluster.Cluster) Interface {
|
||||
}
|
||||
|
||||
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()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range defaultRoles {
|
||||
instance, err := client.RbacV1().ClusterRoles().Get(context.TODO(), defaultRoles[i].Name, metav1.GetOptions{})
|
||||
for i := range initClusterRoles {
|
||||
instance, err := client.RbacV1().ClusterRoles().Get(context.TODO(), initClusterRoles[i].Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if !strings.Contains(strings.ToLower(err.Error()), "not found") {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if instance == nil {
|
||||
_, err = client.RbacV1().ClusterRoles().Create(context.TODO(), &defaultRoles[i], metav1.CreateOptions{})
|
||||
if instance == nil || instance.Name == "" {
|
||||
_, err = client.RbacV1().ClusterRoles().Create(context.TODO(), &initClusterRoles[i], metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -14,6 +14,15 @@ export function getCurrentUser() {
|
||||
return get(authUrl)
|
||||
}
|
||||
|
||||
export function getCurrentClusterUser(clusterName) {
|
||||
return get(`${authUrl}/${clusterName}`)
|
||||
}
|
||||
|
||||
export function isLogin() {
|
||||
return get(`${authUrl}/status`)
|
||||
}
|
||||
|
||||
|
||||
export function getNamespaces(clusterName) {
|
||||
return get(`${authUrl}/${clusterName}/namespaces`)
|
||||
}
|
||||
@@ -1,19 +1,7 @@
|
||||
import {post, get, del} from "@/plugins/request"
|
||||
import {get} from "@/plugins/request"
|
||||
|
||||
const authUrl = "/api/v1/clusters"
|
||||
|
||||
export function create(data) {
|
||||
return post(authUrl, data)
|
||||
}
|
||||
|
||||
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}`)
|
||||
export function getCluster(name) {
|
||||
return get(`${authUrl}/${name}`)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<el-dropdown trigger="click" @command="handleProjectSwitch">
|
||||
<span class="el-dropdown-link">
|
||||
<i class="iconfont iconnamesapce" style="color: #3884c5;margin-right: 3px" :icon="['fas', 'globe']"/>
|
||||
<span>{{ getProjectName }}</span>
|
||||
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="">全部</el-dropdown-item>
|
||||
<el-dropdown-item disabled divided></el-dropdown-item>
|
||||
<el-dropdown-item v-for="(value,key) in namespaceOptions" :key="key" :command="value">{{ value }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import {getNamespaces} from '@/api/auth'
|
||||
|
||||
export default {
|
||||
name: "ProjectSwitch",
|
||||
data() {
|
||||
return {
|
||||
namespaceOptions: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getProjectName() {
|
||||
const p = sessionStorage.getItem("project")
|
||||
return (p === null) ? "Global" : p
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleProjectSwitch(command) {
|
||||
if (!command) {
|
||||
sessionStorage.removeItem("project")
|
||||
} else {
|
||||
sessionStorage.setItem("project", command)
|
||||
}
|
||||
location.reload()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const cluster = this.$store.getters.cluster
|
||||
getNamespaces(cluster).then((data) => {
|
||||
this.namespaceOptions = data.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
<el-dropdown trigger="click" @command="handleCommand">
|
||||
<span class="el-dropdown-link">
|
||||
<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>
|
||||
</span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
@@ -22,7 +22,7 @@
|
||||
name: "UserSetting",
|
||||
computed: {
|
||||
...mapGetters([
|
||||
"name"
|
||||
"nickName"
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
<home></home>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<user-setting></user-setting>
|
||||
<project-switch></project-switch>
|
||||
<user-setting style="margin-left: 20px"></user-setting>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -14,16 +15,18 @@
|
||||
import SidebarToggleButton from "@/components/layout/sidebar/SidebarToggleButton"
|
||||
import UserSetting from "@/business/app-layout/header-components/UserSetting"
|
||||
import Home from "@/business/app-layout/header-components/Home"
|
||||
import ProjectSwitch from "@/business/app-layout/header-components/ProjectSwitch";
|
||||
|
||||
export default {
|
||||
name: "HorizontalHeader",
|
||||
components: { Home, UserSetting, SidebarToggleButton }
|
||||
components: {ProjectSwitch, Home, UserSetting, SidebarToggleButton}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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%;
|
||||
@@ -50,5 +53,5 @@ export default {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,10 +3,10 @@
|
||||
<complex-table :selects.sync="selects" :data="data" :pagination-config="page" @search="search()">
|
||||
<template #header>
|
||||
<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") }}
|
||||
</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") }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
@@ -58,6 +58,8 @@ import ComplexTable from "@/components/complex-table"
|
||||
import {listNamespace, deleteNamespace} from "@/api/namespaces"
|
||||
import KoTableOperations from "@/components/ko-table-operations"
|
||||
import {downloadYaml} from "@/utils/actions"
|
||||
import {checkPermissions} from "@/utils/permission"
|
||||
|
||||
|
||||
export default {
|
||||
name: "NamespaceList",
|
||||
@@ -73,21 +75,31 @@ export default {
|
||||
path: "/namespaces/edit/"+row.metadata.name ,
|
||||
query: { yamlShow: false }
|
||||
})
|
||||
},
|
||||
disabled:()=>{
|
||||
return !checkPermissions({apiGroup:"",resource:"namespaces",verb:"update"})
|
||||
}
|
||||
},
|
||||
{
|
||||
label: this.$t("commons.button.edit_yaml"),
|
||||
icon: "el-icon-edit",
|
||||
disabled:()=>{
|
||||
return !checkPermissions({apiGroup:"",resource:"namespaces",verb:"update"})
|
||||
},
|
||||
click: (row) => {
|
||||
this.$router.push({
|
||||
path: "/namespaces/edit/"+row.metadata.name ,
|
||||
query: { yamlShow: true }
|
||||
})
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
label: this.$t("commons.button.download_yaml"),
|
||||
icon: "el-icon-download",
|
||||
disabled:()=>{
|
||||
return !checkPermissions({apiGroup:"",resource:"namespaces",verb:"get"})
|
||||
},
|
||||
click: (row) => {
|
||||
downloadYaml(row.metadata.name + ".yml", row)
|
||||
}
|
||||
@@ -95,6 +107,9 @@ export default {
|
||||
{
|
||||
label: this.$t("commons.button.delete"),
|
||||
icon: "el-icon-delete",
|
||||
disabled:()=>{
|
||||
return !checkPermissions({apiGroup:"",resource:"namespaces",verb:"delete"})
|
||||
},
|
||||
click: (row) => {
|
||||
this.onDelete(row)
|
||||
}
|
||||
|
||||
@@ -2,33 +2,26 @@
|
||||
<layout-content :header="$t('business.dashboard.dashboard')">
|
||||
<el-row :gutter="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-col :span="6">
|
||||
<el-col :span="8">
|
||||
<span class="title">{{ $t("commons.table.name") }}</span>
|
||||
<div style="text-align: center">
|
||||
<h1>测试</h1>
|
||||
<h1>{{cluster.name}}</h1>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="8">
|
||||
<span class="title">{{ $t("business.cluster.version") }}</span>
|
||||
<div class="line"></div>
|
||||
<div style="text-align: center">
|
||||
<h1>v1.18.0</h1>
|
||||
<h1>{{cluster.status.version}}</h1>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<span class="title">{{ $t("business.cluster.nodes") }}</span>
|
||||
<div class="line"></div>
|
||||
<div style="text-align: center">
|
||||
<h1>2</h1>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="8">
|
||||
<span class="title">{{ $t("commons.table.created_time") }}</span>
|
||||
<div class="line"></div>
|
||||
<div style="text-align: center">
|
||||
<h1>16 days ago</h1>
|
||||
<h1>{{fromNow(cluster.createAt)}} Days</h1>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -55,7 +48,9 @@
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-row :gutter="24" v-has-permissions="{apiGroup:'',resource:'events',verb:'list'}">
|
||||
<!-- <el-row :gutter="24">-->
|
||||
|
||||
<h3>Events</h3>
|
||||
<complex-table :pagination-config="page" :data="events" @search="search()" v-loading="loading">
|
||||
<el-table-column label="Reason" prop="reason" fix max-width="50px">
|
||||
@@ -89,24 +84,28 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LayoutContent from "@/components/layout/LayoutContent"
|
||||
import KoCharts from "@/components/ko-charts"
|
||||
import {listNamespace} from "@/api/namespaces"
|
||||
import {listIngresses} from "@/api/ingress"
|
||||
import {listPvs} from "@/api/pv"
|
||||
import {listDeployments} from "@/api/deployments"
|
||||
import {listStatefulSets} from "@/api/statefulsets"
|
||||
import {listJobs} from "@/api/jobs"
|
||||
import {listDaemonSets} from "@/api/daemonsets"
|
||||
import {listServices} from "@/api/services"
|
||||
import ComplexTable from "@/components/complex-table"
|
||||
import {listEvents} from "@/api/events"
|
||||
import LayoutContent from "@/components/layout/LayoutContent"
|
||||
import KoCharts from "@/components/ko-charts"
|
||||
import {listNamespace} from "@/api/namespaces"
|
||||
import {listIngresses} from "@/api/ingress"
|
||||
import {listPvs} from "@/api/pv"
|
||||
import {listDeployments} from "@/api/deployments"
|
||||
import {listStatefulSets} from "@/api/statefulsets"
|
||||
import {listJobs} from "@/api/jobs"
|
||||
import {listDaemonSets} from "@/api/daemonsets"
|
||||
import {listServices} from "@/api/services"
|
||||
import {listNodes} from "@/api/nodes"
|
||||
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",
|
||||
components: { ComplexTable, KoCharts, LayoutContent },
|
||||
data () {
|
||||
components: {ComplexTable, KoCharts, LayoutContent},
|
||||
data() {
|
||||
return {
|
||||
cluster: null,
|
||||
clusterName: "",
|
||||
resources: [],
|
||||
page: {
|
||||
@@ -118,7 +117,27 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
listResources () {
|
||||
|
||||
fromNow(date) {
|
||||
const a = new Date(date)
|
||||
const b = new Date()
|
||||
return parseInt(Math.abs(b - a) / 1000 / 60 / 60 / 24)
|
||||
},
|
||||
listResources() {
|
||||
getCluster(this.clusterName).then(res => {
|
||||
this.cluster = res.data;
|
||||
})
|
||||
if (checkPermissions({apiGroup: "", resource: "nodes", verb: "list"})) {
|
||||
listNodes(this.clusterName).then(res => {
|
||||
const nodes = {
|
||||
name: "Nodes",
|
||||
count: res.items.length,
|
||||
data: this.getData(res.items, "status.phase")
|
||||
}
|
||||
this.resources.push(nodes)
|
||||
})
|
||||
}
|
||||
if (checkPermissions({apiGroup: "", resource: "namespaces", verb: "list"})) {
|
||||
listNamespace(this.clusterName).then(res => {
|
||||
const namespaces = {
|
||||
name: "Namespaces",
|
||||
@@ -127,6 +146,8 @@ export default {
|
||||
}
|
||||
this.resources.push(namespaces)
|
||||
})
|
||||
}
|
||||
if (checkPermissions({apiGroup: "networking.k8s.io", resource: "ingresses", verb: "list"})) {
|
||||
listIngresses(this.clusterName).then(res => {
|
||||
const ingresses = {
|
||||
name: "Ingresses",
|
||||
@@ -138,6 +159,8 @@ export default {
|
||||
}
|
||||
this.resources.push(ingresses)
|
||||
})
|
||||
}
|
||||
if (checkPermissions({apiGroup: "", resource: "persistentvolumes", verb: "list"})) {
|
||||
listPvs(this.clusterName).then(res => {
|
||||
const persistentVolumes = {
|
||||
name: "PersistentVolumes",
|
||||
@@ -146,6 +169,8 @@ export default {
|
||||
}
|
||||
this.resources.push(persistentVolumes)
|
||||
})
|
||||
}
|
||||
if (checkPermissions({apiGroup: "apps", resource: "deployments", verb: "list"})) {
|
||||
listDeployments(this.clusterName).then(res => {
|
||||
const deployments = {
|
||||
name: "Deployments",
|
||||
@@ -154,6 +179,8 @@ export default {
|
||||
}
|
||||
this.resources.push(deployments)
|
||||
})
|
||||
}
|
||||
if (checkPermissions({apiGroup: "apps", resource: "statefulsets", verb: "list"})) {
|
||||
listStatefulSets(this.clusterName).then(res => {
|
||||
const statefulSets = {
|
||||
name: "StatefulSets",
|
||||
@@ -162,6 +189,8 @@ export default {
|
||||
}
|
||||
this.resources.push(statefulSets)
|
||||
})
|
||||
}
|
||||
if (checkPermissions({apiGroup: "batch", resource: "jobs", verb: "list"})) {
|
||||
listJobs(this.clusterName).then(res => {
|
||||
const jobs = {
|
||||
name: "Jobs",
|
||||
@@ -170,6 +199,8 @@ export default {
|
||||
}
|
||||
this.resources.push(jobs)
|
||||
})
|
||||
}
|
||||
if (checkPermissions({apiGroup: "apps", resource: "daemonsets", verb: "list"})) {
|
||||
listDaemonSets(this.clusterName).then(res => {
|
||||
const daemonSets = {
|
||||
name: "DaemonSets",
|
||||
@@ -178,6 +209,8 @@ export default {
|
||||
}
|
||||
this.resources.push(daemonSets)
|
||||
})
|
||||
}
|
||||
if (checkPermissions({apiGroup: "", resource: "services", verb: "list"})) {
|
||||
listServices(this.clusterName).then(res => {
|
||||
const services = {
|
||||
name: "Services",
|
||||
@@ -186,17 +219,20 @@ export default {
|
||||
}
|
||||
this.resources.push(services)
|
||||
})
|
||||
}
|
||||
this.search()
|
||||
},
|
||||
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) {
|
||||
getData(items, keys) {
|
||||
let key = []
|
||||
let result = []
|
||||
for (const item of items) {
|
||||
@@ -219,7 +255,7 @@ export default {
|
||||
}
|
||||
return result
|
||||
},
|
||||
traverse (obj, keys) {
|
||||
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
|
||||
@@ -243,11 +279,11 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
created() {
|
||||
this.clusterName = this.$route.query.cluster
|
||||
this.listResources()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -101,7 +101,6 @@
|
||||
if (valid) {
|
||||
this.loading = true
|
||||
this.$store.dispatch("user/login", this.form).then(() => {
|
||||
console.log("123")
|
||||
this.$router.push({path: this.redirect || "/", query: this.otherQuery})
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
:class="{'collapse':collapse}">
|
||||
<transition name="sidebar-logo-fade"
|
||||
mode="out-in">
|
||||
<router-link v-if="collapse"
|
||||
<div v-if="collapse"
|
||||
key="collapse"
|
||||
class="sidebar-logo-link"
|
||||
to="/">
|
||||
@@ -11,8 +11,8 @@
|
||||
:src="collapseLogo"
|
||||
class="sidebar-logo"
|
||||
alt="Sidebar Logo">
|
||||
</router-link>
|
||||
<router-link v-else
|
||||
</div>
|
||||
<div v-else
|
||||
key="expand"
|
||||
class="sidebar-logo-link"
|
||||
to="/">
|
||||
@@ -20,7 +20,7 @@
|
||||
:src="logo"
|
||||
class="sidebar-logo"
|
||||
alt="Sidebar Logo">
|
||||
</router-link>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
@@ -48,9 +48,9 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~@/styles/common/variables";
|
||||
@import "~@/styles/common/variables";
|
||||
|
||||
.sidebar-logo-container {
|
||||
.sidebar-logo-container {
|
||||
position: relative;
|
||||
height: $header-height;
|
||||
line-height: $header-height;
|
||||
@@ -88,19 +88,19 @@ export default {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-logo-fade-enter-active {
|
||||
.sidebar-logo-fade-enter-active {
|
||||
transition: opacity 0.1s;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-logo-fade-leave-active {
|
||||
.sidebar-logo-fade-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-logo-fade-enter,
|
||||
.sidebar-logo-fade-leave-to {
|
||||
.sidebar-logo-fade-enter,
|
||||
.sidebar-logo-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -60,6 +60,9 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
hasOneShowingChild(children = [], parent) {
|
||||
if (parent.parent){
|
||||
return false
|
||||
}
|
||||
const showingChildren = children.filter(item => {
|
||||
if (item.hidden) {
|
||||
return false
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
},
|
||||
},
|
||||
created() {
|
||||
console.log(this.permission_routes)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
8
web/dashboard/src/directive/index.js
Normal file
8
web/dashboard/src/directive/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import Permission from "./permission";
|
||||
|
||||
export default {
|
||||
install(Vue) {
|
||||
Vue.directive('has-permissions', Permission.hasPermissions);
|
||||
}
|
||||
}
|
||||
|
||||
26
web/dashboard/src/directive/permission/index.js
Normal file
26
web/dashboard/src/directive/permission/index.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import {checkPermissions} from '@/utils/permission'
|
||||
|
||||
|
||||
function hasPermissions(el, binding) {
|
||||
const {value, modifiers} = binding
|
||||
const hasPermissions = checkPermissions(value)
|
||||
if (!hasPermissions) {
|
||||
if (modifiers && modifiers.disable) {
|
||||
el.setAttribute("disabled", true)
|
||||
} else {
|
||||
el.parentNode && el.parentNode.removeChild(el)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
hasPermissions: {
|
||||
inserted(el, binding) {
|
||||
hasPermissions(el, binding)
|
||||
},
|
||||
update(el, binding) {
|
||||
hasPermissions(el, binding)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ import "./permission"
|
||||
import "@/styles/common/ekko.css"
|
||||
import filters from "./filters";
|
||||
import JsonViewer from 'vue-json-viewer'
|
||||
import directives from "./directive";
|
||||
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@@ -34,6 +36,7 @@ library.add(fas, far, fab)
|
||||
|
||||
Vue.use(icons);
|
||||
Vue.use(filters);
|
||||
Vue.use(directives)
|
||||
Vue.use(JsonViewer)
|
||||
|
||||
new Vue({
|
||||
|
||||
@@ -2,21 +2,31 @@ import router from "./router"
|
||||
import NProgress from "nprogress"
|
||||
import "nprogress/nprogress.css"
|
||||
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 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) {
|
||||
next()
|
||||
} else {
|
||||
try {
|
||||
const user = await store.dispatch("user/getCurrentUser")
|
||||
const accessRoutes = await store.dispatch("permission/generateRoutes", user)
|
||||
const {clusterRoles} = await store.dispatch("user/getCurrentUser")
|
||||
const accessRoutes = await store.dispatch("permission/generateRoutes", clusterRoles)
|
||||
if (accessRoutes.length > 0) {
|
||||
const root = {
|
||||
path: "/",
|
||||
component: Layout,
|
||||
redirect: accessRoutes[0].path,
|
||||
}
|
||||
router.addRoute(root)
|
||||
}
|
||||
router.addRoutes(accessRoutes)
|
||||
next({ ...to, replace: true })
|
||||
next({...to, replace: true})
|
||||
} catch (error) {
|
||||
NProgress.done()
|
||||
}
|
||||
@@ -26,22 +36,24 @@ const generateRoutes = async (to, from, next) => {
|
||||
//路由前置钩子,根据实际需求修改
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
NProgress.start()
|
||||
if (!to.query["cluster"]) {
|
||||
if (from.query["cluster"]) {
|
||||
const q = to.query
|
||||
q["cluster"] = from.query["cluster"]
|
||||
next({ path: to.path, query: q })
|
||||
NProgress.done()
|
||||
} else {
|
||||
console.log("no cluster")
|
||||
}
|
||||
}
|
||||
const isLogin = await store.dispatch("user/isLogin")
|
||||
if (isLogin) {
|
||||
if (to.path === "/login") {
|
||||
next({ path: "/" })
|
||||
next({path: "/"})
|
||||
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 {
|
||||
/* has not login*/
|
||||
|
||||
@@ -76,11 +76,6 @@ const promise = (request, loading = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
loading.status = true
|
||||
request.then(response => {
|
||||
// if (response.data.success) {
|
||||
// resolve(response.data)
|
||||
// } else {
|
||||
// reject(response.message)
|
||||
// }
|
||||
resolve(response.data)
|
||||
loading.status = false
|
||||
}).catch(error => {
|
||||
|
||||
@@ -7,7 +7,7 @@ const modules = require.context("./modules", true, /\.js$/)
|
||||
|
||||
// 修复路由变更后报错的问题
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -47,12 +47,12 @@ export const rolesRoutes = [
|
||||
r2.sort ??= Number.MAX_VALUE
|
||||
return r1.sort - r2.sort
|
||||
}),
|
||||
{ path: "*", redirect: "/", hidden: true }
|
||||
{path: "*", redirect: "/", hidden: true}
|
||||
]
|
||||
|
||||
const createRouter = () => new Router({
|
||||
mode: 'history',
|
||||
scrollBehavior: () => ({ y: 0 }),
|
||||
scrollBehavior: () => ({y: 0}),
|
||||
routes: constantRoutes,
|
||||
base: "dashboard"
|
||||
})
|
||||
@@ -60,7 +60,7 @@ const createRouter = () => new Router({
|
||||
const router = createRouter()
|
||||
|
||||
|
||||
export function resetRouter () {
|
||||
export function resetRouter() {
|
||||
const newRouter = createRouter()
|
||||
router.matcher = newRouter.matcher // reset router
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import Layout from "@/business/app-layout/horizontal-layout"
|
||||
|
||||
const AccessControl = {
|
||||
path: "/accesscontrol",
|
||||
parent: true,
|
||||
sort: 5,
|
||||
component: Layout,
|
||||
name: "Access Control",
|
||||
@@ -13,6 +14,11 @@ const AccessControl = {
|
||||
children: [
|
||||
{
|
||||
path: "/serviceaccounts",
|
||||
requirePermission:{
|
||||
apiGroup:"",
|
||||
resource:"serviceaccounts",
|
||||
verb:"list",
|
||||
},
|
||||
component: () => import("@/business/access-control/service-accounts"),
|
||||
name: "ServiceAccounts",
|
||||
meta: {
|
||||
@@ -22,6 +28,11 @@ const AccessControl = {
|
||||
},
|
||||
{
|
||||
path: "/rolebindings",
|
||||
requirePermission:{
|
||||
apiGroup:"rbac.authorization.k8s.io",
|
||||
resource:"rolebindings",
|
||||
verb:"list",
|
||||
},
|
||||
component: () => import("@/business/access-control/role-bindings"),
|
||||
name: "RoleBindings",
|
||||
meta: {
|
||||
@@ -31,6 +42,11 @@ const AccessControl = {
|
||||
},
|
||||
{
|
||||
path: "/roles",
|
||||
requirePermission:{
|
||||
apiGroup:"rbac.authorization.k8s.io",
|
||||
resource:"roles",
|
||||
verb:"list",
|
||||
},
|
||||
component: () => import("@/business/access-control/roles"),
|
||||
name: "Roles",
|
||||
meta: {
|
||||
@@ -40,6 +56,11 @@ const AccessControl = {
|
||||
},
|
||||
{
|
||||
path: "/podsecuritypolicy",
|
||||
requirePermission:{
|
||||
apiGroup:"policy",
|
||||
resource:"podsecuritypolicies",
|
||||
verb:"list",
|
||||
},
|
||||
component: () => import("@/business/access-control/pod-security-policies"),
|
||||
name: "PodSecurityPolicy",
|
||||
meta: {
|
||||
|
||||
@@ -2,6 +2,8 @@ import Layout from "@/business/app-layout/horizontal-layout"
|
||||
|
||||
const Clusters = {
|
||||
path: "/cluster",
|
||||
parent: true,
|
||||
global: true,
|
||||
sort: 1,
|
||||
component: Layout,
|
||||
name: "Cluster",
|
||||
@@ -12,6 +14,11 @@ const Clusters = {
|
||||
children: [
|
||||
{
|
||||
path: "/nodes",
|
||||
requirePermission: {
|
||||
apiGroup: "",
|
||||
resource: "nodes",
|
||||
verb: "list",
|
||||
},
|
||||
component: () => import("@/business/cluster/nodes"),
|
||||
name: "Nodes",
|
||||
meta: {
|
||||
@@ -32,6 +39,11 @@ const Clusters = {
|
||||
path: "/namespaces",
|
||||
component: () => import("@/business/cluster/namespaces"),
|
||||
name: "Namespaces",
|
||||
requirePermission: {
|
||||
apiGroup: "",
|
||||
resource: "namespaces",
|
||||
verb: "list",
|
||||
},
|
||||
meta: {
|
||||
title: "Namespaces"
|
||||
}
|
||||
@@ -69,6 +81,11 @@ const Clusters = {
|
||||
path: "/events",
|
||||
component: () => import("@/business/cluster/events"),
|
||||
name: "events",
|
||||
requirePermission: {
|
||||
apiGroup: "",
|
||||
resource: "events",
|
||||
verb: "list",
|
||||
},
|
||||
meta: {
|
||||
title: "Events",
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import Layout from "@/business/app-layout/horizontal-layout"
|
||||
|
||||
const Configuration = {
|
||||
path: "/configuration",
|
||||
parent: true,
|
||||
sort: 2,
|
||||
component: Layout,
|
||||
name: "Configuration",
|
||||
|
||||
@@ -3,6 +3,7 @@ import Layout from "@/business/app-layout/horizontal-layout"
|
||||
const Dashboard = {
|
||||
path: "/dashboard",
|
||||
sort: 0,
|
||||
global: true,
|
||||
component: Layout,
|
||||
name: "Dashboard",
|
||||
children: [
|
||||
|
||||
@@ -12,6 +12,11 @@ const Network = {
|
||||
children: [
|
||||
{
|
||||
path: "/services",
|
||||
requirePermission: {
|
||||
apiGroup: "",
|
||||
resource: "services",
|
||||
verb: "list",
|
||||
},
|
||||
component: () => import("@/business/network/services"),
|
||||
name: "Services",
|
||||
meta: {
|
||||
@@ -49,6 +54,11 @@ const Network = {
|
||||
},
|
||||
{
|
||||
path: "/ingresses",
|
||||
requirePermission: {
|
||||
apiGroup: "networking.k8s.io",
|
||||
resource: "ingresses",
|
||||
verb: "list",
|
||||
},
|
||||
component: () => import("@/business/network/ingresses"),
|
||||
name: "Ingresses",
|
||||
meta: {
|
||||
@@ -67,6 +77,11 @@ const Network = {
|
||||
},
|
||||
{
|
||||
path: "/endpoints",
|
||||
requirePermission: {
|
||||
apiGroup: "",
|
||||
resource: "endpoints",
|
||||
verb: "list",
|
||||
},
|
||||
component: () => import("@/business/network/endpoints"),
|
||||
name: "Endpoints",
|
||||
meta: {
|
||||
@@ -75,6 +90,11 @@ const Network = {
|
||||
},
|
||||
{
|
||||
path: "/networkpolicies",
|
||||
requirePermission: {
|
||||
apiGroup: "",
|
||||
resource: "networkpolicies",
|
||||
verb: "list",
|
||||
},
|
||||
component: () => import("@/business/network/network-policies"),
|
||||
name: "NetworkPolicies",
|
||||
meta: {
|
||||
|
||||
@@ -3,6 +3,7 @@ import Layout from "@/business/app-layout/horizontal-layout"
|
||||
const Storage = {
|
||||
path: "/storage",
|
||||
sort: 3,
|
||||
parent: true,
|
||||
component: Layout,
|
||||
name: "Storage",
|
||||
meta: {
|
||||
@@ -11,7 +12,12 @@ const Storage = {
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/pv",
|
||||
path: "/persistentvolumes",
|
||||
requirePermission: {
|
||||
apiGroup: "",
|
||||
resource: "persistentvolumes",
|
||||
verb: "list",
|
||||
},
|
||||
component: () => import("@/business/storage/pvs"),
|
||||
name: "Pvs",
|
||||
meta: {
|
||||
@@ -19,7 +25,12 @@ const Storage = {
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/pvcs",
|
||||
path: "/persistentvolumeclaims",
|
||||
requirePermission: {
|
||||
apiGroup: "",
|
||||
resource: "persistentvolumeclaims",
|
||||
verb: "list",
|
||||
},
|
||||
component: () => import("@/business/storage/pvcs"),
|
||||
name: "Pvcs",
|
||||
meta: {
|
||||
@@ -28,6 +39,11 @@ const Storage = {
|
||||
},
|
||||
{
|
||||
path: "/storageclasses",
|
||||
requirePermission: {
|
||||
apiGroup: "storage.k8s.io",
|
||||
resource: "storageclasses",
|
||||
verb: "list",
|
||||
},
|
||||
component: () => import("@/business/storage/storage-classes"),
|
||||
name: "StorageClasses",
|
||||
meta: {
|
||||
|
||||
@@ -2,6 +2,7 @@ import Layout from "@/business/app-layout/horizontal-layout";
|
||||
|
||||
const Workloads = {
|
||||
path: "/workloads",
|
||||
parent: true,
|
||||
sort: 4,
|
||||
component: Layout,
|
||||
name: "Workloads",
|
||||
@@ -12,6 +13,11 @@ const Workloads = {
|
||||
children: [
|
||||
{
|
||||
path: "/deployments",
|
||||
requirePermission: {
|
||||
apiGroup: "apps",
|
||||
resource: "deployments",
|
||||
verb: "list",
|
||||
},
|
||||
component: () => import("@/business/workloads/deployments"),
|
||||
name: "Deployments",
|
||||
meta: {
|
||||
@@ -46,80 +52,13 @@ const Workloads = {
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
path: "/cronjobs",
|
||||
component: () => import("@/business/workloads/cronjobs"),
|
||||
name: "CronJobs",
|
||||
meta: {
|
||||
title: "CronJobs",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/cronjobs/detail/:namespace/:name",
|
||||
name: "CronJobDetail",
|
||||
hidden: true,
|
||||
component: () => import("@/business/workloads/cronjobs/detail"),
|
||||
meta: {
|
||||
activeMenu: "/cronjobs",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/cronjobs/create",
|
||||
name: "CronJobCreate",
|
||||
hidden: true,
|
||||
component: () => import("@/business/workloads/cronjobs/create"),
|
||||
meta: {
|
||||
activeMenu: "/cronjobs",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/cronjobs/edit/:namespace/:name",
|
||||
name: "CronJobEdit",
|
||||
hidden: true,
|
||||
component: () => import("@/business/workloads/cronjobs/edit"),
|
||||
meta: {
|
||||
activeMenu: "/cronjobs",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
path: "/jobs",
|
||||
component: () => import("@/business/workloads/jobs"),
|
||||
name: "Jobs",
|
||||
meta: {
|
||||
title: "Jobs",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/jobs/detail/:namespace/:name",
|
||||
name: "JobDetail",
|
||||
hidden: true,
|
||||
component: () => import("@/business/workloads/jobs/detail"),
|
||||
meta: {
|
||||
activeMenu: "/jobs",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/jobs/create",
|
||||
name: "JobCreate",
|
||||
hidden: true,
|
||||
component: () => import("@/business/workloads/jobs/create"),
|
||||
meta: {
|
||||
activeMenu: "/jobs",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/jobs/edit/:namespace/:name",
|
||||
name: "JobEdit",
|
||||
hidden: true,
|
||||
component: () => import("@/business/workloads/jobs/edit"),
|
||||
meta: {
|
||||
activeMenu: "/jobs",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
path: "/daemonsets",
|
||||
requirePermission: {
|
||||
apiGroup: "apps",
|
||||
resource: "daemonsets",
|
||||
verb: "list",
|
||||
},
|
||||
component: () => import("@/business/workloads/daemonsets"),
|
||||
name: "DaemonSets",
|
||||
meta: {
|
||||
@@ -156,6 +95,11 @@ const Workloads = {
|
||||
|
||||
{
|
||||
path: "/statefulsets",
|
||||
requirePermission: {
|
||||
apiGroup: "apps",
|
||||
resource: "statefulsets",
|
||||
verb: "list",
|
||||
},
|
||||
component: () => import("@/business/workloads/statefulsets"),
|
||||
name: "StatefulSets",
|
||||
meta: {
|
||||
@@ -190,8 +134,96 @@ const Workloads = {
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
path: "/cronjobs",
|
||||
requirePermission: {
|
||||
apiGroup: "batch",
|
||||
resource: "cronjobs",
|
||||
verb: "list",
|
||||
},
|
||||
component: () => import("@/business/workloads/cronjobs"),
|
||||
name: "CronJobs",
|
||||
meta: {
|
||||
title: "CronJobs",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/cronjobs/detail/:namespace/:name",
|
||||
name: "CronJobDetail",
|
||||
hidden: true,
|
||||
component: () => import("@/business/workloads/cronjobs/detail"),
|
||||
meta: {
|
||||
activeMenu: "/cronjobs",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/cronjobs/create",
|
||||
name: "CronJobCreate",
|
||||
hidden: true,
|
||||
component: () => import("@/business/workloads/cronjobs/create"),
|
||||
meta: {
|
||||
activeMenu: "/cronjobs",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/cronjobs/edit/:namespace/:name",
|
||||
name: "CronJobEdit",
|
||||
hidden: true,
|
||||
component: () => import("@/business/workloads/cronjobs/edit"),
|
||||
meta: {
|
||||
activeMenu: "/cronjobs",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
path: "/jobs",
|
||||
requirePermission: {
|
||||
apiGroup: "batch",
|
||||
resource: "jobs",
|
||||
verb: "list",
|
||||
},
|
||||
component: () => import("@/business/workloads/jobs"),
|
||||
name: "Jobs",
|
||||
meta: {
|
||||
title: "Jobs",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/jobs/detail/:namespace/:name",
|
||||
name: "JobDetail",
|
||||
hidden: true,
|
||||
component: () => import("@/business/workloads/jobs/detail"),
|
||||
meta: {
|
||||
activeMenu: "/jobs",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/jobs/create",
|
||||
name: "JobCreate",
|
||||
hidden: true,
|
||||
component: () => import("@/business/workloads/jobs/create"),
|
||||
meta: {
|
||||
activeMenu: "/jobs",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/jobs/edit/:namespace/:name",
|
||||
name: "JobEdit",
|
||||
hidden: true,
|
||||
component: () => import("@/business/workloads/jobs/edit"),
|
||||
meta: {
|
||||
activeMenu: "/jobs",
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
path: "/pods",
|
||||
requirePermission: {
|
||||
apiGroup: "",
|
||||
resource: "pods",
|
||||
verb: "list",
|
||||
},
|
||||
component: () => import("@/business/workloads/pods"),
|
||||
name: "Pods",
|
||||
meta: {
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
const getters = {
|
||||
sidebar: state => state.app.sidebar,
|
||||
name: state => state.user.name,
|
||||
nickName: state => state.user.nickName,
|
||||
// language: state => state.user.language,
|
||||
permission_routes: state => state.permission.routes,
|
||||
menu: state => state.user.menu,
|
||||
roles: state => state.user.roles,
|
||||
cluster: state => state.user.cluster,
|
||||
clusterRoles: state => state.user.clusterRoles,
|
||||
}
|
||||
export default getters
|
||||
|
||||
@@ -5,24 +5,37 @@ const state = {
|
||||
addRoutes: []
|
||||
}
|
||||
|
||||
// function hasPermission(user, route) {
|
||||
// if (user && route){
|
||||
// console.log("")
|
||||
// }
|
||||
// return true
|
||||
// }
|
||||
function hasPermission(clusterRoles, route) {
|
||||
if (route.requirePermission) {
|
||||
for (const clusterRole of clusterRoles) {
|
||||
if (clusterRole.rules.length > 0) {
|
||||
for (const rule of clusterRole.rules) {
|
||||
if (rule.apiGroups.includes("*") || rule.apiGroups.includes(route.requirePermission.apiGroup)) {
|
||||
if (rule.resources.includes("*") || rule.resources.includes(route.requirePermission.resource)) {
|
||||
if (rule.verbs.includes("*") || rule.verbs.includes(route.requirePermission.verb)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
export function filterRolesRoutes (routes, user) {
|
||||
export function filterRolesRoutes(routes, clusterRoles) {
|
||||
const res = []
|
||||
routes.forEach(route => {
|
||||
const tmp = { ...route }
|
||||
// if (hasPermission(user, tmp)) {
|
||||
const tmp = {...route}
|
||||
if (hasPermission(clusterRoles, tmp)) {
|
||||
if (tmp.children) {
|
||||
tmp.children = filterRolesRoutes(tmp.children, user)
|
||||
tmp.children = filterRolesRoutes(tmp.children, clusterRoles)
|
||||
}
|
||||
res.push(tmp)
|
||||
// }
|
||||
}
|
||||
})
|
||||
|
||||
return res
|
||||
@@ -36,12 +49,28 @@ const mutations = {
|
||||
}
|
||||
|
||||
const actions = {
|
||||
generateRoutes ({ commit }, p) {
|
||||
generateRoutes({commit}, p) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const user = p
|
||||
const clusterRoles = p
|
||||
let accessedRoutes
|
||||
try {
|
||||
accessedRoutes = filterRolesRoutes(rolesRoutes, user)
|
||||
accessedRoutes = filterRolesRoutes(rolesRoutes, clusterRoles)
|
||||
for (const route of accessedRoutes) {
|
||||
if (route.parent) {
|
||||
let hidden = true
|
||||
if (route.children.length > 0) {
|
||||
for (const childRoute of route.children) {
|
||||
hidden = hidden && childRoute.hidden
|
||||
}
|
||||
if (hidden) {
|
||||
route.hidden = true
|
||||
}
|
||||
} else {
|
||||
route.hidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commit("SET_ROUTES", accessedRoutes)
|
||||
resolve(accessedRoutes)
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import {login, isLogin, logout, getCurrentUser} from "@/api/auth"
|
||||
import {login, isLogin, logout, getCurrentClusterUser} from "@/api/auth"
|
||||
import {resetRouter} from "@/router"
|
||||
import {getLanguage, setLanguage} from "@/i18n"
|
||||
import store from "../../store"
|
||||
|
||||
const state = {
|
||||
login: false,
|
||||
name: "",
|
||||
currentProject: "",
|
||||
language: getLanguage(),
|
||||
roles: [],
|
||||
cluster: ""
|
||||
cluster: "",
|
||||
clusterRoles: [],
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
@@ -21,6 +22,9 @@ const mutations = {
|
||||
SET_NAME: (state, name) => {
|
||||
state.name = name
|
||||
},
|
||||
SET_NICK_NAME: (state, nickName) => {
|
||||
state.nickName = nickName
|
||||
},
|
||||
SET_LANGUAGE: (state, language) => {
|
||||
state.language = language
|
||||
setLanguage(language)
|
||||
@@ -28,20 +32,23 @@ const mutations = {
|
||||
SET_ROLES: (state, roles) => {
|
||||
state.roles = roles
|
||||
},
|
||||
SET_CURRENT_MENU: (state, menu) => {
|
||||
state.menu = menu
|
||||
},
|
||||
SET_CURRENT_CLUSTER: (state, name) => {
|
||||
state.cluster = name
|
||||
},
|
||||
SET_CLUSTER_ROLES: (state, clusterRoles) => {
|
||||
state.clusterRoles = clusterRoles
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
login ({ commit }, userInfo) {
|
||||
const { username, password } = userInfo
|
||||
setCurrentCluster({commit}, clusterName) {
|
||||
commit("SET_CURRENT_CLUSTER", clusterName)
|
||||
},
|
||||
login({commit}, userInfo) {
|
||||
const {username, password} = userInfo
|
||||
return new Promise((resolve, reject) => {
|
||||
commit("LOGIN")
|
||||
login({ username: username.trim(), password: password }).then(response => {
|
||||
login({username: username.trim(), password: password}).then(response => {
|
||||
commit("LOGIN")
|
||||
resolve(response)
|
||||
}).catch(error => {
|
||||
@@ -50,7 +57,7 @@ const actions = {
|
||||
})
|
||||
},
|
||||
|
||||
isLogin ({ commit }) {
|
||||
isLogin({commit}) {
|
||||
return new Promise((resolve) => {
|
||||
if (state.login) {
|
||||
resolve(true)
|
||||
@@ -67,22 +74,25 @@ const actions = {
|
||||
})
|
||||
})
|
||||
},
|
||||
getCurrentUser ({ commit }) {
|
||||
getCurrentUser({commit}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getCurrentUser().then(data => {
|
||||
const clusterName = store.getters.cluster
|
||||
getCurrentClusterUser(clusterName).then(data => {
|
||||
const user = data.data
|
||||
user["roles"] = ["ADMIN"]
|
||||
const { name, roles } = user
|
||||
const {name, nickName, roles, clusterRoles} = user
|
||||
commit("SET_NAME", name)
|
||||
commit("SET_ROLES", roles)
|
||||
commit("SET_LANGUAGE", "zh-CN")
|
||||
commit("SET_CLUSTER_ROLES", clusterRoles)
|
||||
commit("SET_NICK_NAME", nickName)
|
||||
resolve(user)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
logout ({ commit }) {
|
||||
logout({commit}) {
|
||||
logout().then(() => {
|
||||
commit("LOGOUT")
|
||||
commit("SET_ROLES", [])
|
||||
|
||||
19
web/dashboard/src/utils/permission.js
Normal file
19
web/dashboard/src/utils/permission.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import store from '@/store'
|
||||
|
||||
export const checkPermissions = function (p) {
|
||||
const userClusterRoles = store.getters && store.getters.clusterRoles
|
||||
for (const clusterRole of userClusterRoles) {
|
||||
if (clusterRole.rules.length > 0) {
|
||||
for (const rule of clusterRole.rules) {
|
||||
if (rule.apiGroups.includes("*") || rule.apiGroups.includes(p.apiGroup)) {
|
||||
if (rule.resources.includes("*") || rule.resources.includes(p.resource)) {
|
||||
if (rule.verbs.includes("*") || rule.verbs.includes(p.verb)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -39,8 +39,8 @@ export function updateClusterMember(name, memberName, member) {
|
||||
}
|
||||
|
||||
|
||||
export function getClusterMember(name, memberName, kind) {
|
||||
return get(`${baseUrl}/${name}/members/${memberName}?kind=${kind}`)
|
||||
export function getClusterMember(name, memberName) {
|
||||
return get(`${baseUrl}/${name}/members/${memberName}`)
|
||||
}
|
||||
|
||||
export function deleteClusterMember(name, memberName, kind) {
|
||||
@@ -70,3 +70,15 @@ export function listClusterResourceByGroupVersion(name, groupVersion) {
|
||||
export function deleteClusterRole(name, clusterRoleName) {
|
||||
return del(`${baseUrl}/${name}/clusterroles/${clusterRoleName}`)
|
||||
}
|
||||
|
||||
export function listNamespaces(name) {
|
||||
return get(`${baseUrl}/${name}/namespaces`)
|
||||
}
|
||||
|
||||
export function listProjects(name) {
|
||||
return get(`${baseUrl}/${name}/projects`)
|
||||
}
|
||||
|
||||
export function createProject(name, project) {
|
||||
return post(`${baseUrl}/${name}/projects`, project)
|
||||
}
|
||||
|
||||
@@ -14,14 +14,16 @@
|
||||
{{ row.metadata.name }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.description')" min-width="100" fix>
|
||||
|
||||
<el-table-column :label="$t('business.cluster.scope')" min-width="100" fix>
|
||||
<template v-slot:default="{row}">
|
||||
{{$t("business.cluster_role."+row.metadata.annotations["ekko-i18n"])}}
|
||||
{{ $t('business.cluster.' + row.metadata.labels["kubeoperator.io/role-type"]) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.creat_by')" min-width="100" fix>
|
||||
|
||||
<el-table-column :label="$t('commons.table.built_in')" min-width="100" fix>
|
||||
<template v-slot:default="{row}">
|
||||
{{ row.metadata.annotations["created-by"] }}
|
||||
{{ $t('commons.bool.' + row.metadata.annotations["builtin"]) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.created_time')" min-width="100" fix>
|
||||
@@ -39,10 +41,12 @@
|
||||
center z-index="20">
|
||||
<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-select v-model="ruleForm.apiGroup" style="width:100%" @change="onAPIGroupChange">
|
||||
<el-option v-for="(item,index) in apiGroupsOptions" :key="index"
|
||||
:value="item.preferredVersion.groupVersion">
|
||||
{{item.preferredVersion.groupVersion}}
|
||||
:value="item.name">
|
||||
<span v-if="item.name">
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -52,8 +56,8 @@
|
||||
@change="onResourcesChange">
|
||||
<el-option v-for="(item,index) in apiResourceOptions"
|
||||
:key="index"
|
||||
:value="item.name">
|
||||
{{item.name}}
|
||||
:value="item">
|
||||
{{ item }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -64,7 +68,7 @@
|
||||
<el-option v-for="(item,index) in verbOptions"
|
||||
:key="index"
|
||||
:value="item">
|
||||
{{item}}
|
||||
{{ item }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -83,16 +87,21 @@
|
||||
width="60%"
|
||||
center z-index="10">
|
||||
|
||||
<el-form :model="clusterRoleForm" label-position="left" label- width="60px">
|
||||
<el-form-item label="名称">
|
||||
<el-input v-model="clusterRoleForm.name"></el-input>
|
||||
<el-form :model="clusterRoleForm" label-position="left" label-width="144px">
|
||||
<el-form-item :label="$t('commons.table.name')">
|
||||
<el-input v-model="clusterRoleForm.name" style="width: 80%"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="规则">
|
||||
<el-form-item :label="$t('business.cluster.scope')">
|
||||
<el-radio v-model="clusterRoleForm.type" label="cluster">{{ $t('business.cluster.cluster') }}</el-radio>
|
||||
<el-radio v-model="clusterRoleForm.type" label="namespace">{{ $t('business.cluster.namespace') }}</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="规则" label-width="144px">
|
||||
<el-button @click="onRuleCreate"><i class="el-icon-plus "></i></el-button>
|
||||
<el-table
|
||||
:data="clusterRoleForm.rules"
|
||||
border
|
||||
style="width: 100%">
|
||||
style="width: 80%">
|
||||
<el-table-column
|
||||
prop="apiGroup"
|
||||
label="API Group"
|
||||
@@ -103,7 +112,7 @@
|
||||
label="API Resource"
|
||||
>
|
||||
<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>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -111,7 +120,7 @@
|
||||
label="Verbs"
|
||||
>
|
||||
<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>
|
||||
</el-table-column>
|
||||
<el-table-column>
|
||||
@@ -159,7 +168,7 @@
|
||||
label="API Resource"
|
||||
>
|
||||
<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>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -167,7 +176,7 @@
|
||||
label="Verbs"
|
||||
>
|
||||
<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>
|
||||
</el-table-column>
|
||||
<el-table-column>
|
||||
@@ -193,19 +202,19 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LayoutContent from "@/components/layout/LayoutContent"
|
||||
import ComplexTable from "@/components/complex-table"
|
||||
import {
|
||||
import LayoutContent from "@/components/layout/LayoutContent"
|
||||
import ComplexTable from "@/components/complex-table"
|
||||
import {
|
||||
listClusterRoles,
|
||||
listClusterApiGroups,
|
||||
listClusterResourceByGroupVersion,
|
||||
createClusterRole,
|
||||
deleteClusterRole,
|
||||
updateClusterRole
|
||||
} from "@/api/clusters";
|
||||
} from "@/api/clusters";
|
||||
|
||||
|
||||
export default {
|
||||
export default {
|
||||
name: "ClusterRoles",
|
||||
props: ["name"],
|
||||
components: {LayoutContent, ComplexTable},
|
||||
@@ -221,6 +230,7 @@
|
||||
memberOptions: [],
|
||||
clusterRoleForm: {
|
||||
name: "",
|
||||
type: "cluster",
|
||||
rules: [],
|
||||
},
|
||||
editForm: {
|
||||
@@ -230,7 +240,7 @@
|
||||
resourcesDisable: false,
|
||||
verbsDisable: false,
|
||||
ruleForm: {
|
||||
groupVersion: "",
|
||||
apiGroup: "",
|
||||
resources: [],
|
||||
verbs: []
|
||||
},
|
||||
@@ -240,6 +250,9 @@
|
||||
icon: "el-icon-edit",
|
||||
click: (row) => {
|
||||
this.onEdit(row)
|
||||
},
|
||||
disabled: (row) => {
|
||||
return row.metadata.annotations['builtin'] === 'true'
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -247,6 +260,9 @@
|
||||
icon: "el-icon-delete",
|
||||
click: (row) => {
|
||||
this.onDelete(row)
|
||||
},
|
||||
disabled: (row) => {
|
||||
return row.metadata.annotations['builtin'] === 'true'
|
||||
}
|
||||
},
|
||||
],
|
||||
@@ -266,11 +282,17 @@
|
||||
this.clusterRoleForm = {
|
||||
name: "",
|
||||
rules: [],
|
||||
type: "cluster",
|
||||
}
|
||||
this.createDialogOpened = true
|
||||
listClusterApiGroups(this.name).then(data => {
|
||||
this.apiGroupsOptions.push({preferredVersion: {groupVersion: "*"}})
|
||||
this.apiGroupsOptions.push({name: "*"})
|
||||
this.apiGroupsOptions = this.apiGroupsOptions.concat(data.data)
|
||||
for (const group of this.apiGroupsOptions) {
|
||||
if (group.name === "") {
|
||||
group.name = "core"
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
@@ -280,22 +302,35 @@
|
||||
this.editForm.rules = []
|
||||
for (const rule of row.rules) {
|
||||
if (rule.apiGroups) {
|
||||
this.editForm.rules.push({
|
||||
apiGroup: rule.apiGroups[0],
|
||||
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({preferredVersion: {groupVersion: "*"}})
|
||||
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 = {
|
||||
groupVersion: "",
|
||||
apiGroup: "",
|
||||
resources: [],
|
||||
verbs: [],
|
||||
}
|
||||
@@ -309,14 +344,14 @@
|
||||
this.ruleForm.verbs = []
|
||||
this.resourcesDisable = false
|
||||
this.verbsDisable = false
|
||||
if (this.ruleForm.groupVersion === "*") {
|
||||
this.apiResourceOptions = [{name: "*"}]
|
||||
if (this.ruleForm.apiGroup === "*") {
|
||||
this.apiResourceOptions = ["*"]
|
||||
this.ruleForm.resources = ["*"]
|
||||
this.resourcesDisable = true
|
||||
return
|
||||
}
|
||||
listClusterResourceByGroupVersion(this.name, this.ruleForm.groupVersion).then(data => {
|
||||
this.apiResourceOptions.push({name: "*"})
|
||||
listClusterResourceByGroupVersion(this.name, this.ruleForm.apiGroup).then(data => {
|
||||
this.apiResourceOptions.push("*")
|
||||
this.apiResourceOptions = this.apiResourceOptions.concat(data.data);
|
||||
})
|
||||
},
|
||||
@@ -331,20 +366,18 @@
|
||||
}
|
||||
},
|
||||
onRuleConfirm() {
|
||||
switch (this.operation) {
|
||||
case "create":
|
||||
this.clusterRoleForm.rules.push({
|
||||
apiGroup: this.ruleForm.groupVersion,
|
||||
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({
|
||||
apiGroup: this.ruleForm.groupVersion,
|
||||
resources: this.ruleForm.resources,
|
||||
verbs: this.ruleForm.verbs
|
||||
})
|
||||
this.editForm.rules.push(rule)
|
||||
break
|
||||
}
|
||||
this.ruleDialogOpened = false
|
||||
@@ -376,6 +409,9 @@
|
||||
const req = {
|
||||
metadata: {
|
||||
name: this.clusterRoleForm.name,
|
||||
labels: {
|
||||
"kubeoperator.io/role-type": this.clusterRoleForm.type
|
||||
}
|
||||
},
|
||||
rules: []
|
||||
}
|
||||
@@ -405,7 +441,6 @@
|
||||
verbs: rule.verbs,
|
||||
})
|
||||
}
|
||||
console.log(123)
|
||||
updateClusterRole(this.name, req.metadata.name, req).then(() => {
|
||||
this.list()
|
||||
this.editDialogOpened = false
|
||||
@@ -415,7 +450,7 @@
|
||||
created() {
|
||||
this.list()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<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-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+'/members'">成员</el-menu-item>
|
||||
<el-menu-item :index="'/clusters/detail/'+name+'/clusterroles'">角色</el-menu-item>
|
||||
</el-menu>
|
||||
<br/>
|
||||
<div class="detailClass">
|
||||
@@ -13,9 +13,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LayoutContent from "@/components/layout/LayoutContent"
|
||||
import LayoutContent from "@/components/layout/LayoutContent"
|
||||
|
||||
export default {
|
||||
export default {
|
||||
name: "ClusterEdit",
|
||||
props: ["name"],
|
||||
components: {LayoutContent},
|
||||
@@ -23,19 +23,19 @@
|
||||
return {}
|
||||
},
|
||||
methods: {},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.menuClass {
|
||||
.menuClass {
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
width: calc(100vw - 330px);
|
||||
}
|
||||
}
|
||||
|
||||
.detailClass {
|
||||
.detailClass {
|
||||
margin-top: 60px;
|
||||
height: calc(100% - 140px);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -14,11 +14,6 @@
|
||||
{{ row.name }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.kind')" min-width="100" fix>
|
||||
<template v-slot:default="{row}">
|
||||
{{ row.kind}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.created_time')" min-width="100" fix>
|
||||
<template v-slot:default="{row}">
|
||||
{{ row.createAt }}
|
||||
@@ -29,38 +24,100 @@
|
||||
|
||||
|
||||
<el-dialog
|
||||
title="提示"
|
||||
:visible.sync="createDialogOpened"
|
||||
width="20%"
|
||||
center>
|
||||
<el-form :model="memberForm" label-position="left" label- width="60px">
|
||||
<el-form-item label="主体类型">
|
||||
<el-select v-model="memberForm.subjectKind" @change="onSubjectKindChange">
|
||||
<el-option value="User">
|
||||
用户
|
||||
</el-option>
|
||||
<el-option value="Group">
|
||||
用户组
|
||||
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 v-model="memberForm.subjectName">
|
||||
<el-option v-for="(item, index) in memberOptions" :key="index" :value="item.name">
|
||||
{{item.name}}
|
||||
|
||||
<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-select v-model="memberForm.clusterRoles" multiple placeholder="请选择">
|
||||
<el-radio-group v-model="memberForm.roleType">
|
||||
<el-radio label="admin">管理者</el-radio>
|
||||
<el-radio label="viewer">只读者</el-radio>
|
||||
<el-radio label="custom">自定义</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<div v-if="memberForm.roleType==='custom'">
|
||||
<el-form-item>
|
||||
<el-select v-model="memberForm.customClusterRoles" multiple style="width: 80%"
|
||||
placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item,index) in clusterRolesOptions"
|
||||
:key="index"
|
||||
:value="item.metadata.name">
|
||||
{{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}">
|
||||
<span v-for="v in row.roles" :key="v">{{ v }}<br/></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="256x">
|
||||
<template v-slot:default="{row}">
|
||||
<el-button
|
||||
size="mini" circle @click="onNamespaceRoleDelete(row)">
|
||||
<i class="el-icon-delete"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="createDialogOpened = false">取 消</el-button>
|
||||
@@ -68,61 +125,57 @@
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
|
||||
<el-dialog
|
||||
title="提示"
|
||||
title="编辑成员"
|
||||
:visible.sync="editDialogOpened"
|
||||
width="20%"
|
||||
width="60%"
|
||||
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-form :model="editForm" label-position="left" label-width="144px">
|
||||
<el-form-item label="用户名称">
|
||||
<el-select v-model="editForm.userName" style="width: 80%" disabled="disabled">
|
||||
<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-select v-model="editForm.clusterRoles" multiple placeholder="请选择">
|
||||
<el-radio-group v-model="editForm.roleType">
|
||||
<el-radio label="admin">管理者</el-radio>
|
||||
<el-radio label="viewer">只读者</el-radio>
|
||||
<el-radio label="custom">自定义</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<div v-if="editForm.roleType==='custom'">
|
||||
<el-form-item>
|
||||
<el-select v-model="editForm.customClusterRoles" multiple style="width: 80%"
|
||||
placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item,index) in clusterRolesOptions"
|
||||
:key="index"
|
||||
:value="item.metadata.name">
|
||||
{{item.metadata.name}}
|
||||
{{ item.metadata.name }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
import LayoutContent from "@/components/layout/LayoutContent"
|
||||
import ComplexTable from "@/components/complex-table"
|
||||
import {createClusterMember, listClusterMembers} from "@/api/clusters"
|
||||
import {listUsers} from "@/api/users"
|
||||
import {listGroups} from "@/api/groups"
|
||||
import {listClusterRoles, deleteClusterMember, getClusterMember, updateClusterMember} from "@/api/clusters";
|
||||
import LayoutContent from "@/components/layout/LayoutContent"
|
||||
import ComplexTable from "@/components/complex-table"
|
||||
import {createClusterMember, listClusterMembers, listNamespaces} from "@/api/clusters"
|
||||
import {listUsers} from "@/api/users"
|
||||
import {listClusterRoles, deleteClusterMember, getClusterMember, updateClusterMember} from "@/api/clusters";
|
||||
|
||||
|
||||
export default {
|
||||
export default {
|
||||
name: "ClusterMembers",
|
||||
props: ["name"],
|
||||
components: {LayoutContent, ComplexTable},
|
||||
@@ -130,17 +183,25 @@
|
||||
return {
|
||||
createDialogOpened: false,
|
||||
editDialogOpened: false,
|
||||
memberOptions: [],
|
||||
namespaceRoleDialogOpened: false,
|
||||
userOptions: [],
|
||||
clusterRolesOptions: [],
|
||||
namespaceRoleOptions: [],
|
||||
namespaceOptions: [],
|
||||
namespaceRoleForm: {
|
||||
namespace: "",
|
||||
roles: [],
|
||||
},
|
||||
memberForm: {
|
||||
subjectKind: "",
|
||||
subjectName: "",
|
||||
clusterRoles: []
|
||||
userName: "",
|
||||
customClusterRoles: [],
|
||||
namespaceRoles: [],
|
||||
roleType: "admin"
|
||||
},
|
||||
editForm: {
|
||||
subjectKind: "",
|
||||
subjectName: "",
|
||||
clusterRoles: []
|
||||
userName: "",
|
||||
customClusterRoles: [],
|
||||
roleType: ""
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
@@ -161,6 +222,19 @@
|
||||
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
|
||||
@@ -170,23 +244,42 @@
|
||||
})
|
||||
},
|
||||
onCreate() {
|
||||
this.memberForm.subjectKind = ""
|
||||
this.memberForm.clusterRoles = []
|
||||
this.createDialogOpened = true
|
||||
listClusterRoles(this.name).then(data => {
|
||||
console.log(data)
|
||||
this.clusterRolesOptions = data.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;
|
||||
})
|
||||
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
|
||||
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
|
||||
@@ -203,45 +296,68 @@
|
||||
})
|
||||
});
|
||||
},
|
||||
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;
|
||||
})
|
||||
}
|
||||
onNamespaceRoleCreate() {
|
||||
this.namespaceRoleForm.namespaceRoles = []
|
||||
this.namespaceRoleDialogOpened = true
|
||||
this.namespaceRoleForm.namespace = ""
|
||||
},
|
||||
onEditConfirm() {
|
||||
updateClusterMember(this.name, this.editForm.subjectName, {
|
||||
name: this.editForm.subjectName,
|
||||
kind: this.editForm.subjectKind,
|
||||
clusterRoles: this.editForm.clusterRoles
|
||||
}).then(() => {
|
||||
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() {
|
||||
createClusterMember(this.name, {
|
||||
kind: this.memberForm.subjectKind,
|
||||
name: this.memberForm.subjectName,
|
||||
clusterRoles: this.memberForm.clusterRoles
|
||||
}).then(() => {
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<template>
|
||||
<span>2</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ClusterOverview"
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -86,13 +86,13 @@ const message = {
|
||||
},
|
||||
cluster: {
|
||||
cluster: "集群",
|
||||
namespace: "命名空间",
|
||||
scope: "作用域",
|
||||
version: "版本",
|
||||
list: "集群列表",
|
||||
import: "导入集群",
|
||||
edit: "编辑",
|
||||
nodes: "节点",
|
||||
api_server_help: "例如: https://172.16.10.100:8443",
|
||||
router_help: "装有 kube-proxy 的任意节点的且可以被访问到的 IP 地址",
|
||||
label: "标签",
|
||||
description: "描述",
|
||||
cluster_detail: "集群详情",
|
||||
@@ -106,7 +106,7 @@ const message = {
|
||||
expect: "敬请期待",
|
||||
management: "管理",
|
||||
open_dashboard: "控制台",
|
||||
cluster_version: "版本"
|
||||
cluster_version: "版本",
|
||||
|
||||
},
|
||||
namespace: {
|
||||
|
||||
@@ -61,9 +61,8 @@ const Clusters = {
|
||||
meta: {
|
||||
activeMenu: "/clusters",
|
||||
},
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ const getters = {
|
||||
nickName: state => state.user.nickName,
|
||||
// language: state => state.user.language,
|
||||
permission_routes: state => state.permission.routes,
|
||||
menu: state => state.user.menu,
|
||||
roles: state => state.user.roles,
|
||||
permissions: state => state.user.permissions
|
||||
}
|
||||
export default getters
|
||||
|
||||
@@ -5,9 +5,7 @@ import {getLanguage, setLanguage} from "@/i18n"
|
||||
const state = {
|
||||
login: false,
|
||||
name: "",
|
||||
currentProject: "",
|
||||
language: getLanguage(),
|
||||
roles: [],
|
||||
permissions: {}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user