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)]" + /> +