package controller import ( "net/http" "strconv" "ezops/model" "github.com/gin-gonic/gin" ) func (h *KubernetesHandler) ListServices(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Query("namespace") client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: err.Error()}) return } svcs, err := client.ListServices() if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}) return } type SvcSummary struct { Namespace string `json:"namespace"` Name string `json:"name"` Type string `json:"type"` ClusterIP string `json:"cluster_ip"` Ports int `json:"port_count"` } result := []SvcSummary{} for _, s := range svcs.Items { if namespace != "" && s.Metadata.Namespace != namespace { continue } result = append(result, SvcSummary{ Namespace: s.Metadata.Namespace, Name: s.Metadata.Name, Type: s.Spec.Type, ClusterIP: s.Spec.ClusterIP, Ports: len(s.Spec.Ports), }) } c.JSON(http.StatusOK, model.Response{Code: 200, Message: "success", Data: result}) } // ListIngresses 获取Ingress列表 // @Summary 获取Ingress列表 // @Description 获取指定集群的Ingress列表 // @Tags K8s集群 // @Produce json // @Security Bearer // @Param id path int true "集群ID" // @Param namespace query string false "命名空间" // @Success 200 {object} model.Response{data=[]model.KubernetesIngress} // @Router /kubernetes-clusters/{id}/ingresses [get] func (h *KubernetesHandler) ListIngresses(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) namespace := c.Query("namespace") client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: err.Error()}) return } ings, err := client.ListIngresses() if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}) return } type IngSummary struct { Namespace string `json:"namespace"` Name string `json:"name"` Hosts []string `json:"hosts"` TLS bool `json:"tls"` } result := []IngSummary{} for _, ing := range ings.Items { if namespace != "" && ing.Metadata.Namespace != namespace { continue } hosts := []string{} for _, rule := range ing.Spec.Rules { if rule.Host != "" { hosts = append(hosts, rule.Host) } } result = append(result, IngSummary{ Namespace: ing.Metadata.Namespace, Name: ing.Metadata.Name, Hosts: hosts, TLS: len(ing.Spec.TLS) > 0, }) } c.JSON(http.StatusOK, model.Response{Code: 200, Message: "success", Data: result}) } // GetTopology 获取集群拓扑布局 // Logan.Li func (h *KubernetesHandler) GetTopology(c *gin.Context) { clusterID := c.Param("id") var layout model.TopologyLayout result := h.db.Where("cluster_id = ?", clusterID).First(&layout) if result.Error != nil { c.JSON(http.StatusOK, model.Response{Code: 200, Data: &model.TopologyLayout{}}) return } c.JSON(http.StatusOK, model.Response{Code: 200, Data: layout}) } // SaveTopology 保存集群拓扑布局 // Logan.Li func (h *KubernetesHandler) SaveTopology(c *gin.Context) { clusterID, _ := strconv.Atoi(c.Param("id")) var req struct { LayoutData string `json:"layout_data"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "参数错误"}) return } var layout model.TopologyLayout h.db.Where("cluster_id = ?", clusterID).First(&layout) layout.ClusterID = uint(clusterID) layout.LayoutData = req.LayoutData if layout.ID == 0 { h.db.Create(&layout) } else { h.db.Save(&layout) } c.JSON(http.StatusOK, model.Response{Code: 200, Message: "保存成功"}) } // SyncCluster 同步集群资源(连接真实K8s API拉取数据) 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, model.Response{Code: 500, Message: err.Error()}) return } cms, err := client.ListConfigMaps(namespace) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}) return } // 扁平化字段,与前端表格列 name/namespace/key_count 对齐 // Logan.Li type CMSummary struct { Namespace string `json:"namespace"` Name string `json:"name"` KeyCount int `json:"key_count"` } result := make([]CMSummary, 0, len(cms.Items)) for _, cm := range cms.Items { result = append(result, CMSummary{ Namespace: cm.Metadata.Namespace, Name: cm.Metadata.Name, KeyCount: len(cm.Data), }) } c.JSON(http.StatusOK, model.Response{Code: 200, Data: result}) } // 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, model.Response{Code: 500, Message: err.Error()}) return } secrets, err := client.ListSecrets(namespace) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}) return } // 扁平化字段,与前端表格列 name/namespace/type 对齐 // Logan.Li type SecretSummary struct { Namespace string `json:"namespace"` Name string `json:"name"` Type string `json:"type"` KeyCount int `json:"key_count"` } result := make([]SecretSummary, 0, len(secrets.Items)) for _, s := range secrets.Items { result = append(result, SecretSummary{ Namespace: s.Metadata.Namespace, Name: s.Metadata.Name, Type: s.Type, KeyCount: len(s.DataKeys), }) } c.JSON(http.StatusOK, model.Response{Code: 200, Data: result}) } // ============================================================================ // Helpers // ============================================================================ // resolveClusterCode resolves a cluster code to its numeric ID 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) 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, model.Response{Code: 500, Message: err.Error()}); return } svcs, err := client.ListServices() if err != nil { c.JSON(500, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return } type SvcPortDetail struct { Name string `json:"name,omitempty"` Port int `json:"port"` TargetPort interface{} `json:"targetPort"` NodePort int `json:"nodePort,omitempty"` Protocol string `json:"protocol,omitempty"` } type SvcSummary struct { Namespace string `json:"namespace"` Name string `json:"name"` Type string `json:"type"` ClusterIP string `json:"cluster_ip"` Ports int `json:"ports"` PortList []SvcPortDetail `json:"portList,omitempty"` Selector map[string]string `json:"selector,omitempty"` } result := []SvcSummary{} for _, s := range svcs.Items { if s.Metadata.Namespace != ns { continue } ports := []SvcPortDetail{} for _, p := range s.Spec.Ports { ports = append(ports, SvcPortDetail{ Name: p.Name, Port: p.Port, TargetPort: p.TargetPort, NodePort: p.NodePort, Protocol: p.Protocol, }) } result = append(result, SvcSummary{ Namespace: s.Metadata.Namespace, Name: s.Metadata.Name, Type: s.Spec.Type, ClusterIP: s.Spec.ClusterIP, Ports: len(s.Spec.Ports), PortList: ports, Selector: s.Spec.Selector, }) } c.JSON(200, model.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, model.Response{Code: 500, Message: err.Error()}); return } ings, err := client.ListIngresses() if err != nil { c.JSON(500, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return } type PathDetail struct { Path string `json:"path"` PathType string `json:"pathType,omitempty"` BackendSvc string `json:"backendService,omitempty"` BackendPort string `json:"backendPort,omitempty"` } type RuleDetail struct { Host string `json:"host"` Paths []PathDetail `json:"paths"` } type IngSummary struct { Namespace string `json:"namespace"` Name string `json:"name"` Hosts []string `json:"hosts"` TLS bool `json:"tls"` IngressClass string `json:"ingressClass,omitempty"` Rules []RuleDetail `json:"rules,omitempty"` } result := []IngSummary{} for _, ing := range ings.Items { if ing.Metadata.Namespace != ns { continue } hosts := []string{} rules := []RuleDetail{} for _, rule := range ing.Spec.Rules { if rule.Host != "" { hosts = append(hosts, rule.Host) } rd := RuleDetail{Host: rule.Host, Paths: []PathDetail{}} if rule.HTTP != nil { for _, p := range rule.HTTP.Paths { pd := PathDetail{Path: p.Path, PathType: p.PathType, BackendSvc: p.Backend.Service.Name} if p.Backend.Service.Port.Number > 0 { pd.BackendPort = strconv.Itoa(p.Backend.Service.Port.Number) } else if p.Backend.Service.Port.Name != "" { pd.BackendPort = p.Backend.Service.Port.Name } rd.Paths = append(rd.Paths, pd) } } rules = append(rules, rd) } // ingressClassName from annotations ingClass := "" if ing.Metadata.Annotations != nil { ingClass = ing.Metadata.Annotations["kubernetes.io/ingress.class"] } result = append(result, IngSummary{ Namespace: ing.Metadata.Namespace, Name: ing.Metadata.Name, Hosts: hosts, TLS: len(ing.Spec.TLS) > 0, IngressClass: ingClass, Rules: rules, }) } c.JSON(200, model.Response{Code: 200, Data: result}) } func (h *KubernetesHandler) ListPVCsNS(c *gin.Context) { clusterID := h.resolveClusterCode(c.Param("clusterCode")) if clusterID == 0 { c.JSON(500, model.Response{Code: 500, Message: "cluster not found"}); return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } ns := c.Param("namespace") pvcs, err := client.ListPVCs(ns) if err != nil { c.JSON(500, model.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return } type PVCSummary struct { Namespace string `json:"namespace"` Name string `json:"name"` Phase string `json:"phase"` Size string `json:"size"` StorageClass string `json:"storage_class"` } result := []PVCSummary{} for _, p := range pvcs.Items { size := "" if p.Status.Capacity != nil { if s, ok := p.Status.Capacity["storage"]; ok { size = s } } result = append(result, PVCSummary{ Namespace: p.Metadata.Namespace, Name: p.Metadata.Name, Phase: p.Status.Phase, Size: size, StorageClass: p.Spec.StorageClassName, }) } c.JSON(200, model.Response{Code: 200, Data: result}) } // DeletePVCNS deletes a PVC (code-based) func (h *KubernetesHandler) DeletePVCNS(c *gin.Context) { clusterID := h.resolveClusterCode(c.Param("clusterCode")) if clusterID == 0 { c.JSON(500, model.Response{Code: 500, Message: "cluster not found"}); return } client, _, err := h.getK8sClient(clusterID) if err != nil { c.JSON(500, model.Response{Code: 500, Message: err.Error()}); return } if err := client.DeletePVC(c.Param("namespace"), c.Param("name")); err != nil { c.JSON(500, model.Response{Code: 500, Message: "删除失败: " + err.Error()}); return } c.JSON(200, model.Response{Code: 200, Message: "已删除"}) } // ============================================================================ // StatefulSet / DaemonSet handlers (code-based) // ============================================================================ // ListStatefulSetsNS lists StatefulSets in a namespace