From aaab923f4efeabd46c65a79e056a2befb1a817de Mon Sep 17 00:00:00 2001 From: attacker Date: Thu, 25 Jun 2026 01:32:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20K8s=E2=86=92Kubernetes=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E7=BB=9F=E4=B8=80=20+=20=E5=B7=A5=E5=85=B7=E5=AE=89?= =?UTF-8?q?=E8=A3=85bug=E4=BF=AE=E5=A4=8D=20+=20=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E5=8F=AF=E8=A7=86=E5=8C=96=E7=BC=96=E8=BE=91=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 工具安装bug修复 (P0 critical): - 根因: ToolConfigHandler.InstallTool的goroutine只打了日志, 从未调用实际安装逻辑 - 修复: 注入ToolInstaller到handler, 新增ForceInstall方法绕过sync.Once - 影响: 流水线工具配置页「安装」按钮现在能真正触发下载安装 2. K8s→Kubernetes命名统一: - Dashboard/Prometheus/AIOpsAssistant等8个文件12处用户可见文案 - 内部变量名/文件名/API group保持不变 3. 资源可视化编辑器 (新增ResourceVisualEditor.tsx): - ConfigMap: 键值对表格编辑器(增删改) - Secret: 键值对编辑器(自动Base64编解码) - Service: 端口/类型/选择器可视化配置 - Ingress: Host/Path路由规则可视化编辑 - PVC: 存储大小/访问模式/StorageClass配置 - 双模式: 可视化编辑 ↔ YAML编辑一键切换 - NamespaceResourcePage: 所有资源类型添加编辑按钮入口 --- backend/internal/api/handlers/tool_config.go | 17 +- backend/internal/api/routes/routes.go | 2 +- backend/pkg/toolinstaller/installer.go | 18 + .../src/components/aiops/AIOpsAssistant.tsx | 2 +- .../src/components/dashboard/Dashboard.tsx | 2 +- .../components/dashboard/DashboardRecent.tsx | 2 +- .../kubernetes/ClusterComparePage.tsx | 2 +- .../components/kubernetes/KubernetesNodes.tsx | 2 +- .../kubernetes/NamespaceResourcePage.tsx | 43 +- .../kubernetes/ResourceVisualEditor.tsx | 664 ++++++++++++++++++ .../components/kubernetes/TopologyView.tsx | 6 +- .../monitor/dashboard/PrometheusDashboard.tsx | 2 +- frontend/src/services/api.ts | 2 +- frontend/src/types/index.ts | 4 +- 14 files changed, 748 insertions(+), 20 deletions(-) create mode 100644 frontend/src/components/kubernetes/ResourceVisualEditor.tsx diff --git a/backend/internal/api/handlers/tool_config.go b/backend/internal/api/handlers/tool_config.go index 147d11c..6d9b703 100644 --- a/backend/internal/api/handlers/tool_config.go +++ b/backend/internal/api/handlers/tool_config.go @@ -6,6 +6,7 @@ import ( "os/exec" "ezops/internal/models" + "ezops/pkg/toolinstaller" "github.com/gin-gonic/gin" "gorm.io/gorm" @@ -15,12 +16,13 @@ import ( // ToolConfigHandler handles deploy tool configuration type ToolConfigHandler struct { - db *gorm.DB + db *gorm.DB + installer *toolinstaller.ToolInstaller } // NewToolConfigHandler creates handler -func NewToolConfigHandler(db *gorm.DB) *ToolConfigHandler { - return &ToolConfigHandler{db: db} +func NewToolConfigHandler(db *gorm.DB, ti *toolinstaller.ToolInstaller) *ToolConfigHandler { + return &ToolConfigHandler{db: db, installer: ti} } // ListTools returns all configured tools @@ -127,8 +129,17 @@ func (h *ToolConfigHandler) InstallTool(c *gin.Context) { tool.DefaultVersion = version } + // Mark as installing immediately + h.db.Model(&tool).Update("status", models.ToolInstalling) + + // Background installation — actually call the installer go func() { log.Printf("[ToolConfig] Background install triggered for %s (version: %s)", tool.Name, version) + if err := h.installer.ForceInstall(tool.Name, version); err != nil { + log.Printf("[ToolConfig] ERROR installing %s: %v", tool.Name, err) + } else { + log.Printf("[ToolConfig] %s installed successfully", tool.Name) + } }() c.JSON(http.StatusOK, models.Response{Code: 200, Message: "安装已触发", Data: gin.H{"version": version}}) diff --git a/backend/internal/api/routes/routes.go b/backend/internal/api/routes/routes.go index e9537fe..ff3a28f 100644 --- a/backend/internal/api/routes/routes.go +++ b/backend/internal/api/routes/routes.go @@ -558,7 +558,7 @@ func Register(r *gin.Engine, db *gorm.DB, log *zap.Logger, cfg *config.Config, c } // 工具配置路由(发布配置 Tab ④)// Logan.Li - toolConfigHandler := handlers.NewToolConfigHandler(db) + toolConfigHandler := handlers.NewToolConfigHandler(db, ti) tools := authorized.Group("/deploy/tools") { tools.GET("", toolConfigHandler.ListTools) diff --git a/backend/pkg/toolinstaller/installer.go b/backend/pkg/toolinstaller/installer.go index 6048c14..7b0071b 100644 --- a/backend/pkg/toolinstaller/installer.go +++ b/backend/pkg/toolinstaller/installer.go @@ -45,6 +45,24 @@ func (ti *ToolInstaller) ensureOnce(name string) *sync.Once { return o } +// ForceInstall unconditionally installs a tool (bypasses sync.Once). +// Used by the manual "安装" button in the tool config UI. +// The version parameter overrides DefaultVersion if non-empty. +func (ti *ToolInstaller) ForceInstall(toolName string, version string) error { + var tool models.DeployTool + if err := ti.db.Where("name = ?", toolName).First(&tool).Error; err != nil { + return fmt.Errorf("tool %s not found in DB: %w", toolName, err) + } + + // Override version if specified + if version != "" && version != tool.DefaultVersion { + ti.db.Model(&tool).Update("default_version", version) + tool.DefaultVersion = version + } + + return ti.installTool(&tool) +} + // EnsureAvailable checks if a tool is available and installs it if configured for lazy loading. // Returns nil if the tool is ready, or an error if installation failed. func (ti *ToolInstaller) EnsureAvailable(toolName string) error { diff --git a/frontend/src/components/aiops/AIOpsAssistant.tsx b/frontend/src/components/aiops/AIOpsAssistant.tsx index d5cdac3..4953fde 100644 --- a/frontend/src/components/aiops/AIOpsAssistant.tsx +++ b/frontend/src/components/aiops/AIOpsAssistant.tsx @@ -103,7 +103,7 @@ async function loadReportContext(): Promise { ]); const s: any = stats || {}; const ss = s.server_stats || {}, ks = s.kubernetes_stats || {}, as = s.alert_stats || {}, ps = s.pipeline_stats || {}, cs = s.cost_stats || {}; - const overview = `服务器:${ss.total ?? 0}(运行${ss.running ?? 0}) | K8s集群:${ks.total ?? 0} 节点:${ks.total_nodes ?? 0} Pod:${ks.total_pods ?? 0} | 告警:${as.total ?? 0}(firing ${as.firing ?? 0}) | 流水线:${ps.total ?? 0}(成功率${ps.success_rate ?? 0}%) | 本月成本:¥${cs.month_cost ?? 0}`; + const overview = `服务器:${ss.total ?? 0}(运行${ss.running ?? 0}) | Kubernetes集群:${ks.total ?? 0} 节点:${ks.total_nodes ?? 0} Pod:${ks.total_pods ?? 0} | 告警:${as.total ?? 0}(firing ${as.firing ?? 0}) | 流水线:${ps.total ?? 0}(成功率${ps.success_rate ?? 0}%) | 本月成本:¥${cs.month_cost ?? 0}`; const alertList: any[] = Array.isArray(alerts) ? alerts : (alerts as any)?.list || []; const releaseList: any[] = Array.isArray(releases) ? releases : (releases as any)?.list || []; const alertLines = alertList.slice(0, 10).map((a, i) => `${i + 1}.[${a.severity || '?'}|${a.status || '?'}] ${a.rule_name || '-'} → ${a.target || '-'}`); diff --git a/frontend/src/components/dashboard/Dashboard.tsx b/frontend/src/components/dashboard/Dashboard.tsx index e0b86df..84e45c8 100644 --- a/frontend/src/components/dashboard/Dashboard.tsx +++ b/frontend/src/components/dashboard/Dashboard.tsx @@ -145,7 +145,7 @@ export function Dashboard() {
- K8s集群 + Kubernetes 集群 Kubernetes集群健康状态
+
+
+ {entries.map((entry, i) => ( +
+ update(i, 'key', e.target.value)} + placeholder="键名 (如 LOG_LEVEL)" + className="flex-1 font-mono text-xs h-8 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]" + /> +