package controller import ( "fmt" "gorm.io/gorm" "strconv" "time" "ezops/model" k8sclient "ezops/pkg/kubernetesclient" "ezops/pkg/wshub" "github.com/gin-gonic/gin" ) // KubernetesHandler K8s处理器 type KubernetesHandler struct { db *gorm.DB wsMgr *wshub.WSManager } // NewKubernetesHandler 创建K8s处理器 func NewKubernetesHandler(db *gorm.DB, wsMgr *wshub.WSManager) *KubernetesHandler { return &KubernetesHandler{db: db, wsMgr: wsMgr} } // ============================================================================ // Shared helpers (used by all k8s_*.go files in this package) // ============================================================================ func (h *KubernetesHandler) resolveClusterCode(code string) int { if code == "" { return 0 } var cluster model.KubernetesCluster if err := h.db.Where("code = ?", code).First(&cluster).Error; err != nil { return 0 } return int(cluster.ID) } // formatAge converts a K8s creationTimestamp (RFC3339) to a human-readable relative age func formatAge(creationTimestamp string) string { if creationTimestamp == "" { return "-" } t, err := time.Parse(time.RFC3339, creationTimestamp) if err != nil { return creationTimestamp } d := time.Since(t) switch { case d < time.Minute: return fmt.Sprintf("%ds", int(d.Seconds())) case d < time.Hour: return fmt.Sprintf("%dm", int(d.Minutes())) case d < 24*time.Hour: return fmt.Sprintf("%dh", int(d.Hours())) case d < 30*24*time.Hour: return fmt.Sprintf("%dd", int(d.Hours()/24)) case d < 365*24*time.Hour: return fmt.Sprintf("%dM", int(d.Hours()/(24*30))) default: return fmt.Sprintf("%dy", int(d.Hours()/(24*365))) } } // getK8sClientByCode resolves cluster by code, falls back to ID func (h *KubernetesHandler) getK8sClientByCode(c *gin.Context) (*k8sclient.Client, *model.KubernetesCluster, error) { clusterID, _ := strconv.Atoi(c.Param("id")) if clusterID == 0 { // 支持 :clusterCode 或 :id 参数 code := c.Param("clusterCode") if code == "" { code = c.Param("id") } clusterID = h.resolveClusterCode(code) } 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) getK8sClient(clusterID int) (*k8sclient.Client, *model.KubernetesCluster, error) { var cluster model.KubernetesCluster if err := h.db.First(&cluster, clusterID).Error; err != nil { return nil, nil, fmt.Errorf("cluster not found") } client, err := buildK8sClient(&cluster) if err != nil { return nil, nil, err } return client, &cluster, nil } // buildK8sClient 根据集群配置构造客户端,支持 kubeconfig 或 token+api_server // Logan.Li func buildK8sClient(cluster *model.KubernetesCluster) (*k8sclient.Client, error) { if cluster.KubeConfig != "" { return k8sclient.NewClient(cluster.KubeConfig) } if cluster.Token != "" && cluster.APIServer != "" { return k8sclient.NewClientFromToken(cluster.APIServer, cluster.Token) } return nil, fmt.Errorf("no kubeconfig or token+api_server configured") } // ============================================================================ // PVC (存储) handlers // ============================================================================ // ListPVCsNS lists PersistentVolumeClaims in a namespace (code-based)