package controller import ( "net/http" "strconv" "ezops/model" k8sclient "ezops/pkg/kubernetesclient" "github.com/gin-gonic/gin" ) 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 { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: err.Error()}) return } namespaces, err := client.ListNamespaces() if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Data: namespaces}) } // ============================================================================ // Namespace Management (M14 — 资源配额 + CRUD) // ============================================================================ // NamespaceDetail is the enriched namespace info for the management page type NamespaceDetail struct { Name string `json:"name"` Status string `json:"status"` Age string `json:"age"` Labels map[string]string `json:"labels"` Annotations map[string]string `json:"annotations,omitempty"` ResourceCounts map[string]int `json:"resource_counts"` HasResourceQuota bool `json:"has_resource_quota"` HasLimitRange bool `json:"has_limit_range"` ResourceQuotas []k8sclient.ResourceQuotaItem `json:"resource_quotas,omitempty"` LimitRanges []k8sclient.LimitRangeItem `json:"limit_ranges,omitempty"` } // GetNamespacesDetail returns all namespaces with metadata, resource counts and quota info func (h *KubernetesHandler) GetNamespacesDetail(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } if clusterID == 0 { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "cluster not specified"}) return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: err.Error()}) return } nsList, err := client.ListNamespacesDetailed() if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}) return } // Efficient: fetch all resources once, group by namespace depCounts := map[string]int{} if deps, err := client.ListDeployments(); err == nil { for _, d := range deps.Items { depCounts[d.Metadata.Namespace]++ } } podCounts := map[string]int{} if pods, err := client.ListPods(); err == nil { for _, p := range pods.Items { podCounts[p.Metadata.Namespace]++ } } svcCounts := map[string]int{} if svcs, err := client.ListServices(); err == nil { for _, s := range svcs.Items { svcCounts[s.Metadata.Namespace]++ } } // All-namespace ResourceQuotas quotaByNS := map[string][]k8sclient.ResourceQuotaItem{} if quotas, err := client.ListResourceQuotas(""); err == nil { for _, q := range quotas { quotaByNS[q.Metadata.Namespace] = append(quotaByNS[q.Metadata.Namespace], q) } } // All-namespace LimitRanges lrByNS := map[string][]k8sclient.LimitRangeItem{} if lrs, err := client.ListLimitRanges(""); err == nil { for _, lr := range lrs { lrByNS[lr.Metadata.Namespace] = append(lrByNS[lr.Metadata.Namespace], lr) } } result := make([]NamespaceDetail, 0, len(nsList.Items)) for _, ns := range nsList.Items { name := ns.Metadata.Name nd := NamespaceDetail{ Name: name, Status: ns.Status.Phase, Age: formatAge(ns.Metadata.CreationTimestamp), Labels: ns.Metadata.Labels, Annotations: ns.Metadata.Annotations, ResourceCounts: map[string]int{ "deployments": depCounts[name], "pods": podCounts[name], "services": svcCounts[name], }, } if nd.Labels == nil { nd.Labels = map[string]string{} } if qs, ok := quotaByNS[name]; ok && len(qs) > 0 { nd.HasResourceQuota = true nd.ResourceQuotas = qs } if lrs, ok := lrByNS[name]; ok && len(lrs) > 0 { nd.HasLimitRange = true nd.LimitRanges = lrs } result = append(result, nd) } c.JSON(http.StatusOK, model.Response{Code: 200, Data: result}) } // GetNamespaceDetailHandler returns a single namespace detail with quota info func (h *KubernetesHandler) GetNamespaceDetailHandler(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } if clusterID == 0 { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "cluster not specified"}) return } nsName := c.Param("namespace") if nsName == "" { nsName = c.Param("name") } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: err.Error()}) return } ns, err := client.GetNamespace(nsName) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}) return } nd := NamespaceDetail{ Name: ns.Metadata.Name, Status: ns.Status.Phase, Age: formatAge(ns.Metadata.CreationTimestamp), Labels: ns.Metadata.Labels, Annotations: ns.Metadata.Annotations, ResourceCounts: map[string]int{}, } if nd.Labels == nil { nd.Labels = map[string]string{} } // Fetch resource counts (single namespace) if deps, err := client.ListDeployments(); err == nil { count := 0 for _, d := range deps.Items { if d.Metadata.Namespace == nsName { count++ } } nd.ResourceCounts["deployments"] = count } if pods, err := client.ListPods(); err == nil { count := 0 for _, p := range pods.Items { if p.Metadata.Namespace == nsName { count++ } } nd.ResourceCounts["pods"] = count } if svcs, err := client.ListServices(); err == nil { count := 0 for _, s := range svcs.Items { if s.Metadata.Namespace == nsName { count++ } } nd.ResourceCounts["services"] = count } // Fetch quotas + limit ranges for this namespace if quotas, err := client.ListResourceQuotas(nsName); err == nil && len(quotas) > 0 { nd.HasResourceQuota = true nd.ResourceQuotas = quotas } if lrs, err := client.ListLimitRanges(nsName); err == nil && len(lrs) > 0 { nd.HasLimitRange = true nd.LimitRanges = lrs } c.JSON(http.StatusOK, model.Response{Code: 200, Data: nd}) } // CreateNamespaceHandler creates a new namespace func (h *KubernetesHandler) CreateNamespaceHandler(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } if clusterID == 0 { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "cluster not specified"}) return } var req struct { Name string `json:"name" binding:"required"` Labels map[string]string `json:"labels"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "name is required"}) return } // Safety: block critical system namespaces systemNS := map[string]bool{ "kube-system": true, "kube-public": true, "kube-node-lease": true, "default": true, "kubernetes-dashboard": true, } if systemNS[req.Name] { c.JSON(http.StatusForbidden, model.Response{Code: 403, Message: "cannot create system namespace: " + req.Name}) return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: err.Error()}) return } ns, err := client.CreateNamespace(req.Name, req.Labels) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Data: ns, Message: "命名空间创建成功"}) } // DeleteNamespaceHandler deletes a namespace func (h *KubernetesHandler) DeleteNamespaceHandler(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { clusterID = h.resolveClusterCode(c.Param("clusterCode")) } if clusterID == 0 { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "cluster not specified"}) return } nsName := c.Param("namespace") if nsName == "" { nsName = c.Param("name") } // Safety: block deletion of critical system namespaces systemNS := map[string]bool{ "kube-system": true, "kube-public": true, "kube-node-lease": true, "default": true, "devops": true, } if systemNS[nsName] { c.JSON(http.StatusForbidden, model.Response{Code: 403, Message: "禁止删除系统命名空间: " + nsName}) return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: err.Error()}) return } if err := client.DeleteNamespace(nsName); err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Message: "命名空间已删除"}) } // ============================================================================ // ConfigMap & Secret (Kuboard-style namespace resources) // ============================================================================ // ListConfigMaps 获取命名空间下的 ConfigMap 列表