diff --git a/internal/web/ui.go b/internal/web/ui.go index 21ca473..0b271d6 100644 --- a/internal/web/ui.go +++ b/internal/web/ui.go @@ -299,7 +299,9 @@ func tablerIconSVG(name string) string { "preview": ``, "apply": ``, "service": ``, + "task": ``, "result": ``, + "logs": ``, "meta": ``, "template": ``, "profile": ``, @@ -335,12 +337,10 @@ func (u *UI) Routes() (chi.Router, error) { })) r.Get("/", func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, "/ui/devices", http.StatusFound) + http.Redirect(w, r, "/ui/dashboard", http.StatusFound) }) - r.Get("/dashboard", func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, "/ui/devices", http.StatusFound) - }) + r.Get("/dashboard", u.pageDashboard) r.Get("/devices", u.pageDevices) r.Get("/devices/{id}/control", u.pageDeviceControl) r.Get("/assets", u.pageAssets) @@ -354,6 +354,7 @@ func (u *UI) Routes() (chi.Router, error) { r.Get("/audit", u.pageAudit) r.Get("/system", u.pageSystem) r.Get("/device-config", u.pageDeviceConfig) + r.Get("/device-config/{id}", u.pageDeviceConfigDetail) r.Get("/devices-add", u.pageDeviceAdd) r.Post("/devices-add", u.actionDeviceAdd) r.Post("/devices/batch-action", u.actionDevicesBatchAction) @@ -442,7 +443,31 @@ func (u *UI) ensureDevicesLoaded() { } func (u *UI) pageDashboard(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, "/ui/devices", http.StatusFound) + data := u.deviceOverviewPageData(r, nil, "") + if u.tasks != nil { + for _, task := range u.tasks.ListTasks() { + switch task.Status { + case models.TaskRunning: + data.RunningTaskCount++ + case models.TaskFailed: + data.FailedTaskCount++ + case models.TaskSuccess: + data.SuccessTaskCount++ + } + } + } + data.Title = "总览" + data.Tasks = nil + if u.tasks != nil { + data.Tasks = u.tasks.ListTasks() + } + data.AttentionDevices = nil + for _, dev := range data.Devices { + if dev != nil && !dev.Online { + data.AttentionDevices = append(data.AttentionDevices, dev) + } + } + u.render(w, r, "dashboard", data) } func (u *UI) pageDevices(w http.ResponseWriter, r *http.Request) { @@ -454,7 +479,21 @@ func (u *UI) pageDeviceAdd(w http.ResponseWriter, r *http.Request) { } func (u *UI) pageDeviceConfig(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, "/ui/assets", http.StatusFound) + u.ensureDevicesLoaded() + u.render(w, r, "device_config", PageData{ + Title: "配置管理", + Devices: u.registry.GetDevices(), + }) +} + +func (u *UI) pageDeviceConfigDetail(w http.ResponseWriter, r *http.Request) { + id := chi.URLParam(r, "id") + dev, ok := u.findDevice(id) + if !ok { + http.NotFound(w, r) + return + } + u.render(w, r, "device_control", u.deviceConfigWorkspacePageData(dev)) } func (u *UI) actionDeviceAdd(w http.ResponseWriter, r *http.Request) { @@ -647,12 +686,7 @@ func (u *UI) deviceDetailPageData(dev *models.Device) PageData { func (u *UI) pageDeviceControl(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") - dev, ok := u.findDevice(id) - if !ok { - http.NotFound(w, r) - return - } - u.render(w, r, "device_control", u.deviceControlPageData(dev)) + http.Redirect(w, r, "/ui/device-config/"+url.PathEscape(id), http.StatusFound) } func (u *UI) actionDeviceAction(w http.ResponseWriter, r *http.Request) { @@ -692,8 +726,8 @@ func (u *UI) actionDeviceAction(w http.ResponseWriter, r *http.Request) { body, code, err := u.agent.Do(method, dev.IP, dev.AgentPort, path, nil) msg := fmt.Sprintf("%s %s -> %d", method, path, code) returnTo := strings.TrimSpace(r.FormValue("return_to")) - if returnTo == "control" { - data := u.deviceControlPageData(dev) + if returnTo == "control" || returnTo == "config" { + data := u.deviceConfigWorkspacePageData(dev) data.Message = msg data.RawText = string(body) data.ResultTitle = "执行结果摘要" @@ -1016,7 +1050,7 @@ func (u *UI) pageModels(w http.ResponseWriter, r *http.Request) { } func (u *UI) pageDiagnostics(w http.ResponseWriter, r *http.Request) { - u.render(w, r, "diagnostics", PageData{Title: "日志分析", Devices: u.registry.GetDevices()}) + u.render(w, r, "diagnostics", PageData{Title: "诊断", Devices: u.registry.GetDevices()}) } func (u *UI) pageRecognition(w http.ResponseWriter, r *http.Request) { @@ -1024,7 +1058,7 @@ func (u *UI) pageRecognition(w http.ResponseWriter, r *http.Request) { } func (u *UI) pageLogs(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, "/ui/audit", http.StatusFound) + http.Redirect(w, r, "/ui/diagnostics", http.StatusFound) } func (u *UI) pageAPIConsole(w http.ResponseWriter, r *http.Request) { @@ -1038,6 +1072,19 @@ func (u *UI) pageAssets(w http.ResponseWriter, r *http.Request) { func (u *UI) pageAssetTemplates(w http.ResponseWriter, r *http.Request) { data := u.assetPageData("templates") + if name := strings.TrimSpace(r.URL.Query().Get("name")); name != "" { + if item, err := u.preview.GetTemplateAsset(name); err == nil { + data.AssetTemplate = item + } else if data.Error == "" { + data.Error = err.Error() + } + } else if len(data.AssetTemplates) > 0 { + if item, err := u.preview.GetTemplateAsset(data.AssetTemplates[0].Name); err == nil { + data.AssetTemplate = item + } else if data.Error == "" { + data.Error = err.Error() + } + } u.render(w, r, "asset_templates", data) } @@ -1049,13 +1096,28 @@ func (u *UI) pageAssetTemplate(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) return } - data.Title = "模板详情" data.AssetTemplate = item - u.render(w, r, "asset_template", data) + u.render(w, r, "asset_templates", data) } func (u *UI) pageAssetProfiles(w http.ResponseWriter, r *http.Request) { data := u.assetPageData("profiles") + selected := strings.TrimSpace(r.URL.Query().Get("name")) + if selected == "" && len(data.AssetProfiles) > 0 { + selected = data.AssetProfiles[0].Name + } + if selected != "" { + editor, err := u.preview.GetProfileEditor(selected) + if err == nil { + data.AssetProfileEditor = editor + data.SelectedProfile = editor.Name + if len(editor.Instances) > 0 && editor.Instances[0].Template != "" { + data.SelectedTemplate = editor.Instances[0].Template + } + } else if data.Error == "" { + data.Error = err.Error() + } + } u.render(w, r, "asset_profiles", data) } @@ -1066,8 +1128,8 @@ func (u *UI) pageAssetProfile(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) return } - data.Title = "业务配置编辑" - u.render(w, r, "asset_profile", data) + data.Title = "识别配置" + u.render(w, r, "asset_profiles", data) } func (u *UI) actionAssetProfileSave(w http.ResponseWriter, r *http.Request) { @@ -1079,7 +1141,7 @@ func (u *UI) actionAssetProfileSave(w http.ResponseWriter, r *http.Request) { } if err := u.preview.SaveProfileEditor(editor); err != nil { data.Error = err.Error() - u.render(w, r, "asset_profile", data) + u.render(w, r, "asset_profiles", data) return } if editor.Name != name { @@ -1087,11 +1149,24 @@ func (u *UI) actionAssetProfileSave(w http.ResponseWriter, r *http.Request) { } else { data.Message = "业务配置已保存" } - u.render(w, r, "asset_profile", data) + u.render(w, r, "asset_profiles", data) } func (u *UI) pageAssetOverlays(w http.ResponseWriter, r *http.Request) { data := u.assetPageData("overlays") + if name := strings.TrimSpace(r.URL.Query().Get("name")); name != "" { + if item, err := u.preview.GetOverlayAsset(name); err == nil { + data.AssetOverlay = item + } else if data.Error == "" { + data.Error = err.Error() + } + } else if len(data.AssetOverlays) > 0 { + if item, err := u.preview.GetOverlayAsset(data.AssetOverlays[0].Name); err == nil { + data.AssetOverlay = item + } else if data.Error == "" { + data.Error = err.Error() + } + } u.render(w, r, "asset_overlays", data) } @@ -1103,14 +1178,13 @@ func (u *UI) pageAssetOverlay(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) return } - data.Title = "配置叠加项详情" data.AssetOverlay = item - u.render(w, r, "asset_overlay", data) + u.render(w, r, "asset_overlays", data) } func (u *UI) assetPageData(tab string) PageData { data := PageData{ - Title: "配置资产", + Title: "识别配置", AssetTab: tab, } if u.preview == nil { @@ -1388,8 +1462,8 @@ func (u *UI) actionDeviceConfigCandidateApply(w http.ResponseWriter, r *http.Req } returnTo := strings.TrimSpace(r.FormValue("return_to")) var data PageData - if returnTo == "control" { - data = u.deviceControlPageData(dev) + if returnTo == "control" || returnTo == "config" { + data = u.deviceConfigWorkspacePageData(dev) } else { data = u.configPreviewPageData(dev) } @@ -1401,11 +1475,7 @@ func (u *UI) actionDeviceConfigCandidateApply(w http.ResponseWriter, r *http.Req body, code, err := u.agent.Do("POST", dev.IP, dev.AgentPort, "/v1/config/candidate/apply", []byte(`{}`)) data.Message = fmt.Sprintf("POST /v1/config/candidate/apply -> %d", code) data.RawText = prettyJSON(body) - if returnTo == "control" { - data.ResultTitle = "应用候选配置结果" - } else { - data.ResultTitle = "应用候选配置结果" - } + data.ResultTitle = "应用候选配置结果" if err != nil { data.Error = err.Error() } else { @@ -1417,7 +1487,7 @@ func (u *UI) actionDeviceConfigCandidateApply(w http.ResponseWriter, r *http.Req data.ConfigStatusErr = "" } } - if returnTo == "control" { + if returnTo == "control" || returnTo == "config" { u.render(w, r, "device_control", data) return } @@ -1460,6 +1530,12 @@ func (u *UI) deviceControlPageData(dev *models.Device) PageData { return data } +func (u *UI) deviceConfigWorkspacePageData(dev *models.Device) PageData { + data := u.deviceControlPageData(dev) + data.Title = "配置管理" + return data +} + func (u *UI) listTemplatesSafe() ([]service.Template, error) { if u.templates == nil { return nil, nil diff --git a/internal/web/ui/templates/dashboard.html b/internal/web/ui/templates/dashboard.html index 8062c79..b044ec2 100644 --- a/internal/web/ui/templates/dashboard.html +++ b/internal/web/ui/templates/dashboard.html @@ -2,17 +2,17 @@