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 @@
-

视觉节点态势

-
面向多台视觉识别边缘节点的运行总览。
+

全局 KPI

+
只看 fleet 级运行态势、最近任务和需要关注的异常设备。
设备总数
{{.DeviceCount}}
已纳管的边缘设备
-
在线节点
{{.OnlineCount}}
可执行识别任务
+
在线率
{{.OnlineCount}} / {{.DeviceCount}}
在线设备占比
离线节点
{{.OfflineCount}}
需要检查网络或设备服务
执行中任务
{{.RunningTaskCount}}
正在下发或控制节点
@@ -33,9 +33,9 @@
按常见现场操作顺序进入对应页面。
查看设备列表 - 配置管理 配置识别方案 - 日志分析 + 批量任务 + 诊断
@@ -59,7 +59,7 @@
-

需要关注的节点

+

异常设备

diff --git a/internal/web/ui/templates/diagnostics.html b/internal/web/ui/templates/diagnostics.html index 11a54eb..732f1a7 100644 --- a/internal/web/ui/templates/diagnostics.html +++ b/internal/web/ui/templates/diagnostics.html @@ -3,12 +3,18 @@

诊断工作台

-
按设备进入诊断日志、运行指标和高级接口排查。
+
诊断域集中承载日志分析、系统状态、审计记录和高级排障入口。
高级调试
+
+
日志分析
Logs
按设备查看诊断日志和运行指标
+
系统状态
System
查看发现、健康和接口状态
+
审计记录
Audit
追踪任务与关键操作
+
+

日志分析

@@ -40,4 +46,21 @@
+ +
+
+

系统状态

+
查看平台健康、接口和发现能力。
+ +
+
+

审计记录

+
统一查看任务执行和关键操作留痕。
+ +
+
{{end}} diff --git a/internal/web/ui/templates/layout.html b/internal/web/ui/templates/layout.html index 9f355af..4ec2c10 100644 --- a/internal/web/ui/templates/layout.html +++ b/internal/web/ui/templates/layout.html @@ -20,13 +20,14 @@
diff --git a/internal/web/ui/templates/tasks.html b/internal/web/ui/templates/tasks.html index bfa8dbf..9a99993 100644 --- a/internal/web/ui/templates/tasks.html +++ b/internal/web/ui/templates/tasks.html @@ -1,6 +1,7 @@ {{define "tasks"}}
-

创建任务

+

批量操作

+
任务域负责批量下发、批量重启、批量回滚和执行历史。
@@ -30,7 +31,7 @@
-

任务列表

+

执行历史

@@ -58,7 +59,12 @@
-

高级入口

- +

常用动作

+
+ 批量下发 + 批量重启 + 批量回滚 + 高级调试 +
{{end}}