refactor: 全局重命名 k8s → kubernetes (前后端+路由+表名+文件)
后端: - pkg/k8sclient → pkg/kubernetesclient - models/k8s.go → models/kubernetes.go - handlers/k8s.go → handlers/kubernetes.go - K8sCluster/K8sNode/.. → KubernetesCluster/KubernetesNode/.. - K8sHandler → KubernetesHandler - 路由: /k8s-clusters → /kubernetes-clusters - 路由: /k8s/:code/namespace/:ns/... → /kubernetes/:code/namespace/:ns/... (Kuboard风格) - 表前缀: container_k8s_ → container_kubernetes_ 前端: - components/k8s/ → components/kubernetes/ - K8sClusters → KubernetesClusters - K8sWorkloads → KubernetesWorkloads - K8sNodes → KubernetesNodes - k8sApi → kubernetesApi - 路由前缀: /k8s/ → /kubernetes/ 测试验证: ✅ /kubernetes/k8s-cluster-liren/namespace/kube-system/deployments → 3 items ✅ /kubernetes/k8s-cluster-liren/namespace/kube-system/pods → 7 items ✅ /kubernetes/k8s-cluster-liren/namespace/kube-system/services → 4 items ✅ /kubernetes/k8s-cluster-liren/namespace/kube-system/configmaps → 6 items ✅ /kubernetes/k8s-cluster-liren/namespace/kube-system/secrets → 1 item ✅ /kubernetes/k8s-cluster-liren/namespaces → 4 items
This commit is contained in:
1 parent
f7b667b878
commit
cac1414917
27 files changed
+779
-284
No files matched your search
@@ -134,7 +134,7 @@ func (h *AuditHandler) GetAuditModules(c *gin.Context) {
|
|||||||
modules := []map[string]string{
|
modules := []map[string]string{
|
||||||
{"value": "user", "label": "用户管理"},
|
{"value": "user", "label": "用户管理"},
|
||||||
{"value": "server", "label": "服务器管理"},
|
{"value": "server", "label": "服务器管理"},
|
||||||
{"value": "k8s", "label": "Kubernetes"},
|
{"value": "kubernetes", "label": "Kubernetes"},
|
||||||
{"value": "alert", "label": "监控告警"},
|
{"value": "alert", "label": "监控告警"},
|
||||||
{"value": "pipeline", "label": "应用发布"},
|
{"value": "pipeline", "label": "应用发布"},
|
||||||
{"value": "automation", "label": "自动化运维"},
|
{"value": "automation", "label": "自动化运维"},
|
||||||
|
|||||||
@@ -53,16 +53,16 @@ func (h *DashboardHandler) GetStats(c *gin.Context) {
|
|||||||
stats.ServerStats = serverStats
|
stats.ServerStats = serverStats
|
||||||
|
|
||||||
// K8s集群统计
|
// K8s集群统计
|
||||||
var k8sStats models.K8sClusterStats
|
var k8sStats models.KubernetesClusterStats
|
||||||
h.db.Model(&models.K8sCluster{}).Count(&k8sStats.Total)
|
h.db.Model(&models.KubernetesCluster{}).Count(&k8sStats.Total)
|
||||||
h.db.Model(&models.K8sCluster{}).Where("status = ?", "healthy").Count(&k8sStats.Healthy)
|
h.db.Model(&models.KubernetesCluster{}).Where("status = ?", "healthy").Count(&k8sStats.Healthy)
|
||||||
h.db.Model(&models.K8sCluster{}).Where("status = ?", "warning").Count(&k8sStats.Warning)
|
h.db.Model(&models.KubernetesCluster{}).Where("status = ?", "warning").Count(&k8sStats.Warning)
|
||||||
h.db.Model(&models.K8sCluster{}).Where("status = ?", "error").Count(&k8sStats.Error)
|
h.db.Model(&models.KubernetesCluster{}).Where("status = ?", "error").Count(&k8sStats.Error)
|
||||||
|
|
||||||
var totalNodes, totalPods, totalServices int64
|
var totalNodes, totalPods, totalServices int64
|
||||||
h.db.Model(&models.K8sNode{}).Count(&totalNodes)
|
h.db.Model(&models.KubernetesNode{}).Count(&totalNodes)
|
||||||
h.db.Model(&models.K8sPod{}).Count(&totalPods)
|
h.db.Model(&models.KubernetesPod{}).Count(&totalPods)
|
||||||
h.db.Model(&models.K8sService{}).Count(&totalServices)
|
h.db.Model(&models.KubernetesService{}).Count(&totalServices)
|
||||||
k8sStats.TotalNodes = totalNodes
|
k8sStats.TotalNodes = totalNodes
|
||||||
k8sStats.TotalPods = totalPods
|
k8sStats.TotalPods = totalPods
|
||||||
k8sStats.TotalServices = totalServices
|
k8sStats.TotalServices = totalServices
|
||||||
|
|||||||
+429
-76
@@ -11,20 +11,20 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"ezops/internal/models"
|
"ezops/internal/models"
|
||||||
"ezops/pkg/k8sclient"
|
"ezops/pkg/kubernetesclient"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// K8sHandler K8s处理器
|
// KubernetesHandler K8s处理器
|
||||||
type K8sHandler struct {
|
type KubernetesHandler struct {
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewK8sHandler 创建K8s处理器
|
// NewKubernetesHandler 创建K8s处理器
|
||||||
func NewK8sHandler(db *gorm.DB) *K8sHandler {
|
func NewKubernetesHandler(db *gorm.DB) *KubernetesHandler {
|
||||||
return &K8sHandler{db: db}
|
return &KubernetesHandler{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListClusters 获取集群列表
|
// ListClusters 获取集群列表
|
||||||
@@ -36,15 +36,15 @@ func NewK8sHandler(db *gorm.DB) *K8sHandler {
|
|||||||
// @Param page query int false "页码"
|
// @Param page query int false "页码"
|
||||||
// @Param page_size query int false "每页数量"
|
// @Param page_size query int false "每页数量"
|
||||||
// @Success 200 {object} models.Response{data=models.ListResponse}
|
// @Success 200 {object} models.Response{data=models.ListResponse}
|
||||||
// @Router /k8s-clusters [get]
|
// @Router /kubernetes-clusters [get]
|
||||||
func (h *K8sHandler) ListClusters(c *gin.Context) {
|
func (h *KubernetesHandler) ListClusters(c *gin.Context) {
|
||||||
var query models.PageQuery
|
var query models.PageQuery
|
||||||
c.ShouldBindQuery(&query)
|
c.ShouldBindQuery(&query)
|
||||||
|
|
||||||
var total int64
|
var total int64
|
||||||
h.db.Model(&models.K8sCluster{}).Count(&total)
|
h.db.Model(&models.KubernetesCluster{}).Count(&total)
|
||||||
|
|
||||||
var clusters []models.K8sCluster
|
var clusters []models.KubernetesCluster
|
||||||
h.db.Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&clusters)
|
h.db.Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&clusters)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, models.Response{
|
c.JSON(http.StatusOK, models.Response{
|
||||||
@@ -64,12 +64,12 @@ func (h *K8sHandler) ListClusters(c *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param id path int true "集群ID"
|
// @Param id path int true "集群ID"
|
||||||
// @Success 200 {object} models.Response{data=models.K8sCluster}
|
// @Success 200 {object} models.Response{data=models.KubernetesCluster}
|
||||||
// @Router /k8s-clusters/{id} [get]
|
// @Router /kubernetes-clusters/{id} [get]
|
||||||
func (h *K8sHandler) GetCluster(c *gin.Context) {
|
func (h *KubernetesHandler) GetCluster(c *gin.Context) {
|
||||||
id, _ := strconv.Atoi(c.Param("id"))
|
id, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
|
||||||
var cluster models.K8sCluster
|
var cluster models.KubernetesCluster
|
||||||
if err := h.db.First(&cluster, id).Error; err != nil {
|
if err := h.db.First(&cluster, id).Error; err != nil {
|
||||||
c.JSON(http.StatusNotFound, models.Response{
|
c.JSON(http.StatusNotFound, models.Response{
|
||||||
Code: 404,
|
Code: 404,
|
||||||
@@ -92,11 +92,11 @@ func (h *K8sHandler) GetCluster(c *gin.Context) {
|
|||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param request body models.CreateK8sClusterRequest true "创建请求"
|
// @Param request body models.CreateKubernetesClusterRequest true "创建请求"
|
||||||
// @Success 200 {object} models.Response{data=models.K8sCluster}
|
// @Success 200 {object} models.Response{data=models.KubernetesCluster}
|
||||||
// @Router /k8s-clusters [post]
|
// @Router /kubernetes-clusters [post]
|
||||||
func (h *K8sHandler) CreateCluster(c *gin.Context) {
|
func (h *KubernetesHandler) CreateCluster(c *gin.Context) {
|
||||||
var req models.CreateK8sClusterRequest
|
var req models.CreateKubernetesClusterRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, models.Response{
|
c.JSON(http.StatusBadRequest, models.Response{
|
||||||
Code: 400,
|
Code: 400,
|
||||||
@@ -105,7 +105,7 @@ func (h *K8sHandler) CreateCluster(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cluster := models.K8sCluster{
|
cluster := models.KubernetesCluster{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Code: req.Code,
|
Code: req.Code,
|
||||||
Version: req.Version,
|
Version: req.Version,
|
||||||
@@ -141,10 +141,10 @@ func (h *K8sHandler) CreateCluster(c *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param id path int true "集群ID"
|
// @Param id path int true "集群ID"
|
||||||
// @Param request body models.CreateK8sClusterRequest true "更新请求"
|
// @Param request body models.CreateKubernetesClusterRequest true "更新请求"
|
||||||
// @Success 200 {object} models.Response{data=models.K8sCluster}
|
// @Success 200 {object} models.Response{data=models.KubernetesCluster}
|
||||||
// @Router /k8s-clusters/{id} [put]
|
// @Router /kubernetes-clusters/{id} [put]
|
||||||
func (h *K8sHandler) UpdateCluster(c *gin.Context) {
|
func (h *KubernetesHandler) UpdateCluster(c *gin.Context) {
|
||||||
id, _ := strconv.Atoi(c.Param("id"))
|
id, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
|
||||||
// 用 map 接收,避免 binding:"required" 校验 // Logan.Li
|
// 用 map 接收,避免 binding:"required" 校验 // Logan.Li
|
||||||
@@ -157,7 +157,7 @@ func (h *K8sHandler) UpdateCluster(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var cluster models.K8sCluster
|
var cluster models.KubernetesCluster
|
||||||
if err := h.db.First(&cluster, id).Error; err != nil {
|
if err := h.db.First(&cluster, id).Error; err != nil {
|
||||||
c.JSON(http.StatusNotFound, models.Response{
|
c.JSON(http.StatusNotFound, models.Response{
|
||||||
Code: 404,
|
Code: 404,
|
||||||
@@ -219,10 +219,10 @@ func (h *K8sHandler) UpdateCluster(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetClusterDetail 返回集群完整信息(含 token/kube_config,供编辑回填) // Logan.Li
|
// GetClusterDetail 返回集群完整信息(含 token/kube_config,供编辑回填) // Logan.Li
|
||||||
func (h *K8sHandler) GetClusterDetail(c *gin.Context) {
|
func (h *KubernetesHandler) GetClusterDetail(c *gin.Context) {
|
||||||
id, _ := strconv.Atoi(c.Param("id"))
|
id, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
|
||||||
var cluster models.K8sCluster
|
var cluster models.KubernetesCluster
|
||||||
if err := h.db.First(&cluster, id).Error; err != nil {
|
if err := h.db.First(&cluster, id).Error; err != nil {
|
||||||
c.JSON(http.StatusNotFound, models.Response{
|
c.JSON(http.StatusNotFound, models.Response{
|
||||||
Code: 404,
|
Code: 404,
|
||||||
@@ -264,11 +264,11 @@ func (h *K8sHandler) GetClusterDetail(c *gin.Context) {
|
|||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param id path int true "集群ID"
|
// @Param id path int true "集群ID"
|
||||||
// @Success 200 {object} models.Response
|
// @Success 200 {object} models.Response
|
||||||
// @Router /k8s-clusters/{id} [delete]
|
// @Router /kubernetes-clusters/{id} [delete]
|
||||||
func (h *K8sHandler) DeleteCluster(c *gin.Context) {
|
func (h *KubernetesHandler) DeleteCluster(c *gin.Context) {
|
||||||
id, _ := strconv.Atoi(c.Param("id"))
|
id, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
|
||||||
if err := h.db.Delete(&models.K8sCluster{}, id).Error; err != nil {
|
if err := h.db.Delete(&models.KubernetesCluster{}, id).Error; err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, models.Response{
|
c.JSON(http.StatusInternalServerError, models.Response{
|
||||||
Code: 500,
|
Code: 500,
|
||||||
Message: "删除失败",
|
Message: "删除失败",
|
||||||
@@ -289,9 +289,9 @@ func (h *K8sHandler) DeleteCluster(c *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param id path int true "集群ID"
|
// @Param id path int true "集群ID"
|
||||||
// @Success 200 {object} models.Response{data=[]models.K8sNode}
|
// @Success 200 {object} models.Response{data=[]models.KubernetesNode}
|
||||||
// @Router /k8s-clusters/{id}/nodes [get]
|
// @Router /kubernetes-clusters/{id}/nodes [get]
|
||||||
func (h *K8sHandler) ListNodes(c *gin.Context) {
|
func (h *KubernetesHandler) ListNodes(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
|
||||||
client, _, err := h.getK8sClient(clusterID)
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
@@ -363,9 +363,9 @@ func (h *K8sHandler) ListNodes(c *gin.Context) {
|
|||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param id path int true "集群ID"
|
// @Param id path int true "集群ID"
|
||||||
// @Param namespace query string false "命名空间"
|
// @Param namespace query string false "命名空间"
|
||||||
// @Success 200 {object} models.Response{data=[]models.K8sPod}
|
// @Success 200 {object} models.Response{data=[]models.KubernetesPod}
|
||||||
// @Router /k8s-clusters/{id}/pods [get]
|
// @Router /kubernetes-clusters/{id}/pods [get]
|
||||||
func (h *K8sHandler) ListPods(c *gin.Context) {
|
func (h *KubernetesHandler) ListPods(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
namespace := c.Query("namespace")
|
namespace := c.Query("namespace")
|
||||||
|
|
||||||
@@ -423,9 +423,9 @@ func (h *K8sHandler) ListPods(c *gin.Context) {
|
|||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param id path int true "集群ID"
|
// @Param id path int true "集群ID"
|
||||||
// @Param namespace query string false "命名空间"
|
// @Param namespace query string false "命名空间"
|
||||||
// @Success 200 {object} models.Response{data=[]models.K8sDeployment}
|
// @Success 200 {object} models.Response{data=[]models.KubernetesDeployment}
|
||||||
// @Router /k8s-clusters/{id}/deployments [get]
|
// @Router /kubernetes-clusters/{id}/deployments [get]
|
||||||
func (h *K8sHandler) ListDeployments(c *gin.Context) {
|
func (h *KubernetesHandler) ListDeployments(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
namespace := c.Query("namespace")
|
namespace := c.Query("namespace")
|
||||||
|
|
||||||
@@ -477,9 +477,9 @@ func (h *K8sHandler) ListDeployments(c *gin.Context) {
|
|||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param id path int true "集群ID"
|
// @Param id path int true "集群ID"
|
||||||
// @Param namespace query string false "命名空间"
|
// @Param namespace query string false "命名空间"
|
||||||
// @Success 200 {object} models.Response{data=[]models.K8sService}
|
// @Success 200 {object} models.Response{data=[]models.KubernetesService}
|
||||||
// @Router /k8s-clusters/{id}/services [get]
|
// @Router /kubernetes-clusters/{id}/services [get]
|
||||||
func (h *K8sHandler) ListServices(c *gin.Context) {
|
func (h *KubernetesHandler) ListServices(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
namespace := c.Query("namespace")
|
namespace := c.Query("namespace")
|
||||||
|
|
||||||
@@ -525,9 +525,9 @@ func (h *K8sHandler) ListServices(c *gin.Context) {
|
|||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param id path int true "集群ID"
|
// @Param id path int true "集群ID"
|
||||||
// @Param namespace query string false "命名空间"
|
// @Param namespace query string false "命名空间"
|
||||||
// @Success 200 {object} models.Response{data=[]models.K8sIngress}
|
// @Success 200 {object} models.Response{data=[]models.KubernetesIngress}
|
||||||
// @Router /k8s-clusters/{id}/ingresses [get]
|
// @Router /kubernetes-clusters/{id}/ingresses [get]
|
||||||
func (h *K8sHandler) ListIngresses(c *gin.Context) {
|
func (h *KubernetesHandler) ListIngresses(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
namespace := c.Query("namespace")
|
namespace := c.Query("namespace")
|
||||||
|
|
||||||
@@ -570,7 +570,7 @@ func (h *K8sHandler) ListIngresses(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetTopology 获取集群拓扑布局 // Logan.Li
|
// GetTopology 获取集群拓扑布局 // Logan.Li
|
||||||
func (h *K8sHandler) GetTopology(c *gin.Context) {
|
func (h *KubernetesHandler) GetTopology(c *gin.Context) {
|
||||||
clusterID := c.Param("id")
|
clusterID := c.Param("id")
|
||||||
var layout models.TopologyLayout
|
var layout models.TopologyLayout
|
||||||
result := h.db.Where("cluster_id = ?", clusterID).First(&layout)
|
result := h.db.Where("cluster_id = ?", clusterID).First(&layout)
|
||||||
@@ -582,7 +582,7 @@ func (h *K8sHandler) GetTopology(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SaveTopology 保存集群拓扑布局 // Logan.Li
|
// SaveTopology 保存集群拓扑布局 // Logan.Li
|
||||||
func (h *K8sHandler) SaveTopology(c *gin.Context) {
|
func (h *KubernetesHandler) SaveTopology(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
var req struct {
|
var req struct {
|
||||||
LayoutData string `json:"layout_data"`
|
LayoutData string `json:"layout_data"`
|
||||||
@@ -607,10 +607,10 @@ func (h *K8sHandler) SaveTopology(c *gin.Context) {
|
|||||||
|
|
||||||
|
|
||||||
// SyncCluster 同步集群资源(连接真实K8s API拉取数据)
|
// SyncCluster 同步集群资源(连接真实K8s API拉取数据)
|
||||||
func (h *K8sHandler) SyncCluster(c *gin.Context) {
|
func (h *KubernetesHandler) SyncCluster(c *gin.Context) {
|
||||||
id, _ := strconv.Atoi(c.Param("id"))
|
id, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
|
||||||
var cluster models.K8sCluster
|
var cluster models.KubernetesCluster
|
||||||
if err := h.db.First(&cluster, id).Error; err != nil {
|
if err := h.db.First(&cluster, id).Error; err != nil {
|
||||||
c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "集群不存在"})
|
c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "集群不存在"})
|
||||||
return
|
return
|
||||||
@@ -632,7 +632,7 @@ func (h *K8sHandler) SyncCluster(c *gin.Context) {
|
|||||||
// Sync nodes
|
// Sync nodes
|
||||||
if nodes, err := client.ListNodes(); err == nil {
|
if nodes, err := client.ListNodes(); err == nil {
|
||||||
// Use raw SQL for reliability (avoid GORM soft-delete / auto-timestamp issues)
|
// Use raw SQL for reliability (avoid GORM soft-delete / auto-timestamp issues)
|
||||||
h.db.Exec("DELETE FROM k8s_nodes WHERE cluster_id = ?", cluster.ID)
|
h.db.Exec("DELETE FROM kubernetes_nodes WHERE cluster_id = ?", cluster.ID)
|
||||||
for _, n := range nodes.Items {
|
for _, n := range nodes.Items {
|
||||||
role := "worker"
|
role := "worker"
|
||||||
if _, ok := n.Metadata.Labels["node-role.kubernetes.io/master"]; ok {
|
if _, ok := n.Metadata.Labels["node-role.kubernetes.io/master"]; ok {
|
||||||
@@ -656,7 +656,7 @@ func (h *K8sHandler) SyncCluster(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
labelsJSON, _ := json.Marshal(n.Metadata.Labels)
|
labelsJSON, _ := json.Marshal(n.Metadata.Labels)
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
err2 := h.db.Exec(`INSERT INTO k8s_nodes (cluster_id, name, ip, role, status, os, kubelet_version, cpu, memory, max_pods, labels, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
err2 := h.db.Exec(`INSERT INTO kubernetes_nodes (cluster_id, name, ip, role, status, os, kubelet_version, cpu, memory, max_pods, labels, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
cluster.ID, n.Metadata.Name, ip, role, nodeStatus, n.Status.NodeInfo.OSImage, n.Status.NodeInfo.KubeletVersion,
|
cluster.ID, n.Metadata.Name, ip, role, nodeStatus, n.Status.NodeInfo.OSImage, n.Status.NodeInfo.KubeletVersion,
|
||||||
parseIntResource(n.Status.Capacity["cpu"]), parseMemResource(n.Status.Capacity["memory"]), parseIntResource(n.Status.Capacity["pods"]),
|
parseIntResource(n.Status.Capacity["cpu"]), parseMemResource(n.Status.Capacity["memory"]), parseIntResource(n.Status.Capacity["pods"]),
|
||||||
string(labelsJSON), now, now).Error
|
string(labelsJSON), now, now).Error
|
||||||
@@ -672,7 +672,7 @@ func (h *K8sHandler) SyncCluster(c *gin.Context) {
|
|||||||
|
|
||||||
// Sync pods
|
// Sync pods
|
||||||
if pods, err := client.ListPods(); err == nil {
|
if pods, err := client.ListPods(); err == nil {
|
||||||
h.db.Where("cluster_id = ?", cluster.ID).Delete(&models.K8sPod{})
|
h.db.Where("cluster_id = ?", cluster.ID).Delete(&models.KubernetesPod{})
|
||||||
for _, p := range pods.Items {
|
for _, p := range pods.Items {
|
||||||
image := ""
|
image := ""
|
||||||
if len(p.Spec.Containers) > 0 {
|
if len(p.Spec.Containers) > 0 {
|
||||||
@@ -683,7 +683,7 @@ func (h *K8sHandler) SyncCluster(c *gin.Context) {
|
|||||||
restarts = p.Status.ContainerStatuses[0].RestartCount
|
restarts = p.Status.ContainerStatuses[0].RestartCount
|
||||||
}
|
}
|
||||||
labelsJSON, _ := json.Marshal(p.Metadata.Labels)
|
labelsJSON, _ := json.Marshal(p.Metadata.Labels)
|
||||||
h.db.Create(&models.K8sPod{
|
h.db.Create(&models.KubernetesPod{
|
||||||
ClusterID: cluster.ID,
|
ClusterID: cluster.ID,
|
||||||
Namespace: p.Metadata.Namespace,
|
Namespace: p.Metadata.Namespace,
|
||||||
Name: p.Metadata.Name,
|
Name: p.Metadata.Name,
|
||||||
@@ -702,14 +702,14 @@ func (h *K8sHandler) SyncCluster(c *gin.Context) {
|
|||||||
|
|
||||||
// Sync deployments
|
// Sync deployments
|
||||||
if deps, err := client.ListDeployments(); err == nil {
|
if deps, err := client.ListDeployments(); err == nil {
|
||||||
h.db.Where("cluster_id = ?", cluster.ID).Delete(&models.K8sDeployment{})
|
h.db.Where("cluster_id = ?", cluster.ID).Delete(&models.KubernetesDeployment{})
|
||||||
for _, d := range deps.Items {
|
for _, d := range deps.Items {
|
||||||
image := ""
|
image := ""
|
||||||
if len(d.Spec.Template.Spec.Containers) > 0 {
|
if len(d.Spec.Template.Spec.Containers) > 0 {
|
||||||
image = d.Spec.Template.Spec.Containers[0].Image
|
image = d.Spec.Template.Spec.Containers[0].Image
|
||||||
}
|
}
|
||||||
labelsJSON, _ := json.Marshal(d.Metadata.Labels)
|
labelsJSON, _ := json.Marshal(d.Metadata.Labels)
|
||||||
h.db.Create(&models.K8sDeployment{
|
h.db.Create(&models.KubernetesDeployment{
|
||||||
ClusterID: cluster.ID,
|
ClusterID: cluster.ID,
|
||||||
Namespace: d.Metadata.Namespace,
|
Namespace: d.Metadata.Namespace,
|
||||||
Name: d.Metadata.Name,
|
Name: d.Metadata.Name,
|
||||||
@@ -727,12 +727,12 @@ func (h *K8sHandler) SyncCluster(c *gin.Context) {
|
|||||||
|
|
||||||
// Sync services
|
// Sync services
|
||||||
if svcs, err := client.ListServices(); err == nil {
|
if svcs, err := client.ListServices(); err == nil {
|
||||||
h.db.Where("cluster_id = ?", cluster.ID).Delete(&models.K8sService{})
|
h.db.Where("cluster_id = ?", cluster.ID).Delete(&models.KubernetesService{})
|
||||||
for _, s := range svcs.Items {
|
for _, s := range svcs.Items {
|
||||||
portsJSON, _ := json.Marshal(s.Spec.Ports)
|
portsJSON, _ := json.Marshal(s.Spec.Ports)
|
||||||
selectorJSON, _ := json.Marshal(s.Spec.Selector)
|
selectorJSON, _ := json.Marshal(s.Spec.Selector)
|
||||||
labelsJSON, _ := json.Marshal(s.Metadata.Labels)
|
labelsJSON, _ := json.Marshal(s.Metadata.Labels)
|
||||||
h.db.Create(&models.K8sService{
|
h.db.Create(&models.KubernetesService{
|
||||||
ClusterID: cluster.ID,
|
ClusterID: cluster.ID,
|
||||||
Namespace: s.Metadata.Namespace,
|
Namespace: s.Metadata.Namespace,
|
||||||
Name: s.Metadata.Name,
|
Name: s.Metadata.Name,
|
||||||
@@ -748,7 +748,7 @@ func (h *K8sHandler) SyncCluster(c *gin.Context) {
|
|||||||
|
|
||||||
// Sync ingresses
|
// Sync ingresses
|
||||||
if ings, err := client.ListIngresses(); err == nil {
|
if ings, err := client.ListIngresses(); err == nil {
|
||||||
h.db.Where("cluster_id = ?", cluster.ID).Delete(&models.K8sIngress{})
|
h.db.Where("cluster_id = ?", cluster.ID).Delete(&models.KubernetesIngress{})
|
||||||
for _, ing := range ings.Items {
|
for _, ing := range ings.Items {
|
||||||
hosts := []string{}
|
hosts := []string{}
|
||||||
for _, rule := range ing.Spec.Rules {
|
for _, rule := range ing.Spec.Rules {
|
||||||
@@ -764,7 +764,7 @@ func (h *K8sHandler) SyncCluster(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
tlsHostsJSON, _ := json.Marshal(tlsHosts)
|
tlsHostsJSON, _ := json.Marshal(tlsHosts)
|
||||||
labelsJSON, _ := json.Marshal(ing.Metadata.Labels)
|
labelsJSON, _ := json.Marshal(ing.Metadata.Labels)
|
||||||
h.db.Create(&models.K8sIngress{
|
h.db.Create(&models.KubernetesIngress{
|
||||||
ClusterID: cluster.ID,
|
ClusterID: cluster.ID,
|
||||||
Namespace: ing.Metadata.Namespace,
|
Namespace: ing.Metadata.Namespace,
|
||||||
Name: ing.Metadata.Name,
|
Name: ing.Metadata.Name,
|
||||||
@@ -800,7 +800,7 @@ func (h *K8sHandler) SyncCluster(c *gin.Context) {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// GetPodLogs 获取 Pod 日志(支持 stream=true 实时流)
|
// GetPodLogs 获取 Pod 日志(支持 stream=true 实时流)
|
||||||
func (h *K8sHandler) GetPodLogs(c *gin.Context) {
|
func (h *KubernetesHandler) GetPodLogs(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
namespace := c.Param("namespace")
|
namespace := c.Param("namespace")
|
||||||
podName := c.Param("podName")
|
podName := c.Param("podName")
|
||||||
@@ -857,7 +857,7 @@ func (h *K8sHandler) GetPodLogs(c *gin.Context) {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// ExecTerminal WebSocket 终端 — 前端 WS → 后端 → K8s SPDY exec
|
// ExecTerminal WebSocket 终端 — 前端 WS → 后端 → K8s SPDY exec
|
||||||
func (h *K8sHandler) ExecTerminal(c *gin.Context) {
|
func (h *KubernetesHandler) ExecTerminal(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
namespace := c.Param("namespace")
|
namespace := c.Param("namespace")
|
||||||
podName := c.Param("podName")
|
podName := c.Param("podName")
|
||||||
@@ -970,7 +970,7 @@ func (h *K8sHandler) ExecTerminal(c *gin.Context) {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// ExecInPod 在 Pod 中执行命令 (文件浏览 / 单次命令)
|
// ExecInPod 在 Pod 中执行命令 (文件浏览 / 单次命令)
|
||||||
func (h *K8sHandler) ExecInPod(c *gin.Context) {
|
func (h *KubernetesHandler) ExecInPod(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
namespace := c.Param("namespace")
|
namespace := c.Param("namespace")
|
||||||
podName := c.Param("podName")
|
podName := c.Param("podName")
|
||||||
@@ -1008,7 +1008,7 @@ func (h *K8sHandler) ExecInPod(c *gin.Context) {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// ScaleDeployment 缩放 Deployment 副本数
|
// ScaleDeployment 缩放 Deployment 副本数
|
||||||
func (h *K8sHandler) ScaleDeployment(c *gin.Context) {
|
func (h *KubernetesHandler) ScaleDeployment(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
namespace := c.Param("namespace")
|
namespace := c.Param("namespace")
|
||||||
name := c.Param("name")
|
name := c.Param("name")
|
||||||
@@ -1040,7 +1040,7 @@ func (h *K8sHandler) ScaleDeployment(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RestartDeployment 重启 Deployment (滚动重启)
|
// RestartDeployment 重启 Deployment (滚动重启)
|
||||||
func (h *K8sHandler) RestartDeployment(c *gin.Context) {
|
func (h *KubernetesHandler) RestartDeployment(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
namespace := c.Param("namespace")
|
namespace := c.Param("namespace")
|
||||||
name := c.Param("name")
|
name := c.Param("name")
|
||||||
@@ -1064,7 +1064,7 @@ func (h *K8sHandler) RestartDeployment(c *gin.Context) {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// ListHPAs 获取 HPA 列表
|
// ListHPAs 获取 HPA 列表
|
||||||
func (h *K8sHandler) ListHPAs(c *gin.Context) {
|
func (h *KubernetesHandler) ListHPAs(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
|
||||||
client, _, err := h.getK8sClient(clusterID)
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
@@ -1083,7 +1083,7 @@ func (h *K8sHandler) ListHPAs(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateHPA 创建 HPA
|
// CreateHPA 创建 HPA
|
||||||
func (h *K8sHandler) CreateHPA(c *gin.Context) {
|
func (h *KubernetesHandler) CreateHPA(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
|
||||||
var req k8sclient.CreateHPARequest
|
var req k8sclient.CreateHPARequest
|
||||||
@@ -1112,7 +1112,7 @@ func (h *K8sHandler) CreateHPA(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeleteHPA 删除 HPA
|
// DeleteHPA 删除 HPA
|
||||||
func (h *K8sHandler) DeleteHPA(c *gin.Context) {
|
func (h *KubernetesHandler) DeleteHPA(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
namespace := c.Param("namespace")
|
namespace := c.Param("namespace")
|
||||||
name := c.Param("name")
|
name := c.Param("name")
|
||||||
@@ -1136,7 +1136,7 @@ func (h *K8sHandler) DeleteHPA(c *gin.Context) {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// ListCronJobs 获取 CronJob 列表
|
// ListCronJobs 获取 CronJob 列表
|
||||||
func (h *K8sHandler) ListCronJobs(c *gin.Context) {
|
func (h *KubernetesHandler) ListCronJobs(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
|
||||||
client, _, err := h.getK8sClient(clusterID)
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
@@ -1155,7 +1155,7 @@ func (h *K8sHandler) ListCronJobs(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateCronJob 创建 CronJob
|
// CreateCronJob 创建 CronJob
|
||||||
func (h *K8sHandler) CreateCronJob(c *gin.Context) {
|
func (h *KubernetesHandler) CreateCronJob(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
|
||||||
var req k8sclient.CreateCronJobRequest
|
var req k8sclient.CreateCronJobRequest
|
||||||
@@ -1180,7 +1180,7 @@ func (h *K8sHandler) CreateCronJob(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SuspendCronJob 暂停/恢复 CronJob
|
// SuspendCronJob 暂停/恢复 CronJob
|
||||||
func (h *K8sHandler) SuspendCronJob(c *gin.Context) {
|
func (h *KubernetesHandler) SuspendCronJob(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
namespace := c.Param("namespace")
|
namespace := c.Param("namespace")
|
||||||
name := c.Param("name")
|
name := c.Param("name")
|
||||||
@@ -1209,7 +1209,7 @@ func (h *K8sHandler) SuspendCronJob(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeleteCronJob 删除 CronJob
|
// DeleteCronJob 删除 CronJob
|
||||||
func (h *K8sHandler) DeleteCronJob(c *gin.Context) {
|
func (h *KubernetesHandler) DeleteCronJob(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
namespace := c.Param("namespace")
|
namespace := c.Param("namespace")
|
||||||
name := c.Param("name")
|
name := c.Param("name")
|
||||||
@@ -1229,7 +1229,7 @@ func (h *K8sHandler) DeleteCronJob(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TriggerCronJob 手动触发 CronJob
|
// TriggerCronJob 手动触发 CronJob
|
||||||
func (h *K8sHandler) TriggerCronJob(c *gin.Context) {
|
func (h *KubernetesHandler) TriggerCronJob(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
namespace := c.Param("namespace")
|
namespace := c.Param("namespace")
|
||||||
name := c.Param("name")
|
name := c.Param("name")
|
||||||
@@ -1253,7 +1253,7 @@ func (h *K8sHandler) TriggerCronJob(c *gin.Context) {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// UpdateDeploymentImage 更新 Deployment 容器镜像 (CI webhook)
|
// UpdateDeploymentImage 更新 Deployment 容器镜像 (CI webhook)
|
||||||
func (h *K8sHandler) UpdateDeploymentImage(c *gin.Context) {
|
func (h *KubernetesHandler) UpdateDeploymentImage(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
namespace := c.Param("namespace")
|
namespace := c.Param("namespace")
|
||||||
name := c.Param("name")
|
name := c.Param("name")
|
||||||
@@ -1290,8 +1290,12 @@ func (h *K8sHandler) UpdateDeploymentImage(c *gin.Context) {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// ListNamespaces 获取命名空间列表
|
// ListNamespaces 获取命名空间列表
|
||||||
func (h *K8sHandler) ListNamespaces(c *gin.Context) {
|
func (h *KubernetesHandler) ListNamespaces(c *gin.Context) {
|
||||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
// Support clusterCode param from new routes
|
||||||
|
if clusterID == 0 {
|
||||||
|
clusterID = h.resolveClusterCode(c.Param("clusterCode"))
|
||||||
|
}
|
||||||
|
|
||||||
client, _, err := h.getK8sClient(clusterID)
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1308,6 +1312,355 @@ func (h *K8sHandler) ListNamespaces(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, models.Response{Code: 200, Data: namespaces})
|
c.JSON(http.StatusOK, models.Response{Code: 200, Data: namespaces})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// ConfigMap & Secret (Kuboard-style namespace resources)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// ListConfigMaps 获取命名空间下的 ConfigMap 列表
|
||||||
|
func (h *KubernetesHandler) ListConfigMaps(c *gin.Context) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 {
|
||||||
|
clusterID = h.resolveClusterCode(c.Param("clusterCode"))
|
||||||
|
}
|
||||||
|
namespace := c.Param("namespace")
|
||||||
|
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cms, err := client.ListConfigMaps(namespace)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "K8s API error: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, models.Response{Code: 200, Data: cms.Items})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSecrets 获取命名空间下的 Secret 列表(仅名称+类型,不含值)
|
||||||
|
func (h *KubernetesHandler) ListSecrets(c *gin.Context) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 {
|
||||||
|
clusterID = h.resolveClusterCode(c.Param("clusterCode"))
|
||||||
|
}
|
||||||
|
namespace := c.Param("namespace")
|
||||||
|
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets, err := client.ListSecrets(namespace)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, models.Response{Code: 500, Message: "K8s API error: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, models.Response{Code: 200, Data: secrets.Items})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Helpers
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// resolveClusterCode resolves a cluster code to its numeric ID
|
||||||
|
func (h *KubernetesHandler) resolveClusterCode(code string) int {
|
||||||
|
if code == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var cluster models.KubernetesCluster
|
||||||
|
if err := h.db.Where("code = ?", code).First(&cluster).Error; err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return int(cluster.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getK8sClientByCode resolves cluster by code, falls back to ID
|
||||||
|
func (h *KubernetesHandler) getK8sClientByCode(c *gin.Context) (*k8sclient.Client, *models.KubernetesCluster, error) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 {
|
||||||
|
clusterID = h.resolveClusterCode(c.Param("clusterCode"))
|
||||||
|
}
|
||||||
|
if clusterID == 0 {
|
||||||
|
return nil, nil, fmt.Errorf("cluster not found")
|
||||||
|
}
|
||||||
|
return h.getK8sClient(clusterID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// NS wrappers — read namespace from path (Kuboard-style routes)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
func (h *KubernetesHandler) ListDeploymentsNS(c *gin.Context) {
|
||||||
|
h.listDeploymentsWithNS(c, c.Param("namespace"))
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) ListPodsNS(c *gin.Context) {
|
||||||
|
h.listPodsWithNS(c, c.Param("namespace"))
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) ListServicesNS(c *gin.Context) {
|
||||||
|
h.listServicesWithNS(c, c.Param("namespace"))
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) ListIngressesNS(c *gin.Context) {
|
||||||
|
h.listIngressesWithNS(c, c.Param("namespace"))
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) ListHPAsNS(c *gin.Context) {
|
||||||
|
h.listHPAsWithNS(c, c.Param("namespace"))
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) CreateHPANS(c *gin.Context) {
|
||||||
|
h.createHPAWithNS(c, c.Param("namespace"))
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) DeleteHPANS(c *gin.Context) {
|
||||||
|
h.deleteHPAWithNS(c, c.Param("namespace"))
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) ListCronJobsNS(c *gin.Context) {
|
||||||
|
h.listCronJobsWithNS(c, c.Param("namespace"))
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) CreateCronJobNS(c *gin.Context) {
|
||||||
|
h.createCronJobWithNS(c, c.Param("namespace"))
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) SuspendCronJobNS(c *gin.Context) {
|
||||||
|
h.suspendCronJobWithNS(c, c.Param("namespace"))
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) TriggerCronJobNS(c *gin.Context) {
|
||||||
|
h.triggerCronJobWithNS(c, c.Param("namespace"))
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) DeleteCronJobNS(c *gin.Context) {
|
||||||
|
h.deleteCronJobWithNS(c, c.Param("namespace"))
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) ScaleDeploymentNS(c *gin.Context) {
|
||||||
|
h.scaleDeploymentWithNS(c, c.Param("namespace"))
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) RestartDeploymentNS(c *gin.Context) {
|
||||||
|
h.restartDeploymentWithNS(c, c.Param("namespace"))
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) UpdateDeploymentImageNS(c *gin.Context) {
|
||||||
|
h.updateDeploymentImageWithNS(c, c.Param("namespace"))
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) GetPodLogsNS(c *gin.Context) {
|
||||||
|
h.getPodLogsWithNS(c, c.Param("namespace"))
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) ExecInPodNS(c *gin.Context) {
|
||||||
|
h.execInPodWithNS(c, c.Param("namespace"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal helpers that resolve clusterCode and inject namespace
|
||||||
|
func (h *KubernetesHandler) listDeploymentsWithNS(c *gin.Context, ns string) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||||
|
deps, err := client.ListDeployments()
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return }
|
||||||
|
type DepSummary struct {
|
||||||
|
Namespace, Name string; Replicas, AvailableReplicas, ReadyReplicas int; Image string
|
||||||
|
}
|
||||||
|
result := []DepSummary{}
|
||||||
|
for _, d := range deps.Items {
|
||||||
|
if d.Metadata.Namespace != ns { continue }
|
||||||
|
image := ""; if len(d.Spec.Template.Spec.Containers) > 0 { image = d.Spec.Template.Spec.Containers[0].Image }
|
||||||
|
result = append(result, DepSummary{d.Metadata.Namespace, d.Metadata.Name, d.Spec.Replicas, d.Status.AvailableReplicas, d.Status.ReadyReplicas, image})
|
||||||
|
}
|
||||||
|
c.JSON(200, models.Response{Code: 200, Data: result})
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) listPodsWithNS(c *gin.Context, ns string) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||||
|
pods, err := client.ListPods()
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return }
|
||||||
|
type PodSummary struct { Namespace, Name, Status, NodeName, IP string; RestartCount int; Image string }
|
||||||
|
result := []PodSummary{}
|
||||||
|
for _, p := range pods.Items {
|
||||||
|
if p.Metadata.Namespace != ns { continue }
|
||||||
|
image := ""; if len(p.Spec.Containers) > 0 { image = p.Spec.Containers[0].Image }
|
||||||
|
restarts := 0; if len(p.Status.ContainerStatuses) > 0 { restarts = p.Status.ContainerStatuses[0].RestartCount }
|
||||||
|
result = append(result, PodSummary{p.Metadata.Namespace, p.Metadata.Name, p.Status.Phase, p.Spec.NodeName, p.Status.PodIP, restarts, image})
|
||||||
|
}
|
||||||
|
c.JSON(200, models.Response{Code: 200, Data: result})
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) listServicesWithNS(c *gin.Context, ns string) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||||
|
svcs, err := client.ListServices()
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return }
|
||||||
|
type SvcSummary struct { Namespace, Name, Type, ClusterIP string; Ports int }
|
||||||
|
result := []SvcSummary{}
|
||||||
|
for _, s := range svcs.Items {
|
||||||
|
if s.Metadata.Namespace != ns { continue }
|
||||||
|
result = append(result, SvcSummary{s.Metadata.Namespace, s.Metadata.Name, s.Spec.Type, s.Spec.ClusterIP, len(s.Spec.Ports)})
|
||||||
|
}
|
||||||
|
c.JSON(200, models.Response{Code: 200, Data: result})
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) listIngressesWithNS(c *gin.Context, ns string) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||||
|
ings, err := client.ListIngresses()
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return }
|
||||||
|
type IngSummary struct { Namespace, Name string; Hosts []string; TLS bool }
|
||||||
|
result := []IngSummary{}
|
||||||
|
for _, ing := range ings.Items {
|
||||||
|
if ing.Metadata.Namespace != ns { continue }
|
||||||
|
hosts := []string{}
|
||||||
|
for _, rule := range ing.Spec.Rules { if rule.Host != "" { hosts = append(hosts, rule.Host) } }
|
||||||
|
result = append(result, IngSummary{ing.Metadata.Namespace, ing.Metadata.Name, hosts, len(ing.Spec.TLS) > 0})
|
||||||
|
}
|
||||||
|
c.JSON(200, models.Response{Code: 200, Data: result})
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) listHPAsWithNS(c *gin.Context, ns string) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||||
|
hpas, err := client.ListHPAs()
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return }
|
||||||
|
filtered := []k8sclient.HPAItem{}
|
||||||
|
for _, h := range hpas.Items { if h.Metadata.Namespace == ns { filtered = append(filtered, h) } }
|
||||||
|
c.JSON(200, models.Response{Code: 200, Data: filtered})
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) createHPAWithNS(c *gin.Context, ns string) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||||
|
var req k8sclient.CreateHPARequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, models.Response{Code: 400, Message: "参数错误"}); return }
|
||||||
|
if req.Namespace == "" { req.Namespace = ns }
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||||
|
hpa, err := client.CreateHPA(req)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: "创建失败: " + err.Error()}); return }
|
||||||
|
c.JSON(200, models.Response{Code: 200, Data: hpa})
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) deleteHPAWithNS(c *gin.Context, ns string) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||||
|
if err := client.DeleteHPA(ns, c.Param("name")); err != nil { c.JSON(500, models.Response{Code: 500, Message: "删除失败: " + err.Error()}); return }
|
||||||
|
c.JSON(200, models.Response{Code: 200, Message: "已删除"})
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) listCronJobsWithNS(c *gin.Context, ns string) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||||
|
cjs, err := client.ListCronJobs()
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return }
|
||||||
|
filtered := []k8sclient.CronJobItem{}
|
||||||
|
for _, cj := range cjs.Items { if cj.Metadata.Namespace == ns { filtered = append(filtered, cj) } }
|
||||||
|
c.JSON(200, models.Response{Code: 200, Data: filtered})
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) createCronJobWithNS(c *gin.Context, ns string) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||||
|
var req k8sclient.CreateCronJobRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, models.Response{Code: 400, Message: "参数错误"}); return }
|
||||||
|
if req.Namespace == "" { req.Namespace = ns }
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||||
|
cj, err := client.CreateCronJob(req)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: "创建失败: " + err.Error()}); return }
|
||||||
|
c.JSON(200, models.Response{Code: 200, Data: cj})
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) suspendCronJobWithNS(c *gin.Context, ns string) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||||
|
var req struct { Suspend bool `json:"suspend"` }; c.ShouldBindJSON(&req)
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||||
|
if err := client.SuspendCronJob(ns, c.Param("name"), req.Suspend); err != nil { c.JSON(500, models.Response{Code: 500, Message: "操作失败: " + err.Error()}); return }
|
||||||
|
action := "恢复"; if req.Suspend { action = "暂停" }
|
||||||
|
c.JSON(200, models.Response{Code: 200, Message: "CronJob 已" + action})
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) triggerCronJobWithNS(c *gin.Context, ns string) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||||
|
if err := client.TriggerCronJob(ns, c.Param("name")); err != nil { c.JSON(500, models.Response{Code: 500, Message: "触发失败: " + err.Error()}); return }
|
||||||
|
c.JSON(200, models.Response{Code: 200, Message: "手动触发成功"})
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) deleteCronJobWithNS(c *gin.Context, ns string) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||||
|
if err := client.DeleteCronJob(ns, c.Param("name")); err != nil { c.JSON(500, models.Response{Code: 500, Message: "删除失败: " + err.Error()}); return }
|
||||||
|
c.JSON(200, models.Response{Code: 200, Message: "已删除"})
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) scaleDeploymentWithNS(c *gin.Context, ns string) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||||
|
var req struct { Replicas int `json:"replicas"` }
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, models.Response{Code: 400, Message: "参数错误"}); return }
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||||
|
if err := client.ScaleDeployment(ns, c.Param("name"), req.Replicas); err != nil { c.JSON(500, models.Response{Code: 500, Message: "扩缩容失败: " + err.Error()}); return }
|
||||||
|
c.JSON(200, models.Response{Code: 200, Message: fmt.Sprintf("已设置为 %d 副本", req.Replicas)})
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) restartDeploymentWithNS(c *gin.Context, ns string) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||||
|
if err := client.RestartDeployment(ns, c.Param("name")); err != nil { c.JSON(500, models.Response{Code: 500, Message: "重启失败: " + err.Error()}); return }
|
||||||
|
c.JSON(200, models.Response{Code: 200, Message: "重启指令已下发"})
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) updateDeploymentImageWithNS(c *gin.Context, ns string) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||||
|
var req struct { Image string `json:"image"`; ContainerName string `json:"container_name"` }
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, models.Response{Code: 400, Message: "参数错误"}); return }
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||||
|
if err := client.UpdateDeploymentImage(ns, c.Param("name"), req.ContainerName, req.Image); err != nil { c.JSON(500, models.Response{Code: 500, Message: "更新镜像失败: " + err.Error()}); return }
|
||||||
|
c.JSON(200, models.Response{Code: 200, Message: fmt.Sprintf("镜像已更新为 %s", req.Image)})
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) getPodLogsWithNS(c *gin.Context, ns string) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||||
|
podName := c.Param("podName")
|
||||||
|
container := c.Query("container")
|
||||||
|
tailLines, _ := strconv.Atoi(c.DefaultQuery("tail", "200"))
|
||||||
|
stream := c.Query("stream") == "true"
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||||
|
if stream {
|
||||||
|
body, err := client.GetPodLogsStream(ns, podName, container, tailLines)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: "获取日志流失败: " + err.Error()}); return }
|
||||||
|
defer body.Close()
|
||||||
|
c.Header("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
c.Header("Cache-Control", "no-cache")
|
||||||
|
c.Stream(func(w io.Writer) bool {
|
||||||
|
buf := make([]byte, 4096); n, err := body.Read(buf)
|
||||||
|
if n > 0 { w.Write(buf[:n]); return true }
|
||||||
|
return err == nil
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logs, err := client.GetPodLogs(ns, podName, container, tailLines)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: "获取日志失败: " + err.Error()}); return }
|
||||||
|
c.JSON(200, models.Response{Code: 200, Data: gin.H{"logs": logs, "pod": podName, "namespace": ns}})
|
||||||
|
}
|
||||||
|
func (h *KubernetesHandler) execInPodWithNS(c *gin.Context, ns string) {
|
||||||
|
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||||
|
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||||
|
podName := c.Param("podName")
|
||||||
|
var req struct { Command []string `json:"command"`; Container string `json:"container"` }
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, models.Response{Code: 400, Message: "参数错误"}); return }
|
||||||
|
client, _, err := h.getK8sClient(clusterID)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||||
|
result, err := client.ExecInPod(ns, podName, req.Container, req.Command)
|
||||||
|
if err != nil { c.JSON(500, models.Response{Code: 500, Message: "执行失败: " + err.Error()}); return }
|
||||||
|
c.JSON(200, models.Response{Code: 200, Data: result})
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Helpers
|
// Helpers
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -1342,8 +1695,8 @@ func parseMemResource(val string) int64 {
|
|||||||
|
|
||||||
|
|
||||||
// getK8sClient builds a K8s API client from cluster's stored kubeconfig
|
// getK8sClient builds a K8s API client from cluster's stored kubeconfig
|
||||||
func (h *K8sHandler) getK8sClient(clusterID int) (*k8sclient.Client, *models.K8sCluster, error) {
|
func (h *KubernetesHandler) getK8sClient(clusterID int) (*k8sclient.Client, *models.KubernetesCluster, error) {
|
||||||
var cluster models.K8sCluster
|
var cluster models.KubernetesCluster
|
||||||
if err := h.db.First(&cluster, clusterID).Error; err != nil {
|
if err := h.db.First(&cluster, clusterID).Error; err != nil {
|
||||||
return nil, nil, fmt.Errorf("cluster not found")
|
return nil, nil, fmt.Errorf("cluster not found")
|
||||||
}
|
}
|
||||||
@@ -65,8 +65,8 @@ func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config, c
|
|||||||
r.GET("/ws/metrics", middleware.JWTAuth(&cfg.JWT), metricsWSHandler.ServeWS)
|
r.GET("/ws/metrics", middleware.JWTAuth(&cfg.JWT), metricsWSHandler.ServeWS)
|
||||||
|
|
||||||
// WebSocket K8s exec 交互终端 // Logan.Li
|
// WebSocket K8s exec 交互终端 // Logan.Li
|
||||||
k8sHandler := handlers.NewK8sHandler(db)
|
k8sHandler := handlers.NewKubernetesHandler(db)
|
||||||
r.GET("/ws/k8s-exec/:id/namespaces/:namespace/pods/:podName", middleware.JWTAuth(&cfg.JWT), k8sHandler.ExecTerminal)
|
r.GET("/ws/kubernetes-exec/:id/namespaces/:namespace/pods/:podName", middleware.JWTAuth(&cfg.JWT), k8sHandler.ExecTerminal)
|
||||||
|
|
||||||
// 公开路由
|
// 公开路由
|
||||||
authHandler := handlers.NewUserHandler(db, cfg)
|
authHandler := handlers.NewUserHandler(db, cfg)
|
||||||
@@ -235,7 +235,7 @@ func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
// K8s集群路由
|
// K8s集群路由
|
||||||
clusters := authorized.Group("/k8s-clusters")
|
clusters := authorized.Group("/kubernetes-clusters")
|
||||||
{
|
{
|
||||||
clusters.GET("", k8sHandler.ListClusters)
|
clusters.GET("", k8sHandler.ListClusters)
|
||||||
clusters.GET("/:id", k8sHandler.GetCluster)
|
clusters.GET("/:id", k8sHandler.GetCluster)
|
||||||
@@ -275,6 +275,53 @@ func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config, c
|
|||||||
clusters.GET("/:id/namespaces", k8sHandler.ListNamespaces)
|
clusters.GET("/:id/namespaces", k8sHandler.ListNamespaces)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Kuboard-style namespace-scoped routes (/kubernetes/{clusterCode}/namespace/{ns}/...)
|
||||||
|
// ============================================================================
|
||||||
|
kubernetesRouter := authorized.Group("/kubernetes")
|
||||||
|
{
|
||||||
|
// Namespace overview
|
||||||
|
kubernetesRouter.GET("/:clusterCode/namespace/:namespace", func(c *gin.Context) {
|
||||||
|
k8sHandler.ListDeploymentsNS(c)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Deployments
|
||||||
|
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/deployments", k8sHandler.ListDeploymentsNS)
|
||||||
|
kubernetesRouter.POST("/:clusterCode/namespace/:namespace/deployments/:name/scale", k8sHandler.ScaleDeploymentNS)
|
||||||
|
kubernetesRouter.POST("/:clusterCode/namespace/:namespace/deployments/:name/restart", k8sHandler.RestartDeploymentNS)
|
||||||
|
kubernetesRouter.POST("/:clusterCode/namespace/:namespace/deployments/:name/update-image", k8sHandler.UpdateDeploymentImageNS)
|
||||||
|
|
||||||
|
// Pods
|
||||||
|
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/pods", k8sHandler.ListPodsNS)
|
||||||
|
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/pods/:podName/logs", k8sHandler.GetPodLogsNS)
|
||||||
|
kubernetesRouter.POST("/:clusterCode/namespace/:namespace/pods/:podName/exec", k8sHandler.ExecInPodNS)
|
||||||
|
|
||||||
|
// Services
|
||||||
|
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/services", k8sHandler.ListServicesNS)
|
||||||
|
|
||||||
|
// Ingresses
|
||||||
|
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/ingresses", k8sHandler.ListIngressesNS)
|
||||||
|
|
||||||
|
// HPA
|
||||||
|
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/hpas", k8sHandler.ListHPAsNS)
|
||||||
|
kubernetesRouter.POST("/:clusterCode/namespace/:namespace/hpas", k8sHandler.CreateHPANS)
|
||||||
|
kubernetesRouter.DELETE("/:clusterCode/namespace/:namespace/hpas/:name", k8sHandler.DeleteHPANS)
|
||||||
|
|
||||||
|
// CronJob
|
||||||
|
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/cronjobs", k8sHandler.ListCronJobsNS)
|
||||||
|
kubernetesRouter.POST("/:clusterCode/namespace/:namespace/cronjobs", k8sHandler.CreateCronJobNS)
|
||||||
|
kubernetesRouter.POST("/:clusterCode/namespace/:namespace/cronjobs/:name/suspend", k8sHandler.SuspendCronJobNS)
|
||||||
|
kubernetesRouter.POST("/:clusterCode/namespace/:namespace/cronjobs/:name/trigger", k8sHandler.TriggerCronJobNS)
|
||||||
|
kubernetesRouter.DELETE("/:clusterCode/namespace/:namespace/cronjobs/:name", k8sHandler.DeleteCronJobNS)
|
||||||
|
|
||||||
|
// ConfigMap / Secret
|
||||||
|
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/configmaps", k8sHandler.ListConfigMaps)
|
||||||
|
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/secrets", k8sHandler.ListSecrets)
|
||||||
|
|
||||||
|
// Namespace list
|
||||||
|
kubernetesRouter.GET("/:clusterCode/namespaces", k8sHandler.ListNamespaces)
|
||||||
|
}
|
||||||
|
|
||||||
// 告警路由
|
// 告警路由
|
||||||
alertHandler := handlers.NewAlertHandler(db)
|
alertHandler := handlers.NewAlertHandler(db)
|
||||||
alerts := authorized.Group("/alerts")
|
alerts := authorized.Group("/alerts")
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ type StatusRequest struct {
|
|||||||
// DashboardStats 仪表盘统计
|
// DashboardStats 仪表盘统计
|
||||||
type DashboardStats struct {
|
type DashboardStats struct {
|
||||||
ServerStats ServerStats `json:"server_stats"`
|
ServerStats ServerStats `json:"server_stats"`
|
||||||
K8sStats K8sClusterStats `json:"k8s_stats"`
|
K8sStats KubernetesClusterStats `json:"kubernetes_stats"`
|
||||||
AlertStats AlertStats `json:"alert_stats"`
|
AlertStats AlertStats `json:"alert_stats"`
|
||||||
PipelineStats PipelineStats `json:"pipeline_stats"`
|
PipelineStats PipelineStats `json:"pipeline_stats"`
|
||||||
CostStats CostStats `json:"cost_stats"`
|
CostStats CostStats `json:"cost_stats"`
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// K8sCluster K8s集群模型
|
// KubernetesCluster K8s集群模型
|
||||||
type K8sCluster struct {
|
type KubernetesCluster struct {
|
||||||
ID uint `gorm:"primarykey" json:"id"`
|
ID uint `gorm:"primarykey" json:"id"`
|
||||||
Name string `gorm:"size:128;not null" json:"name"`
|
Name string `gorm:"size:128;not null" json:"name"`
|
||||||
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
|
Code string `gorm:"size:64;uniqueIndex;not null" json:"code"`
|
||||||
@@ -31,12 +31,12 @@ type K8sCluster struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TableName 表名
|
// TableName 表名
|
||||||
func (K8sCluster) TableName() string {
|
func (KubernetesCluster) TableName() string {
|
||||||
return "container_clusters"
|
return "container_clusters"
|
||||||
}
|
}
|
||||||
|
|
||||||
// K8sNode K8s节点模型
|
// KubernetesNode K8s节点模型
|
||||||
type K8sNode struct {
|
type KubernetesNode struct {
|
||||||
ID uint `gorm:"primarykey" json:"id"`
|
ID uint `gorm:"primarykey" json:"id"`
|
||||||
ClusterID uint `gorm:"not null" json:"cluster_id"`
|
ClusterID uint `gorm:"not null" json:"cluster_id"`
|
||||||
Name string `gorm:"size:128;not null" json:"name"`
|
Name string `gorm:"size:128;not null" json:"name"`
|
||||||
@@ -58,16 +58,16 @@ type K8sNode struct {
|
|||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
// 关联
|
// 关联
|
||||||
Cluster *K8sCluster `json:"cluster,omitempty"`
|
Cluster *KubernetesCluster `json:"cluster,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName 表名
|
// TableName 表名
|
||||||
func (K8sNode) TableName() string {
|
func (KubernetesNode) TableName() string {
|
||||||
return "k8s_nodes"
|
return "kubernetes_nodes"
|
||||||
}
|
}
|
||||||
|
|
||||||
// K8sPod K8s Pod模型
|
// KubernetesPod K8s Pod模型
|
||||||
type K8sPod struct {
|
type KubernetesPod struct {
|
||||||
ID uint `gorm:"primarykey" json:"id"`
|
ID uint `gorm:"primarykey" json:"id"`
|
||||||
ClusterID uint `gorm:"not null" json:"cluster_id"`
|
ClusterID uint `gorm:"not null" json:"cluster_id"`
|
||||||
Namespace string `gorm:"size:64;not null" json:"namespace"`
|
Namespace string `gorm:"size:64;not null" json:"namespace"`
|
||||||
@@ -84,16 +84,16 @@ type K8sPod struct {
|
|||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
// 关联
|
// 关联
|
||||||
Cluster *K8sCluster `json:"cluster,omitempty"`
|
Cluster *KubernetesCluster `json:"cluster,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName 表名
|
// TableName 表名
|
||||||
func (K8sPod) TableName() string {
|
func (KubernetesPod) TableName() string {
|
||||||
return "k8s_pods"
|
return "kubernetes_pods"
|
||||||
}
|
}
|
||||||
|
|
||||||
// K8sDeployment K8s Deployment模型
|
// KubernetesDeployment K8s Deployment模型
|
||||||
type K8sDeployment struct {
|
type KubernetesDeployment struct {
|
||||||
ID uint `gorm:"primarykey" json:"id"`
|
ID uint `gorm:"primarykey" json:"id"`
|
||||||
ClusterID uint `gorm:"not null" json:"cluster_id"`
|
ClusterID uint `gorm:"not null" json:"cluster_id"`
|
||||||
Namespace string `gorm:"size:64;not null" json:"namespace"`
|
Namespace string `gorm:"size:64;not null" json:"namespace"`
|
||||||
@@ -108,16 +108,16 @@ type K8sDeployment struct {
|
|||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
// 关联
|
// 关联
|
||||||
Cluster *K8sCluster `json:"cluster,omitempty"`
|
Cluster *KubernetesCluster `json:"cluster,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName 表名
|
// TableName 表名
|
||||||
func (K8sDeployment) TableName() string {
|
func (KubernetesDeployment) TableName() string {
|
||||||
return "k8s_deployments"
|
return "kubernetes_deployments"
|
||||||
}
|
}
|
||||||
|
|
||||||
// K8sService K8s Service模型
|
// KubernetesService K8s Service模型
|
||||||
type K8sService struct {
|
type KubernetesService struct {
|
||||||
ID uint `gorm:"primarykey" json:"id"`
|
ID uint `gorm:"primarykey" json:"id"`
|
||||||
ClusterID uint `gorm:"not null" json:"cluster_id"`
|
ClusterID uint `gorm:"not null" json:"cluster_id"`
|
||||||
Namespace string `gorm:"size:64;not null" json:"namespace"`
|
Namespace string `gorm:"size:64;not null" json:"namespace"`
|
||||||
@@ -132,16 +132,16 @@ type K8sService struct {
|
|||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
// 关联
|
// 关联
|
||||||
Cluster *K8sCluster `json:"cluster,omitempty"`
|
Cluster *KubernetesCluster `json:"cluster,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName 表名
|
// TableName 表名
|
||||||
func (K8sService) TableName() string {
|
func (KubernetesService) TableName() string {
|
||||||
return "k8s_services"
|
return "kubernetes_services"
|
||||||
}
|
}
|
||||||
|
|
||||||
// K8sIngress K8s Ingress模型
|
// KubernetesIngress K8s Ingress模型
|
||||||
type K8sIngress struct {
|
type KubernetesIngress struct {
|
||||||
ID uint `gorm:"primarykey" json:"id"`
|
ID uint `gorm:"primarykey" json:"id"`
|
||||||
ClusterID uint `gorm:"not null" json:"cluster_id"`
|
ClusterID uint `gorm:"not null" json:"cluster_id"`
|
||||||
Namespace string `gorm:"size:64;not null" json:"namespace"`
|
Namespace string `gorm:"size:64;not null" json:"namespace"`
|
||||||
@@ -155,16 +155,16 @@ type K8sIngress struct {
|
|||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
// 关联
|
// 关联
|
||||||
Cluster *K8sCluster `json:"cluster,omitempty"`
|
Cluster *KubernetesCluster `json:"cluster,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName 表名
|
// TableName 表名
|
||||||
func (K8sIngress) TableName() string {
|
func (KubernetesIngress) TableName() string {
|
||||||
return "k8s_ingresses"
|
return "kubernetes_ingresses"
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateK8sClusterRequest 创建K8s集群请求
|
// CreateKubernetesClusterRequest 创建K8s集群请求
|
||||||
type CreateK8sClusterRequest struct {
|
type CreateKubernetesClusterRequest struct {
|
||||||
Name string `json:"name" binding:"required"`
|
Name string `json:"name" binding:"required"`
|
||||||
Code string `json:"code" binding:"required"`
|
Code string `json:"code" binding:"required"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
@@ -176,8 +176,8 @@ type CreateK8sClusterRequest struct {
|
|||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// K8sClusterStats K8s集群统计
|
// KubernetesClusterStats K8s集群统计
|
||||||
type K8sClusterStats struct {
|
type KubernetesClusterStats struct {
|
||||||
Total int64 `json:"total"`
|
Total int64 `json:"total"`
|
||||||
Healthy int64 `json:"healthy"`
|
Healthy int64 `json:"healthy"`
|
||||||
Warning int64 `json:"warning"`
|
Warning int64 `json:"warning"`
|
||||||
@@ -66,12 +66,12 @@ func AutoMigrate(db *gorm.DB) error {
|
|||||||
&models.CloudAccount{},
|
&models.CloudAccount{},
|
||||||
&models.Region{},
|
&models.Region{},
|
||||||
&models.Domain{},
|
&models.Domain{},
|
||||||
&models.K8sCluster{},
|
&models.KubernetesCluster{},
|
||||||
&models.K8sNode{},
|
&models.KubernetesNode{},
|
||||||
&models.K8sPod{},
|
&models.KubernetesPod{},
|
||||||
&models.K8sDeployment{},
|
&models.KubernetesDeployment{},
|
||||||
&models.K8sService{},
|
&models.KubernetesService{},
|
||||||
&models.K8sIngress{},
|
&models.KubernetesIngress{},
|
||||||
&models.AlertRule{},
|
&models.AlertRule{},
|
||||||
&models.AlertEvent{},
|
&models.AlertEvent{},
|
||||||
&models.NotifyChannel{},
|
&models.NotifyChannel{},
|
||||||
|
|||||||
@@ -880,3 +880,68 @@ func (c *Client) ListNamespaces() ([]string, error) {
|
|||||||
}
|
}
|
||||||
return names, nil
|
return names, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- ConfigMap ---
|
||||||
|
|
||||||
|
// ConfigMapList response
|
||||||
|
type ConfigMapList struct {
|
||||||
|
Items []ConfigMapItem `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigMapItem represents a ConfigMap
|
||||||
|
type ConfigMapItem struct {
|
||||||
|
Metadata ObjectMeta `json:"metadata"`
|
||||||
|
Data map[string]string `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListConfigMaps fetches ConfigMaps in a namespace
|
||||||
|
func (c *Client) ListConfigMaps(namespace string) (*ConfigMapList, error) {
|
||||||
|
var result ConfigMapList
|
||||||
|
path := fmt.Sprintf("/api/v1/namespaces/%s/configmaps", namespace)
|
||||||
|
if err := c.get(path, &result); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Secret ---
|
||||||
|
|
||||||
|
// SecretList response
|
||||||
|
type SecretList struct {
|
||||||
|
Items []SecretItem `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecretItem represents a Secret (return keys only, not values)
|
||||||
|
type SecretItem struct {
|
||||||
|
Metadata ObjectMeta `json:"metadata"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
DataKeys []string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSecrets fetches Secrets in a namespace (names+types only, no data)
|
||||||
|
func (c *Client) ListSecrets(namespace string) (*SecretList, error) {
|
||||||
|
var result struct {
|
||||||
|
Items []struct {
|
||||||
|
Metadata ObjectMeta `json:"metadata"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Data map[string]string `json:"data"`
|
||||||
|
} `json:"items"`
|
||||||
|
}
|
||||||
|
path := fmt.Sprintf("/api/v1/namespaces/%s/secrets", namespace)
|
||||||
|
if err := c.get(path, &result); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items := make([]SecretItem, len(result.Items))
|
||||||
|
for i, item := range result.Items {
|
||||||
|
keys := make([]string, 0, len(item.Data))
|
||||||
|
for k := range item.Data {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
items[i] = SecretItem{
|
||||||
|
Metadata: item.Metadata,
|
||||||
|
Type: item.Type,
|
||||||
|
DataKeys: keys,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &SecretList{Items: items}, nil
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@ func generateMockData(db *gorm.DB) error {
|
|||||||
if err := generateDomains(db); err != nil {
|
if err := generateDomains(db); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := generateK8sClusters(db); err != nil {
|
if err := generateKubernetesClusters(db); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := generateAlertRules(db); err != nil {
|
if err := generateAlertRules(db); err != nil {
|
||||||
@@ -164,8 +164,8 @@ func generateDomains(db *gorm.DB) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateK8sClusters(db *gorm.DB) error {
|
func generateKubernetesClusters(db *gorm.DB) error {
|
||||||
clusters := []models.K8sCluster{
|
clusters := []models.KubernetesCluster{
|
||||||
{Name: "prod-cluster", Code: "prod", Version: "v1.28.0", Status: "healthy", NodeCount: 10, PodCount: 150, ServiceCount: 30, DeploymentCount: 15, APIEndpoint: "https://prod-api.ezops.local:6443", CPUUsage: 65.5, MemoryUsage: 72.3, Description: "生产环境 Kubernetes 集群"},
|
{Name: "prod-cluster", Code: "prod", Version: "v1.28.0", Status: "healthy", NodeCount: 10, PodCount: 150, ServiceCount: 30, DeploymentCount: 15, APIEndpoint: "https://prod-api.ezops.local:6443", CPUUsage: 65.5, MemoryUsage: 72.3, Description: "生产环境 Kubernetes 集群"},
|
||||||
{Name: "staging-cluster", Code: "staging", Version: "v1.28.0", Status: "healthy", NodeCount: 5, PodCount: 80, ServiceCount: 15, DeploymentCount: 10, APIEndpoint: "https://staging-api.ezops.local:6443", CPUUsage: 45.2, MemoryUsage: 55.0, Description: "预发环境 Kubernetes 集群"},
|
{Name: "staging-cluster", Code: "staging", Version: "v1.28.0", Status: "healthy", NodeCount: 5, PodCount: 80, ServiceCount: 15, DeploymentCount: 10, APIEndpoint: "https://staging-api.ezops.local:6443", CPUUsage: 45.2, MemoryUsage: 55.0, Description: "预发环境 Kubernetes 集群"},
|
||||||
{Name: "dev-cluster", Code: "dev", Version: "v1.27.0", Status: "warning", NodeCount: 3, PodCount: 45, ServiceCount: 8, DeploymentCount: 8, APIEndpoint: "https://dev-api.ezops.local:6443", CPUUsage: 80.1, MemoryUsage: 85.0, Description: "开发环境 Kubernetes 集群"},
|
{Name: "dev-cluster", Code: "dev", Version: "v1.27.0", Status: "warning", NodeCount: 3, PodCount: 45, ServiceCount: 8, DeploymentCount: 8, APIEndpoint: "https://dev-api.ezops.local:6443", CPUUsage: 80.1, MemoryUsage: 85.0, Description: "开发环境 Kubernetes 集群"},
|
||||||
@@ -179,7 +179,7 @@ func generateK8sClusters(db *gorm.DB) error {
|
|||||||
for _, cluster := range clusters {
|
for _, cluster := range clusters {
|
||||||
// 生成节点
|
// 生成节点
|
||||||
for j := 0; j < cluster.NodeCount; j++ {
|
for j := 0; j < cluster.NodeCount; j++ {
|
||||||
node := models.K8sNode{
|
node := models.KubernetesNode{
|
||||||
ClusterID: cluster.ID,
|
ClusterID: cluster.ID,
|
||||||
Name: fmt.Sprintf("%s-node-%d", cluster.Code, j+1),
|
Name: fmt.Sprintf("%s-node-%d", cluster.Code, j+1),
|
||||||
IP: fmt.Sprintf("192.168.%d.%d", rand.Intn(256), rand.Intn(256)),
|
IP: fmt.Sprintf("192.168.%d.%d", rand.Intn(256), rand.Intn(256)),
|
||||||
@@ -227,7 +227,7 @@ func generateK8sClusters(db *gorm.DB) error {
|
|||||||
if available < 0 {
|
if available < 0 {
|
||||||
available = 0
|
available = 0
|
||||||
}
|
}
|
||||||
dep := models.K8sDeployment{
|
dep := models.KubernetesDeployment{
|
||||||
ClusterID: cluster.ID,
|
ClusterID: cluster.ID,
|
||||||
Namespace: namespaces[rand.Intn(len(namespaces))],
|
Namespace: namespaces[rand.Intn(len(namespaces))],
|
||||||
Name: depNames[d],
|
Name: depNames[d],
|
||||||
@@ -253,7 +253,7 @@ func generateK8sClusters(db *gorm.DB) error {
|
|||||||
}
|
}
|
||||||
for p := 0; p < numPods; p++ {
|
for p := 0; p < numPods; p++ {
|
||||||
status := podStatuses[rand.Intn(len(podStatuses))]
|
status := podStatuses[rand.Intn(len(podStatuses))]
|
||||||
pod := models.K8sPod{
|
pod := models.KubernetesPod{
|
||||||
ClusterID: cluster.ID,
|
ClusterID: cluster.ID,
|
||||||
Namespace: namespaces[rand.Intn(len(namespaces))],
|
Namespace: namespaces[rand.Intn(len(namespaces))],
|
||||||
Name: fmt.Sprintf("%s-pod-%d", cluster.Code, p+1),
|
Name: fmt.Sprintf("%s-pod-%d", cluster.Code, p+1),
|
||||||
@@ -297,7 +297,7 @@ func generateK8sClusters(db *gorm.DB) error {
|
|||||||
default:
|
default:
|
||||||
ports = `[{"port":3306,"protocol":"TCP","targetPort":3306}]`
|
ports = `[{"port":3306,"protocol":"TCP","targetPort":3306}]`
|
||||||
}
|
}
|
||||||
svc := models.K8sService{
|
svc := models.KubernetesService{
|
||||||
ClusterID: cluster.ID,
|
ClusterID: cluster.ID,
|
||||||
Namespace: namespaces[rand.Intn(len(namespaces))],
|
Namespace: namespaces[rand.Intn(len(namespaces))],
|
||||||
Name: svcNames[s],
|
Name: svcNames[s],
|
||||||
@@ -323,7 +323,7 @@ func generateK8sClusters(db *gorm.DB) error {
|
|||||||
numIngs := 2 + rand.Intn(3)
|
numIngs := 2 + rand.Intn(3)
|
||||||
for ig := 0; ig < numIngs && ig < len(ingNames); ig++ {
|
for ig := 0; ig < numIngs && ig < len(ingNames); ig++ {
|
||||||
hosts := fmt.Sprintf(`["%s.ezops.local"]`, ingNames[ig])
|
hosts := fmt.Sprintf(`["%s.ezops.local"]`, ingNames[ig])
|
||||||
ing := models.K8sIngress{
|
ing := models.KubernetesIngress{
|
||||||
ClusterID: cluster.ID,
|
ClusterID: cluster.ID,
|
||||||
Namespace: namespaces[rand.Intn(len(namespaces))],
|
Namespace: namespaces[rand.Intn(len(namespaces))],
|
||||||
Name: ingNames[ig],
|
Name: ingNames[ig],
|
||||||
|
|||||||
+10
-10
@@ -1,5 +1,5 @@
|
|||||||
// Standalone script: seed K8s Pods/Deployments/Services/Ingresses for existing clusters.
|
// Standalone script: seed K8s Pods/Deployments/Services/Ingresses for existing clusters.
|
||||||
// Usage: go run scripts/seed_k8s_resources.go
|
// Usage: go run scripts/seed_kubernetes_resources.go
|
||||||
//go:build ignore
|
//go:build ignore
|
||||||
|
|
||||||
package main
|
package main
|
||||||
@@ -23,7 +23,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
var clusters []models.K8sCluster
|
var clusters []models.KubernetesCluster
|
||||||
db.Find(&clusters)
|
db.Find(&clusters)
|
||||||
if len(clusters) == 0 {
|
if len(clusters) == 0 {
|
||||||
fmt.Println("No clusters found — run mock.go first")
|
fmt.Println("No clusters found — run mock.go first")
|
||||||
@@ -54,7 +54,7 @@ func main() {
|
|||||||
for _, cluster := range clusters {
|
for _, cluster := range clusters {
|
||||||
// Skip if pods already exist for this cluster
|
// Skip if pods already exist for this cluster
|
||||||
var podCount int64
|
var podCount int64
|
||||||
db.Model(&models.K8sPod{}).Where("cluster_id = ?", cluster.ID).Count(&podCount)
|
db.Model(&models.KubernetesPod{}).Where("cluster_id = ?", cluster.ID).Count(&podCount)
|
||||||
if podCount > 0 {
|
if podCount > 0 {
|
||||||
fmt.Printf("Cluster %s already has %d pods, skipping\n", cluster.Name, podCount)
|
fmt.Printf("Cluster %s already has %d pods, skipping\n", cluster.Name, podCount)
|
||||||
continue
|
continue
|
||||||
@@ -79,7 +79,7 @@ func main() {
|
|||||||
if available < 0 {
|
if available < 0 {
|
||||||
available = 0
|
available = 0
|
||||||
}
|
}
|
||||||
db.Create(&models.K8sDeployment{
|
db.Create(&models.KubernetesDeployment{
|
||||||
ClusterID: cluster.ID,
|
ClusterID: cluster.ID,
|
||||||
Namespace: namespaces[rand.Intn(len(namespaces))],
|
Namespace: namespaces[rand.Intn(len(namespaces))],
|
||||||
Name: depNames[d],
|
Name: depNames[d],
|
||||||
@@ -94,7 +94,7 @@ func main() {
|
|||||||
// Pods
|
// Pods
|
||||||
numPods := 12 + rand.Intn(18)
|
numPods := 12 + rand.Intn(18)
|
||||||
for p := 0; p < numPods; p++ {
|
for p := 0; p < numPods; p++ {
|
||||||
db.Create(&models.K8sPod{
|
db.Create(&models.KubernetesPod{
|
||||||
ClusterID: cluster.ID,
|
ClusterID: cluster.ID,
|
||||||
Namespace: namespaces[rand.Intn(len(namespaces))],
|
Namespace: namespaces[rand.Intn(len(namespaces))],
|
||||||
Name: fmt.Sprintf("%s-pod-%d", cluster.Code, p+1),
|
Name: fmt.Sprintf("%s-pod-%d", cluster.Code, p+1),
|
||||||
@@ -126,7 +126,7 @@ func main() {
|
|||||||
default:
|
default:
|
||||||
ports = `[{"port":3306,"protocol":"TCP","targetPort":3306}]`
|
ports = `[{"port":3306,"protocol":"TCP","targetPort":3306}]`
|
||||||
}
|
}
|
||||||
svc := models.K8sService{
|
svc := models.KubernetesService{
|
||||||
ClusterID: cluster.ID,
|
ClusterID: cluster.ID,
|
||||||
Namespace: namespaces[rand.Intn(len(namespaces))],
|
Namespace: namespaces[rand.Intn(len(namespaces))],
|
||||||
Name: svcNames[s],
|
Name: svcNames[s],
|
||||||
@@ -150,7 +150,7 @@ func main() {
|
|||||||
numIngs = len(ingNames)
|
numIngs = len(ingNames)
|
||||||
}
|
}
|
||||||
for ig := 0; ig < numIngs; ig++ {
|
for ig := 0; ig < numIngs; ig++ {
|
||||||
db.Create(&models.K8sIngress{
|
db.Create(&models.KubernetesIngress{
|
||||||
ClusterID: cluster.ID,
|
ClusterID: cluster.ID,
|
||||||
Namespace: namespaces[rand.Intn(len(namespaces))],
|
Namespace: namespaces[rand.Intn(len(namespaces))],
|
||||||
Name: ingNames[ig],
|
Name: ingNames[ig],
|
||||||
@@ -162,9 +162,9 @@ func main() {
|
|||||||
|
|
||||||
// Update cluster counts to match actual data
|
// Update cluster counts to match actual data
|
||||||
var depCnt, podCnt, svcCnt int64
|
var depCnt, podCnt, svcCnt int64
|
||||||
db.Model(&models.K8sDeployment{}).Where("cluster_id = ?", cluster.ID).Count(&depCnt)
|
db.Model(&models.KubernetesDeployment{}).Where("cluster_id = ?", cluster.ID).Count(&depCnt)
|
||||||
db.Model(&models.K8sPod{}).Where("cluster_id = ?", cluster.ID).Count(&podCnt)
|
db.Model(&models.KubernetesPod{}).Where("cluster_id = ?", cluster.ID).Count(&podCnt)
|
||||||
db.Model(&models.K8sService{}).Where("cluster_id = ?", cluster.ID).Count(&svcCnt)
|
db.Model(&models.KubernetesService{}).Where("cluster_id = ?", cluster.ID).Count(&svcCnt)
|
||||||
db.Model(&cluster).Updates(map[string]interface{}{
|
db.Model(&cluster).Updates(map[string]interface{}{
|
||||||
"deployment_count": depCnt,
|
"deployment_count": depCnt,
|
||||||
})
|
})
|
||||||
@@ -12,10 +12,10 @@ import { DomainManager } from '@/components/cloud/DomainManager';
|
|||||||
import { RegionManager } from '@/components/cloud/RegionManager';
|
import { RegionManager } from '@/components/cloud/RegionManager';
|
||||||
import { PlatformManager } from '@/components/cloud/PlatformManager';
|
import { PlatformManager } from '@/components/cloud/PlatformManager';
|
||||||
import { CloudBilling } from '@/components/cloud/CloudBilling';
|
import { CloudBilling } from '@/components/cloud/CloudBilling';
|
||||||
import { K8sClusters } from '@/components/k8s/K8sClusters';
|
import { KubernetesClusters } from '@/components/kubernetes/KubernetesClusters';
|
||||||
import { K8sNodes } from '@/components/k8s/K8sNodes';
|
import { KubernetesNodes } from '@/components/kubernetes/KubernetesNodes';
|
||||||
import { K8sWorkloads } from '@/components/k8s/K8sWorkloads';
|
import { KubernetesWorkloads } from '@/components/kubernetes/KubernetesWorkloads';
|
||||||
import { TopologyView } from '@/components/k8s/TopologyView';
|
import { TopologyView } from '@/components/kubernetes/TopologyView';
|
||||||
import { AlertManager } from '@/components/monitor/AlertManager';
|
import { AlertManager } from '@/components/monitor/AlertManager';
|
||||||
import { AppReleaseManager } from '@/components/deploy/AppRelease';
|
import { AppReleaseManager } from '@/components/deploy/AppRelease';
|
||||||
import { ApprovalFlowManager } from '@/components/approval/ApprovalFlow';
|
import { ApprovalFlowManager } from '@/components/approval/ApprovalFlow';
|
||||||
@@ -65,9 +65,9 @@ function App() {
|
|||||||
<Route path="cloud-billing" element={<CloudBilling />} />
|
<Route path="cloud-billing" element={<CloudBilling />} />
|
||||||
{/* 资源监控(Agent 指标 + 中间件 + SNMP + 拨测) */}
|
{/* 资源监控(Agent 指标 + 中间件 + SNMP + 拨测) */}
|
||||||
<Route path="monitor-center" element={<MonitorCenter />} />
|
<Route path="monitor-center" element={<MonitorCenter />} />
|
||||||
<Route path="clusters" element={<K8sClusters />} />
|
<Route path="clusters" element={<KubernetesClusters />} />
|
||||||
<Route path="nodes" element={<K8sNodes />} />
|
<Route path="nodes" element={<KubernetesNodes />} />
|
||||||
<Route path="workloads" element={<K8sWorkloads />} />
|
<Route path="workloads" element={<KubernetesWorkloads />} />
|
||||||
<Route path="topology" element={<TopologyView />} />
|
<Route path="topology" element={<TopologyView />} />
|
||||||
{/* 应用发布 */}
|
{/* 应用发布 */}
|
||||||
<Route path="pipelines" element={<PipelineManager />} />
|
<Route path="pipelines" element={<PipelineManager />} />
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ async function loadReportContext(): Promise<string> {
|
|||||||
dashboardStatsApi.getReleases(10).catch(() => null),
|
dashboardStatsApi.getReleases(10).catch(() => null),
|
||||||
]);
|
]);
|
||||||
const s: any = stats || {};
|
const s: any = stats || {};
|
||||||
const ss = s.server_stats || {}, ks = s.k8s_stats || {}, as = s.alert_stats || {}, ps = s.pipeline_stats || {}, cs = s.cost_stats || {};
|
const ss = s.server_stats || {}, ks = s.kubernetes_stats || {}, as = s.alert_stats || {}, ps = s.pipeline_stats || {}, cs = s.cost_stats || {};
|
||||||
const overview = `服务器:${ss.total ?? 0}(运行${ss.running ?? 0}) | K8s集群:${ks.total ?? 0} 节点:${ks.total_nodes ?? 0} Pod:${ks.total_pods ?? 0} | 告警:${as.total ?? 0}(firing ${as.firing ?? 0}) | 流水线:${ps.total ?? 0}(成功率${ps.success_rate ?? 0}%) | 本月成本:¥${cs.month_cost ?? 0}`;
|
const overview = `服务器:${ss.total ?? 0}(运行${ss.running ?? 0}) | K8s集群:${ks.total ?? 0} 节点:${ks.total_nodes ?? 0} Pod:${ks.total_pods ?? 0} | 告警:${as.total ?? 0}(firing ${as.firing ?? 0}) | 流水线:${ps.total ?? 0}(成功率${ps.success_rate ?? 0}%) | 本月成本:¥${cs.month_cost ?? 0}`;
|
||||||
const alertList: any[] = Array.isArray(alerts) ? alerts : (alerts as any)?.list || [];
|
const alertList: any[] = Array.isArray(alerts) ? alerts : (alerts as any)?.list || [];
|
||||||
const releaseList: any[] = Array.isArray(releases) ? releases : (releases as any)?.list || [];
|
const releaseList: any[] = Array.isArray(releases) ? releases : (releases as any)?.list || [];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Clock, Server, Cloud, Bell, DollarSign } from 'lucide-react';
|
import { Clock, Server, Cloud, Bell, DollarSign } from 'lucide-react';
|
||||||
import { dashboardStatsApi, serverApi, k8sApi } from '@/services/api';
|
import { dashboardStatsApi, serverApi, kubernetesApi } from '@/services/api';
|
||||||
import { StatCard } from './DashboardStats';
|
import { StatCard } from './DashboardStats';
|
||||||
import { CostOverviewCard, TopResourceUsageCard, ResourceUsageCard, computeMonthChange } from './DashboardCharts';
|
import { CostOverviewCard, TopResourceUsageCard, ResourceUsageCard, computeMonthChange } from './DashboardCharts';
|
||||||
import {
|
import {
|
||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
CertificateExpiryCard,
|
CertificateExpiryCard,
|
||||||
ReleaseHistoryCard,
|
ReleaseHistoryCard,
|
||||||
ServerStatusCard,
|
ServerStatusCard,
|
||||||
K8sClusterCard,
|
KubernetesClusterCard,
|
||||||
} from './DashboardRecent';
|
} from './DashboardRecent';
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
@@ -35,7 +35,7 @@ export function Dashboard() {
|
|||||||
console.error('Failed to fetch servers:', e);
|
console.error('Failed to fetch servers:', e);
|
||||||
return null;
|
return null;
|
||||||
}),
|
}),
|
||||||
k8sApi.getClusters().catch((e) => {
|
kubernetesApi.getClusters().catch((e) => {
|
||||||
console.error('Failed to fetch k8s clusters:', e);
|
console.error('Failed to fetch k8s clusters:', e);
|
||||||
return null;
|
return null;
|
||||||
}),
|
}),
|
||||||
@@ -82,7 +82,7 @@ export function Dashboard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const serverStats = stats?.server_stats;
|
const serverStats = stats?.server_stats;
|
||||||
const k8sStats = stats?.k8s_stats;
|
const k8sStats = stats?.kubernetes_stats;
|
||||||
const alertStats = stats?.alert_stats;
|
const alertStats = stats?.alert_stats;
|
||||||
const costStats = stats?.cost_stats;
|
const costStats = stats?.cost_stats;
|
||||||
const costChange = computeMonthChange(costStats?.month_trend);
|
const costChange = computeMonthChange(costStats?.month_trend);
|
||||||
@@ -165,7 +165,7 @@ export function Dashboard() {
|
|||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
<ReleaseHistoryCard releases={releases} />
|
<ReleaseHistoryCard releases={releases} />
|
||||||
<ServerStatusCard servers={servers} />
|
<ServerStatusCard servers={servers} />
|
||||||
<K8sClusterCard clusters={clusters} />
|
<KubernetesClusterCard clusters={clusters} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ export function ServerStatusCard({ servers }: { servers: any[] }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// K8s集群状态卡片
|
// K8s集群状态卡片
|
||||||
export function K8sClusterCard({ clusters }: { clusters: any[] }) {
|
export function KubernetesClusterCard({ clusters }: { clusters: any[] }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
return (
|
return (
|
||||||
<Card className="bg-slate-900 border-slate-800">
|
<Card className="bg-slate-900 border-slate-800">
|
||||||
|
|||||||
+18
-18
@@ -14,17 +14,17 @@ import {
|
|||||||
Gauge, Clock, Play, Pause, Trash2, RefreshCw, Loader2,
|
Gauge, Clock, Play, Pause, Trash2, RefreshCw, Loader2,
|
||||||
CheckCircle, AlertTriangle, XCircle, ExternalLink,
|
CheckCircle, AlertTriangle, XCircle, ExternalLink,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { k8sApi } from '@/services/api';
|
import { kubernetesApi } from '@/services/api';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
|
|
||||||
interface DeploymentDetailProps {
|
interface DeploymentDetailProps {
|
||||||
clusterId: string;
|
clusterCode: string;
|
||||||
namespace: string;
|
namespace: string;
|
||||||
deploymentName: string;
|
deploymentName: string;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack }: DeploymentDetailProps) {
|
export function DeploymentDetail({ clusterCode, namespace, deploymentName, onBack }: DeploymentDetailProps) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [deployment, setDeployment] = useState<any>(null);
|
const [deployment, setDeployment] = useState<any>(null);
|
||||||
const [pods, setPods] = useState<any[]>([]);
|
const [pods, setPods] = useState<any[]>([]);
|
||||||
@@ -47,9 +47,9 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const [depRes, podRes, hpaRes] = await Promise.all([
|
const [depRes, podRes, hpaRes] = await Promise.all([
|
||||||
k8sApi.getDeployments(clusterId).catch(() => []),
|
kubernetesApi.getNamespaceDeployments(clusterCode, namespace).catch(() => []),
|
||||||
k8sApi.getPods(clusterId).catch(() => []),
|
kubernetesApi.getNamespacePods(clusterCode, namespace).catch(() => []),
|
||||||
k8sApi.getHPAs(clusterId).catch(() => ({ items: [] })),
|
kubernetesApi.getHPAs(clusterCode, namespace).catch(() => ({ items: [] })),
|
||||||
]);
|
]);
|
||||||
const toArray = (v: any) => (Array.isArray(v) ? v : (v?.list || v?.items || []));
|
const toArray = (v: any) => (Array.isArray(v) ? v : (v?.list || v?.items || []));
|
||||||
const allDeps = toArray(depRes);
|
const allDeps = toArray(depRes);
|
||||||
@@ -80,7 +80,7 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [clusterId, namespace, deploymentName]);
|
}, [clusterCode, namespace, deploymentName]);
|
||||||
|
|
||||||
useEffect(() => { loadData(); }, [loadData]);
|
useEffect(() => { loadData(); }, [loadData]);
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack
|
|||||||
if (newReplicas < 0) return;
|
if (newReplicas < 0) return;
|
||||||
setActionLoading('scale');
|
setActionLoading('scale');
|
||||||
try {
|
try {
|
||||||
await k8sApi.scaleDeployment(clusterId, namespace, deploymentName, newReplicas);
|
await kubernetesApi.scaleDeployment(clusterCode, namespace, deploymentName, newReplicas);
|
||||||
toast({ title: '扩缩容成功', description: `副本数已设置为 ${newReplicas}` });
|
toast({ title: '扩缩容成功', description: `副本数已设置为 ${newReplicas}` });
|
||||||
loadData();
|
loadData();
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -100,7 +100,7 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack
|
|||||||
const handleRestart = async () => {
|
const handleRestart = async () => {
|
||||||
setActionLoading('restart');
|
setActionLoading('restart');
|
||||||
try {
|
try {
|
||||||
await k8sApi.restartDeployment(clusterId, namespace, deploymentName);
|
await kubernetesApi.restartDeployment(clusterCode, namespace, deploymentName);
|
||||||
toast({ title: '重启指令已下发', description: 'Deployment 正在滚动重启' });
|
toast({ title: '重启指令已下发', description: 'Deployment 正在滚动重启' });
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
toast({ title: '重启失败', description: e?.message || String(e), variant: 'destructive' });
|
toast({ title: '重启失败', description: e?.message || String(e), variant: 'destructive' });
|
||||||
@@ -111,7 +111,7 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack
|
|||||||
if (!newImage.trim()) return;
|
if (!newImage.trim()) return;
|
||||||
setActionLoading('image');
|
setActionLoading('image');
|
||||||
try {
|
try {
|
||||||
await k8sApi.updateDeploymentImage(clusterId, namespace, deploymentName, newImage);
|
await kubernetesApi.updateDeploymentImage(clusterCode, namespace, deploymentName, newImage);
|
||||||
toast({ title: '镜像更新成功', description: `正在滚动更新为 ${newImage}` });
|
toast({ title: '镜像更新成功', description: `正在滚动更新为 ${newImage}` });
|
||||||
setShowImageUpdate(false);
|
setShowImageUpdate(false);
|
||||||
loadData();
|
loadData();
|
||||||
@@ -125,7 +125,7 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack
|
|||||||
setShowLogs(true);
|
setShowLogs(true);
|
||||||
setLogs('');
|
setLogs('');
|
||||||
try {
|
try {
|
||||||
const res: any = await k8sApi.getPodLogs(clusterId, namespace, podName, logTail);
|
const res: any = await kubernetesApi.getPodLogs(clusterCode, namespace, podName, logTail);
|
||||||
setLogs(res?.data?.logs || res?.logs || '无日志');
|
setLogs(res?.data?.logs || res?.logs || '无日志');
|
||||||
} catch {
|
} catch {
|
||||||
setLogs('获取日志失败');
|
setLogs('获取日志失败');
|
||||||
@@ -136,7 +136,7 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack
|
|||||||
// Not direct toggle — delete HPA
|
// Not direct toggle — delete HPA
|
||||||
if (!confirm(`确定删除 HPA "${hpa.metadata?.name}"?`)) return;
|
if (!confirm(`确定删除 HPA "${hpa.metadata?.name}"?`)) return;
|
||||||
try {
|
try {
|
||||||
await k8sApi.deleteHPA(clusterId, namespace, hpa.metadata?.name);
|
await kubernetesApi.deleteHPA(clusterCode, namespace, hpa.metadata?.name);
|
||||||
toast({ title: 'HPA 已删除' });
|
toast({ title: 'HPA 已删除' });
|
||||||
loadData();
|
loadData();
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -226,7 +226,7 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-lg font-bold text-slate-500">未配置</p>
|
<p className="text-lg font-bold text-slate-500">未配置</p>
|
||||||
<CreateHPADialog
|
<CreateHPADialog
|
||||||
clusterId={clusterId}
|
clusterCode={clusterCode}
|
||||||
namespace={namespace}
|
namespace={namespace}
|
||||||
deploymentName={deploymentName}
|
deploymentName={deploymentName}
|
||||||
onCreated={loadData}
|
onCreated={loadData}
|
||||||
@@ -407,7 +407,7 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack
|
|||||||
setLogs('');
|
setLogs('');
|
||||||
// Stream mode
|
// Stream mode
|
||||||
const token = localStorage.getItem('token') || '';
|
const token = localStorage.getItem('token') || '';
|
||||||
const url = `${window.location.protocol}//${window.location.host}/api/v1/k8s-clusters/${clusterId}/namespaces/${namespace}/pods/${logPod}/logs?tail=${logTail}&stream=true`;
|
const url = `${window.location.protocol}//${window.location.host}/api/v1/kubernetes/${clusterCode}/namespace/${namespace}/pods/${logPod}/logs?tail=${logTail}&stream=true`;
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
const resp = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
||||||
const reader = resp.body?.getReader();
|
const reader = resp.body?.getReader();
|
||||||
@@ -444,7 +444,7 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<CardTitle className="text-sm text-slate-400">HPA 自动扩缩容</CardTitle>
|
<CardTitle className="text-sm text-slate-400">HPA 自动扩缩容</CardTitle>
|
||||||
<CreateHPADialog
|
<CreateHPADialog
|
||||||
clusterId={clusterId}
|
clusterCode={clusterCode}
|
||||||
namespace={namespace}
|
namespace={namespace}
|
||||||
deploymentName={deploymentName}
|
deploymentName={deploymentName}
|
||||||
onCreated={loadData}
|
onCreated={loadData}
|
||||||
@@ -506,8 +506,8 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Create HPA Dialog (inline)
|
// Create HPA Dialog (inline)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
function CreateHPADialog({ clusterId, namespace, deploymentName, onCreated }: {
|
function CreateHPADialog({ clusterCode, namespace, deploymentName, onCreated }: {
|
||||||
clusterId: string; namespace: string; deploymentName: string; onCreated: () => void;
|
clusterCode: string; namespace: string; deploymentName: string; onCreated: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -527,7 +527,7 @@ function CreateHPADialog({ clusterId, namespace, deploymentName, onCreated }: {
|
|||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
await k8sApi.createHPA(clusterId, {
|
await kubernetesApi.createHPA(clusterCode, {
|
||||||
name: form.name,
|
name: form.name,
|
||||||
namespace,
|
namespace,
|
||||||
targetKind: 'Deployment',
|
targetKind: 'Deployment',
|
||||||
+7
-7
@@ -4,7 +4,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { Cloud, XCircle } from 'lucide-react';
|
import { Cloud, XCircle } from 'lucide-react';
|
||||||
import { k8sApi } from '@/services/api';
|
import { kubernetesApi } from '@/services/api';
|
||||||
import {
|
import {
|
||||||
AreaChart,
|
AreaChart,
|
||||||
Area,
|
Area,
|
||||||
@@ -26,7 +26,7 @@ const resourceData = [
|
|||||||
{ time: '24:00', cpu: 48, memory: 56 },
|
{ time: '24:00', cpu: 48, memory: 56 },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function K8sClusterDetail({ cluster, onClose }: { cluster: any; onClose: () => void }) {
|
export function KubernetesClusterDetail({ cluster, onClose }: { cluster: any; onClose: () => void }) {
|
||||||
const [nodes, setNodes] = useState<any[]>([]);
|
const [nodes, setNodes] = useState<any[]>([]);
|
||||||
const [pods, setPods] = useState<any[]>([]);
|
const [pods, setPods] = useState<any[]>([]);
|
||||||
const [deployments, setDeployments] = useState<any[]>([]);
|
const [deployments, setDeployments] = useState<any[]>([]);
|
||||||
@@ -37,11 +37,11 @@ export function K8sClusterDetail({ cluster, onClose }: { cluster: any; onClose:
|
|||||||
try {
|
try {
|
||||||
// 后端 ListNodes/ListPods/etc 返回 data 直接为数组(非 {list,total})
|
// 后端 ListNodes/ListPods/etc 返回 data 直接为数组(非 {list,total})
|
||||||
const [nodesRes, podsRes, depRes, svcRes, ingRes] = await Promise.all([
|
const [nodesRes, podsRes, depRes, svcRes, ingRes] = await Promise.all([
|
||||||
k8sApi.getNodes(clusterId).catch(() => []),
|
kubernetesApi.getNodes(clusterId).catch(() => []),
|
||||||
k8sApi.getPods(clusterId).catch(() => []),
|
kubernetesApi.getPods(clusterId).catch(() => []),
|
||||||
k8sApi.getDeployments(clusterId).catch(() => []),
|
kubernetesApi.getDeployments(clusterId).catch(() => []),
|
||||||
k8sApi.getServices(clusterId).catch(() => []),
|
kubernetesApi.getServices(clusterId).catch(() => []),
|
||||||
k8sApi.getIngresses(clusterId).catch(() => []),
|
kubernetesApi.getIngresses(clusterId).catch(() => []),
|
||||||
]);
|
]);
|
||||||
const toArray = (v: any) => (Array.isArray(v) ? v : (v?.list || []));
|
const toArray = (v: any) => (Array.isArray(v) ? v : (v?.list || []));
|
||||||
setNodes(toArray(nodesRes));
|
setNodes(toArray(nodesRes));
|
||||||
+5
-5
@@ -12,7 +12,7 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { k8sApi } from '@/services/api';
|
import { kubernetesApi } from '@/services/api';
|
||||||
|
|
||||||
const emptyForm = {
|
const emptyForm = {
|
||||||
name: '',
|
name: '',
|
||||||
@@ -24,7 +24,7 @@ const emptyForm = {
|
|||||||
kube_config: '',
|
kube_config: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function K8sClusterDialog({
|
export function KubernetesClusterDialog({
|
||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
editingCluster,
|
editingCluster,
|
||||||
@@ -45,7 +45,7 @@ export function K8sClusterDialog({
|
|||||||
if (!open) return;
|
if (!open) return;
|
||||||
if (editingCluster && editingCluster.id) {
|
if (editingCluster && editingCluster.id) {
|
||||||
// 编辑模式:拉取详情获取 has_token/has_kube_config 标记 // Logan.Li
|
// 编辑模式:拉取详情获取 has_token/has_kube_config 标记 // Logan.Li
|
||||||
k8sApi.getClusterDetail(editingCluster.id).then((res: any) => {
|
kubernetesApi.getClusterDetail(editingCluster.id).then((res: any) => {
|
||||||
const d = res || {};
|
const d = res || {};
|
||||||
setHasToken(!!d.has_token);
|
setHasToken(!!d.has_token);
|
||||||
setHasKubeConfig(!!d.has_kube_config);
|
setHasKubeConfig(!!d.has_kube_config);
|
||||||
@@ -106,9 +106,9 @@ export function K8sClusterDialog({
|
|||||||
kube_config: formData.config_type === 1 ? formData.kube_config : '',
|
kube_config: formData.config_type === 1 ? formData.kube_config : '',
|
||||||
};
|
};
|
||||||
if (editingCluster) {
|
if (editingCluster) {
|
||||||
await k8sApi.updateCluster(editingCluster.id, payload);
|
await kubernetesApi.updateCluster(editingCluster.id, payload);
|
||||||
} else {
|
} else {
|
||||||
await k8sApi.createCluster(payload);
|
await kubernetesApi.createCluster(payload);
|
||||||
}
|
}
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
onSaved();
|
onSaved();
|
||||||
+10
-10
@@ -27,12 +27,12 @@ import {
|
|||||||
Trash2,
|
Trash2,
|
||||||
Search,
|
Search,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { k8sApi } from '@/services/api';
|
import { kubernetesApi } from '@/services/api';
|
||||||
import type { K8sCluster } from '@/types';
|
import type { KubernetesCluster } from '@/types';
|
||||||
import { K8sClusterDetail } from './K8sClusterDetail';
|
import { KubernetesClusterDetail } from './KubernetesClusterDetail';
|
||||||
import { K8sClusterDialog } from './K8sClusterDialog';
|
import { KubernetesClusterDialog } from './KubernetesClusterDialog';
|
||||||
|
|
||||||
export function K8sClusters() {
|
export function KubernetesClusters() {
|
||||||
const [clusters, setClusters] = useState<any[]>([]);
|
const [clusters, setClusters] = useState<any[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [selectedCluster, setSelectedCluster] = useState<string | null>(null);
|
const [selectedCluster, setSelectedCluster] = useState<string | null>(null);
|
||||||
@@ -45,7 +45,7 @@ export function K8sClusters() {
|
|||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const res: any = await k8sApi.getClusters();
|
const res: any = await kubernetesApi.getClusters();
|
||||||
setClusters(res?.list || []);
|
setClusters(res?.list || []);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('加载集群列表失败:', e);
|
console.error('加载集群列表失败:', e);
|
||||||
@@ -61,7 +61,7 @@ export function K8sClusters() {
|
|||||||
|
|
||||||
const handleDelete = async (id: string) => {
|
const handleDelete = async (id: string) => {
|
||||||
try {
|
try {
|
||||||
await k8sApi.deleteCluster(id);
|
await kubernetesApi.deleteCluster(id);
|
||||||
loadData();
|
loadData();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('删除集群失败:', e);
|
console.error('删除集群失败:', e);
|
||||||
@@ -74,7 +74,7 @@ export function K8sClusters() {
|
|||||||
(cluster.version || '').includes(searchQuery)
|
(cluster.version || '').includes(searchQuery)
|
||||||
);
|
);
|
||||||
|
|
||||||
const getStatusBadge = (status: K8sCluster['status']) => {
|
const getStatusBadge = (status: KubernetesCluster['status']) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'healthy':
|
case 'healthy':
|
||||||
return (
|
return (
|
||||||
@@ -345,11 +345,11 @@ export function K8sClusters() {
|
|||||||
|
|
||||||
{/* Cluster Detail */}
|
{/* Cluster Detail */}
|
||||||
{clusterDetail && (
|
{clusterDetail && (
|
||||||
<K8sClusterDetail cluster={clusterDetail} onClose={() => setSelectedCluster(null)} />
|
<KubernetesClusterDetail cluster={clusterDetail} onClose={() => setSelectedCluster(null)} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 创建/编辑集群 Dialog */}
|
{/* 创建/编辑集群 Dialog */}
|
||||||
<K8sClusterDialog
|
<KubernetesClusterDialog
|
||||||
open={dialogOpen}
|
open={dialogOpen}
|
||||||
onOpenChange={setDialogOpen}
|
onOpenChange={setDialogOpen}
|
||||||
editingCluster={editingCluster}
|
editingCluster={editingCluster}
|
||||||
+4
-4
@@ -10,9 +10,9 @@ import {
|
|||||||
Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
|
Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
|
||||||
} from '@/components/ui/table';
|
} from '@/components/ui/table';
|
||||||
import { RefreshCw, Search, Server, Loader2 } from 'lucide-react';
|
import { RefreshCw, Search, Server, Loader2 } from 'lucide-react';
|
||||||
import { k8sApi } from '@/services/api';
|
import { kubernetesApi } from '@/services/api';
|
||||||
|
|
||||||
export function K8sNodes() {
|
export function KubernetesNodes() {
|
||||||
const [clusters, setClusters] = useState<any[]>([]);
|
const [clusters, setClusters] = useState<any[]>([]);
|
||||||
const [selectedCluster, setSelectedCluster] = useState('');
|
const [selectedCluster, setSelectedCluster] = useState('');
|
||||||
const [nodes, setNodes] = useState<any[]>([]);
|
const [nodes, setNodes] = useState<any[]>([]);
|
||||||
@@ -22,7 +22,7 @@ export function K8sNodes() {
|
|||||||
|
|
||||||
const loadClusters = useCallback(async () => {
|
const loadClusters = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const res: any = await k8sApi.getClusters();
|
const res: any = await kubernetesApi.getClusters();
|
||||||
const list = res?.list || [];
|
const list = res?.list || [];
|
||||||
setClusters(list);
|
setClusters(list);
|
||||||
if (list.length > 0 && !selectedCluster) setSelectedCluster(String(list[0].id));
|
if (list.length > 0 && !selectedCluster) setSelectedCluster(String(list[0].id));
|
||||||
@@ -35,7 +35,7 @@ export function K8sNodes() {
|
|||||||
if (!selectedCluster) return;
|
if (!selectedCluster) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const res: any = await k8sApi.getNodes(selectedCluster);
|
const res: any = await kubernetesApi.getNodes(selectedCluster);
|
||||||
setNodes(Array.isArray(res) ? res : (res?.list || []));
|
setNodes(Array.isArray(res) ? res : (res?.list || []));
|
||||||
} catch { setNodes([]); } finally { setLoading(false); }
|
} catch { setNodes([]); } finally { setLoading(false); }
|
||||||
}, [selectedCluster]);
|
}, [selectedCluster]);
|
||||||
+27
-16
@@ -14,11 +14,11 @@ import {
|
|||||||
RefreshCw, Search, Boxes, Loader2, Container,
|
RefreshCw, Search, Boxes, Loader2, Container,
|
||||||
Plus, Minus, RotateCw, Upload, FileText, Eye,
|
Plus, Minus, RotateCw, Upload, FileText, Eye,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { k8sApi } from '@/services/api';
|
import { kubernetesApi } from '@/services/api';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { DeploymentDetail } from './DeploymentDetail';
|
import { DeploymentDetail } from './DeploymentDetail';
|
||||||
|
|
||||||
export function K8sWorkloads() {
|
export function KubernetesWorkloads() {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [clusters, setClusters] = useState<any[]>([]);
|
const [clusters, setClusters] = useState<any[]>([]);
|
||||||
const [selectedCluster, setSelectedCluster] = useState('');
|
const [selectedCluster, setSelectedCluster] = useState('');
|
||||||
@@ -36,10 +36,10 @@ export function K8sWorkloads() {
|
|||||||
|
|
||||||
const loadClusters = useCallback(async () => {
|
const loadClusters = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const res: any = await k8sApi.getClusters();
|
const res: any = await kubernetesApi.getClusters();
|
||||||
const list = res?.list || [];
|
const list = res?.list || [];
|
||||||
setClusters(list);
|
setClusters(list);
|
||||||
if (list.length > 0 && !selectedCluster) setSelectedCluster(String(list[0].id));
|
if (list.length > 0 && !selectedCluster) setSelectedCluster(list[0].code || String(list[0].id));
|
||||||
} catch { setClusters([]); }
|
} catch { setClusters([]); }
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ export function K8sWorkloads() {
|
|||||||
const loadNamespaces = useCallback(async () => {
|
const loadNamespaces = useCallback(async () => {
|
||||||
if (!selectedCluster) return;
|
if (!selectedCluster) return;
|
||||||
try {
|
try {
|
||||||
const res: any = await k8sApi.getNamespaces(selectedCluster);
|
const res: any = await kubernetesApi.getNamespaces(selectedCluster);
|
||||||
const nsList: string[] = Array.isArray(res) ? res : (res?.list || res?.data || []);
|
const nsList: string[] = Array.isArray(res) ? res : (res?.list || res?.data || []);
|
||||||
setNamespaces(nsList);
|
setNamespaces(nsList);
|
||||||
} catch { setNamespaces([]); }
|
} catch { setNamespaces([]); }
|
||||||
@@ -60,16 +60,27 @@ export function K8sWorkloads() {
|
|||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
if (!selectedCluster) return;
|
if (!selectedCluster) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
const clusterCode = selectedCluster;
|
||||||
try {
|
try {
|
||||||
const [depRes, podRes] = await Promise.all([
|
if (selectedNamespace !== 'all') {
|
||||||
k8sApi.getDeployments(selectedCluster).catch(() => []),
|
const [depRes, podRes] = await Promise.all([
|
||||||
k8sApi.getPods(selectedCluster).catch(() => []),
|
kubernetesApi.getNamespaceDeployments(clusterCode, selectedNamespace).catch(() => []),
|
||||||
]);
|
kubernetesApi.getNamespacePods(clusterCode, selectedNamespace).catch(() => []),
|
||||||
const toArray = (v: any) => (Array.isArray(v) ? v : (v?.list || []));
|
]);
|
||||||
setDeployments(toArray(depRes));
|
const toArray = (v: any) => (Array.isArray(v) ? v : (v?.list || []));
|
||||||
setPods(toArray(podRes));
|
setDeployments(toArray(depRes));
|
||||||
|
setPods(toArray(podRes));
|
||||||
|
} else {
|
||||||
|
const [depRes, podRes] = await Promise.all([
|
||||||
|
kubernetesApi.getDeployments(clusterCode).catch(() => []),
|
||||||
|
kubernetesApi.getPods(clusterCode).catch(() => []),
|
||||||
|
]);
|
||||||
|
const toArray = (v: any) => (Array.isArray(v) ? v : (v?.list || []));
|
||||||
|
setDeployments(toArray(depRes));
|
||||||
|
setPods(toArray(podRes));
|
||||||
|
}
|
||||||
} catch { setDeployments([]); setPods([]); } finally { setLoading(false); }
|
} catch { setDeployments([]); setPods([]); } finally { setLoading(false); }
|
||||||
}, [selectedCluster]);
|
}, [selectedCluster, selectedNamespace]);
|
||||||
|
|
||||||
useEffect(() => { loadData(); }, [loadData]);
|
useEffect(() => { loadData(); }, [loadData]);
|
||||||
|
|
||||||
@@ -98,7 +109,7 @@ export function K8sWorkloads() {
|
|||||||
try {
|
try {
|
||||||
const newReplicas = (dep.replicas || 0) + delta;
|
const newReplicas = (dep.replicas || 0) + delta;
|
||||||
if (newReplicas < 0) return;
|
if (newReplicas < 0) return;
|
||||||
await k8sApi.scaleDeployment(selectedCluster, dep.namespace, dep.name, newReplicas);
|
await kubernetesApi.scaleDeployment(selectedCluster, dep.namespace, dep.name, newReplicas);
|
||||||
toast({ title: '扩缩容成功', description: `${dep.name} → ${newReplicas} 副本` });
|
toast({ title: '扩缩容成功', description: `${dep.name} → ${newReplicas} 副本` });
|
||||||
loadData();
|
loadData();
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -112,7 +123,7 @@ export function K8sWorkloads() {
|
|||||||
if (selectedDeployment) {
|
if (selectedDeployment) {
|
||||||
return (
|
return (
|
||||||
<DeploymentDetail
|
<DeploymentDetail
|
||||||
clusterId={selectedCluster}
|
clusterCode={selectedCluster}
|
||||||
namespace={selectedDeployment.namespace}
|
namespace={selectedDeployment.namespace}
|
||||||
deploymentName={selectedDeployment.name}
|
deploymentName={selectedDeployment.name}
|
||||||
onBack={() => { setSelectedDeployment(null); loadData(); }}
|
onBack={() => { setSelectedDeployment(null); loadData(); }}
|
||||||
@@ -134,7 +145,7 @@ export function K8sWorkloads() {
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="bg-slate-800 border-slate-700">
|
<SelectContent className="bg-slate-800 border-slate-700">
|
||||||
{clusters.map((c) => (
|
{clusters.map((c) => (
|
||||||
<SelectItem key={c.id} value={String(c.id)}>{c.name}</SelectItem>
|
<SelectItem key={c.id} value={c.code || String(c.id)}>{c.name}</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
+9
-9
@@ -14,7 +14,7 @@ import { Textarea } from '@/components/ui/textarea';
|
|||||||
import {
|
import {
|
||||||
Search, RefreshCw, Loader2, Cloud, Save, Plus, Trash2, Link2, Pencil, RotateCcw,
|
Search, RefreshCw, Loader2, Cloud, Save, Plus, Trash2, Link2, Pencil, RotateCcw,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { k8sApi } from '@/services/api';
|
import { kubernetesApi } from '@/services/api';
|
||||||
import { useConfirm } from '@/components/ui/confirm-dialog';
|
import { useConfirm } from '@/components/ui/confirm-dialog';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import type { TopologyNode, TopologyEdge } from '@/types';
|
import type { TopologyNode, TopologyEdge } from '@/types';
|
||||||
@@ -50,7 +50,7 @@ export function TopologyView() {
|
|||||||
|
|
||||||
const loadClusters = useCallback(async () => {
|
const loadClusters = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const res: any = await k8sApi.getClusters();
|
const res: any = await kubernetesApi.getClusters();
|
||||||
const list = res?.list || [];
|
const list = res?.list || [];
|
||||||
setClusters(list);
|
setClusters(list);
|
||||||
if (list.length > 0 && !selectedClusterId) setSelectedClusterId(String(list[0].id));
|
if (list.length > 0 && !selectedClusterId) setSelectedClusterId(String(list[0].id));
|
||||||
@@ -62,7 +62,7 @@ export function TopologyView() {
|
|||||||
// 加载已保存的布局 // Logan.Li
|
// 加载已保存的布局 // Logan.Li
|
||||||
const loadSavedLayout = useCallback(async (clusterId: string) => {
|
const loadSavedLayout = useCallback(async (clusterId: string) => {
|
||||||
try {
|
try {
|
||||||
const res: any = await k8sApi.getTopology(clusterId);
|
const res: any = await kubernetesApi.getTopology(clusterId);
|
||||||
const layout = res as any;
|
const layout = res as any;
|
||||||
if (layout?.layout_data) {
|
if (layout?.layout_data) {
|
||||||
const data = JSON.parse(layout.layout_data) as TopologyLayoutData;
|
const data = JSON.parse(layout.layout_data) as TopologyLayoutData;
|
||||||
@@ -86,10 +86,10 @@ export function TopologyView() {
|
|||||||
try {
|
try {
|
||||||
const toArray = (v: any) => (Array.isArray(v) ? v : (v?.list || []));
|
const toArray = (v: any) => (Array.isArray(v) ? v : (v?.list || []));
|
||||||
const [nodesRes, svcRes, depRes, ingRes] = await Promise.all([
|
const [nodesRes, svcRes, depRes, ingRes] = await Promise.all([
|
||||||
k8sApi.getNodes(selectedClusterId).catch(() => []),
|
kubernetesApi.getNodes(selectedClusterId).catch(() => []),
|
||||||
k8sApi.getServices(selectedClusterId).catch(() => []),
|
kubernetesApi.getServices(selectedClusterId).catch(() => []),
|
||||||
k8sApi.getDeployments(selectedClusterId).catch(() => []),
|
kubernetesApi.getDeployments(selectedClusterId).catch(() => []),
|
||||||
k8sApi.getIngresses(selectedClusterId).catch(() => []),
|
kubernetesApi.getIngresses(selectedClusterId).catch(() => []),
|
||||||
]);
|
]);
|
||||||
const cluster = clusters.find((c) => String(c.id) === selectedClusterId) || {};
|
const cluster = clusters.find((c) => String(c.id) === selectedClusterId) || {};
|
||||||
const built = buildTopology(cluster, toArray(ingRes), toArray(svcRes), toArray(depRes), toArray(nodesRes));
|
const built = buildTopology(cluster, toArray(ingRes), toArray(svcRes), toArray(depRes), toArray(nodesRes));
|
||||||
@@ -246,7 +246,7 @@ export function TopologyView() {
|
|||||||
customNodes: topology.nodes.filter((n) => isCustomNode(n.id)),
|
customNodes: topology.nodes.filter((n) => isCustomNode(n.id)),
|
||||||
customEdges: topology.edges.filter((e) => e.id.startsWith('custom-edge')),
|
customEdges: topology.edges.filter((e) => e.id.startsWith('custom-edge')),
|
||||||
};
|
};
|
||||||
await k8sApi.saveTopology(selectedClusterId, JSON.stringify(layoutData));
|
await kubernetesApi.saveTopology(selectedClusterId, JSON.stringify(layoutData));
|
||||||
toast.success('布局已保存');
|
toast.success('布局已保存');
|
||||||
} catch { toast.error('保存失败'); }
|
} catch { toast.error('保存失败'); }
|
||||||
finally { setSaving(false); }
|
finally { setSaving(false); }
|
||||||
@@ -255,7 +255,7 @@ export function TopologyView() {
|
|||||||
const handleResetLayout = async () => {
|
const handleResetLayout = async () => {
|
||||||
const ok = await _confirm({ title: '重置布局', description: '将清除已保存的布局并重新自动排列', variant: 'destructive' });
|
const ok = await _confirm({ title: '重置布局', description: '将清除已保存的布局并重新自动排列', variant: 'destructive' });
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
try { await k8sApi.saveTopology(selectedClusterId, ''); } catch { /* */ }
|
try { await kubernetesApi.saveTopology(selectedClusterId, ''); } catch { /* */ }
|
||||||
setSavedLayout(null);
|
setSavedLayout(null);
|
||||||
setEditMode(false);
|
setEditMode(false);
|
||||||
setConnectMode(false);
|
setConnectMode(false);
|
||||||
File renamed without changes.
@@ -62,7 +62,7 @@ const menuItems: MenuItem[] = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'k8s',
|
id: 'kubernetes',
|
||||||
name: 'Kubernetes',
|
name: 'Kubernetes',
|
||||||
icon: Cloud,
|
icon: Cloud,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// 主机监控大屏 — Grafana 风格:统计卡片 + 资源条状图 + 可排序列表 // Logan.Li
|
// 主机监控大屏 — Grafana 风格:统计卡片 + 资源条状图 + 可排序列表 // Logan.Li
|
||||||
// 参照: Grafana node-dashboard.json (表格+时序) / k8s-dashboard.json (bargauge+阈值)
|
// 参照: Grafana node-dashboard.json (表格+时序) / kubernetes-dashboard.json (bargauge+阈值)
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export const PrometheusDashboard: React.FC<Props> = ({ dsUrl }) => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [targets, setTargets] = useState(0);
|
const [targets, setTargets] = useState(0);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [tab, setTab] = useState('k8s');
|
const [tab, setTab] = useState('kubernetes');
|
||||||
const [k8sD, setK8sD] = useState<any>({});
|
const [k8sD, setK8sD] = useState<any>({});
|
||||||
const [nodeD, setNodeD] = useState<any>({});
|
const [nodeD, setNodeD] = useState<any>({});
|
||||||
const [podD, setPodD] = useState<any>({});
|
const [podD, setPodD] = useState<any>({});
|
||||||
@@ -143,13 +143,13 @@ export const PrometheusDashboard: React.FC<Props> = ({ dsUrl }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loadAll = async (u: string) => {
|
const loadAll = async (u: string) => {
|
||||||
for (const t of ['k8s','node','pod','jvm']) {
|
for (const t of ['kubernetes','node','pod','jvm']) {
|
||||||
try {
|
try {
|
||||||
const data: any = await http.post('/prometheus/dashboard', { url: u, type: t });
|
const data: any = await http.post('/prometheus/dashboard', { url: u, type: t });
|
||||||
const items: any = {};
|
const items: any = {};
|
||||||
for (const [k, v] of Object.entries(data||{}))
|
for (const [k, v] of Object.entries(data||{}))
|
||||||
items[k] = Array.isArray(v) ? v : ((v as any)?.data?.result ? parsePrune((v as any).data.result) : v);
|
items[k] = Array.isArray(v) ? v : ((v as any)?.data?.result ? parsePrune((v as any).data.result) : v);
|
||||||
if (t==='k8s') setK8sD(items); if (t==='node') setNodeD(items);
|
if (t==='kubernetes') setK8sD(items); if (t==='node') setNodeD(items);
|
||||||
if (t==='pod') setPodD(items); if (t==='jvm') setJvmD(items);
|
if (t==='pod') setPodD(items); if (t==='jvm') setJvmD(items);
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
@@ -175,12 +175,12 @@ export const PrometheusDashboard: React.FC<Props> = ({ dsUrl }) => {
|
|||||||
: (
|
: (
|
||||||
<Tabs value={tab} onValueChange={setTab}>
|
<Tabs value={tab} onValueChange={setTab}>
|
||||||
<TabsList className="bg-[var(--dg-bg-tertiary)] mb-3">
|
<TabsList className="bg-[var(--dg-bg-tertiary)] mb-3">
|
||||||
<TabsTrigger value="k8s" className="text-xs"><Container className="w-3 h-3 mr-1" />K8s</TabsTrigger>
|
<TabsTrigger value="kubernetes" className="text-xs"><Container className="w-3 h-3 mr-1" />K8s</TabsTrigger>
|
||||||
<TabsTrigger value="node" className="text-xs"><Server className="w-3 h-3 mr-1" />Node</TabsTrigger>
|
<TabsTrigger value="node" className="text-xs"><Server className="w-3 h-3 mr-1" />Node</TabsTrigger>
|
||||||
<TabsTrigger value="pod" className="text-xs"><Box className="w-3 h-3 mr-1" />Pod</TabsTrigger>
|
<TabsTrigger value="pod" className="text-xs"><Box className="w-3 h-3 mr-1" />Pod</TabsTrigger>
|
||||||
<TabsTrigger value="jvm" className="text-xs"><Zap className="w-3 h-3 mr-1" />JVM</TabsTrigger>
|
<TabsTrigger value="jvm" className="text-xs"><Zap className="w-3 h-3 mr-1" />JVM</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="k8s"><K8sPanel d={k8sD} /></TabsContent>
|
<TabsContent value="kubernetes"><K8sPanel d={k8sD} /></TabsContent>
|
||||||
<TabsContent value="node"><NodePanel d={nodeD} /></TabsContent>
|
<TabsContent value="node"><NodePanel d={nodeD} /></TabsContent>
|
||||||
<TabsContent value="pod"><PodPanel d={podD} /></TabsContent>
|
<TabsContent value="pod"><PodPanel d={podD} /></TabsContent>
|
||||||
<TabsContent value="jvm"><JvmPanel d={jvmD} /></TabsContent>
|
<TabsContent value="jvm"><JvmPanel d={jvmD} /></TabsContent>
|
||||||
|
|||||||
@@ -156,100 +156,119 @@ export const serverApi = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ==================== K8s 集群 API ====================
|
// ==================== K8s 集群 API ====================
|
||||||
export const k8sApi = {
|
export const kubernetesApi = {
|
||||||
getClusters: async () => {
|
getClusters: async () => {
|
||||||
return http.get<ListResponse>('/k8s-clusters');
|
return http.get<ListResponse>('/kubernetes-clusters');
|
||||||
},
|
},
|
||||||
getClusterDetail: async (id: number | string) => {
|
getClusterDetail: async (id: number | string) => {
|
||||||
return http.get(`/k8s-clusters/${id}/detail`);
|
return http.get(`/kubernetes-clusters/${id}/detail`);
|
||||||
},
|
},
|
||||||
createCluster: async (data: any) => {
|
createCluster: async (data: any) => {
|
||||||
return http.post('/k8s-clusters', data);
|
return http.post('/kubernetes-clusters', data);
|
||||||
},
|
},
|
||||||
updateCluster: async (id: number | string, data: any) => {
|
updateCluster: async (id: number | string, data: any) => {
|
||||||
return http.put(`/k8s-clusters/${id}`, data);
|
return http.put(`/kubernetes-clusters/${id}`, data);
|
||||||
},
|
},
|
||||||
deleteCluster: async (id: number | string) => {
|
deleteCluster: async (id: number | string) => {
|
||||||
return http.delete(`/k8s-clusters/${id}`);
|
return http.delete(`/kubernetes-clusters/${id}`);
|
||||||
},
|
},
|
||||||
getNodes: async (clusterId: number | string) => {
|
getNodes: async (clusterId: number | string) => {
|
||||||
return http.get(`/k8s-clusters/${clusterId}/nodes`);
|
return http.get(`/kubernetes-clusters/${clusterId}/nodes`);
|
||||||
},
|
},
|
||||||
getPods: async (clusterId: number | string) => {
|
getPods: async (clusterId: number | string) => {
|
||||||
return http.get(`/k8s-clusters/${clusterId}/pods`);
|
return http.get(`/kubernetes-clusters/${clusterId}/pods`);
|
||||||
},
|
},
|
||||||
getDeployments: async (clusterId: number | string) => {
|
getDeployments: async (clusterId: number | string) => {
|
||||||
return http.get(`/k8s-clusters/${clusterId}/deployments`);
|
return http.get(`/kubernetes-clusters/${clusterId}/deployments`);
|
||||||
},
|
},
|
||||||
getServices: async (clusterId: number | string) => {
|
getServices: async (clusterId: number | string) => {
|
||||||
return http.get(`/k8s-clusters/${clusterId}/services`);
|
return http.get(`/kubernetes-clusters/${clusterId}/services`);
|
||||||
},
|
},
|
||||||
getIngresses: async (clusterId: number | string) => {
|
getIngresses: async (clusterId: number | string) => {
|
||||||
return http.get(`/k8s-clusters/${clusterId}/ingresses`);
|
return http.get(`/kubernetes-clusters/${clusterId}/ingresses`);
|
||||||
},
|
},
|
||||||
syncCluster: async (clusterId: number | string) => {
|
syncCluster: async (clusterId: number | string) => {
|
||||||
return http.post(`/k8s-clusters/${clusterId}/sync`);
|
return http.post(`/kubernetes-clusters/${clusterId}/sync`);
|
||||||
},
|
},
|
||||||
getTopology: async (clusterId: number | string) => { // Logan.Li
|
getTopology: async (clusterId: number | string) => { // Logan.Li
|
||||||
return http.get(`/k8s-clusters/${clusterId}/topology`);
|
return http.get(`/kubernetes-clusters/${clusterId}/topology`);
|
||||||
},
|
},
|
||||||
saveTopology: async (clusterId: number | string, layoutData: string) => { // Logan.Li
|
saveTopology: async (clusterId: number | string, layoutData: string) => { // Logan.Li
|
||||||
return http.put(`/k8s-clusters/${clusterId}/topology`, { layout_data: layoutData });
|
return http.put(`/kubernetes-clusters/${clusterId}/topology`, { layout_data: layoutData });
|
||||||
},
|
},
|
||||||
|
|
||||||
// === 工作负载操作 (Logan.Li) ===
|
// === 工作负载操作 (Logan.Li) - Kuboard-style routes ===
|
||||||
getPodLogs: async (clusterId: number | string, namespace: string, podName: string, tail: number = 200, container?: string) => {
|
getPodLogs: async (clusterCode: string, namespace: string, podName: string, tail: number = 200, container?: string) => {
|
||||||
const params = new URLSearchParams({ tail: String(tail) });
|
const params = new URLSearchParams({ tail: String(tail) });
|
||||||
if (container) params.set('container', container);
|
if (container) params.set('container', container);
|
||||||
return http.get(`/k8s-clusters/${clusterId}/namespaces/${namespace}/pods/${podName}/logs?${params}`);
|
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/pods/${podName}/logs?${params}`);
|
||||||
},
|
},
|
||||||
execPod: async (clusterId: number | string, namespace: string, podName: string, command: string[], container?: string) => {
|
execPod: async (clusterCode: string, namespace: string, podName: string, command: string[], container?: string) => {
|
||||||
return http.post(`/k8s-clusters/${clusterId}/namespaces/${namespace}/pods/${podName}/exec`, { command, container });
|
return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/pods/${podName}/exec`, { command, container });
|
||||||
},
|
},
|
||||||
scaleDeployment: async (clusterId: number | string, namespace: string, name: string, replicas: number) => {
|
scaleDeployment: async (clusterCode: string, namespace: string, name: string, replicas: number) => {
|
||||||
return http.post(`/k8s-clusters/${clusterId}/namespaces/${namespace}/deployments/${name}/scale`, { replicas });
|
return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments/${name}/scale`, { replicas });
|
||||||
},
|
},
|
||||||
restartDeployment: async (clusterId: number | string, namespace: string, name: string) => {
|
restartDeployment: async (clusterCode: string, namespace: string, name: string) => {
|
||||||
return http.post(`/k8s-clusters/${clusterId}/namespaces/${namespace}/deployments/${name}/restart`);
|
return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments/${name}/restart`);
|
||||||
},
|
},
|
||||||
updateDeploymentImage: async (clusterId: number | string, namespace: string, name: string, image: string, containerName?: string) => {
|
updateDeploymentImage: async (clusterCode: string, namespace: string, name: string, image: string, containerName?: string) => {
|
||||||
return http.post(`/k8s-clusters/${clusterId}/namespaces/${namespace}/deployments/${name}/update-image`, { image, container_name: containerName });
|
return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments/${name}/update-image`, { image, container_name: containerName });
|
||||||
},
|
|
||||||
deletePod: async (clusterId: number | string, namespace: string, name: string) => {
|
|
||||||
return http.delete(`/k8s-clusters/${clusterId}/namespaces/${namespace}/pods/${name}`);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// === HPA 管理 (Logan.Li) ===
|
// === HPA 管理 (Logan.Li) ===
|
||||||
getHPAs: async (clusterId: number | string) => {
|
getHPAs: async (clusterCode: string, namespace?: string) => {
|
||||||
return http.get(`/k8s-clusters/${clusterId}/hpas`);
|
if (namespace) return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/hpas`);
|
||||||
|
return http.get(`/kubernetes-clusters/${clusterCode}/hpas`); // fallback old route
|
||||||
},
|
},
|
||||||
createHPA: async (clusterId: number | string, data: { name: string; namespace: string; targetKind: string; targetName: string; minReplicas: number; maxReplicas: number; cpuPercent: number; memPercent: number }) => {
|
createHPA: async (clusterCode: string, data: { name: string; namespace: string; targetKind: string; targetName: string; minReplicas: number; maxReplicas: number; cpuPercent: number; memPercent: number }) => {
|
||||||
return http.post(`/k8s-clusters/${clusterId}/hpas`, data);
|
return http.post(`/kubernetes/${clusterCode}/namespace/${data.namespace}/hpas`, data);
|
||||||
},
|
},
|
||||||
deleteHPA: async (clusterId: number | string, namespace: string, name: string) => {
|
deleteHPA: async (clusterCode: string, namespace: string, name: string) => {
|
||||||
return http.delete(`/k8s-clusters/${clusterId}/namespaces/${namespace}/hpas/${name}`);
|
return http.delete(`/kubernetes/${clusterCode}/namespace/${namespace}/hpas/${name}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
// === CronJob 管理 (Logan.Li) ===
|
// === CronJob 管理 (Logan.Li) ===
|
||||||
getCronJobs: async (clusterId: number | string) => {
|
getCronJobs: async (clusterCode: string, namespace?: string) => {
|
||||||
return http.get(`/k8s-clusters/${clusterId}/cronjobs`);
|
if (namespace) return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/cronjobs`);
|
||||||
|
return http.get(`/kubernetes-clusters/${clusterCode}/cronjobs`);
|
||||||
},
|
},
|
||||||
createCronJob: async (clusterId: number | string, data: { name: string; namespace: string; schedule: string; image: string; command: string[] }) => {
|
createCronJob: async (clusterCode: string, data: { name: string; namespace: string; schedule: string; image: string; command: string[] }) => {
|
||||||
return http.post(`/k8s-clusters/${clusterId}/cronjobs`, data);
|
return http.post(`/kubernetes/${clusterCode}/namespace/${data.namespace}/cronjobs`, data);
|
||||||
},
|
},
|
||||||
suspendCronJob: async (clusterId: number | string, namespace: string, name: string, suspend: boolean) => {
|
suspendCronJob: async (clusterCode: string, namespace: string, name: string, suspend: boolean) => {
|
||||||
return http.post(`/k8s-clusters/${clusterId}/namespaces/${namespace}/cronjobs/${name}/suspend`, { suspend });
|
return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/cronjobs/${name}/suspend`, { suspend });
|
||||||
},
|
},
|
||||||
triggerCronJob: async (clusterId: number | string, namespace: string, name: string) => {
|
triggerCronJob: async (clusterCode: string, namespace: string, name: string) => {
|
||||||
return http.post(`/k8s-clusters/${clusterId}/namespaces/${namespace}/cronjobs/${name}/trigger`);
|
return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/cronjobs/${name}/trigger`);
|
||||||
},
|
},
|
||||||
deleteCronJob: async (clusterId: number | string, namespace: string, name: string) => {
|
deleteCronJob: async (clusterCode: string, namespace: string, name: string) => {
|
||||||
return http.delete(`/k8s-clusters/${clusterId}/namespaces/${namespace}/cronjobs/${name}`);
|
return http.delete(`/kubernetes/${clusterCode}/namespace/${namespace}/cronjobs/${name}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// === Namespace-scoped resources (Kuboard-style) ===
|
||||||
|
getNamespaceDeployments: async (clusterCode: string, namespace: string) => {
|
||||||
|
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments`);
|
||||||
|
},
|
||||||
|
getNamespacePods: async (clusterCode: string, namespace: string) => {
|
||||||
|
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/pods`);
|
||||||
|
},
|
||||||
|
getNamespaceServices: async (clusterCode: string, namespace: string) => {
|
||||||
|
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/services`);
|
||||||
|
},
|
||||||
|
getNamespaceIngresses: async (clusterCode: string, namespace: string) => {
|
||||||
|
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/ingresses`);
|
||||||
|
},
|
||||||
|
getNamespaceConfigMaps: async (clusterCode: string, namespace: string) => {
|
||||||
|
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/configmaps`);
|
||||||
|
},
|
||||||
|
getNamespaceSecrets: async (clusterCode: string, namespace: string) => {
|
||||||
|
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/secrets`);
|
||||||
},
|
},
|
||||||
|
|
||||||
// === Namespace (Logan.Li) ===
|
// === Namespace (Logan.Li) ===
|
||||||
getNamespaces: async (clusterId: number | string) => {
|
getNamespaces: async (clusterCode: string) => {
|
||||||
return http.get(`/k8s-clusters/${clusterId}/namespaces`);
|
return http.get(`/kubernetes/${clusterCode}/namespaces`);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export interface Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Kubernetes集群
|
// Kubernetes集群
|
||||||
export interface K8sCluster {
|
export interface KubernetesCluster {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
@@ -34,7 +34,7 @@ export interface K8sCluster {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// K8s节点
|
// K8s节点
|
||||||
export interface K8sNode {
|
export interface KubernetesNode {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
clusterId: string;
|
clusterId: string;
|
||||||
@@ -51,7 +51,7 @@ export interface K8sNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// K8s Pod
|
// K8s Pod
|
||||||
export interface K8sPod {
|
export interface KubernetesPod {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
namespace: string;
|
namespace: string;
|
||||||
|
|||||||
Reference in new issue
Block a user