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" .}}
+
+
+
+
当前配置 {{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 @@
-
配置管理
-
面向单台设备的设备运行配置、服务状态和服务管理。
+
单设备配置已并入设备详情
+
配置查看、服务控制、候选配置应用和回滚,都已经收敛到设备详情工作台。
-
返回设备列表
+
去设备列表
-
设备运行配置
-
{{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)
+ }
+ }
+}