312 lines
9.5 KiB
Go
312 lines
9.5 KiB
Go
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 列表
|