feat(k8s): M11 StatefulSet/DaemonSet 详情页 — WorkloadViewPage 重写 + YAML 路由冲突修复
后端: - routes.go: 为 statefulsets/daemonsets 注册专门 YAML 子路由(Gin radix tree 冲突) - kubernetes.go: GetResourceYAMLNS/UpdateResourceYAMLNS 添加 URL path fallback 前端: - WorkloadViewPage.tsx 重写: 修复 kind 提取 bug(useLocation) + 更新策略/选择器/PVC模板/InitContainers - NamespaceResourcePage.tsx: handleDetail 添加 STS/DS 点击导航 验证: DS YAML API 200 ✅ | DS Detail 200 ✅ | Dep YAML 200 ✅ | SPA路由 200 ✅ | TS零错误 ✅
This commit is contained in:
1 parent
87b7755094
commit
d1c557bb6c
14 files changed
+1103
-178
No files matched your search
@@ -1830,6 +1830,35 @@ func (h *KubernetesHandler) getK8sClientByCode(c *gin.Context) (*k8sclient.Clien
|
||||
func (h *KubernetesHandler) ListDeploymentsNS(c *gin.Context) {
|
||||
h.listDeploymentsWithNS(c, c.Param("namespace"))
|
||||
}
|
||||
|
||||
// GetDeploymentNS 获取单个 Deployment 详情(避免前端 404)// Logan.Li
|
||||
func (h *KubernetesHandler) GetDeploymentNS(c *gin.Context) {
|
||||
ns := c.Param("namespace")
|
||||
name := c.Param("name")
|
||||
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 }
|
||||
for _, d := range deps.Items {
|
||||
if d.Metadata.Namespace == ns && d.Metadata.Name == name {
|
||||
image := ""
|
||||
if len(d.Spec.Template.Spec.Containers) > 0 { image = d.Spec.Template.Spec.Containers[0].Image }
|
||||
c.JSON(200, models.Response{Code: 200, Data: map[string]interface{}{
|
||||
"namespace": d.Metadata.Namespace,
|
||||
"name": d.Metadata.Name,
|
||||
"replicas": d.Spec.Replicas,
|
||||
"available_replicas": d.Status.AvailableReplicas,
|
||||
"ready_replicas": d.Status.ReadyReplicas,
|
||||
"image": image,
|
||||
"paused": d.Spec.Paused,
|
||||
}})
|
||||
return
|
||||
}
|
||||
}
|
||||
c.JSON(404, models.Response{Code: 404, Message: "Deployment not found"})
|
||||
}
|
||||
func (h *KubernetesHandler) ListPodsNS(c *gin.Context) {
|
||||
h.listPodsWithNS(c, c.Param("namespace"))
|
||||
}
|
||||
@@ -2504,6 +2533,16 @@ func (h *KubernetesHandler) GetResourceYAMLNS(c *gin.Context) {
|
||||
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||
ns := c.Param("namespace")
|
||||
resource := c.Param("resource")
|
||||
// Fallback: specific routes (statefulsets/:name/yaml, daemonsets/:name/yaml) have no :resource param
|
||||
if resource == "" {
|
||||
pathParts := strings.Split(c.Request.URL.Path, "/")
|
||||
for _, p := range pathParts {
|
||||
if p == "statefulsets" || p == "daemonsets" || p == "deployments" || p == "services" || p == "configmaps" || p == "secrets" {
|
||||
resource = p
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
name := c.Param("name")
|
||||
// Build API path based on resource type
|
||||
var apiPath string
|
||||
@@ -2549,6 +2588,16 @@ func (h *KubernetesHandler) UpdateResourceYAMLNS(c *gin.Context) {
|
||||
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||
ns := c.Param("namespace")
|
||||
resource := c.Param("resource")
|
||||
// Fallback: specific routes (statefulsets/:name/yaml, daemonsets/:name/yaml) have no :resource param
|
||||
if resource == "" {
|
||||
pathParts := strings.Split(c.Request.URL.Path, "/")
|
||||
for _, p := range pathParts {
|
||||
if p == "statefulsets" || p == "daemonsets" || p == "deployments" || p == "services" || p == "configmaps" || p == "secrets" {
|
||||
resource = p
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
name := c.Param("name")
|
||||
yamlData, err := io.ReadAll(c.Request.Body)
|
||||
if err != nil { c.JSON(400, models.Response{Code: 400, Message: "读取body失败"}); return }
|
||||
@@ -2768,6 +2817,123 @@ func (h *KubernetesHandler) ListDaemonSetsNS(c *gin.Context) {
|
||||
}
|
||||
c.JSON(200, models.Response{Code: 200, Data: result})
|
||||
}
|
||||
// ScaleStatefulSetNS scales a StatefulSet (Kuboard code route)
|
||||
func (h *KubernetesHandler) ScaleStatefulSetNS(c *gin.Context) {
|
||||
clusterID := h.resolveClusterCode(c.Param("clusterCode"))
|
||||
if clusterID == 0 { c.JSON(500, models.Response{Code: 500, Message: "cluster not found"}); return }
|
||||
client, _, err := h.getK8sClient(clusterID)
|
||||
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||
ns := c.Param("namespace")
|
||||
name := c.Param("name")
|
||||
var req struct{ Replicas int `json:"replicas"` }
|
||||
if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, models.Response{Code: 400, Message: "参数错误"}); return }
|
||||
if err := client.ScaleStatefulSet(ns, name, req.Replicas); err != nil {
|
||||
c.JSON(500, models.Response{Code: 500, Message: "扩缩容失败: " + err.Error()}); return
|
||||
}
|
||||
c.JSON(200, models.Response{Code: 200, Message: "StatefulSet 副本数已更新为 " + strconv.Itoa(req.Replicas)})
|
||||
}
|
||||
|
||||
// ScaleStatefulSet scales a StatefulSet (ID route)
|
||||
func (h *KubernetesHandler) ScaleStatefulSet(c *gin.Context) {
|
||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||
if clusterID == 0 { c.JSON(500, models.Response{Code: 500, Message: "cluster not found"}); return }
|
||||
client, _, err := h.getK8sClient(clusterID)
|
||||
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||
ns := c.Param("namespace")
|
||||
name := c.Param("name")
|
||||
var req struct{ Replicas int `json:"replicas"` }
|
||||
if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, models.Response{Code: 400, Message: "参数错误"}); return }
|
||||
if err := client.ScaleStatefulSet(ns, name, req.Replicas); err != nil {
|
||||
c.JSON(500, models.Response{Code: 500, Message: "扩缩容失败: " + err.Error()}); return
|
||||
}
|
||||
c.JSON(200, models.Response{Code: 200, Message: "StatefulSet 副本数已更新为 " + strconv.Itoa(req.Replicas)})
|
||||
}
|
||||
|
||||
// RestartStatefulSetNS restarts a StatefulSet (Kuboard code route)
|
||||
func (h *KubernetesHandler) RestartStatefulSetNS(c *gin.Context) {
|
||||
clusterID := h.resolveClusterCode(c.Param("clusterCode"))
|
||||
if clusterID == 0 { c.JSON(500, models.Response{Code: 500, Message: "cluster not found"}); return }
|
||||
client, _, err := h.getK8sClient(clusterID)
|
||||
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||
ns := c.Param("namespace")
|
||||
name := c.Param("name")
|
||||
if err := client.RestartStatefulSet(ns, name); err != nil {
|
||||
c.JSON(500, models.Response{Code: 500, Message: "重启失败: " + err.Error()}); return
|
||||
}
|
||||
c.JSON(200, models.Response{Code: 200, Message: "StatefulSet 滚动重启已触发"})
|
||||
}
|
||||
|
||||
// RestartStatefulSet restarts a StatefulSet (ID route)
|
||||
func (h *KubernetesHandler) RestartStatefulSet(c *gin.Context) {
|
||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||
if clusterID == 0 { c.JSON(500, models.Response{Code: 500, Message: "cluster not found"}); return }
|
||||
client, _, err := h.getK8sClient(clusterID)
|
||||
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||
ns := c.Param("namespace")
|
||||
name := c.Param("name")
|
||||
if err := client.RestartStatefulSet(ns, name); err != nil {
|
||||
c.JSON(500, models.Response{Code: 500, Message: "重启失败: " + err.Error()}); return
|
||||
}
|
||||
c.JSON(200, models.Response{Code: 200, Message: "StatefulSet 滚动重启已触发"})
|
||||
}
|
||||
|
||||
// RestartDaemonSetNS restarts a DaemonSet (Kuboard code route)
|
||||
func (h *KubernetesHandler) RestartDaemonSetNS(c *gin.Context) {
|
||||
clusterID := h.resolveClusterCode(c.Param("clusterCode"))
|
||||
if clusterID == 0 { c.JSON(500, models.Response{Code: 500, Message: "cluster not found"}); return }
|
||||
client, _, err := h.getK8sClient(clusterID)
|
||||
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||
ns := c.Param("namespace")
|
||||
name := c.Param("name")
|
||||
if err := client.RestartDaemonSet(ns, name); err != nil {
|
||||
c.JSON(500, models.Response{Code: 500, Message: "重启失败: " + err.Error()}); return
|
||||
}
|
||||
c.JSON(200, models.Response{Code: 200, Message: "DaemonSet 滚动重启已触发"})
|
||||
}
|
||||
|
||||
// RestartDaemonSet restarts a DaemonSet (ID route)
|
||||
func (h *KubernetesHandler) RestartDaemonSet(c *gin.Context) {
|
||||
clusterID, _ := strconv.Atoi(c.Param("id"))
|
||||
if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) }
|
||||
if clusterID == 0 { c.JSON(500, models.Response{Code: 500, Message: "cluster not found"}); return }
|
||||
client, _, err := h.getK8sClient(clusterID)
|
||||
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||
ns := c.Param("namespace")
|
||||
name := c.Param("name")
|
||||
if err := client.RestartDaemonSet(ns, name); err != nil {
|
||||
c.JSON(500, models.Response{Code: 500, Message: "重启失败: " + err.Error()}); return
|
||||
}
|
||||
c.JSON(200, models.Response{Code: 200, Message: "DaemonSet 滚动重启已触发"})
|
||||
}
|
||||
|
||||
// GetStatefulSetDetailNS returns a single StatefulSet detail (Kuboard code route)
|
||||
func (h *KubernetesHandler) GetStatefulSetDetailNS(c *gin.Context) {
|
||||
clusterID := h.resolveClusterCode(c.Param("clusterCode"))
|
||||
if clusterID == 0 { c.JSON(500, models.Response{Code: 500, Message: "cluster not found"}); return }
|
||||
client, _, err := h.getK8sClient(clusterID)
|
||||
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||
ns := c.Param("namespace")
|
||||
name := c.Param("name")
|
||||
sts, err := client.GetStatefulSet(ns, name)
|
||||
if err != nil { c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return }
|
||||
c.JSON(200, models.Response{Code: 200, Data: sts})
|
||||
}
|
||||
|
||||
// GetDaemonSetDetailNS returns a single DaemonSet detail (Kuboard code route)
|
||||
func (h *KubernetesHandler) GetDaemonSetDetailNS(c *gin.Context) {
|
||||
clusterID := h.resolveClusterCode(c.Param("clusterCode"))
|
||||
if clusterID == 0 { c.JSON(500, models.Response{Code: 500, Message: "cluster not found"}); return }
|
||||
client, _, err := h.getK8sClient(clusterID)
|
||||
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||
ns := c.Param("namespace")
|
||||
name := c.Param("name")
|
||||
ds, err := client.GetDaemonSet(ns, name)
|
||||
if err != nil { c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return }
|
||||
c.JSON(200, models.Response{Code: 200, Data: ds})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Pod Metrics (kubectl top pods) — per-container CPU/Memory usage via metrics-server
|
||||
// ============================================================================
|
||||
|
||||
@@ -342,6 +342,7 @@ func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config, c
|
||||
|
||||
// Deployments
|
||||
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/deployments", k8sHandler.ListDeploymentsNS)
|
||||
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/deployments/:name", k8sHandler.GetDeploymentNS)
|
||||
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/pause", k8sHandler.PauseDeploymentNS)
|
||||
@@ -412,8 +413,21 @@ func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config, c
|
||||
// StatefulSet / DaemonSet
|
||||
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/statefulsets", k8sHandler.ListStatefulSetsNS)
|
||||
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/daemonsets", k8sHandler.ListDaemonSetsNS)
|
||||
// StatefulSet operations (Kuboard code routes)
|
||||
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/statefulsets/:name", k8sHandler.GetStatefulSetDetailNS)
|
||||
kubernetesRouter.POST("/:clusterCode/namespace/:namespace/statefulsets/:name/scale", k8sHandler.ScaleStatefulSetNS)
|
||||
kubernetesRouter.POST("/:clusterCode/namespace/:namespace/statefulsets/:name/restart", k8sHandler.RestartStatefulSetNS)
|
||||
// DaemonSet operations (Kuboard code routes)
|
||||
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/daemonsets/:name", k8sHandler.GetDaemonSetDetailNS)
|
||||
kubernetesRouter.POST("/:clusterCode/namespace/:namespace/daemonsets/:name/restart", k8sHandler.RestartDaemonSetNS)
|
||||
|
||||
// YAML view/edit
|
||||
// StatefulSet/DaemonSet YAML (specific routes to avoid Gin radix tree conflict with generic /:resource/:name/yaml)
|
||||
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/statefulsets/:name/yaml", k8sHandler.GetResourceYAMLNS)
|
||||
kubernetesRouter.PUT("/:clusterCode/namespace/:namespace/statefulsets/:name/yaml", k8sHandler.UpdateResourceYAMLNS)
|
||||
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/daemonsets/:name/yaml", k8sHandler.GetResourceYAMLNS)
|
||||
kubernetesRouter.PUT("/:clusterCode/namespace/:namespace/daemonsets/:name/yaml", k8sHandler.UpdateResourceYAMLNS)
|
||||
|
||||
// YAML view/edit (generic — works for resources without specific static routes)
|
||||
kubernetesRouter.GET("/:clusterCode/namespace/:namespace/:resource/:name/yaml", k8sHandler.GetResourceYAMLNS)
|
||||
kubernetesRouter.PUT("/:clusterCode/namespace/:namespace/:resource/:name/yaml", k8sHandler.UpdateResourceYAMLNS)
|
||||
kubernetesRouter.DELETE("/:clusterCode/namespace/:namespace/:resource/:name/yaml", k8sHandler.DeleteResourceYAMLNS)
|
||||
|
||||
Reference in new issue
Block a user