From 74dfbbf7b547512f167a83278f6ea6d6430b69a8 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Mon, 27 Apr 2026 10:37:29 +0800 Subject: [PATCH] feat: fold single-device operations into device workspace --- internal/web/ui.go | 33 ++-- internal/web/ui/templates/config_preview.html | 2 +- internal/web/ui/templates/device.html | 163 ++++++++++++++---- internal/web/ui/templates/device_config.html | 76 +++----- internal/web/ui_test.go | 16 ++ 5 files changed, 182 insertions(+), 108 deletions(-) diff --git a/internal/web/ui.go b/internal/web/ui.go index 0b271d6..5d14ce7 100644 --- a/internal/web/ui.go +++ b/internal/web/ui.go @@ -481,19 +481,14 @@ func (u *UI) pageDeviceAdd(w http.ResponseWriter, r *http.Request) { func (u *UI) pageDeviceConfig(w http.ResponseWriter, r *http.Request) { u.ensureDevicesLoaded() u.render(w, r, "device_config", PageData{ - Title: "配置管理", + 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)) + http.Redirect(w, r, "/ui/devices/"+url.PathEscape(id)+"#device-config", http.StatusFound) } func (u *UI) actionDeviceAdd(w http.ResponseWriter, r *http.Request) { @@ -667,26 +662,18 @@ func (u *UI) pageDevice(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) return } - status, raw, statusErr := u.loadConfigStatus(dev) - data := PageData{Title: "设备详情", Device: dev, ConfigStatus: status, ConfigStatusText: raw} - if statusErr != nil { - data.ConfigStatusErr = statusErr.Error() - } - u.render(w, r, "device", data) + u.render(w, r, "device", u.deviceDetailPageData(dev)) } func (u *UI) deviceDetailPageData(dev *models.Device) PageData { - status, raw, statusErr := u.loadConfigStatus(dev) - data := PageData{Title: "设备详情", Device: dev, ConfigStatus: status, ConfigStatusText: raw} - if statusErr != nil { - data.ConfigStatusErr = statusErr.Error() - } + data := u.deviceControlPageData(dev) + data.Title = "设备详情" return data } func (u *UI) pageDeviceControl(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") - http.Redirect(w, r, "/ui/device-config/"+url.PathEscape(id), http.StatusFound) + http.Redirect(w, r, "/ui/devices/"+url.PathEscape(id)+"#device-config", http.StatusFound) } func (u *UI) actionDeviceAction(w http.ResponseWriter, r *http.Request) { @@ -727,14 +714,14 @@ func (u *UI) actionDeviceAction(w http.ResponseWriter, r *http.Request) { msg := fmt.Sprintf("%s %s -> %d", method, path, code) returnTo := strings.TrimSpace(r.FormValue("return_to")) if returnTo == "control" || returnTo == "config" { - data := u.deviceConfigWorkspacePageData(dev) + data := u.deviceDetailPageData(dev) data.Message = msg data.RawText = string(body) data.ResultTitle = "执行结果摘要" if err != nil { data.Error = err.Error() } - u.render(w, r, "device_control", data) + u.render(w, r, "device", data) return } data := PageData{Title: "设备详情", Device: dev, Message: msg, RawText: string(body)} @@ -1463,7 +1450,7 @@ func (u *UI) actionDeviceConfigCandidateApply(w http.ResponseWriter, r *http.Req returnTo := strings.TrimSpace(r.FormValue("return_to")) var data PageData if returnTo == "control" || returnTo == "config" { - data = u.deviceConfigWorkspacePageData(dev) + data = u.deviceDetailPageData(dev) } else { data = u.configPreviewPageData(dev) } @@ -1488,7 +1475,7 @@ func (u *UI) actionDeviceConfigCandidateApply(w http.ResponseWriter, r *http.Req } } if returnTo == "control" || returnTo == "config" { - u.render(w, r, "device_control", data) + u.render(w, r, "device", data) return } u.render(w, r, "config_preview", data) diff --git a/internal/web/ui/templates/config_preview.html b/internal/web/ui/templates/config_preview.html index 37aaada..e48dc2c 100644 --- a/internal/web/ui/templates/config_preview.html +++ b/internal/web/ui/templates/config_preview.html @@ -47,7 +47,7 @@ {{end}} - 返回设备控制 + 返回设备详情 diff --git a/internal/web/ui/templates/device.html b/internal/web/ui/templates/device.html index ca329f3..6853ef9 100644 --- a/internal/web/ui/templates/device.html +++ b/internal/web/ui/templates/device.html @@ -1,28 +1,60 @@ {{define "device"}} {{template "device_header" .}} -{{template "device_tabs" .}} -
+
+
+
+

设备工作台

+
单设备查看、服务控制、配置应用、模型资源和日志指标都收敛在这里完成。
+
+ +
+
+
+
设备状态
+
{{if .Device.Online}}在线{{else}}离线{{end}}
+
+
+
当前配置
+
{{if and .ConfigStatus .ConfigStatus.Metadata.ConfigID}}{{.ConfigStatus.Metadata.ConfigID}}{{else}}待读取{{end}}
+
+
+
服务状态
+
{{if and .ConfigStatus .ConfigStatus.MediaServer.Running}}运行中{{else}}未确认{{end}}
+
+
+
+ +
+ +
+ +
-

{{icon "status"}}设备状态

+

{{icon "overview"}}概览

{{if .Device.Online}}在线{{else}}离线{{end}}
-
- -
-
-
当前业务配置{{if and .ConfigStatus .ConfigStatus.Metadata.BusinessName}}{{.ConfigStatus.Metadata.BusinessName}}{{else}}-{{end}}
- {{if displayDeviceTechnicalName .Device}}
设备主机名{{displayDeviceTechnicalName .Device}}
{{end}} +
设备名称{{displayDeviceName .Device .ConfigStatus}}
设备 ID{{.Device.DeviceID}}
管理地址{{.Device.IP}}:{{.Device.AgentPort}}
视频端口{{.Device.MediaPort}}
最后心跳{{ago .Device.LastSeenMs}}
版本{{if .Device.Version}}{{.Device.Version}}{{else}}-{{end}}
-
Build ID{{if .Device.BuildID}}{{.Device.BuildID}}{{else}}-{{end}}
+
当前业务配置{{if and .ConfigStatus .ConfigStatus.Metadata.BusinessName}}{{.ConfigStatus.Metadata.BusinessName}}{{else}}-{{end}}
通道名{{if and .ConfigStatus .ConfigStatus.Metadata.InstanceName}}{{.ConfigStatus.Metadata.InstanceName}}{{else if .Device.InstanceName}}{{.Device.InstanceName}}{{else}}-{{end}}
@@ -38,13 +70,10 @@
配置 ID{{if .ConfigStatus.Metadata.ConfigID}}{{.ConfigStatus.Metadata.ConfigID}}{{else}}未标记{{end}}
配置版本{{if .ConfigStatus.Metadata.ConfigVersion}}{{.ConfigStatus.Metadata.ConfigVersion}}{{else}}未标记{{end}}
-
业务名称{{if .ConfigStatus.Metadata.BusinessName}}{{.ConfigStatus.Metadata.BusinessName}}{{else}}-{{end}}
模板{{if .ConfigStatus.Metadata.Template}}{{.ConfigStatus.Metadata.Template}}{{else}}-{{end}}
业务配置{{if .ConfigStatus.Metadata.Profile}}{{.ConfigStatus.Metadata.Profile}}{{else}}-{{end}}
-
配置叠加项{{if .ConfigStatus.Metadata.Overlays}}{{range $i, $name := .ConfigStatus.Metadata.Overlays}}{{if $i}}, {{end}}{{$name}}{{end}}{{else}}-{{end}}
-
服务状态{{if .ConfigStatus.MediaServer.Running}}运行中{{else}}未运行{{end}}
+
叠加项{{if .ConfigStatus.Metadata.Overlays}}{{range $i, $name := .ConfigStatus.Metadata.Overlays}}{{if $i}}, {{end}}{{$name}}{{end}}{{else}}-{{end}}
配置文件{{.ConfigStatus.ConfigPath}}
-
SHA{{shortHash .ConfigStatus.Sha256}}
{{else}}
@@ -55,27 +84,95 @@
-
-
-
-

{{icon "overview"}}最近状态摘要

+
+
+
+
+

{{icon "service"}}运行与服务

+
+ {{if and .ConfigStatus .ConfigStatus.MediaServer.Running}}运行中{{else}}待确认{{end}}
-
-
-
-
服务
-
{{if and .ConfigStatus .ConfigStatus.MediaServer.Running}}运行中{{else}}未确认{{end}}
+
+
媒体服务{{if and .ConfigStatus .ConfigStatus.MediaServer.Running}}运行中{{else}}未确认{{end}}
+
上一份配置{{if and .ConfigStatus .ConfigStatus.PreviousConfig .ConfigStatus.PreviousConfig.Metadata.ConfigID}}{{.ConfigStatus.PreviousConfig.Metadata.ConfigID}}{{else}}-{{end}}
-
-
配置
-
{{if and .ConfigStatus .ConfigStatus.Metadata.ConfigID}}{{.ConfigStatus.Metadata.ConfigID}}{{else}}待读取{{end}}
+
+
+ + + +
+
+ + + +
+
+ + + +
-
-
版本
-
{{if .Device.Version}}{{.Device.Version}}{{else}}-{{end}}
-
-
-
+ -{{template "device_panel_end" .}} +
+
+
+

{{icon "apply"}}设备配置

+
+ {{icon "preview"}}编辑和上传候选配置 +
+
+
当前配置{{if and .ConfigStatus .ConfigStatus.Metadata.ConfigID}}{{.ConfigStatus.Metadata.ConfigID}}{{else}}待读取{{end}}
+
候选配置{{if and .ConfigStatus .ConfigStatus.Candidate .ConfigStatus.Candidate.Exists}}{{if .ConfigStatus.Candidate.Metadata.ConfigID}}{{.ConfigStatus.Candidate.Metadata.ConfigID}}{{else}}已存在{{end}}{{else}}未上传{{end}}
+
+
+
+ + +
+
+ + + +
+
+
+ +
+
+
+

{{icon "assets"}}模型与资源

+
+ 进入模型管理 +
+
+
模型入口通过模型管理页上传到设备
+
人脸库通过识别配置与资源页维护
+
+
+
+ +
+ 查看识别配置资产 +
+
+ +
+
+
+

{{icon "result"}}日志与指标

+
+
+ + {{if .ResultTitle}}
{{.ResultTitle}}
{{end}} + {{if .Message}}
{{.Message}}
{{end}} + {{if .Error}}
{{.Error}}
{{end}} + {{if .RawText}}
{{.RawText}}
{{end}} +
+
{{end}} diff --git a/internal/web/ui/templates/device_config.html b/internal/web/ui/templates/device_config.html index 45365af..4a7edd3 100644 --- a/internal/web/ui/templates/device_config.html +++ b/internal/web/ui/templates/device_config.html @@ -2,62 +2,36 @@
-

配置管理

-
面向单台设备的设备运行配置、服务状态和服务管理。
+

单设备配置已并入设备详情

+
配置查看、服务控制、候选配置应用和回滚,都已经收敛到设备详情工作台。
- 返回设备列表 + 去设备列表
-

设备运行配置

-
- - - - - - {{range .Devices}} - - - - - - - - - {{else}} - - {{end}} - -
设备状态管理地址服务状态配置操作服务管理
{{if .DeviceName}}{{.DeviceName}}{{else}}{{.DeviceID}}{{end}}
{{.DeviceID}}
{{if .Online}}在线{{else}}离线{{end}}{{.IP}}:{{.AgentPort}} -
- - -
-
- -
- - - -
-
- - -
-
-
-
-
-
-
-
-
-
暂无设备。请先在“新增设备”页扫描或手动添加。
+

设备入口

+
{{end}} diff --git a/internal/web/ui_test.go b/internal/web/ui_test.go index 3894db8..3ccc294 100644 --- a/internal/web/ui_test.go +++ b/internal/web/ui_test.go @@ -2155,3 +2155,19 @@ func TestUI_DeviceDetailActsAsSingleDeviceWorkspace(t *testing.T) { t.Fatalf("device detail should not contain cross-module config shortcut: %s", html) } } + +func TestUI_DeviceWorkspaceContainsSingleDeviceOperations(t *testing.T) { + ui := newTestUI(t) + html := renderPage(t, ui, "/ui/devices/edge-01") + + for _, text := range []string{"概览", "运行与服务", "设备配置", "模型与资源", "日志与指标"} { + if !strings.Contains(html, text) { + t.Fatalf("expected device workspace section %q in html: %s", text, html) + } + } + for _, removed := range []string{"返回设备选择", "配置管理面向单台设备"} { + if strings.Contains(html, removed) { + t.Fatalf("did not expect legacy standalone config flow text %q in html: %s", removed, html) + } + } +}