From cac14149177deb80bfacf76138f3c1f3108f1c76 Mon Sep 17 00:00:00 2001 From: attacker Date: Fri, 19 Jun 2026 08:10:12 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=85=A8=E5=B1=80=E9=87=8D?= =?UTF-8?q?=E5=91=BD=E5=90=8D=20k8s=20=E2=86=92=20kubernetes=20(=E5=89=8D?= =?UTF-8?q?=E5=90=8E=E7=AB=AF+=E8=B7=AF=E7=94=B1+=E8=A1=A8=E5=90=8D+?= =?UTF-8?q?=E6=96=87=E4=BB=B6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端: - 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 --- backend/internal/api/handlers/audit.go | 2 +- backend/internal/api/handlers/dashboard.go | 16 +- .../api/handlers/{k8s.go => kubernetes.go} | 505 +++++++++++++++--- backend/internal/api/routes/routes.go | 53 +- backend/internal/models/common.go | 2 +- .../internal/models/{k8s.go => kubernetes.go} | 64 +-- backend/pkg/database/database.go | 12 +- .../{k8sclient => kubernetesclient}/client.go | 65 +++ backend/scripts/mock.go | 16 +- ...ources.go => seed_kubernetes_resources.go} | 20 +- frontend/src/App.tsx | 14 +- .../src/components/aiops/AIOpsAssistant.tsx | 2 +- .../src/components/dashboard/Dashboard.tsx | 10 +- .../components/dashboard/DashboardRecent.tsx | 2 +- .../{k8s => kubernetes}/DeploymentDetail.tsx | 36 +- .../KubernetesClusterDetail.tsx} | 14 +- .../KubernetesClusterDialog.tsx} | 10 +- .../KubernetesClusters.tsx} | 20 +- .../KubernetesNodes.tsx} | 8 +- .../KubernetesWorkloads.tsx} | 43 +- .../{k8s => kubernetes}/TopologyView.tsx | 18 +- .../{k8s => kubernetes}/topology-helpers.tsx | 0 frontend/src/components/layout/Sidebar.tsx | 2 +- .../dashboard/HostMonitorDashboard.tsx | 2 +- .../monitor/dashboard/PrometheusDashboard.tsx | 10 +- frontend/src/services/api.ts | 111 ++-- frontend/src/types/index.ts | 6 +- 27 files changed, 779 insertions(+), 284 deletions(-) rename backend/internal/api/handlers/{k8s.go => kubernetes.go} (61%) rename backend/internal/models/{k8s.go => kubernetes.go} (84%) rename backend/pkg/{k8sclient => kubernetesclient}/client.go (93%) rename backend/scripts/{seed_k8s_resources.go => seed_kubernetes_resources.go} (89%) rename frontend/src/components/{k8s => kubernetes}/DeploymentDetail.tsx (95%) rename frontend/src/components/{k8s/K8sClusterDetail.tsx => kubernetes/KubernetesClusterDetail.tsx} (98%) rename frontend/src/components/{k8s/K8sClusterDialog.tsx => kubernetes/KubernetesClusterDialog.tsx} (97%) rename frontend/src/components/{k8s/K8sClusters.tsx => kubernetes/KubernetesClusters.tsx} (96%) rename frontend/src/components/{k8s/K8sNodes.tsx => kubernetes/KubernetesNodes.tsx} (97%) rename frontend/src/components/{k8s/K8sWorkloads.tsx => kubernetes/KubernetesWorkloads.tsx} (91%) rename frontend/src/components/{k8s => kubernetes}/TopologyView.tsx (97%) rename frontend/src/components/{k8s => kubernetes}/topology-helpers.tsx (100%) diff --git a/backend/internal/api/handlers/audit.go b/backend/internal/api/handlers/audit.go index 8a32b88..2392b74 100644 --- a/backend/internal/api/handlers/audit.go +++ b/backend/internal/api/handlers/audit.go @@ -134,7 +134,7 @@ func (h *AuditHandler) GetAuditModules(c *gin.Context) { modules := []map[string]string{ {"value": "user", "label": "用户管理"}, {"value": "server", "label": "服务器管理"}, - {"value": "k8s", "label": "Kubernetes"}, + {"value": "kubernetes", "label": "Kubernetes"}, {"value": "alert", "label": "监控告警"}, {"value": "pipeline", "label": "应用发布"}, {"value": "automation", "label": "自动化运维"}, diff --git a/backend/internal/api/handlers/dashboard.go b/backend/internal/api/handlers/dashboard.go index 2b1863c..3996fed 100644 --- a/backend/internal/api/handlers/dashboard.go +++ b/backend/internal/api/handlers/dashboard.go @@ -53,16 +53,16 @@ func (h *DashboardHandler) GetStats(c *gin.Context) { stats.ServerStats = serverStats // K8s集群统计 - var k8sStats models.K8sClusterStats - h.db.Model(&models.K8sCluster{}).Count(&k8sStats.Total) - h.db.Model(&models.K8sCluster{}).Where("status = ?", "healthy").Count(&k8sStats.Healthy) - h.db.Model(&models.K8sCluster{}).Where("status = ?", "warning").Count(&k8sStats.Warning) - h.db.Model(&models.K8sCluster{}).Where("status = ?", "error").Count(&k8sStats.Error) + var k8sStats models.KubernetesClusterStats + h.db.Model(&models.KubernetesCluster{}).Count(&k8sStats.Total) + h.db.Model(&models.KubernetesCluster{}).Where("status = ?", "healthy").Count(&k8sStats.Healthy) + h.db.Model(&models.KubernetesCluster{}).Where("status = ?", "warning").Count(&k8sStats.Warning) + h.db.Model(&models.KubernetesCluster{}).Where("status = ?", "error").Count(&k8sStats.Error) var totalNodes, totalPods, totalServices int64 - h.db.Model(&models.K8sNode{}).Count(&totalNodes) - h.db.Model(&models.K8sPod{}).Count(&totalPods) - h.db.Model(&models.K8sService{}).Count(&totalServices) + h.db.Model(&models.KubernetesNode{}).Count(&totalNodes) + h.db.Model(&models.KubernetesPod{}).Count(&totalPods) + h.db.Model(&models.KubernetesService{}).Count(&totalServices) k8sStats.TotalNodes = totalNodes k8sStats.TotalPods = totalPods k8sStats.TotalServices = totalServices diff --git a/backend/internal/api/handlers/k8s.go b/backend/internal/api/handlers/kubernetes.go similarity index 61% rename from backend/internal/api/handlers/k8s.go rename to backend/internal/api/handlers/kubernetes.go index b3fb2f6..3a945c2 100644 --- a/backend/internal/api/handlers/k8s.go +++ b/backend/internal/api/handlers/kubernetes.go @@ -11,20 +11,20 @@ import ( "time" "ezops/internal/models" - "ezops/pkg/k8sclient" + "ezops/pkg/kubernetesclient" "github.com/gin-gonic/gin" "gorm.io/gorm" ) -// K8sHandler K8s处理器 -type K8sHandler struct { +// KubernetesHandler K8s处理器 +type KubernetesHandler struct { db *gorm.DB } -// NewK8sHandler 创建K8s处理器 -func NewK8sHandler(db *gorm.DB) *K8sHandler { - return &K8sHandler{db: db} +// NewKubernetesHandler 创建K8s处理器 +func NewKubernetesHandler(db *gorm.DB) *KubernetesHandler { + return &KubernetesHandler{db: db} } // ListClusters 获取集群列表 @@ -36,15 +36,15 @@ func NewK8sHandler(db *gorm.DB) *K8sHandler { // @Param page query int false "页码" // @Param page_size query int false "每页数量" // @Success 200 {object} models.Response{data=models.ListResponse} -// @Router /k8s-clusters [get] -func (h *K8sHandler) ListClusters(c *gin.Context) { +// @Router /kubernetes-clusters [get] +func (h *KubernetesHandler) ListClusters(c *gin.Context) { var query models.PageQuery c.ShouldBindQuery(&query) 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) c.JSON(http.StatusOK, models.Response{ @@ -64,12 +64,12 @@ func (h *K8sHandler) ListClusters(c *gin.Context) { // @Produce json // @Security Bearer // @Param id path int true "集群ID" -// @Success 200 {object} models.Response{data=models.K8sCluster} -// @Router /k8s-clusters/{id} [get] -func (h *K8sHandler) GetCluster(c *gin.Context) { +// @Success 200 {object} models.Response{data=models.KubernetesCluster} +// @Router /kubernetes-clusters/{id} [get] +func (h *KubernetesHandler) GetCluster(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) - var cluster models.K8sCluster + var cluster models.KubernetesCluster if err := h.db.First(&cluster, id).Error; err != nil { c.JSON(http.StatusNotFound, models.Response{ Code: 404, @@ -92,11 +92,11 @@ func (h *K8sHandler) GetCluster(c *gin.Context) { // @Accept json // @Produce json // @Security Bearer -// @Param request body models.CreateK8sClusterRequest true "创建请求" -// @Success 200 {object} models.Response{data=models.K8sCluster} -// @Router /k8s-clusters [post] -func (h *K8sHandler) CreateCluster(c *gin.Context) { - var req models.CreateK8sClusterRequest +// @Param request body models.CreateKubernetesClusterRequest true "创建请求" +// @Success 200 {object} models.Response{data=models.KubernetesCluster} +// @Router /kubernetes-clusters [post] +func (h *KubernetesHandler) CreateCluster(c *gin.Context) { + var req models.CreateKubernetesClusterRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, models.Response{ Code: 400, @@ -105,7 +105,7 @@ func (h *K8sHandler) CreateCluster(c *gin.Context) { return } - cluster := models.K8sCluster{ + cluster := models.KubernetesCluster{ Name: req.Name, Code: req.Code, Version: req.Version, @@ -141,10 +141,10 @@ func (h *K8sHandler) CreateCluster(c *gin.Context) { // @Produce json // @Security Bearer // @Param id path int true "集群ID" -// @Param request body models.CreateK8sClusterRequest true "更新请求" -// @Success 200 {object} models.Response{data=models.K8sCluster} -// @Router /k8s-clusters/{id} [put] -func (h *K8sHandler) UpdateCluster(c *gin.Context) { +// @Param request body models.CreateKubernetesClusterRequest true "更新请求" +// @Success 200 {object} models.Response{data=models.KubernetesCluster} +// @Router /kubernetes-clusters/{id} [put] +func (h *KubernetesHandler) UpdateCluster(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) // 用 map 接收,避免 binding:"required" 校验 // Logan.Li @@ -157,7 +157,7 @@ func (h *K8sHandler) UpdateCluster(c *gin.Context) { return } - var cluster models.K8sCluster + var cluster models.KubernetesCluster if err := h.db.First(&cluster, id).Error; err != nil { c.JSON(http.StatusNotFound, models.Response{ Code: 404, @@ -219,10 +219,10 @@ func (h *K8sHandler) UpdateCluster(c *gin.Context) { } // 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")) - var cluster models.K8sCluster + var cluster models.KubernetesCluster if err := h.db.First(&cluster, id).Error; err != nil { c.JSON(http.StatusNotFound, models.Response{ Code: 404, @@ -264,11 +264,11 @@ func (h *K8sHandler) GetClusterDetail(c *gin.Context) { // @Security Bearer // @Param id path int true "集群ID" // @Success 200 {object} models.Response -// @Router /k8s-clusters/{id} [delete] -func (h *K8sHandler) DeleteCluster(c *gin.Context) { +// @Router /kubernetes-clusters/{id} [delete] +func (h *KubernetesHandler) DeleteCluster(c *gin.Context) { 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{ Code: 500, Message: "删除失败", @@ -289,9 +289,9 @@ func (h *K8sHandler) DeleteCluster(c *gin.Context) { // @Produce json // @Security Bearer // @Param id path int true "集群ID" -// @Success 200 {object} models.Response{data=[]models.K8sNode} -// @Router /k8s-clusters/{id}/nodes [get] -func (h *K8sHandler) ListNodes(c *gin.Context) { +// @Success 200 {object} models.Response{data=[]models.KubernetesNode} +// @Router /kubernetes-clusters/{id}/nodes [get] +func (h *KubernetesHandler) ListNodes(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) client, _, err := h.getK8sClient(clusterID) @@ -363,9 +363,9 @@ func (h *K8sHandler) ListNodes(c *gin.Context) { // @Security Bearer // @Param id path int true "集群ID" // @Param namespace query string false "命名空间" -// @Success 200 {object} models.Response{data=[]models.K8sPod} -// @Router /k8s-clusters/{id}/pods [get] -func (h *K8sHandler) ListPods(c *gin.Context) { +// @Success 200 {object} models.Response{data=[]models.KubernetesPod} +// @Router /kubernetes-clusters/{id}/pods [get] +func (h *KubernetesHandler) ListPods(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Query("namespace") @@ -423,9 +423,9 @@ func (h *K8sHandler) ListPods(c *gin.Context) { // @Security Bearer // @Param id path int true "集群ID" // @Param namespace query string false "命名空间" -// @Success 200 {object} models.Response{data=[]models.K8sDeployment} -// @Router /k8s-clusters/{id}/deployments [get] -func (h *K8sHandler) ListDeployments(c *gin.Context) { +// @Success 200 {object} models.Response{data=[]models.KubernetesDeployment} +// @Router /kubernetes-clusters/{id}/deployments [get] +func (h *KubernetesHandler) ListDeployments(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Query("namespace") @@ -477,9 +477,9 @@ func (h *K8sHandler) ListDeployments(c *gin.Context) { // @Security Bearer // @Param id path int true "集群ID" // @Param namespace query string false "命名空间" -// @Success 200 {object} models.Response{data=[]models.K8sService} -// @Router /k8s-clusters/{id}/services [get] -func (h *K8sHandler) ListServices(c *gin.Context) { +// @Success 200 {object} models.Response{data=[]models.KubernetesService} +// @Router /kubernetes-clusters/{id}/services [get] +func (h *KubernetesHandler) ListServices(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Query("namespace") @@ -525,9 +525,9 @@ func (h *K8sHandler) ListServices(c *gin.Context) { // @Security Bearer // @Param id path int true "集群ID" // @Param namespace query string false "命名空间" -// @Success 200 {object} models.Response{data=[]models.K8sIngress} -// @Router /k8s-clusters/{id}/ingresses [get] -func (h *K8sHandler) ListIngresses(c *gin.Context) { +// @Success 200 {object} models.Response{data=[]models.KubernetesIngress} +// @Router /kubernetes-clusters/{id}/ingresses [get] +func (h *KubernetesHandler) ListIngresses(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Query("namespace") @@ -570,7 +570,7 @@ func (h *K8sHandler) ListIngresses(c *gin.Context) { } // GetTopology 获取集群拓扑布局 // Logan.Li -func (h *K8sHandler) GetTopology(c *gin.Context) { +func (h *KubernetesHandler) GetTopology(c *gin.Context) { clusterID := c.Param("id") var layout models.TopologyLayout result := h.db.Where("cluster_id = ?", clusterID).First(&layout) @@ -582,7 +582,7 @@ func (h *K8sHandler) GetTopology(c *gin.Context) { } // SaveTopology 保存集群拓扑布局 // Logan.Li -func (h *K8sHandler) SaveTopology(c *gin.Context) { +func (h *KubernetesHandler) SaveTopology(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) var req struct { LayoutData string `json:"layout_data"` @@ -607,10 +607,10 @@ func (h *K8sHandler) SaveTopology(c *gin.Context) { // SyncCluster 同步集群资源(连接真实K8s API拉取数据) -func (h *K8sHandler) SyncCluster(c *gin.Context) { +func (h *KubernetesHandler) SyncCluster(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) - var cluster models.K8sCluster + var cluster models.KubernetesCluster if err := h.db.First(&cluster, id).Error; err != nil { c.JSON(http.StatusNotFound, models.Response{Code: 404, Message: "集群不存在"}) return @@ -632,7 +632,7 @@ func (h *K8sHandler) SyncCluster(c *gin.Context) { // Sync nodes if nodes, err := client.ListNodes(); err == nil { // 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 { role := "worker" 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) 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, parseIntResource(n.Status.Capacity["cpu"]), parseMemResource(n.Status.Capacity["memory"]), parseIntResource(n.Status.Capacity["pods"]), string(labelsJSON), now, now).Error @@ -672,7 +672,7 @@ func (h *K8sHandler) SyncCluster(c *gin.Context) { // Sync pods 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 { image := "" if len(p.Spec.Containers) > 0 { @@ -683,7 +683,7 @@ func (h *K8sHandler) SyncCluster(c *gin.Context) { restarts = p.Status.ContainerStatuses[0].RestartCount } labelsJSON, _ := json.Marshal(p.Metadata.Labels) - h.db.Create(&models.K8sPod{ + h.db.Create(&models.KubernetesPod{ ClusterID: cluster.ID, Namespace: p.Metadata.Namespace, Name: p.Metadata.Name, @@ -702,14 +702,14 @@ func (h *K8sHandler) SyncCluster(c *gin.Context) { // Sync deployments 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 { image := "" if len(d.Spec.Template.Spec.Containers) > 0 { image = d.Spec.Template.Spec.Containers[0].Image } labelsJSON, _ := json.Marshal(d.Metadata.Labels) - h.db.Create(&models.K8sDeployment{ + h.db.Create(&models.KubernetesDeployment{ ClusterID: cluster.ID, Namespace: d.Metadata.Namespace, Name: d.Metadata.Name, @@ -727,12 +727,12 @@ func (h *K8sHandler) SyncCluster(c *gin.Context) { // Sync services 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 { portsJSON, _ := json.Marshal(s.Spec.Ports) selectorJSON, _ := json.Marshal(s.Spec.Selector) labelsJSON, _ := json.Marshal(s.Metadata.Labels) - h.db.Create(&models.K8sService{ + h.db.Create(&models.KubernetesService{ ClusterID: cluster.ID, Namespace: s.Metadata.Namespace, Name: s.Metadata.Name, @@ -748,7 +748,7 @@ func (h *K8sHandler) SyncCluster(c *gin.Context) { // Sync ingresses 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 { hosts := []string{} for _, rule := range ing.Spec.Rules { @@ -764,7 +764,7 @@ func (h *K8sHandler) SyncCluster(c *gin.Context) { } tlsHostsJSON, _ := json.Marshal(tlsHosts) labelsJSON, _ := json.Marshal(ing.Metadata.Labels) - h.db.Create(&models.K8sIngress{ + h.db.Create(&models.KubernetesIngress{ ClusterID: cluster.ID, Namespace: ing.Metadata.Namespace, Name: ing.Metadata.Name, @@ -800,7 +800,7 @@ func (h *K8sHandler) SyncCluster(c *gin.Context) { // ============================================================================ // GetPodLogs 获取 Pod 日志(支持 stream=true 实时流) -func (h *K8sHandler) GetPodLogs(c *gin.Context) { +func (h *KubernetesHandler) GetPodLogs(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Param("namespace") podName := c.Param("podName") @@ -857,7 +857,7 @@ func (h *K8sHandler) GetPodLogs(c *gin.Context) { // ============================================================================ // 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")) namespace := c.Param("namespace") podName := c.Param("podName") @@ -970,7 +970,7 @@ func (h *K8sHandler) ExecTerminal(c *gin.Context) { // ============================================================================ // ExecInPod 在 Pod 中执行命令 (文件浏览 / 单次命令) -func (h *K8sHandler) ExecInPod(c *gin.Context) { +func (h *KubernetesHandler) ExecInPod(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Param("namespace") podName := c.Param("podName") @@ -1008,7 +1008,7 @@ func (h *K8sHandler) ExecInPod(c *gin.Context) { // ============================================================================ // ScaleDeployment 缩放 Deployment 副本数 -func (h *K8sHandler) ScaleDeployment(c *gin.Context) { +func (h *KubernetesHandler) ScaleDeployment(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Param("namespace") name := c.Param("name") @@ -1040,7 +1040,7 @@ func (h *K8sHandler) ScaleDeployment(c *gin.Context) { } // RestartDeployment 重启 Deployment (滚动重启) -func (h *K8sHandler) RestartDeployment(c *gin.Context) { +func (h *KubernetesHandler) RestartDeployment(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Param("namespace") name := c.Param("name") @@ -1064,7 +1064,7 @@ func (h *K8sHandler) RestartDeployment(c *gin.Context) { // ============================================================================ // ListHPAs 获取 HPA 列表 -func (h *K8sHandler) ListHPAs(c *gin.Context) { +func (h *KubernetesHandler) ListHPAs(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) client, _, err := h.getK8sClient(clusterID) @@ -1083,7 +1083,7 @@ func (h *K8sHandler) ListHPAs(c *gin.Context) { } // CreateHPA 创建 HPA -func (h *K8sHandler) CreateHPA(c *gin.Context) { +func (h *KubernetesHandler) CreateHPA(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) var req k8sclient.CreateHPARequest @@ -1112,7 +1112,7 @@ func (h *K8sHandler) CreateHPA(c *gin.Context) { } // DeleteHPA 删除 HPA -func (h *K8sHandler) DeleteHPA(c *gin.Context) { +func (h *KubernetesHandler) DeleteHPA(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Param("namespace") name := c.Param("name") @@ -1136,7 +1136,7 @@ func (h *K8sHandler) DeleteHPA(c *gin.Context) { // ============================================================================ // ListCronJobs 获取 CronJob 列表 -func (h *K8sHandler) ListCronJobs(c *gin.Context) { +func (h *KubernetesHandler) ListCronJobs(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) client, _, err := h.getK8sClient(clusterID) @@ -1155,7 +1155,7 @@ func (h *K8sHandler) ListCronJobs(c *gin.Context) { } // CreateCronJob 创建 CronJob -func (h *K8sHandler) CreateCronJob(c *gin.Context) { +func (h *KubernetesHandler) CreateCronJob(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) var req k8sclient.CreateCronJobRequest @@ -1180,7 +1180,7 @@ func (h *K8sHandler) CreateCronJob(c *gin.Context) { } // SuspendCronJob 暂停/恢复 CronJob -func (h *K8sHandler) SuspendCronJob(c *gin.Context) { +func (h *KubernetesHandler) SuspendCronJob(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Param("namespace") name := c.Param("name") @@ -1209,7 +1209,7 @@ func (h *K8sHandler) SuspendCronJob(c *gin.Context) { } // DeleteCronJob 删除 CronJob -func (h *K8sHandler) DeleteCronJob(c *gin.Context) { +func (h *KubernetesHandler) DeleteCronJob(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Param("namespace") name := c.Param("name") @@ -1229,7 +1229,7 @@ func (h *K8sHandler) DeleteCronJob(c *gin.Context) { } // TriggerCronJob 手动触发 CronJob -func (h *K8sHandler) TriggerCronJob(c *gin.Context) { +func (h *KubernetesHandler) TriggerCronJob(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Param("namespace") name := c.Param("name") @@ -1253,7 +1253,7 @@ func (h *K8sHandler) TriggerCronJob(c *gin.Context) { // ============================================================================ // UpdateDeploymentImage 更新 Deployment 容器镜像 (CI webhook) -func (h *K8sHandler) UpdateDeploymentImage(c *gin.Context) { +func (h *KubernetesHandler) UpdateDeploymentImage(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Param("namespace") name := c.Param("name") @@ -1290,8 +1290,12 @@ func (h *K8sHandler) UpdateDeploymentImage(c *gin.Context) { // ============================================================================ // ListNamespaces 获取命名空间列表 -func (h *K8sHandler) ListNamespaces(c *gin.Context) { +func (h *KubernetesHandler) ListNamespaces(c *gin.Context) { 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) if err != nil { @@ -1308,6 +1312,355 @@ func (h *K8sHandler) ListNamespaces(c *gin.Context) { 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 // ============================================================================ @@ -1342,8 +1695,8 @@ func parseMemResource(val string) int64 { // getK8sClient builds a K8s API client from cluster's stored kubeconfig -func (h *K8sHandler) getK8sClient(clusterID int) (*k8sclient.Client, *models.K8sCluster, error) { - var cluster models.K8sCluster +func (h *KubernetesHandler) getK8sClient(clusterID int) (*k8sclient.Client, *models.KubernetesCluster, error) { + var cluster models.KubernetesCluster if err := h.db.First(&cluster, clusterID).Error; err != nil { return nil, nil, fmt.Errorf("cluster not found") } diff --git a/backend/internal/api/routes/routes.go b/backend/internal/api/routes/routes.go index 41142ec..a59e6ba 100644 --- a/backend/internal/api/routes/routes.go +++ b/backend/internal/api/routes/routes.go @@ -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) // WebSocket K8s exec 交互终端 // Logan.Li - k8sHandler := handlers.NewK8sHandler(db) - r.GET("/ws/k8s-exec/:id/namespaces/:namespace/pods/:podName", middleware.JWTAuth(&cfg.JWT), k8sHandler.ExecTerminal) + k8sHandler := handlers.NewKubernetesHandler(db) + r.GET("/ws/kubernetes-exec/:id/namespaces/:namespace/pods/:podName", middleware.JWTAuth(&cfg.JWT), k8sHandler.ExecTerminal) // 公开路由 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集群路由 - clusters := authorized.Group("/k8s-clusters") + clusters := authorized.Group("/kubernetes-clusters") { clusters.GET("", k8sHandler.ListClusters) 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) } + // ============================================================================ + // 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) alerts := authorized.Group("/alerts") diff --git a/backend/internal/models/common.go b/backend/internal/models/common.go index aa98417..244afd9 100644 --- a/backend/internal/models/common.go +++ b/backend/internal/models/common.go @@ -59,7 +59,7 @@ type StatusRequest struct { // DashboardStats 仪表盘统计 type DashboardStats struct { ServerStats ServerStats `json:"server_stats"` - K8sStats K8sClusterStats `json:"k8s_stats"` + K8sStats KubernetesClusterStats `json:"kubernetes_stats"` AlertStats AlertStats `json:"alert_stats"` PipelineStats PipelineStats `json:"pipeline_stats"` CostStats CostStats `json:"cost_stats"` diff --git a/backend/internal/models/k8s.go b/backend/internal/models/kubernetes.go similarity index 84% rename from backend/internal/models/k8s.go rename to backend/internal/models/kubernetes.go index 51e8bc4..f57f95d 100644 --- a/backend/internal/models/k8s.go +++ b/backend/internal/models/kubernetes.go @@ -5,8 +5,8 @@ import ( ) -// K8sCluster K8s集群模型 -type K8sCluster struct { +// KubernetesCluster K8s集群模型 +type KubernetesCluster struct { ID uint `gorm:"primarykey" json:"id"` Name string `gorm:"size:128;not null" json:"name"` Code string `gorm:"size:64;uniqueIndex;not null" json:"code"` @@ -31,12 +31,12 @@ type K8sCluster struct { } // TableName 表名 -func (K8sCluster) TableName() string { +func (KubernetesCluster) TableName() string { return "container_clusters" } -// K8sNode K8s节点模型 -type K8sNode struct { +// KubernetesNode K8s节点模型 +type KubernetesNode struct { ID uint `gorm:"primarykey" json:"id"` ClusterID uint `gorm:"not null" json:"cluster_id"` Name string `gorm:"size:128;not null" json:"name"` @@ -58,16 +58,16 @@ type K8sNode struct { UpdatedAt time.Time `json:"updated_at"` // 关联 - Cluster *K8sCluster `json:"cluster,omitempty"` + Cluster *KubernetesCluster `json:"cluster,omitempty"` } // TableName 表名 -func (K8sNode) TableName() string { - return "k8s_nodes" +func (KubernetesNode) TableName() string { + return "kubernetes_nodes" } -// K8sPod K8s Pod模型 -type K8sPod struct { +// KubernetesPod K8s Pod模型 +type KubernetesPod struct { ID uint `gorm:"primarykey" json:"id"` ClusterID uint `gorm:"not null" json:"cluster_id"` Namespace string `gorm:"size:64;not null" json:"namespace"` @@ -84,16 +84,16 @@ type K8sPod struct { UpdatedAt time.Time `json:"updated_at"` // 关联 - Cluster *K8sCluster `json:"cluster,omitempty"` + Cluster *KubernetesCluster `json:"cluster,omitempty"` } // TableName 表名 -func (K8sPod) TableName() string { - return "k8s_pods" +func (KubernetesPod) TableName() string { + return "kubernetes_pods" } -// K8sDeployment K8s Deployment模型 -type K8sDeployment struct { +// KubernetesDeployment K8s Deployment模型 +type KubernetesDeployment struct { ID uint `gorm:"primarykey" json:"id"` ClusterID uint `gorm:"not null" json:"cluster_id"` Namespace string `gorm:"size:64;not null" json:"namespace"` @@ -108,16 +108,16 @@ type K8sDeployment struct { UpdatedAt time.Time `json:"updated_at"` // 关联 - Cluster *K8sCluster `json:"cluster,omitempty"` + Cluster *KubernetesCluster `json:"cluster,omitempty"` } // TableName 表名 -func (K8sDeployment) TableName() string { - return "k8s_deployments" +func (KubernetesDeployment) TableName() string { + return "kubernetes_deployments" } -// K8sService K8s Service模型 -type K8sService struct { +// KubernetesService K8s Service模型 +type KubernetesService struct { ID uint `gorm:"primarykey" json:"id"` ClusterID uint `gorm:"not null" json:"cluster_id"` Namespace string `gorm:"size:64;not null" json:"namespace"` @@ -132,16 +132,16 @@ type K8sService struct { UpdatedAt time.Time `json:"updated_at"` // 关联 - Cluster *K8sCluster `json:"cluster,omitempty"` + Cluster *KubernetesCluster `json:"cluster,omitempty"` } // TableName 表名 -func (K8sService) TableName() string { - return "k8s_services" +func (KubernetesService) TableName() string { + return "kubernetes_services" } -// K8sIngress K8s Ingress模型 -type K8sIngress struct { +// KubernetesIngress K8s Ingress模型 +type KubernetesIngress struct { ID uint `gorm:"primarykey" json:"id"` ClusterID uint `gorm:"not null" json:"cluster_id"` Namespace string `gorm:"size:64;not null" json:"namespace"` @@ -155,16 +155,16 @@ type K8sIngress struct { UpdatedAt time.Time `json:"updated_at"` // 关联 - Cluster *K8sCluster `json:"cluster,omitempty"` + Cluster *KubernetesCluster `json:"cluster,omitempty"` } // TableName 表名 -func (K8sIngress) TableName() string { - return "k8s_ingresses" +func (KubernetesIngress) TableName() string { + return "kubernetes_ingresses" } -// CreateK8sClusterRequest 创建K8s集群请求 -type CreateK8sClusterRequest struct { +// CreateKubernetesClusterRequest 创建K8s集群请求 +type CreateKubernetesClusterRequest struct { Name string `json:"name" binding:"required"` Code string `json:"code" binding:"required"` Version string `json:"version"` @@ -176,8 +176,8 @@ type CreateK8sClusterRequest struct { Description string `json:"description"` } -// K8sClusterStats K8s集群统计 -type K8sClusterStats struct { +// KubernetesClusterStats K8s集群统计 +type KubernetesClusterStats struct { Total int64 `json:"total"` Healthy int64 `json:"healthy"` Warning int64 `json:"warning"` diff --git a/backend/pkg/database/database.go b/backend/pkg/database/database.go index 0cae5db..cedc38e 100644 --- a/backend/pkg/database/database.go +++ b/backend/pkg/database/database.go @@ -66,12 +66,12 @@ func AutoMigrate(db *gorm.DB) error { &models.CloudAccount{}, &models.Region{}, &models.Domain{}, - &models.K8sCluster{}, - &models.K8sNode{}, - &models.K8sPod{}, - &models.K8sDeployment{}, - &models.K8sService{}, - &models.K8sIngress{}, + &models.KubernetesCluster{}, + &models.KubernetesNode{}, + &models.KubernetesPod{}, + &models.KubernetesDeployment{}, + &models.KubernetesService{}, + &models.KubernetesIngress{}, &models.AlertRule{}, &models.AlertEvent{}, &models.NotifyChannel{}, diff --git a/backend/pkg/k8sclient/client.go b/backend/pkg/kubernetesclient/client.go similarity index 93% rename from backend/pkg/k8sclient/client.go rename to backend/pkg/kubernetesclient/client.go index 5175e81..f2ff3b7 100644 --- a/backend/pkg/k8sclient/client.go +++ b/backend/pkg/kubernetesclient/client.go @@ -880,3 +880,68 @@ func (c *Client) ListNamespaces() ([]string, error) { } 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 +} diff --git a/backend/scripts/mock.go b/backend/scripts/mock.go index 82b5756..1b78de8 100644 --- a/backend/scripts/mock.go +++ b/backend/scripts/mock.go @@ -43,7 +43,7 @@ func generateMockData(db *gorm.DB) error { if err := generateDomains(db); err != nil { return err } - if err := generateK8sClusters(db); err != nil { + if err := generateKubernetesClusters(db); err != nil { return err } if err := generateAlertRules(db); err != nil { @@ -164,8 +164,8 @@ func generateDomains(db *gorm.DB) error { return nil } -func generateK8sClusters(db *gorm.DB) error { - clusters := []models.K8sCluster{ +func generateKubernetesClusters(db *gorm.DB) error { + 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: "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 集群"}, @@ -179,7 +179,7 @@ func generateK8sClusters(db *gorm.DB) error { for _, cluster := range clusters { // 生成节点 for j := 0; j < cluster.NodeCount; j++ { - node := models.K8sNode{ + node := models.KubernetesNode{ ClusterID: cluster.ID, Name: fmt.Sprintf("%s-node-%d", cluster.Code, j+1), 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 { available = 0 } - dep := models.K8sDeployment{ + dep := models.KubernetesDeployment{ ClusterID: cluster.ID, Namespace: namespaces[rand.Intn(len(namespaces))], Name: depNames[d], @@ -253,7 +253,7 @@ func generateK8sClusters(db *gorm.DB) error { } for p := 0; p < numPods; p++ { status := podStatuses[rand.Intn(len(podStatuses))] - pod := models.K8sPod{ + pod := models.KubernetesPod{ ClusterID: cluster.ID, Namespace: namespaces[rand.Intn(len(namespaces))], Name: fmt.Sprintf("%s-pod-%d", cluster.Code, p+1), @@ -297,7 +297,7 @@ func generateK8sClusters(db *gorm.DB) error { default: ports = `[{"port":3306,"protocol":"TCP","targetPort":3306}]` } - svc := models.K8sService{ + svc := models.KubernetesService{ ClusterID: cluster.ID, Namespace: namespaces[rand.Intn(len(namespaces))], Name: svcNames[s], @@ -323,7 +323,7 @@ func generateK8sClusters(db *gorm.DB) error { numIngs := 2 + rand.Intn(3) for ig := 0; ig < numIngs && ig < len(ingNames); ig++ { hosts := fmt.Sprintf(`["%s.ezops.local"]`, ingNames[ig]) - ing := models.K8sIngress{ + ing := models.KubernetesIngress{ ClusterID: cluster.ID, Namespace: namespaces[rand.Intn(len(namespaces))], Name: ingNames[ig], diff --git a/backend/scripts/seed_k8s_resources.go b/backend/scripts/seed_kubernetes_resources.go similarity index 89% rename from backend/scripts/seed_k8s_resources.go rename to backend/scripts/seed_kubernetes_resources.go index 93dff51..0df544f 100644 --- a/backend/scripts/seed_k8s_resources.go +++ b/backend/scripts/seed_kubernetes_resources.go @@ -1,5 +1,5 @@ // 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 package main @@ -23,7 +23,7 @@ func main() { } rand.Seed(time.Now().UnixNano()) - var clusters []models.K8sCluster + var clusters []models.KubernetesCluster db.Find(&clusters) if len(clusters) == 0 { fmt.Println("No clusters found — run mock.go first") @@ -54,7 +54,7 @@ func main() { for _, cluster := range clusters { // Skip if pods already exist for this cluster 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 { fmt.Printf("Cluster %s already has %d pods, skipping\n", cluster.Name, podCount) continue @@ -79,7 +79,7 @@ func main() { if available < 0 { available = 0 } - db.Create(&models.K8sDeployment{ + db.Create(&models.KubernetesDeployment{ ClusterID: cluster.ID, Namespace: namespaces[rand.Intn(len(namespaces))], Name: depNames[d], @@ -94,7 +94,7 @@ func main() { // Pods numPods := 12 + rand.Intn(18) for p := 0; p < numPods; p++ { - db.Create(&models.K8sPod{ + db.Create(&models.KubernetesPod{ ClusterID: cluster.ID, Namespace: namespaces[rand.Intn(len(namespaces))], Name: fmt.Sprintf("%s-pod-%d", cluster.Code, p+1), @@ -126,7 +126,7 @@ func main() { default: ports = `[{"port":3306,"protocol":"TCP","targetPort":3306}]` } - svc := models.K8sService{ + svc := models.KubernetesService{ ClusterID: cluster.ID, Namespace: namespaces[rand.Intn(len(namespaces))], Name: svcNames[s], @@ -150,7 +150,7 @@ func main() { numIngs = len(ingNames) } for ig := 0; ig < numIngs; ig++ { - db.Create(&models.K8sIngress{ + db.Create(&models.KubernetesIngress{ ClusterID: cluster.ID, Namespace: namespaces[rand.Intn(len(namespaces))], Name: ingNames[ig], @@ -162,9 +162,9 @@ func main() { // Update cluster counts to match actual data var depCnt, podCnt, svcCnt int64 - db.Model(&models.K8sDeployment{}).Where("cluster_id = ?", cluster.ID).Count(&depCnt) - db.Model(&models.K8sPod{}).Where("cluster_id = ?", cluster.ID).Count(&podCnt) - db.Model(&models.K8sService{}).Where("cluster_id = ?", cluster.ID).Count(&svcCnt) + db.Model(&models.KubernetesDeployment{}).Where("cluster_id = ?", cluster.ID).Count(&depCnt) + db.Model(&models.KubernetesPod{}).Where("cluster_id = ?", cluster.ID).Count(&podCnt) + db.Model(&models.KubernetesService{}).Where("cluster_id = ?", cluster.ID).Count(&svcCnt) db.Model(&cluster).Updates(map[string]interface{}{ "deployment_count": depCnt, }) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5235bff..cfa4b61 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -12,10 +12,10 @@ import { DomainManager } from '@/components/cloud/DomainManager'; import { RegionManager } from '@/components/cloud/RegionManager'; import { PlatformManager } from '@/components/cloud/PlatformManager'; import { CloudBilling } from '@/components/cloud/CloudBilling'; -import { K8sClusters } from '@/components/k8s/K8sClusters'; -import { K8sNodes } from '@/components/k8s/K8sNodes'; -import { K8sWorkloads } from '@/components/k8s/K8sWorkloads'; -import { TopologyView } from '@/components/k8s/TopologyView'; +import { KubernetesClusters } from '@/components/kubernetes/KubernetesClusters'; +import { KubernetesNodes } from '@/components/kubernetes/KubernetesNodes'; +import { KubernetesWorkloads } from '@/components/kubernetes/KubernetesWorkloads'; +import { TopologyView } from '@/components/kubernetes/TopologyView'; import { AlertManager } from '@/components/monitor/AlertManager'; import { AppReleaseManager } from '@/components/deploy/AppRelease'; import { ApprovalFlowManager } from '@/components/approval/ApprovalFlow'; @@ -65,9 +65,9 @@ function App() { } /> {/* 资源监控(Agent 指标 + 中间件 + SNMP + 拨测) */} } /> - } /> - } /> - } /> + } /> + } /> + } /> } /> {/* 应用发布 */} } /> diff --git a/frontend/src/components/aiops/AIOpsAssistant.tsx b/frontend/src/components/aiops/AIOpsAssistant.tsx index d72f8e0..1c3ded4 100644 --- a/frontend/src/components/aiops/AIOpsAssistant.tsx +++ b/frontend/src/components/aiops/AIOpsAssistant.tsx @@ -102,7 +102,7 @@ async function loadReportContext(): Promise { dashboardStatsApi.getReleases(10).catch(() => null), ]); 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 alertList: any[] = Array.isArray(alerts) ? alerts : (alerts as any)?.list || []; const releaseList: any[] = Array.isArray(releases) ? releases : (releases as any)?.list || []; diff --git a/frontend/src/components/dashboard/Dashboard.tsx b/frontend/src/components/dashboard/Dashboard.tsx index d6ebbae..c44bdce 100644 --- a/frontend/src/components/dashboard/Dashboard.tsx +++ b/frontend/src/components/dashboard/Dashboard.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useState } from 'react'; import { Button } from '@/components/ui/button'; 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 { CostOverviewCard, TopResourceUsageCard, ResourceUsageCard, computeMonthChange } from './DashboardCharts'; import { @@ -9,7 +9,7 @@ import { CertificateExpiryCard, ReleaseHistoryCard, ServerStatusCard, - K8sClusterCard, + KubernetesClusterCard, } from './DashboardRecent'; export function Dashboard() { @@ -35,7 +35,7 @@ export function Dashboard() { console.error('Failed to fetch servers:', e); return null; }), - k8sApi.getClusters().catch((e) => { + kubernetesApi.getClusters().catch((e) => { console.error('Failed to fetch k8s clusters:', e); return null; }), @@ -82,7 +82,7 @@ export function Dashboard() { } const serverStats = stats?.server_stats; - const k8sStats = stats?.k8s_stats; + const k8sStats = stats?.kubernetes_stats; const alertStats = stats?.alert_stats; const costStats = stats?.cost_stats; const costChange = computeMonthChange(costStats?.month_trend); @@ -165,7 +165,7 @@ export function Dashboard() {
- +
); diff --git a/frontend/src/components/dashboard/DashboardRecent.tsx b/frontend/src/components/dashboard/DashboardRecent.tsx index fb59f59..fd7fe97 100644 --- a/frontend/src/components/dashboard/DashboardRecent.tsx +++ b/frontend/src/components/dashboard/DashboardRecent.tsx @@ -295,7 +295,7 @@ export function ServerStatusCard({ servers }: { servers: any[] }) { } // K8s集群状态卡片 -export function K8sClusterCard({ clusters }: { clusters: any[] }) { +export function KubernetesClusterCard({ clusters }: { clusters: any[] }) { const navigate = useNavigate(); return ( diff --git a/frontend/src/components/k8s/DeploymentDetail.tsx b/frontend/src/components/kubernetes/DeploymentDetail.tsx similarity index 95% rename from frontend/src/components/k8s/DeploymentDetail.tsx rename to frontend/src/components/kubernetes/DeploymentDetail.tsx index c67b84e..d6400e1 100644 --- a/frontend/src/components/k8s/DeploymentDetail.tsx +++ b/frontend/src/components/kubernetes/DeploymentDetail.tsx @@ -14,17 +14,17 @@ import { Gauge, Clock, Play, Pause, Trash2, RefreshCw, Loader2, CheckCircle, AlertTriangle, XCircle, ExternalLink, } from 'lucide-react'; -import { k8sApi } from '@/services/api'; +import { kubernetesApi } from '@/services/api'; import { useToast } from '@/hooks/use-toast'; interface DeploymentDetailProps { - clusterId: string; + clusterCode: string; namespace: string; deploymentName: string; onBack: () => void; } -export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack }: DeploymentDetailProps) { +export function DeploymentDetail({ clusterCode, namespace, deploymentName, onBack }: DeploymentDetailProps) { const { toast } = useToast(); const [deployment, setDeployment] = useState(null); const [pods, setPods] = useState([]); @@ -47,9 +47,9 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack setLoading(true); try { const [depRes, podRes, hpaRes] = await Promise.all([ - k8sApi.getDeployments(clusterId).catch(() => []), - k8sApi.getPods(clusterId).catch(() => []), - k8sApi.getHPAs(clusterId).catch(() => ({ items: [] })), + kubernetesApi.getNamespaceDeployments(clusterCode, namespace).catch(() => []), + kubernetesApi.getNamespacePods(clusterCode, namespace).catch(() => []), + kubernetesApi.getHPAs(clusterCode, namespace).catch(() => ({ items: [] })), ]); const toArray = (v: any) => (Array.isArray(v) ? v : (v?.list || v?.items || [])); const allDeps = toArray(depRes); @@ -80,7 +80,7 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack } finally { setLoading(false); } - }, [clusterId, namespace, deploymentName]); + }, [clusterCode, namespace, deploymentName]); useEffect(() => { loadData(); }, [loadData]); @@ -89,7 +89,7 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack if (newReplicas < 0) return; setActionLoading('scale'); try { - await k8sApi.scaleDeployment(clusterId, namespace, deploymentName, newReplicas); + await kubernetesApi.scaleDeployment(clusterCode, namespace, deploymentName, newReplicas); toast({ title: '扩缩容成功', description: `副本数已设置为 ${newReplicas}` }); loadData(); } catch (e: any) { @@ -100,7 +100,7 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack const handleRestart = async () => { setActionLoading('restart'); try { - await k8sApi.restartDeployment(clusterId, namespace, deploymentName); + await kubernetesApi.restartDeployment(clusterCode, namespace, deploymentName); toast({ title: '重启指令已下发', description: 'Deployment 正在滚动重启' }); } catch (e: any) { toast({ title: '重启失败', description: e?.message || String(e), variant: 'destructive' }); @@ -111,7 +111,7 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack if (!newImage.trim()) return; setActionLoading('image'); try { - await k8sApi.updateDeploymentImage(clusterId, namespace, deploymentName, newImage); + await kubernetesApi.updateDeploymentImage(clusterCode, namespace, deploymentName, newImage); toast({ title: '镜像更新成功', description: `正在滚动更新为 ${newImage}` }); setShowImageUpdate(false); loadData(); @@ -125,7 +125,7 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack setShowLogs(true); setLogs(''); 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 || '无日志'); } catch { setLogs('获取日志失败'); @@ -136,7 +136,7 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack // Not direct toggle — delete HPA if (!confirm(`确定删除 HPA "${hpa.metadata?.name}"?`)) return; try { - await k8sApi.deleteHPA(clusterId, namespace, hpa.metadata?.name); + await kubernetesApi.deleteHPA(clusterCode, namespace, hpa.metadata?.name); toast({ title: 'HPA 已删除' }); loadData(); } catch (e: any) { @@ -226,7 +226,7 @@ export function DeploymentDetail({ clusterId, namespace, deploymentName, onBack

未配置

HPA 自动扩缩容 void; +function CreateHPADialog({ clusterCode, namespace, deploymentName, onCreated }: { + clusterCode: string; namespace: string; deploymentName: string; onCreated: () => void; }) { const { toast } = useToast(); const [open, setOpen] = useState(false); @@ -527,7 +527,7 @@ function CreateHPADialog({ clusterId, namespace, deploymentName, onCreated }: { } setLoading(true); try { - await k8sApi.createHPA(clusterId, { + await kubernetesApi.createHPA(clusterCode, { name: form.name, namespace, targetKind: 'Deployment', diff --git a/frontend/src/components/k8s/K8sClusterDetail.tsx b/frontend/src/components/kubernetes/KubernetesClusterDetail.tsx similarity index 98% rename from frontend/src/components/k8s/K8sClusterDetail.tsx rename to frontend/src/components/kubernetes/KubernetesClusterDetail.tsx index 4c4f467..255100f 100644 --- a/frontend/src/components/k8s/K8sClusterDetail.tsx +++ b/frontend/src/components/kubernetes/KubernetesClusterDetail.tsx @@ -4,7 +4,7 @@ import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Cloud, XCircle } from 'lucide-react'; -import { k8sApi } from '@/services/api'; +import { kubernetesApi } from '@/services/api'; import { AreaChart, Area, @@ -26,7 +26,7 @@ const resourceData = [ { 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([]); const [pods, setPods] = useState([]); const [deployments, setDeployments] = useState([]); @@ -37,11 +37,11 @@ export function K8sClusterDetail({ cluster, onClose }: { cluster: any; onClose: try { // 后端 ListNodes/ListPods/etc 返回 data 直接为数组(非 {list,total}) const [nodesRes, podsRes, depRes, svcRes, ingRes] = await Promise.all([ - k8sApi.getNodes(clusterId).catch(() => []), - k8sApi.getPods(clusterId).catch(() => []), - k8sApi.getDeployments(clusterId).catch(() => []), - k8sApi.getServices(clusterId).catch(() => []), - k8sApi.getIngresses(clusterId).catch(() => []), + kubernetesApi.getNodes(clusterId).catch(() => []), + kubernetesApi.getPods(clusterId).catch(() => []), + kubernetesApi.getDeployments(clusterId).catch(() => []), + kubernetesApi.getServices(clusterId).catch(() => []), + kubernetesApi.getIngresses(clusterId).catch(() => []), ]); const toArray = (v: any) => (Array.isArray(v) ? v : (v?.list || [])); setNodes(toArray(nodesRes)); diff --git a/frontend/src/components/k8s/K8sClusterDialog.tsx b/frontend/src/components/kubernetes/KubernetesClusterDialog.tsx similarity index 97% rename from frontend/src/components/k8s/K8sClusterDialog.tsx rename to frontend/src/components/kubernetes/KubernetesClusterDialog.tsx index 6b16dd3..d16f32f 100644 --- a/frontend/src/components/k8s/K8sClusterDialog.tsx +++ b/frontend/src/components/kubernetes/KubernetesClusterDialog.tsx @@ -12,7 +12,7 @@ import { DialogHeader, DialogTitle, } from '@/components/ui/dialog'; -import { k8sApi } from '@/services/api'; +import { kubernetesApi } from '@/services/api'; const emptyForm = { name: '', @@ -24,7 +24,7 @@ const emptyForm = { kube_config: '', }; -export function K8sClusterDialog({ +export function KubernetesClusterDialog({ open, onOpenChange, editingCluster, @@ -45,7 +45,7 @@ export function K8sClusterDialog({ if (!open) return; if (editingCluster && editingCluster.id) { // 编辑模式:拉取详情获取 has_token/has_kube_config 标记 // Logan.Li - k8sApi.getClusterDetail(editingCluster.id).then((res: any) => { + kubernetesApi.getClusterDetail(editingCluster.id).then((res: any) => { const d = res || {}; setHasToken(!!d.has_token); setHasKubeConfig(!!d.has_kube_config); @@ -106,9 +106,9 @@ export function K8sClusterDialog({ kube_config: formData.config_type === 1 ? formData.kube_config : '', }; if (editingCluster) { - await k8sApi.updateCluster(editingCluster.id, payload); + await kubernetesApi.updateCluster(editingCluster.id, payload); } else { - await k8sApi.createCluster(payload); + await kubernetesApi.createCluster(payload); } onOpenChange(false); onSaved(); diff --git a/frontend/src/components/k8s/K8sClusters.tsx b/frontend/src/components/kubernetes/KubernetesClusters.tsx similarity index 96% rename from frontend/src/components/k8s/K8sClusters.tsx rename to frontend/src/components/kubernetes/KubernetesClusters.tsx index 19f1f23..233b88c 100644 --- a/frontend/src/components/k8s/K8sClusters.tsx +++ b/frontend/src/components/kubernetes/KubernetesClusters.tsx @@ -27,12 +27,12 @@ import { Trash2, Search, } from 'lucide-react'; -import { k8sApi } from '@/services/api'; -import type { K8sCluster } from '@/types'; -import { K8sClusterDetail } from './K8sClusterDetail'; -import { K8sClusterDialog } from './K8sClusterDialog'; +import { kubernetesApi } from '@/services/api'; +import type { KubernetesCluster } from '@/types'; +import { KubernetesClusterDetail } from './KubernetesClusterDetail'; +import { KubernetesClusterDialog } from './KubernetesClusterDialog'; -export function K8sClusters() { +export function KubernetesClusters() { const [clusters, setClusters] = useState([]); const [loading, setLoading] = useState(true); const [selectedCluster, setSelectedCluster] = useState(null); @@ -45,7 +45,7 @@ export function K8sClusters() { const loadData = async () => { try { setLoading(true); - const res: any = await k8sApi.getClusters(); + const res: any = await kubernetesApi.getClusters(); setClusters(res?.list || []); } catch (e) { console.error('加载集群列表失败:', e); @@ -61,7 +61,7 @@ export function K8sClusters() { const handleDelete = async (id: string) => { try { - await k8sApi.deleteCluster(id); + await kubernetesApi.deleteCluster(id); loadData(); } catch (e) { console.error('删除集群失败:', e); @@ -74,7 +74,7 @@ export function K8sClusters() { (cluster.version || '').includes(searchQuery) ); - const getStatusBadge = (status: K8sCluster['status']) => { + const getStatusBadge = (status: KubernetesCluster['status']) => { switch (status) { case 'healthy': return ( @@ -345,11 +345,11 @@ export function K8sClusters() { {/* Cluster Detail */} {clusterDetail && ( - setSelectedCluster(null)} /> + setSelectedCluster(null)} /> )} {/* 创建/编辑集群 Dialog */} - ([]); const [selectedCluster, setSelectedCluster] = useState(''); const [nodes, setNodes] = useState([]); @@ -22,7 +22,7 @@ export function K8sNodes() { const loadClusters = useCallback(async () => { try { - const res: any = await k8sApi.getClusters(); + const res: any = await kubernetesApi.getClusters(); const list = res?.list || []; setClusters(list); if (list.length > 0 && !selectedCluster) setSelectedCluster(String(list[0].id)); @@ -35,7 +35,7 @@ export function K8sNodes() { if (!selectedCluster) return; setLoading(true); try { - const res: any = await k8sApi.getNodes(selectedCluster); + const res: any = await kubernetesApi.getNodes(selectedCluster); setNodes(Array.isArray(res) ? res : (res?.list || [])); } catch { setNodes([]); } finally { setLoading(false); } }, [selectedCluster]); diff --git a/frontend/src/components/k8s/K8sWorkloads.tsx b/frontend/src/components/kubernetes/KubernetesWorkloads.tsx similarity index 91% rename from frontend/src/components/k8s/K8sWorkloads.tsx rename to frontend/src/components/kubernetes/KubernetesWorkloads.tsx index d71ec72..68876b0 100644 --- a/frontend/src/components/k8s/K8sWorkloads.tsx +++ b/frontend/src/components/kubernetes/KubernetesWorkloads.tsx @@ -14,11 +14,11 @@ import { RefreshCw, Search, Boxes, Loader2, Container, Plus, Minus, RotateCw, Upload, FileText, Eye, } from 'lucide-react'; -import { k8sApi } from '@/services/api'; +import { kubernetesApi } from '@/services/api'; import { useToast } from '@/hooks/use-toast'; import { DeploymentDetail } from './DeploymentDetail'; -export function K8sWorkloads() { +export function KubernetesWorkloads() { const { toast } = useToast(); const [clusters, setClusters] = useState([]); const [selectedCluster, setSelectedCluster] = useState(''); @@ -36,10 +36,10 @@ export function K8sWorkloads() { const loadClusters = useCallback(async () => { try { - const res: any = await k8sApi.getClusters(); + const res: any = await kubernetesApi.getClusters(); const list = res?.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([]); } }, []); @@ -49,7 +49,7 @@ export function K8sWorkloads() { const loadNamespaces = useCallback(async () => { if (!selectedCluster) return; 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 || []); setNamespaces(nsList); } catch { setNamespaces([]); } @@ -60,16 +60,27 @@ export function K8sWorkloads() { const loadData = useCallback(async () => { if (!selectedCluster) return; setLoading(true); + const clusterCode = selectedCluster; try { - const [depRes, podRes] = await Promise.all([ - k8sApi.getDeployments(selectedCluster).catch(() => []), - k8sApi.getPods(selectedCluster).catch(() => []), - ]); - const toArray = (v: any) => (Array.isArray(v) ? v : (v?.list || [])); - setDeployments(toArray(depRes)); - setPods(toArray(podRes)); + if (selectedNamespace !== 'all') { + const [depRes, podRes] = await Promise.all([ + kubernetesApi.getNamespaceDeployments(clusterCode, selectedNamespace).catch(() => []), + kubernetesApi.getNamespacePods(clusterCode, selectedNamespace).catch(() => []), + ]); + const toArray = (v: any) => (Array.isArray(v) ? v : (v?.list || [])); + 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); } - }, [selectedCluster]); + }, [selectedCluster, selectedNamespace]); useEffect(() => { loadData(); }, [loadData]); @@ -98,7 +109,7 @@ export function K8sWorkloads() { try { const newReplicas = (dep.replicas || 0) + delta; 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} 副本` }); loadData(); } catch (e: any) { @@ -112,7 +123,7 @@ export function K8sWorkloads() { if (selectedDeployment) { return ( { setSelectedDeployment(null); loadData(); }} @@ -134,7 +145,7 @@ export function K8sWorkloads() { {clusters.map((c) => ( - {c.name} + {c.name} ))} diff --git a/frontend/src/components/k8s/TopologyView.tsx b/frontend/src/components/kubernetes/TopologyView.tsx similarity index 97% rename from frontend/src/components/k8s/TopologyView.tsx rename to frontend/src/components/kubernetes/TopologyView.tsx index 4fb34b6..6a43f88 100644 --- a/frontend/src/components/k8s/TopologyView.tsx +++ b/frontend/src/components/kubernetes/TopologyView.tsx @@ -14,7 +14,7 @@ import { Textarea } from '@/components/ui/textarea'; import { Search, RefreshCw, Loader2, Cloud, Save, Plus, Trash2, Link2, Pencil, RotateCcw, } from 'lucide-react'; -import { k8sApi } from '@/services/api'; +import { kubernetesApi } from '@/services/api'; import { useConfirm } from '@/components/ui/confirm-dialog'; import { toast } from 'sonner'; import type { TopologyNode, TopologyEdge } from '@/types'; @@ -50,7 +50,7 @@ export function TopologyView() { const loadClusters = useCallback(async () => { try { - const res: any = await k8sApi.getClusters(); + const res: any = await kubernetesApi.getClusters(); const list = res?.list || []; setClusters(list); if (list.length > 0 && !selectedClusterId) setSelectedClusterId(String(list[0].id)); @@ -62,7 +62,7 @@ export function TopologyView() { // 加载已保存的布局 // Logan.Li const loadSavedLayout = useCallback(async (clusterId: string) => { try { - const res: any = await k8sApi.getTopology(clusterId); + const res: any = await kubernetesApi.getTopology(clusterId); const layout = res as any; if (layout?.layout_data) { const data = JSON.parse(layout.layout_data) as TopologyLayoutData; @@ -86,10 +86,10 @@ export function TopologyView() { try { const toArray = (v: any) => (Array.isArray(v) ? v : (v?.list || [])); const [nodesRes, svcRes, depRes, ingRes] = await Promise.all([ - k8sApi.getNodes(selectedClusterId).catch(() => []), - k8sApi.getServices(selectedClusterId).catch(() => []), - k8sApi.getDeployments(selectedClusterId).catch(() => []), - k8sApi.getIngresses(selectedClusterId).catch(() => []), + kubernetesApi.getNodes(selectedClusterId).catch(() => []), + kubernetesApi.getServices(selectedClusterId).catch(() => []), + kubernetesApi.getDeployments(selectedClusterId).catch(() => []), + kubernetesApi.getIngresses(selectedClusterId).catch(() => []), ]); const cluster = clusters.find((c) => String(c.id) === selectedClusterId) || {}; 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)), 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('布局已保存'); } catch { toast.error('保存失败'); } finally { setSaving(false); } @@ -255,7 +255,7 @@ export function TopologyView() { const handleResetLayout = async () => { const ok = await _confirm({ title: '重置布局', description: '将清除已保存的布局并重新自动排列', variant: 'destructive' }); if (!ok) return; - try { await k8sApi.saveTopology(selectedClusterId, ''); } catch { /* */ } + try { await kubernetesApi.saveTopology(selectedClusterId, ''); } catch { /* */ } setSavedLayout(null); setEditMode(false); setConnectMode(false); diff --git a/frontend/src/components/k8s/topology-helpers.tsx b/frontend/src/components/kubernetes/topology-helpers.tsx similarity index 100% rename from frontend/src/components/k8s/topology-helpers.tsx rename to frontend/src/components/kubernetes/topology-helpers.tsx diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx index c56adbe..1ace4c9 100644 --- a/frontend/src/components/layout/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar.tsx @@ -62,7 +62,7 @@ const menuItems: MenuItem[] = [ ], }, { - id: 'k8s', + id: 'kubernetes', name: 'Kubernetes', icon: Cloud, children: [ diff --git a/frontend/src/components/monitor/dashboard/HostMonitorDashboard.tsx b/frontend/src/components/monitor/dashboard/HostMonitorDashboard.tsx index 0ca1078..2ca0c77 100644 --- a/frontend/src/components/monitor/dashboard/HostMonitorDashboard.tsx +++ b/frontend/src/components/monitor/dashboard/HostMonitorDashboard.tsx @@ -1,5 +1,5 @@ // 主机监控大屏 — 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 { useNavigate } from 'react-router-dom'; import { diff --git a/frontend/src/components/monitor/dashboard/PrometheusDashboard.tsx b/frontend/src/components/monitor/dashboard/PrometheusDashboard.tsx index c00c246..23720ae 100644 --- a/frontend/src/components/monitor/dashboard/PrometheusDashboard.tsx +++ b/frontend/src/components/monitor/dashboard/PrometheusDashboard.tsx @@ -122,7 +122,7 @@ export const PrometheusDashboard: React.FC = ({ dsUrl }) => { const [loading, setLoading] = useState(false); const [targets, setTargets] = useState(0); const [error, setError] = useState(''); - const [tab, setTab] = useState('k8s'); + const [tab, setTab] = useState('kubernetes'); const [k8sD, setK8sD] = useState({}); const [nodeD, setNodeD] = useState({}); const [podD, setPodD] = useState({}); @@ -143,13 +143,13 @@ export const PrometheusDashboard: React.FC = ({ dsUrl }) => { }; const loadAll = async (u: string) => { - for (const t of ['k8s','node','pod','jvm']) { + for (const t of ['kubernetes','node','pod','jvm']) { try { const data: any = await http.post('/prometheus/dashboard', { url: u, type: t }); const items: any = {}; 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); - 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); } catch {} } @@ -175,12 +175,12 @@ export const PrometheusDashboard: React.FC = ({ dsUrl }) => { : ( - K8s + K8s Node Pod JVM - + diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 8d174c8..23459b4 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -156,100 +156,119 @@ export const serverApi = { }; // ==================== K8s 集群 API ==================== -export const k8sApi = { +export const kubernetesApi = { getClusters: async () => { - return http.get('/k8s-clusters'); + return http.get('/kubernetes-clusters'); }, getClusterDetail: async (id: number | string) => { - return http.get(`/k8s-clusters/${id}/detail`); + return http.get(`/kubernetes-clusters/${id}/detail`); }, createCluster: async (data: any) => { - return http.post('/k8s-clusters', data); + return http.post('/kubernetes-clusters', data); }, 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) => { - return http.delete(`/k8s-clusters/${id}`); + return http.delete(`/kubernetes-clusters/${id}`); }, getNodes: async (clusterId: number | string) => { - return http.get(`/k8s-clusters/${clusterId}/nodes`); + return http.get(`/kubernetes-clusters/${clusterId}/nodes`); }, getPods: async (clusterId: number | string) => { - return http.get(`/k8s-clusters/${clusterId}/pods`); + return http.get(`/kubernetes-clusters/${clusterId}/pods`); }, getDeployments: async (clusterId: number | string) => { - return http.get(`/k8s-clusters/${clusterId}/deployments`); + return http.get(`/kubernetes-clusters/${clusterId}/deployments`); }, getServices: async (clusterId: number | string) => { - return http.get(`/k8s-clusters/${clusterId}/services`); + return http.get(`/kubernetes-clusters/${clusterId}/services`); }, getIngresses: async (clusterId: number | string) => { - return http.get(`/k8s-clusters/${clusterId}/ingresses`); + return http.get(`/kubernetes-clusters/${clusterId}/ingresses`); }, 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 - return http.get(`/k8s-clusters/${clusterId}/topology`); + return http.get(`/kubernetes-clusters/${clusterId}/topology`); }, 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) === - getPodLogs: async (clusterId: number | string, namespace: string, podName: string, tail: number = 200, container?: string) => { + // === 工作负载操作 (Logan.Li) - Kuboard-style routes === + getPodLogs: async (clusterCode: string, namespace: string, podName: string, tail: number = 200, container?: string) => { const params = new URLSearchParams({ tail: String(tail) }); 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) => { - return http.post(`/k8s-clusters/${clusterId}/namespaces/${namespace}/pods/${podName}/exec`, { command, container }); + execPod: async (clusterCode: string, namespace: string, podName: string, command: string[], container?: string) => { + return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/pods/${podName}/exec`, { command, container }); }, - scaleDeployment: async (clusterId: number | string, namespace: string, name: string, replicas: number) => { - return http.post(`/k8s-clusters/${clusterId}/namespaces/${namespace}/deployments/${name}/scale`, { replicas }); + scaleDeployment: async (clusterCode: string, namespace: string, name: string, replicas: number) => { + return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments/${name}/scale`, { replicas }); }, - restartDeployment: async (clusterId: number | string, namespace: string, name: string) => { - return http.post(`/k8s-clusters/${clusterId}/namespaces/${namespace}/deployments/${name}/restart`); + restartDeployment: async (clusterCode: string, namespace: string, name: string) => { + return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments/${name}/restart`); }, - updateDeploymentImage: async (clusterId: number | 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 }); - }, - deletePod: async (clusterId: number | string, namespace: string, name: string) => { - return http.delete(`/k8s-clusters/${clusterId}/namespaces/${namespace}/pods/${name}`); + updateDeploymentImage: async (clusterCode: string, namespace: string, name: string, image: string, containerName?: string) => { + return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments/${name}/update-image`, { image, container_name: containerName }); }, // === HPA 管理 (Logan.Li) === - getHPAs: async (clusterId: number | string) => { - return http.get(`/k8s-clusters/${clusterId}/hpas`); + getHPAs: async (clusterCode: string, namespace?: string) => { + 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 }) => { - return http.post(`/k8s-clusters/${clusterId}/hpas`, data); + createHPA: async (clusterCode: string, data: { name: string; namespace: string; targetKind: string; targetName: string; minReplicas: number; maxReplicas: number; cpuPercent: number; memPercent: number }) => { + return http.post(`/kubernetes/${clusterCode}/namespace/${data.namespace}/hpas`, data); }, - deleteHPA: async (clusterId: number | string, namespace: string, name: string) => { - return http.delete(`/k8s-clusters/${clusterId}/namespaces/${namespace}/hpas/${name}`); + deleteHPA: async (clusterCode: string, namespace: string, name: string) => { + return http.delete(`/kubernetes/${clusterCode}/namespace/${namespace}/hpas/${name}`); }, // === CronJob 管理 (Logan.Li) === - getCronJobs: async (clusterId: number | string) => { - return http.get(`/k8s-clusters/${clusterId}/cronjobs`); + getCronJobs: async (clusterCode: string, namespace?: string) => { + 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[] }) => { - return http.post(`/k8s-clusters/${clusterId}/cronjobs`, data); + createCronJob: async (clusterCode: string, data: { name: string; namespace: string; schedule: string; image: string; command: string[] }) => { + return http.post(`/kubernetes/${clusterCode}/namespace/${data.namespace}/cronjobs`, data); }, - suspendCronJob: async (clusterId: number | string, namespace: string, name: string, suspend: boolean) => { - return http.post(`/k8s-clusters/${clusterId}/namespaces/${namespace}/cronjobs/${name}/suspend`, { suspend }); + suspendCronJob: async (clusterCode: string, namespace: string, name: string, suspend: boolean) => { + return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/cronjobs/${name}/suspend`, { suspend }); }, - triggerCronJob: async (clusterId: number | string, namespace: string, name: string) => { - return http.post(`/k8s-clusters/${clusterId}/namespaces/${namespace}/cronjobs/${name}/trigger`); + triggerCronJob: async (clusterCode: string, namespace: string, name: string) => { + return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/cronjobs/${name}/trigger`); }, - deleteCronJob: async (clusterId: number | string, namespace: string, name: string) => { - return http.delete(`/k8s-clusters/${clusterId}/namespaces/${namespace}/cronjobs/${name}`); + deleteCronJob: async (clusterCode: string, namespace: string, name: string) => { + 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) === - getNamespaces: async (clusterId: number | string) => { - return http.get(`/k8s-clusters/${clusterId}/namespaces`); + getNamespaces: async (clusterCode: string) => { + return http.get(`/kubernetes/${clusterCode}/namespaces`); }, }; diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index a3d1319..811a732 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -18,7 +18,7 @@ export interface Server { } // Kubernetes集群 -export interface K8sCluster { +export interface KubernetesCluster { id: string; name: string; version: string; @@ -34,7 +34,7 @@ export interface K8sCluster { } // K8s节点 -export interface K8sNode { +export interface KubernetesNode { id: string; name: string; clusterId: string; @@ -51,7 +51,7 @@ export interface K8sNode { } // K8s Pod -export interface K8sPod { +export interface KubernetesPod { id: string; name: string; namespace: string;