mirror of
https://github.com/1Panel-dev/KubePi.git
synced 2025-10-28 09:31:37 +08:00
feat(cluster):导入集群操作
This commit is contained in:
1
go.sum
1
go.sum
@@ -237,6 +237,7 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=
|
||||
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
kContext "context"
|
||||
"fmt"
|
||||
v1 "github.com/KubeOperator/ekko/internal/model/v1"
|
||||
"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/common"
|
||||
pkgV1 "github.com/KubeOperator/ekko/pkg/api/v1"
|
||||
"github.com/KubeOperator/ekko/pkg/util/kubernetes"
|
||||
"github.com/KubeOperator/ekko/pkg/kubernetes"
|
||||
"github.com/asdine/storm/v3"
|
||||
"github.com/google/uuid"
|
||||
"github.com/kataras/iris/v12"
|
||||
"github.com/kataras/iris/v12/context"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
@@ -25,62 +23,64 @@ func NewHandler() *Handler {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Create() iris.Handler {
|
||||
func (h *Handler) CreateCluster() iris.Handler {
|
||||
return func(ctx *context.Context) {
|
||||
var clusterImport Import
|
||||
if err := ctx.ReadJSON(&clusterImport); err != nil {
|
||||
var req Cluster
|
||||
if err := ctx.ReadJSON(&req); err != nil {
|
||||
ctx.StatusCode(iris.StatusBadRequest)
|
||||
ctx.Values().Set("message", err.Error())
|
||||
return
|
||||
}
|
||||
c, err := h.clusterService.Get(clusterImport.Name)
|
||||
if err != nil && err != storm.ErrNotFound {
|
||||
ctx.StatusCode(iris.StatusBadRequest)
|
||||
ctx.Values().Set("message", fmt.Sprintf("error: %s", err.Error()))
|
||||
if req.CaDataStr != "" {
|
||||
req.CaCertificate.CertData = []byte(req.CaDataStr)
|
||||
}
|
||||
if req.Spec.Authentication.Mode == "certificate" {
|
||||
req.Spec.Authentication.Certificate.CertData = []byte(req.CertDataStr)
|
||||
req.Spec.Authentication.Certificate.KeyData = []byte(req.KeyDataStr)
|
||||
}
|
||||
|
||||
client := kubernetes.NewKubernetes(req.Cluster)
|
||||
if err := client.Ping(); err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.Values().Set("message", err.Error())
|
||||
return
|
||||
}
|
||||
if c != nil && c.Name != "" {
|
||||
ctx.StatusCode(iris.StatusBadRequest)
|
||||
ctx.Values().Set("message", fmt.Sprintf("cluster name %s is alerady exist", clusterImport.Name))
|
||||
v, _ := client.Version()
|
||||
req.Status.Version = v.GitVersion
|
||||
u := ctx.Values().Get("profile")
|
||||
profile := u.(session.UserProfile)
|
||||
req.CreatedBy = profile.Name
|
||||
if err := h.clusterService.Create(&req.Cluster, common.DBOptions{}); err != nil {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.Values().Set("message", err.Error())
|
||||
return
|
||||
}
|
||||
client, err := kubernetes.NewKubernetesClient(&kubernetes.Config{
|
||||
Host: clusterImport.ApiServer,
|
||||
Token: clusterImport.Token,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusBadRequest)
|
||||
ctx.Values().Set("message", fmt.Sprintf("import cluster filed: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
_, err = client.CoreV1().Namespaces().List(kContext.TODO(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusBadRequest)
|
||||
ctx.Values().Set("message", fmt.Sprintf("import cluster failed: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
cs := v1Cluster.Cluster{
|
||||
Metadata: v1.Metadata{
|
||||
Name: clusterImport.Name,
|
||||
UUID: uuid.New().String(),
|
||||
},
|
||||
Token: clusterImport.Token,
|
||||
Router: clusterImport.Router,
|
||||
ApiServer: clusterImport.ApiServer,
|
||||
Status: "Running",
|
||||
}
|
||||
err = h.clusterService.Create(&cs)
|
||||
if err != nil {
|
||||
ctx.StatusCode(iris.StatusBadRequest)
|
||||
ctx.Values().Set("message", fmt.Sprintf("create cluster failed: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
ctx.StatusCode(iris.StatusOK)
|
||||
ctx.Values().Set("data", cs)
|
||||
ctx.Values().Set("data", &req)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) ListAll() iris.Handler {
|
||||
func (h *Handler) SearchClusters() iris.Handler {
|
||||
return func(ctx *context.Context) {
|
||||
pageNum, _ := ctx.Values().GetInt(pkgV1.PageNum)
|
||||
pageSize, _ := ctx.Values().GetInt(pkgV1.PageSize)
|
||||
var conditions pkgV1.Conditions
|
||||
if ctx.GetContentLength() > 0 {
|
||||
if err := ctx.ReadJSON(&conditions); err != nil {
|
||||
ctx.StatusCode(iris.StatusBadRequest)
|
||||
ctx.Values().Set("message", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
clusters, total, err := h.clusterService.Search(pageNum, pageSize, conditions, common.DBOptions{})
|
||||
if err != nil && err != storm.ErrNotFound {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.Values().Set("message", err.Error())
|
||||
}
|
||||
ctx.Values().Set("data", pkgV1.Page{Items: clusters, Total: total})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) ListClusters() iris.Handler {
|
||||
return func(ctx *context.Context) {
|
||||
var clusters []v1Cluster.Cluster
|
||||
clusters, err := h.clusterService.All()
|
||||
@@ -94,20 +94,7 @@ func (h *Handler) ListAll() iris.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Search() iris.Handler {
|
||||
return func(ctx *context.Context) {
|
||||
pageNum, _ := ctx.Values().GetInt(pkgV1.PageNum)
|
||||
pageSize, _ := ctx.Values().GetInt(pkgV1.PageSize)
|
||||
clusters, total, err := h.clusterService.Search(pageNum, pageSize)
|
||||
if err != nil && err != storm.ErrNotFound {
|
||||
ctx.StatusCode(iris.StatusInternalServerError)
|
||||
ctx.Values().Set("message", err.Error())
|
||||
}
|
||||
ctx.Values().Set("data", pkgV1.Page{Items: clusters, Total: total})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Delete() iris.Handler {
|
||||
func (h *Handler) DeleteCluster() iris.Handler {
|
||||
return func(ctx *context.Context) {
|
||||
name := ctx.Params().GetString("name")
|
||||
err := h.clusterService.Delete(name)
|
||||
@@ -123,8 +110,8 @@ func (h *Handler) Delete() iris.Handler {
|
||||
func Install(parent iris.Party) {
|
||||
handler := NewHandler()
|
||||
sp := parent.Party("/clusters")
|
||||
sp.Post("", handler.Create())
|
||||
sp.Get("", handler.ListAll())
|
||||
sp.Delete("/{name:string}", handler.Delete())
|
||||
sp.Post("/search", handler.Search())
|
||||
sp.Post("", handler.CreateCluster())
|
||||
sp.Get("", handler.ListClusters())
|
||||
sp.Delete("/:name", handler.DeleteCluster())
|
||||
sp.Post("/search", handler.SearchClusters())
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package cluster
|
||||
|
||||
type Import struct {
|
||||
Name string `json:"name"`
|
||||
ApiServer string `json:"apiServer"`
|
||||
Router string `json:"router"`
|
||||
Token string `json:"token"`
|
||||
import v1Cluster "github.com/KubeOperator/ekko/internal/model/v1/cluster"
|
||||
|
||||
type Cluster struct {
|
||||
v1Cluster.Cluster
|
||||
KeyDataStr string `json:"keyDataStr"`
|
||||
CertDataStr string `json:"certDataStr"`
|
||||
CaDataStr string `json:"caDataStr"`
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ func AddV1Route(app iris.Party) {
|
||||
authParty.Use(resourceNameInvalidHandler())
|
||||
authParty.Get("/", apiResourceHandler(authParty))
|
||||
user.Install(authParty)
|
||||
cluster.Install(v1Party)
|
||||
cluster.Install(authParty)
|
||||
group.Install(authParty)
|
||||
role.Install(authParty)
|
||||
}
|
||||
|
||||
@@ -3,10 +3,45 @@ package cluster
|
||||
import v1 "github.com/KubeOperator/ekko/internal/model/v1"
|
||||
|
||||
type Cluster struct {
|
||||
v1.BaseModel `storm:"inline"`
|
||||
v1.Metadata `storm:"inline"`
|
||||
ApiServer string `json:"apiServer"`
|
||||
Router string `json:"router"`
|
||||
Token string `json:"token"`
|
||||
Status string `json:"status"`
|
||||
v1.BaseModel `storm:"inline"`
|
||||
v1.Metadata `storm:"inline"`
|
||||
CaCertificate Certificate `json:"caCertificate" storm:"inline"`
|
||||
Spec Spec `json:"spec" storm:"inline"`
|
||||
Status Status `json:"status" storm:"inline"`
|
||||
}
|
||||
|
||||
type Spec struct {
|
||||
Connect Connect `json:"connect" storm:"inline"`
|
||||
Authentication Authentication `json:"authentication" storm:"inline"`
|
||||
}
|
||||
|
||||
type Connect struct {
|
||||
Direction string `json:"direction"`
|
||||
Forward Forward `json:"forward" storm:"inline"`
|
||||
}
|
||||
|
||||
type Forward struct {
|
||||
ApiServer string `json:"apiServer"`
|
||||
Proxy Proxy `json:"proxy" storm:"inline"`
|
||||
}
|
||||
|
||||
type Proxy struct {
|
||||
URL string `json:"url"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type Authentication struct {
|
||||
Mode string `json:"mode"`
|
||||
BearerToken string `json:"bearerToken"`
|
||||
Certificate Certificate `json:"certificate" storm:"inline"`
|
||||
}
|
||||
|
||||
type Certificate struct {
|
||||
KeyData []byte `json:"keyData"`
|
||||
CertData []byte `json:"certData"`
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
@@ -1,41 +1,35 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/kataras/iris/v12"
|
||||
"github.com/kataras/iris/v12/context"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func KubernetesClientProxy(ctx *context.Context) {
|
||||
clusterName := ctx.Params().Get("cluster_name")
|
||||
proxyPath := ctx.Params().Get("p")
|
||||
cluster, err := clusterService.Get(clusterName)
|
||||
if err != nil {
|
||||
_, _ = ctx.JSON(iris.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
u, err := url.Parse(cluster.ApiServer)
|
||||
if err != nil {
|
||||
_, _ = ctx.JSON(iris.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
proxy := httputil.NewSingleHostReverseProxy(u)
|
||||
proxy.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
token := fmt.Sprintf("%s %s", keyPrefix, cluster.Token)
|
||||
ctx.Request().Header.Add(AuthorizationHeader, token)
|
||||
ctx.Request().URL.Path = proxyPath
|
||||
proxy.ModifyResponse = func(response *http.Response) error {
|
||||
if response.StatusCode == http.StatusUnauthorized {
|
||||
response.StatusCode = http.StatusInternalServerError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
proxy.ServeHTTP(ctx.ResponseWriter(), ctx.Request())
|
||||
//clusterName := ctx.Params().Get("cluster_name")
|
||||
//proxyPath := ctx.Params().Get("p")
|
||||
//cluster, err := clusterService.Get(clusterName)
|
||||
//if err != nil {
|
||||
// _, _ = ctx.JSON(iris.StatusInternalServerError)
|
||||
// return
|
||||
//}
|
||||
//u, err := url.Parse(cluster.ApiServer)
|
||||
//if err != nil {
|
||||
// _, _ = ctx.JSON(iris.StatusInternalServerError)
|
||||
// return
|
||||
//}
|
||||
//proxy := httputil.NewSingleHostReverseProxy(u)
|
||||
//proxy.Transport = &http.Transport{
|
||||
// TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
//}
|
||||
//token := fmt.Sprintf("%s %s", keyPrefix, cluster.Token)
|
||||
//ctx.Request().Header.Add(AuthorizationHeader, token)
|
||||
//ctx.Request().URL.Path = proxyPath
|
||||
//proxy.ModifyResponse = func(response *http.Response) error {
|
||||
// if response.StatusCode == http.StatusUnauthorized {
|
||||
// response.StatusCode = http.StatusInternalServerError
|
||||
// }
|
||||
// return nil
|
||||
//}
|
||||
//
|
||||
//proxy.ServeHTTP(ctx.ResponseWriter(), ctx.Request())
|
||||
}
|
||||
|
||||
@@ -3,14 +3,19 @@ package cluster
|
||||
import (
|
||||
v1Cluster "github.com/KubeOperator/ekko/internal/model/v1/cluster"
|
||||
"github.com/KubeOperator/ekko/internal/server"
|
||||
"github.com/KubeOperator/ekko/internal/service/v1/common"
|
||||
pkgV1 "github.com/KubeOperator/ekko/pkg/api/v1"
|
||||
"github.com/google/uuid"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Cluster interface {
|
||||
Create(cluster *v1Cluster.Cluster) error
|
||||
common.DBService
|
||||
Create(cluster *v1Cluster.Cluster, options common.DBOptions) error
|
||||
Get(name string) (*v1Cluster.Cluster, error)
|
||||
All() ([]v1Cluster.Cluster, error)
|
||||
Delete(name string) error
|
||||
Search(num, size int) ([]v1Cluster.Cluster, int, error)
|
||||
Search(num, size int, conditions pkgV1.Conditions, options common.DBOptions) ([]v1Cluster.Cluster, int, error)
|
||||
}
|
||||
|
||||
func NewClusterService() Cluster {
|
||||
@@ -18,10 +23,14 @@ func NewClusterService() Cluster {
|
||||
}
|
||||
|
||||
type cluster struct {
|
||||
common.DefaultDBService
|
||||
}
|
||||
|
||||
func (c *cluster) Create(cluster *v1Cluster.Cluster) error {
|
||||
db := server.DB()
|
||||
func (c *cluster) Create(cluster *v1Cluster.Cluster, options common.DBOptions) error {
|
||||
db := c.GetDB(options)
|
||||
cluster.UUID = uuid.New().String()
|
||||
cluster.CreateAt = time.Now()
|
||||
cluster.UpdateAt = time.Now()
|
||||
return db.Save(cluster)
|
||||
}
|
||||
|
||||
@@ -43,8 +52,8 @@ func (c *cluster) All() ([]v1Cluster.Cluster, error) {
|
||||
return clusters, nil
|
||||
}
|
||||
|
||||
func (c *cluster) Search(num, size int) ([]v1Cluster.Cluster, int, error) {
|
||||
db := server.DB()
|
||||
func (c *cluster) Search(num, size int, conditions pkgV1.Conditions, options common.DBOptions) ([]v1Cluster.Cluster, int, error) {
|
||||
db := c.GetDB(options)
|
||||
query := db.Select().Limit(size).Skip((num - 1) * size)
|
||||
count, err := query.Count(&v1Cluster.Cluster{})
|
||||
if err != nil {
|
||||
|
||||
70
pkg/kubernetes/kubernetes.go
Normal file
70
pkg/kubernetes/kubernetes.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
v1Cluster "github.com/KubeOperator/ekko/internal/model/v1/cluster"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
Ping() error
|
||||
Version() (*version.Info, error)
|
||||
Client() (*kubernetes.Clientset, error)
|
||||
}
|
||||
|
||||
type Kubernetes struct {
|
||||
*v1Cluster.Cluster
|
||||
}
|
||||
|
||||
func NewKubernetes(cluster v1Cluster.Cluster) Interface {
|
||||
return &Kubernetes{Cluster: &cluster}
|
||||
}
|
||||
|
||||
func (k *Kubernetes) Client() (*kubernetes.Clientset, error) {
|
||||
if k.Spec.Connect.Direction == "forward" {
|
||||
kubeConf := &rest.Config{
|
||||
Host: k.Spec.Connect.Forward.ApiServer,
|
||||
}
|
||||
if len(k.CaCertificate.CertData) > 0 {
|
||||
kubeConf.CAData = k.CaCertificate.CertData
|
||||
} else {
|
||||
kubeConf.Insecure = true
|
||||
}
|
||||
switch k.Spec.Authentication.Mode {
|
||||
case strings.ToLower("bearer"):
|
||||
kubeConf.BearerToken = k.Spec.Authentication.BearerToken
|
||||
case strings.ToLower("certificate"):
|
||||
kubeConf.TLSClientConfig.CertData = k.Spec.Authentication.Certificate.CertData
|
||||
kubeConf.TLSClientConfig.KeyData=k.Spec.Authentication.Certificate.KeyData
|
||||
}
|
||||
return kubernetes.NewForConfig(kubeConf)
|
||||
|
||||
}
|
||||
return nil, errors.New("")
|
||||
}
|
||||
|
||||
func (k *Kubernetes) Version() (*version.Info, error) {
|
||||
client, err := k.Client()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.ServerVersion()
|
||||
|
||||
}
|
||||
|
||||
func (k *Kubernetes) Ping() error {
|
||||
client, err := k.Client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
41
pkg/kubernetes/kubernetes_test.go
Normal file
41
pkg/kubernetes/kubernetes_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var cert = "-----BEGIN CERTIFICATE-----\nMIIDADCCAeigAwIBAgIBAjANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p\na3ViZUNBMB4XDTIxMDMwODA4MjIyNloXDTIyMDMwOTA4MjIyNlowMTEXMBUGA1UE\nChMOc3lzdGVtOm1hc3RlcnMxFjAUBgNVBAMTDW1pbmlrdWJlLXVzZXIwggEiMA0G\nCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCmp2mjHtThDERXsKjGbTBEr/Do7xGy\nN7nxzyruZykzWERUAVSX6QZgg73ROUdsXFR9Xtg4d7UwoV2xrE0iXOPSnvKjnMtU\n5/buyqGNPXQmrUwfxVmBgABxZEIGRnWU8UXMGKPA3WbkG4P2CKEPz390fNxaD+iC\ntGvjZ9Vvtkt7Tzh/FHUt98+rLZHnD+66EX5I1ketm/QcsNMH3jIAoSIcxoFBuySS\nKWzuLMGb2cuyLJE1rxAoUI79wLnlR7AptJMjCRthZiOnpoGYfqYcIFiJ+bj69OHg\nMwvQRJW2wA9YmNjqXhMQ9u5DP6ynQaSMwc2WduHN12jCdLBiMKS//4PtAgMBAAGj\nPzA9MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH\nAwIwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAGS6Is5lsXcRwrLEx\nNJszecVEGwWJpqf9I0ZkCGkBNyZe5vCWERAPyhWHXDCLHv2n5sUfPyaBMXoWM6LE\nfkOEj7877HgEREjf35Zqd8cYCD//17/g8VQVqEaCPOmWwJgQpTj2v+v3srQoPbFW\nMVrzZSiYvACwzkvKrUby1kltyKMD9jNA0tL3sj1Dqi04wDPS7YQZaV3zGUZCmCe5\nOu8rD2J53DLBs5rz4op/XQZz3h4xuyYEs681T9itS6ZLRgZRpmuPoS6vIxk9TqLT\nhAjA7pmPzOJaBkzQchIMmiC5MlpCrBozIGDD0arpoC02Qscvde9ZyTa9GOQioQ10\nMbRkCA==\n-----END CERTIFICATE-----"
|
||||
var key = "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEApqdpox7U4QxEV7Coxm0wRK/w6O8Rsje58c8q7mcpM1hEVAFU\nl+kGYIO90TlHbFxUfV7YOHe1MKFdsaxNIlzj0p7yo5zLVOf27sqhjT10Jq1MH8VZ\ngYAAcWRCBkZ1lPFFzBijwN1m5BuD9gihD89/dHzcWg/ogrRr42fVb7ZLe084fxR1\nLffPqy2R5w/uuhF+SNZHrZv0HLDTB94yAKEiHMaBQbskkils7izBm9nLsiyRNa8Q\nKFCO/cC55UewKbSTIwkbYWYjp6aBmH6mHCBYifm4+vTh4DML0ESVtsAPWJjY6l4T\nEPbuQz+sp0GkjMHNlnbhzddownSwYjCkv/+D7QIDAQABAoIBAA36ctDc0CxENCNK\nzQ0/sVHBlCpliw1wwSb4Ini2rG0DFVAagHbxc7h6tFwtOsFrCScu4mHyIH+AuXQi\nqKGeOvm6nU195Ewt3LdwxZYsNmbcGEt96SEElITuTN9r34brqdgRpQKTT4MIj8v+\nM0w5Mk6Z/n2LYgw8h+QeHHfvSWuBOdlNCMqx2XI3gGc4wm3vub3t6pGz+05QPbCD\nd/drVyMVt3yIA2HHZawKZ5WxEk2aEhAg9wqNuVcztxEmnnCTtaxtkg9S6FxeQtuK\nq2WvznCUgBCvYQpzNAOolBhjz2zlyKu7/I4F6+ZwpVPwhfvgayYYLWtkFaNPgLXe\nm9V4w90CgYEAwWcqJgI2QA4LT7XbCEXBkO0mvvWX/pO0kqvHaqVSaSNe+y/fseI0\nq3+A97sZ2MDXnpMl+f0z7q8HZecEHp1NhetbCgHTpgBD7tFaJJCEa1PnFzSGCyFL\n1r5NTHLGB01W16bH0+WMmcZM1mtsx/bctLx/K/BNKmTEpD9Gn0FP97sCgYEA3Jfl\nvbI5DiTMEtewm9C/hqOPeFf18N1Azba1YHC3P4tRVFIOHwJZp1UNsq5gSu4lyYDo\n99Odub0YIkMEZBYPHTtBKZhX90i5XvQm7jmiiKWQ8tirBWrsrljdoxopE5tudCFT\noxBJ94QMakFVcoZqJhmpTscRw3JzpN8XlOH+VHcCgYBu1eaLvbzFXMcSuU97IC7c\nFWydBzZCCPf1DkjMT045PrISFc+Gq/IvTnTkg+8+DtYC5KVg7MC0Ss5ckdYEjXV+\nB/E2fPGEMqa72HJmfgPFVmIbJFilTEGgIZM++o+OY74e/E+MmgLHpaMnRo0i09CM\nK3JeBerTHsiqsDCS1+UyPQKBgQC1t39u0/kCOLfPsdRvlvefTu9qAHO+NlUi4Sbq\nyg96jiayImI1kzcNjBgboF/8ec+w/btsI+vjTO0rlC9yz2Ul/GEChde5AjSKDvBf\nACVvEYylMG05qkpMmTIDIRLDbx//FFEUm9+CwUmE4kska6vXtP3uwjhU29x97bU8\nVSqwowKBgAPjfYKh2xawWw+tPOJ1INalYmjBY/eB0ZE/YT4hD/DAO8Y1+tpM9HX5\nJNtYwOrG/cWMahFAFW82SNZ0d9xairhZ+cOGZ4BQVUviMiY547GQAVwmj+nwqpDT\ngq+oQc1yMo69JpMcpQIKNdXB7HPFLHFdSVgY/cfEbuZhhSW2tHeZ\n-----END RSA PRIVATE KEY-----"
|
||||
var ca = "-----BEGIN CERTIFICATE-----\nMIIC5zCCAc+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p\na3ViZUNBMB4XDTIxMDIxODA2MjYzMFoXDTMxMDIxNzA2MjYzMFowFTETMBEGA1UE\nAxMKbWluaWt1YmVDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMXs\neb3dt6ENhYL61IeVV0vYSKobqjL+TvAPEnXPySv6BjT8ejxz8mM6qP/SVrTJnC0H\nGOtiQgRDCrRlsfzce2DsMlMR2XzgQlao0bF5h0pJdkSdvWnJT0rlnca9Fjt6jVB6\nV+WiceFM5U/LiO8fmdp9R1frefxWfYTaT/80XUo0ZVEgZUBu1QFgAfjexal0CZEi\nBFzRMddW6YEUSrHd46X79pXBz3jXZ228q8XRfkAkyuMZaCAa+poIhVbaItHRgJbd\nUtXi24G/Imt7KddDY8Q3MrlgPeFPcBRy5re4trVCJAK+U9+2Oagbe0eEH7wjB2d7\n9cBMKrUKpRpCP/lMsj8CAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgKkMB0GA1UdJQQW\nMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3\nDQEBCwUAA4IBAQCH6yn1WbP9zMks6vlxxiqvF3Vg6xMBB+5iZ3mDrBkEYntGpgrC\nBnGnevMWDXPEZsJRUb8rEJChPpk/zB49Kc+FSWJJXTPMvtCqRr4HJoaxxyYlfjfV\nXQsScaDR2LJy9P57VbUeAOaUqN1BiJfK895C8RN1RPgR3tW92eFz5OM/oOF58Km0\nQ6enpBEeNkMpSS0gpd31fs5aL1xqn2zxIVS+cQDJK/4ct18+XSPasCXKNZmyuldJ\n/dZk2DjdR3rqwqp5QbRYBvlKlF+xdvxgM3UwWPfu/bLjo/jdv2L1ThaIPCLuUqQg\n0iBw+d0wj1Zdb6ipJ7QffjCoHtMXMI5VLDwI\n-----END CERTIFICATE-----"
|
||||
|
||||
func TestNewKubernetes(t *testing.T) {
|
||||
//cs, err := clientcmd.BuildConfigFromFlags("", "/Users/shenchenyang/.kube/config")
|
||||
//if err != nil {
|
||||
// t.Error(err)
|
||||
//}
|
||||
|
||||
cs := rest.Config{
|
||||
Host: "https://127.0.0.1:51404",
|
||||
TLSClientConfig: rest.TLSClientConfig{
|
||||
CertData: []byte(cert),
|
||||
KeyData: []byte(key),
|
||||
Insecure: true,
|
||||
},
|
||||
}
|
||||
|
||||
cls, err := kubernetes.NewForConfig(&cs)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
list, err := cls.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
fmt.Println(len(list.Items))
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewKubernetesClient(t *testing.T) {
|
||||
c, err := NewKubernetesClient(&Config{
|
||||
Host: "",
|
||||
Token: ""
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
l, err := c.CoreV1().ServiceAccounts("default").List(context.TODO(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println(l)
|
||||
|
||||
|
||||
}
|
||||
@@ -1,19 +1,20 @@
|
||||
import {post, get, del} from "@/plugins/request"
|
||||
|
||||
const authUrl = "/api/v1/clusters"
|
||||
const baseUrl = "/api/v1/clusters"
|
||||
|
||||
export function create(data) {
|
||||
return post(authUrl, data)
|
||||
export function createCluster(data) {
|
||||
return post(baseUrl, data)
|
||||
}
|
||||
|
||||
export function listAll(){
|
||||
return get(authUrl)
|
||||
export function listClusters() {
|
||||
return get(baseUrl)
|
||||
}
|
||||
|
||||
export function deleteBy(name){
|
||||
return del(`${authUrl}/${name}`)
|
||||
export function deleteBy(name) {
|
||||
return del(`${baseUrl}/${name}`)
|
||||
}
|
||||
|
||||
export function searchCluster(page,size) {
|
||||
return post(`${authUrl}/search?pageNum=${page}&pageSize=${size}`)
|
||||
export function searchClusters(page, size, conditions) {
|
||||
return post(`${baseUrl}/search?pageNum=${page}&pageSize=${size}`, conditions)
|
||||
}
|
||||
|
||||
|
||||
BIN
web/ekko/src/assets/kubernetes.png
Normal file
BIN
web/ekko/src/assets/kubernetes.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 163 KiB |
@@ -1,74 +0,0 @@
|
||||
<template>
|
||||
<layout-content :header="$t('business.cluster.import')" :back-to="{ name: 'ClusterList' }">
|
||||
<el-row>
|
||||
<el-col :span="4"><br/></el-col>
|
||||
<el-col :span="10">
|
||||
<div class="grid-content bg-purple-light">
|
||||
<el-form :model="form" label-position="left" :rules="rules" label-width="120px">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input v-model="form.name" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="apiServer" prop="apiServer">
|
||||
<el-input v-model="form.apiServer" :placeholder="$t('business.cluster.api_server_help')"
|
||||
clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="router" prop="router">
|
||||
<el-input v-model="form.router" :placeholder="$t('business.cluster.router_help')" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="token" prop="token">
|
||||
<el-input type="textarea" :autosize="{ minRows: 2}" style="width: 100%" v-model="form.token"
|
||||
clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item style="float: right">
|
||||
<el-button @click="onCancel()">{{ $t("commons.button.cancel") }}</el-button>
|
||||
<el-button v-loading="loading" @click="onSubmit" type="primary">{{
|
||||
$t("commons.button.create")
|
||||
}}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</layout-content>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LayoutContent from "@/components/layout/LayoutContent"
|
||||
import {create} from "@/api/clusters"
|
||||
|
||||
export default {
|
||||
name: "ClusterImport",
|
||||
components: { LayoutContent },
|
||||
data () {
|
||||
return {
|
||||
form: {
|
||||
name: "",
|
||||
apiServer: "",
|
||||
router: "",
|
||||
token: ""
|
||||
},
|
||||
rules: {},
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onCancel () {
|
||||
this.$router.push({ name: "ClusterList" })
|
||||
},
|
||||
onSubmit () {
|
||||
create(this.form).then(() => {
|
||||
this.$message({
|
||||
type: "success",
|
||||
message: this.$t("创建成功")
|
||||
})
|
||||
this.$router.push({ name: "ClusterList" })
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,113 +0,0 @@
|
||||
<template>
|
||||
<layout-content :header="$t('business.cluster.list')">
|
||||
<complex-table :search-config="searchConfig" :selects.sync="selects" :data="data"
|
||||
:pagination-config="paginationConfig">
|
||||
<template #header>
|
||||
<el-button-group>
|
||||
<el-button type="primary" size="small" @click="onImport">
|
||||
{{ $t("commons.button.import") }}
|
||||
</el-button>
|
||||
<el-button type="primary" size="small" :disabled="selects.length===0" @click="del()">
|
||||
{{ $t("commons.button.delete") }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</template>
|
||||
<el-table-column type="selection" fix></el-table-column>
|
||||
<el-table-column :label="$t('commons.table.name')" min-width="100" prop="name" fix>
|
||||
<template v-slot:default="{row}">
|
||||
<el-link href="/dashboard">{{ row.name }}</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.status')" min-width="100" prop="name" fix>
|
||||
<template v-slot:default="{row}">
|
||||
{{ row.status }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<fu-table-operations :buttons="buttons" :label="$t('commons.table.action')"/>
|
||||
</complex-table>
|
||||
</layout-content>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LayoutContent from "@/components/layout/LayoutContent"
|
||||
import ComplexTable from "@/components/complex-table"
|
||||
import {deleteBy, searchCluster} from "@/api/clusters"
|
||||
|
||||
export default {
|
||||
name: "ClusterList",
|
||||
components: { ComplexTable, LayoutContent },
|
||||
data () {
|
||||
return {
|
||||
buttons: [
|
||||
{
|
||||
label: this.$t("commons.button.delete"),
|
||||
icon: "el-icon-delete",
|
||||
click: (row) => {
|
||||
this.del(row.name)
|
||||
}
|
||||
},
|
||||
],
|
||||
paginationConfig: {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
},
|
||||
searchConfig: {
|
||||
quickPlaceholder: this.$t("commons.search.quickSearch"),
|
||||
components: [
|
||||
{ field: "name", label: this.$t("commons.table.name"), component: "FuComplexInput", defaultOperator: "eq" },
|
||||
],
|
||||
},
|
||||
data: [],
|
||||
selects: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onImport () {
|
||||
this.$router.push({ name: "ClusterImport" })
|
||||
},
|
||||
search () {
|
||||
searchCluster(this.paginationConfig.currentPage, this.paginationConfig.pageSize).then(res => {
|
||||
this.data = res.data.items
|
||||
this.paginationConfig.total = res.data.total
|
||||
})
|
||||
},
|
||||
del (name) {
|
||||
this.$confirm(
|
||||
this.$t("commons.confirm_message.delete"),
|
||||
this.$t("commons.message_box.prompt"),
|
||||
{
|
||||
confirmButtonText: this.$t("commons.button.confirm"),
|
||||
cancelButtonText: this.$t("commons.button.cancel"),
|
||||
type: "warning"
|
||||
}
|
||||
).then(() => {
|
||||
const ps = []
|
||||
if (name) {
|
||||
ps.push(deleteBy(name))
|
||||
} else {
|
||||
for (const item of this.selects) {
|
||||
ps.push(deleteBy(item.name))
|
||||
}
|
||||
}
|
||||
Promise.all(ps).then(() => {
|
||||
this.search()
|
||||
this.$message({
|
||||
type: "success",
|
||||
message: this.$t("commons.msg.delete_success")
|
||||
})
|
||||
}).catch(() => {
|
||||
this.search()
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.search()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
197
web/ekko/src/business/cluster-management/create/index.vue
Normal file
197
web/ekko/src/business/cluster-management/create/index.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<layout-content :header="$t('business.cluster.import')" :back-to="{ name: 'Clusters' }">
|
||||
<el-row>
|
||||
<el-col :span="4"><br/></el-col>
|
||||
<el-col :span="10">
|
||||
<div class="grid-content bg-purple-light">
|
||||
<el-form :model="form" label-position="left" :rules="rules" label-width="120px">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input v-model="form.name" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-divider content-position="center">连接设置</el-divider>
|
||||
<el-form-item label="连接方式">
|
||||
<el-radio v-model="form.direction" label="forward" @change="onDirectionChange">正向连接
|
||||
</el-radio>
|
||||
<el-radio v-model="form.direction" label="backward" @change="onDirectionChange">反向连接
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
<div v-if="form.direction==='forward'">
|
||||
<el-form-item label="API Server" prop="apiServer">
|
||||
<el-input v-model="form.apiServer" clearable></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Insecure">
|
||||
<el-switch v-model="form.apiServerInsecure"></el-switch>
|
||||
</el-form-item>
|
||||
<div v-if="!form.apiServerInsecure">
|
||||
<el-form-item label="Ca Certificate" prop="caDataStr">
|
||||
<el-input type="textarea" v-model="form.caDataStr" clearable></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
|
||||
<el-form-item label="配置代理">
|
||||
<el-switch v-model="form.proxyEnable"></el-switch>
|
||||
</el-form-item>
|
||||
<div v-if="form.proxyEnable">
|
||||
<el-form-item label="Proxy Server" prop="proxyServer">
|
||||
<el-input v-model="form.proxyServer" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="代理认证">
|
||||
<el-switch v-model="form.proxyAuthEnable"></el-switch>
|
||||
</el-form-item>
|
||||
<div v-if="form.proxyAuthEnable">
|
||||
<el-form-item label="代理用户名" prop="proxyUsername">
|
||||
<el-input v-model="form.proxyServer" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="代理密码" prop="proxyPassword">
|
||||
<el-input v-model="form.proxyServer" clearable></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<el-divider content-position="center">认证设置</el-divider>
|
||||
<el-form-item label="认证方式">
|
||||
<el-radio v-model="form.authenticationMode" label="bearer"
|
||||
@change="onAuthenticationModeChange">Bearer Token
|
||||
</el-radio>
|
||||
<el-radio v-model="form.authenticationMode" label="certificate"
|
||||
@change="onAuthenticationModeChange">证书
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
<div v-if="form.authenticationMode==='bearer'">
|
||||
<el-form-item label="Bearer Token" prop="token">
|
||||
<el-input type="textarea" v-model="form.token" clearable></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-if="form.authenticationMode==='certificate'">
|
||||
<el-form-item label="Certificate" prop="certDataStr">
|
||||
<el-input type="textarea" v-model="form.certDataStr" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Certificate Key" prop="keyDataStr">
|
||||
<el-input type="textarea" v-model="form.keyDataStr" clearable></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<el-form-item>
|
||||
<div style="float: right">
|
||||
<el-button @click="onCancel()">{{ $t("commons.button.cancel") }}</el-button>
|
||||
<el-button type="primary" @click="onConfirm()" :disabled="isSubmitGoing">{{
|
||||
$t("commons.button.confirm") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</layout-content>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LayoutContent from "@/components/layout/LayoutContent"
|
||||
import {createCluster} from "@/api/clusters"
|
||||
|
||||
export default {
|
||||
name: "ClusterCreate",
|
||||
components: {LayoutContent},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
name: "",
|
||||
direction: "forward",
|
||||
apiServer: "",
|
||||
apiServerInsecure: true,
|
||||
authenticationMode: "bearer",
|
||||
token: "",
|
||||
proxyEnable: false,
|
||||
proxyServer: "",
|
||||
proxyAuthEnable: false,
|
||||
proxyUsername: "",
|
||||
proxyPassword: "",
|
||||
certDataStr: "",
|
||||
keyDataStr: "",
|
||||
caDataStr: ""
|
||||
},
|
||||
rules: {},
|
||||
loading: false,
|
||||
isSubmitGoing: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onDirectionChange() {
|
||||
if (this.form.direction === 'forward') {
|
||||
this.form.apiServer = ""
|
||||
this.form.authenticationMode = "bearer"
|
||||
this.form.certificateData = ""
|
||||
this.form.certificateKeyData = ""
|
||||
this.form.token = ""
|
||||
}
|
||||
},
|
||||
onAuthenticationModeChange() {
|
||||
this.form.certificateData = ""
|
||||
this.form.certificateKeyData = ""
|
||||
this.form.token = ""
|
||||
},
|
||||
onCancel() {
|
||||
this.$router.push({name: "Clusters"})
|
||||
},
|
||||
onConfirm() {
|
||||
if (this.isSubmitGoing) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
const req = {
|
||||
apiVersion: "v1",
|
||||
kind: "Cluster",
|
||||
name: this.form.name,
|
||||
spec: {
|
||||
connect: {
|
||||
direction: this.form.direction
|
||||
},
|
||||
authentication: {
|
||||
mode: this.form.authenticationMode
|
||||
}
|
||||
|
||||
},
|
||||
keyDataStr: "",
|
||||
caDataStr: this.form.caDataStr,
|
||||
certDataStr: ""
|
||||
}
|
||||
if (this.form.direction === 'forward') {
|
||||
const forwardConfig = {}
|
||||
forwardConfig.apiServer = this.form.apiServer
|
||||
if (this.form.proxyEnable) {
|
||||
forwardConfig.proxy.username = this.form.proxyUsername
|
||||
forwardConfig.proxy.password = this.form.proxyPassword
|
||||
forwardConfig.proxy.url = this.form.proxyServer
|
||||
}
|
||||
req.spec.connect.forward = forwardConfig
|
||||
}
|
||||
switch (this.form.authenticationMode) {
|
||||
case "bearer":
|
||||
req.spec.authentication.bearerToken = this.form.token
|
||||
break
|
||||
case "certificate":
|
||||
req.certDataStr = this.form.certDataStr
|
||||
req.keyDataStr = this.form.keyDataStr
|
||||
break
|
||||
}
|
||||
console.log(req)
|
||||
createCluster(req).then(() => {
|
||||
this.loading = false
|
||||
this.isSubmitGoing = false
|
||||
this.$message({
|
||||
type: "success",
|
||||
message: this.$t("commons.msg.create_success")
|
||||
})
|
||||
this.$router.push({"name": "Clusters"})
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
75
web/ekko/src/business/cluster-management/index.vue
Normal file
75
web/ekko/src/business/cluster-management/index.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template xmlns:el-col="http://www.w3.org/1999/html">
|
||||
<layout-content>
|
||||
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="4" v-for="(item,index) in items" :key="index">
|
||||
<el-card class="box-card" shadow="hover">
|
||||
<div v-if="!item.add">
|
||||
<div slot="header" class="clearfix">
|
||||
<el-row>
|
||||
<el-col :span="18" style="font-size: 18px">
|
||||
{{item.name}}
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-button icon="el-icon-user" circle></el-button>
|
||||
<el-button icon="el-icon-delete" circle></el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :span="4">
|
||||
<img width="38px" height="38px" src="~@/assets/kubernetes.png" alt="kubernetes.png">
|
||||
</el-col>
|
||||
<el-col :span="20">
|
||||
<table>
|
||||
<tr>
|
||||
<th style="width: 60%;text-align: left">版本</th>
|
||||
<td style="padding-left: 20px">{{item.status.version}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.add" @click="onCreate">
|
||||
<i class="el-icon-plus"></i>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</layout-content>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LayoutContent from "@/components/layout/LayoutContent"
|
||||
import {listClusters} from "@/api/clusters"
|
||||
|
||||
|
||||
export default {
|
||||
name: "ClusterList",
|
||||
components: {LayoutContent},
|
||||
data() {
|
||||
return {
|
||||
items: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onCreate() {
|
||||
this.$router.push({name: "ClusterCreate"})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
listClusters().then(data => {
|
||||
this.items = data.data;
|
||||
this.items.push({
|
||||
add: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -19,7 +19,7 @@ const generateRoutes = async (to, from, next) => {
|
||||
router.addRoute({
|
||||
path: "/",
|
||||
component: Layout,
|
||||
redirect: "/clusterlist",
|
||||
redirect: "/clusters",
|
||||
})
|
||||
router.addRoutes(accessRoutes)
|
||||
next({...to, replace: true})
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import Layout from "@/business/app-layout/horizontal-layout"
|
||||
|
||||
const ClusterList = {
|
||||
path: "/clusterlist",
|
||||
sort: 1,
|
||||
component: Layout,
|
||||
name: "ClusterList",
|
||||
meta: {
|
||||
title: "business.cluster.list",
|
||||
icon: "iconfont iconkubernets",
|
||||
global: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/clusterlist",
|
||||
component: () => import("@/business/cluster-list"),
|
||||
name: "ClusterList",
|
||||
meta: {
|
||||
title: "business.cluster.list",
|
||||
global: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/import",
|
||||
component: () => import("@/business/cluster-list/import"),
|
||||
hidden: true,
|
||||
name: "ClusterImport",
|
||||
meta: {
|
||||
activeMenu: "/clusterlist",
|
||||
global: true
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export default ClusterList
|
||||
34
web/ekko/src/router/modules/clusters.js
Normal file
34
web/ekko/src/router/modules/clusters.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import Layout from "@/business/app-layout/horizontal-layout"
|
||||
|
||||
const Clusters = {
|
||||
path: "/clusters",
|
||||
sort: 1,
|
||||
component: Layout,
|
||||
name: "ClusterManagement",
|
||||
meta: {
|
||||
title: "business.cluster.list",
|
||||
icon: "iconfont iconkubernets",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () => import("@/business/cluster-management"),
|
||||
name: "Clusters",
|
||||
meta: {
|
||||
title: "business.cluster.list",
|
||||
activeMenu: "/clusters",
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "create",
|
||||
component: () => import("@/business/cluster-management/create"),
|
||||
hidden: true,
|
||||
name: "ClusterCreate",
|
||||
meta: {
|
||||
activeMenu: "/clusters",
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export default Clusters
|
||||
Reference in New Issue
Block a user