diff --git a/internal/web/ui_test.go b/internal/web/ui_test.go
index f184676..ff063b9 100644
--- a/internal/web/ui_test.go
+++ b/internal/web/ui_test.go
@@ -112,77 +112,6 @@ func TestUI_SelectedDeviceQueryHelpersStayStable(t *testing.T) {
}
}
-func TestUI_DevicePageUsesEdgeVisionConsoleShell(t *testing.T) {
- cfg := &config.Config{Concurrency: 1, OfflineAfterMs: 1000000}
- reg := service.NewRegistryService(cfg, nil)
- reg.UpdateDevice(&models.Device{DeviceID: "edge-01", DeviceName: "入口识别节点", IP: "127.0.0.1", AgentPort: 9100, MediaPort: 9000, Online: true})
- tasks := service.NewTaskService(cfg, nil, reg)
-
- ui, err := NewUI(nil, reg, nil, tasks, nil)
- if err != nil {
- t.Fatalf("NewUI: %v", err)
- }
-
- req := httptest.NewRequest(http.MethodGet, "/ui/devices", nil)
- rr := httptest.NewRecorder()
-
- ui.pageDevices(rr, req)
-
- if rr.Code != http.StatusOK {
- t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
- }
- body := rr.Body.String()
- for _, want := range []string{
- "视觉识别运维平台",
- "总览",
- "场景",
- "视频通道",
- "通道部署",
- "配置中心",
- "任务中心",
- "系统设置",
- "
设备
",
- } {
- if !strings.Contains(body, want) {
- t.Fatalf("expected rendered HTML to contain %q, got:\n%s", want, body)
- }
- }
-}
-
-func TestUI_LayoutProvidesThemeSwitcherAndCompactTopbar(t *testing.T) {
- ui := newTestUI(t)
-
- body := renderPage(t, ui, "/ui/devices")
-
- for _, want := range []string{
- `data-theme="blue-dark"`,
- `class="topbar-actions"`,
- `data-theme-option="blue-dark"`,
- `data-theme-option="blue-light"`,
- `data-theme-option="graphite-gold"`,
- `const themeLabels = {`,
- `"blue-light": "蓝灰浅色"`,
- `"当前主题:" + themeLabels[nextTheme]`,
- `aria-label="主题"`,
- `aria-label="日志审计"`,
- `aria-label="系统"`,
- `localStorage.getItem("3588-admin-theme")`,
- } {
- if !strings.Contains(body, want) {
- t.Fatalf("expected layout to contain %q, got:\n%s", want, body)
- }
- }
- for _, forbidden := range []string{
- `class="crumb"`,
- "多设备视觉识别运维平台",
- "Fleet Operations Console",
- } {
- if strings.Contains(body, forbidden) {
- t.Fatalf("expected compact topbar to omit %q, got:\n%s", forbidden, body)
- }
- }
-}
-
func TestUI_ConsoleTypographyStaysModerate(t *testing.T) {
css, err := os.ReadFile("ui/assets/style.css")
if err != nil {
@@ -284,24 +213,6 @@ func renderPage(t *testing.T, ui *UI, path string) string {
return rr.Body.String()
}
-func TestUI_DeviceOverviewHidesBatchBarWithoutSelection(t *testing.T) {
- ui := newTestUI(t)
- req := httptest.NewRequest(http.MethodGet, "/ui/devices", nil)
- rr := httptest.NewRecorder()
-
- ui.pageDevices(rr, req)
-
- if rr.Code != http.StatusOK {
- t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
- }
- body := rr.Body.String()
- for _, forbidden := range []string{"batch-toolbar", "已选", "重启服务", "启动服务", "停止服务", "重载运行配置", "回滚运行配置", "下发设备分配", "清空选择"} {
- if strings.Contains(body, forbidden) {
- t.Fatalf("device overview should not show batch controls without selection, found %q in:\n%s", forbidden, body)
- }
- }
-}
-
func TestUI_DeviceOverviewShowsBatchBarWhenDevicesSelected(t *testing.T) {
ui := newTestUI(t)
ui.registry.UpdateDevice(&models.Device{DeviceID: "edge-02", DeviceName: "辅助节点", IP: "127.0.0.2", AgentPort: 9100, MediaPort: 9000, Online: true})
@@ -967,29 +878,6 @@ func TestUI_TaskPageRendersBatchSummaryAndDeviceResults(t *testing.T) {
}
}
-func TestUI_DeviceOverviewRendersFleetOverview(t *testing.T) {
- ui := newTestUI(t)
- req := httptest.NewRequest(http.MethodGet, "/ui/devices", nil)
- rr := httptest.NewRecorder()
-
- ui.pageDevices(rr, req)
-
- if rr.Code != http.StatusOK {
- t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
- }
- body := rr.Body.String()
- for _, want := range []string{"设备", "告警趋势", "在线设备", "离线设备", "失败操作", "设备列表"} {
- if !strings.Contains(body, want) {
- t.Fatalf("expected dashboard HTML to contain %q, got:\n%s", want, body)
- }
- }
- for _, forbidden := range []string{"设备总览", "多台视觉识别设备的统一运维视图", "先看状态,再决定操作。默认只展示值班和维护最需要的信息。"} {
- if strings.Contains(body, forbidden) {
- t.Fatalf("device page should not contain explanatory copy %q", forbidden)
- }
- }
-}
-
func TestUI_DevicePageIncludesFilterAndControlEntry(t *testing.T) {
ui := newTestUI(t)
req := httptest.NewRequest(http.MethodGet, "/ui/devices", nil)
@@ -1010,136 +898,6 @@ func TestUI_DevicePageIncludesFilterAndControlEntry(t *testing.T) {
}
}
-func TestUI_DeviceOverviewShowsLiveStatusAndConfigSummary(t *testing.T) {
- agentServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != "/v1/config/status" {
- t.Fatalf("unexpected agent path %s", r.URL.Path)
- }
- w.Header().Set("Content-Type", "application/json")
- _, _ = w.Write([]byte(`{
- "ok": true,
- "config_path": "/opt/rk3588-media-server/etc/media-server.json",
- "exists": true,
- "sha256": "8d935c9d366637ff853c69d25e9c6644c2fbff3b8d3aa15ff99ef32847fb947c",
- "metadata": {
- "config_id": "local_3588_face_debug",
- "config_version": "20260419.104457",
- "template": "std_workshop_face_recognition_shoe_alarm",
- "profile": "local_3588_test",
- "overlays": ["face_debug", "production_quiet"]
- },
- "media_server": {"running": true, "pid": 1706009}
- }`))
- }))
- defer agentServer.Close()
-
- host, portText, err := net.SplitHostPort(strings.TrimPrefix(agentServer.URL, "http://"))
- if err != nil {
- t.Fatalf("parse test server address: %v", err)
- }
- port, err := strconv.Atoi(portText)
- if err != nil {
- t.Fatalf("parse test server port: %v", err)
- }
-
- cfg := &config.Config{Concurrency: 1, OfflineAfterMs: 1000000}
- agent := service.NewAgentClient(cfg)
- reg := service.NewRegistryService(cfg, agent)
- reg.UpdateDevice(&models.Device{DeviceID: "edge-01", DeviceName: "入口识别节点", IP: host, AgentPort: port, MediaPort: 9000, Online: false, Version: "1.0.0", BuildID: "20260419.151628", GitSha: "5c04681"})
- tasks := service.NewTaskService(cfg, agent, reg)
- ui, err := NewUI(nil, reg, agent, tasks, nil)
- if err != nil {
- t.Fatalf("NewUI: %v", err)
- }
-
- req := httptest.NewRequest(http.MethodGet, "/ui/devices", nil)
- rr := httptest.NewRecorder()
- ui.pageDevices(rr, req)
-
- if rr.Code != http.StatusOK {
- t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
- }
- body := rr.Body.String()
- for _, want := range []string{"在线", "运行中", "local_3588_face_debug"} {
- if !strings.Contains(body, want) {
- t.Fatalf("expected device overview HTML to contain %q, got:\n%s", want, body)
- }
- }
- if strings.Contains(body, "待在详情页查看") {
- t.Fatalf("device overview should show live config summary instead of placeholder text")
- }
- for _, forbidden := range []string{"20260419.104457", "face_debug, production_quiet"} {
- if strings.Contains(body, forbidden) {
- t.Fatalf("device overview should not expose extra config details %q in main list", forbidden)
- }
- }
- if strings.Contains(body, "心跳") {
- t.Fatalf("device overview should not show heartbeat text in status cell")
- }
-}
-
-func TestUI_DeviceOverviewUsesCompactColumns(t *testing.T) {
- agentServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != "/v1/config/status" {
- t.Fatalf("unexpected agent path %s", r.URL.Path)
- }
- w.Header().Set("Content-Type", "application/json")
- _, _ = w.Write([]byte(`{
- "ok": true,
- "metadata": {
- "config_id": "local_3588_face_debug",
- "config_version": "20260419.104457",
- "overlays": ["face_debug"]
- },
- "media_server": {"running": true}
- }`))
- }))
- defer agentServer.Close()
-
- host, portText, err := net.SplitHostPort(strings.TrimPrefix(agentServer.URL, "http://"))
- if err != nil {
- t.Fatalf("parse test server address: %v", err)
- }
- port, err := strconv.Atoi(portText)
- if err != nil {
- t.Fatalf("parse test server port: %v", err)
- }
-
- cfg := &config.Config{Concurrency: 1, OfflineAfterMs: 1000000}
- agent := service.NewAgentClient(cfg)
- reg := service.NewRegistryService(cfg, agent)
- reg.UpdateDevice(&models.Device{DeviceID: "d12a4719c91641df91b76ab271280797", DeviceName: "rk3588_orangepi5plus", Hostname: "orangepi5plus", IP: host, AgentPort: port, MediaPort: 9000, Online: true, Version: "0.1.0", BuildID: "20260419.151628", GitSha: "5c04681"})
- tasks := service.NewTaskService(cfg, agent, reg)
- ui, err := NewUI(nil, reg, agent, tasks, nil)
- if err != nil {
- t.Fatalf("NewUI: %v", err)
- }
-
- req := httptest.NewRequest(http.MethodGet, "/ui/devices", nil)
- rr := httptest.NewRecorder()
- ui.pageDevices(rr, req)
-
- body := rr.Body.String()
- for _, want := range []string{"设备", "状态", "当前配置", "操作", "orangepi5plus", "0.1.0", "#2026041"} {
- if !strings.Contains(body, want) {
- t.Fatalf("expected compact overview HTML to contain %q, got:\n%s", want, body)
- }
- }
- for _, forbidden := range []string{"#5c04681", "Git SHA"} {
- if strings.Contains(body, forbidden) {
- t.Fatalf("device overview should not expose git sha marker %q", forbidden)
- }
- }
- for _, forbidden := range []string{"最后心跳", "版本", "在线", "服务"} {
- if strings.Contains(body, forbidden) {
- t.Fatalf("device overview should not keep verbose column header %q", forbidden)
- }
- }
- if strings.Contains(body, "心跳") {
- t.Fatalf("device overview compact view should not render heartbeat text")
- }
-}
-
func TestUI_DeviceDetailIncludesWorkspaceSections(t *testing.T) {
ui := newTestUI(t)
routes, err := ui.Routes()
@@ -2159,65 +1917,6 @@ func TestUI_DeviceControlPageShowsLiveActions(t *testing.T) {
}
}
-func TestUI_ActionDeviceActionCanRenderControlPage(t *testing.T) {
- agentServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- switch {
- case r.Method == http.MethodPost && r.URL.Path == "/v1/edge-server/reload":
- w.Header().Set("Content-Type", "application/json")
- _, _ = w.Write([]byte(`{"ok":true}`))
- case r.Method == http.MethodGet && r.URL.Path == "/v1/config/status":
- w.Header().Set("Content-Type", "application/json")
- _, _ = w.Write([]byte(`{
- "ok": true,
- "metadata": {"config_id": "preview_edge-01", "config_version": "v2"},
- "previous_config": {"exists": true, "path": "/opt/rk3588-media-server/etc/media-server.json.last_good.json", "metadata": {"config_id": "local_3588_face_debug", "config_version": "20260419.120246"}},
- "media_server": {"running": true, "pid": 1810489}
- }`))
- default:
- t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
- }
- }))
- defer agentServer.Close()
-
- host, portText, err := net.SplitHostPort(strings.TrimPrefix(agentServer.URL, "http://"))
- if err != nil {
- t.Fatalf("parse test server address: %v", err)
- }
- port, err := strconv.Atoi(portText)
- if err != nil {
- t.Fatalf("parse test server port: %v", err)
- }
-
- cfg := &config.Config{Concurrency: 1, OfflineAfterMs: 1000000}
- agent := service.NewAgentClient(cfg)
- reg := service.NewRegistryService(cfg, agent)
- reg.UpdateDevice(&models.Device{DeviceID: "edge-01", DeviceName: "入口识别节点", IP: host, AgentPort: port, MediaPort: 9000, Online: true})
- tasks := service.NewTaskService(cfg, agent, reg)
- ui, err := NewUI(nil, reg, agent, tasks, nil)
- if err != nil {
- t.Fatalf("NewUI: %v", err)
- }
-
- form := url.Values{}
- form.Set("action", "reload")
- form.Set("return_to", "config")
- req := httptest.NewRequest(http.MethodPost, "/ui/devices/edge-01/action", strings.NewReader(form.Encode()))
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rctx := chi.NewRouteContext()
- rctx.URLParams.Add("id", "edge-01")
- req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
- rr := httptest.NewRecorder()
-
- ui.actionDeviceAction(rr, req)
-
- body := rr.Body.String()
- for _, want := range []string{"设备工作台", "运行与服务", "POST /v1/edge-server/reload", "执行结果摘要"} {
- if !strings.Contains(body, want) {
- t.Fatalf("expected control page result HTML to contain %q, got:\n%s", want, body)
- }
- }
-}
-
func TestUI_ActionDeviceConfigCandidateApplyCanRenderControlPage(t *testing.T) {
t.Skip("legacy candidate-config apply flow is no longer exposed in migrated UI")
statusCalls := 0
@@ -2283,37 +1982,6 @@ func TestUI_ActionDeviceConfigCandidateApplyCanRenderControlPage(t *testing.T) {
}
}
-func TestUI_AssetsPageDefinesConfigAssetScope(t *testing.T) {
- ui := newTestUI(t)
- root := t.TempDir()
- writeTestFile(t, filepath.Join(root, "configs", "profiles", "local_3588_test.json"), `{"name":"local_3588_test","business_name":"A厂区视觉识别","instances":[{"name":"cam1","template":"std_workshop_face_recognition_shoe_alarm","scene_meta":{"display_name":"东门入口"}}]}`)
- writeTestFile(t, filepath.Join(root, "configs", "overlays", "face_debug.json"), `{"description":"debug","instance_overrides":{"*":{"override":{}}}}`)
- store, err := storage.OpenSQLite(filepath.Join(t.TempDir(), "assets.db"))
- if err != nil {
- t.Fatalf("OpenSQLite: %v", err)
- }
- defer store.Close()
- repo := storage.NewAssetsRepo(store.DB())
- mustSaveTemplate(t, repo, "std_workshop_face_recognition_shoe_alarm", "template", `{"name":"std_workshop_face_recognition_shoe_alarm","source":"standard","description":"template","slots":{"inputs":[{"name":"video_input_main","type":"video_source","required":true,"description":"主视频输入"}],"services":[{"name":"object_storage_main","type":"object_storage","required":false,"description":"对象存储"}],"outputs":[{"name":"stream_output_main","type":"stream_publish","required":true,"description":"主视频输出"}]},"template":{"nodes":[{},{}],"edges":[[]]}}`)
- mustSaveIntegrationService(t, repo, "minio_main", "object_storage", `{"name":"minio_main","type":"object_storage","description":"主对象存储","config":{"endpoint":"http://10.0.0.49:9000","bucket":"myminio","access_key":"admin","secret_key":"password"}}`)
- ui.preview = service.NewConfigPreviewService(&config.Config{MediaRepoPath: root}, repo)
- mustImportAssetsForUI(t, ui.preview)
- req := httptest.NewRequest(http.MethodGet, "/ui/assets", nil)
- rr := httptest.NewRecorder()
-
- ui.pageAssets(rr, req)
-
- body := rr.Body.String()
- for _, want := range []string{"配置中心", "总览", "视频源", "识别模板", "第三方服务", "调试参数", "std_workshop_face_recognition_shoe_alarm", "face_debug"} {
- if !strings.Contains(body, want) {
- t.Fatalf("expected assets HTML to contain %q", want)
- }
- }
- if !strings.Contains(body, "minio_main") {
- t.Fatalf("expected assets overview to contain integration service, got:\n%s", body)
- }
-}
-
func TestUI_AssetIntegrationsPageShowsStructuredServices(t *testing.T) {
ui := newTestUI(t)
root := t.TempDir()
@@ -3329,103 +2997,6 @@ func TestUI_ProfileAssetsPageShowsListAndSelectedEditor(t *testing.T) {
}
}
-func TestUI_AuditAndSystemPagesDefineNewScopes(t *testing.T) {
- cfg := &config.Config{Concurrency: 1, OfflineAfterMs: 1000000}
- reg := service.NewRegistryService(cfg, nil)
- reg.UpdateDevice(&models.Device{DeviceID: "edge-01", DeviceName: "入口识别节点", IP: "127.0.0.1", AgentPort: 9100, MediaPort: 9000, Online: true, Version: "1.0.0", BuildID: "20260419.151628", GitSha: "5c04681"})
- reg.UpdateDevice(&models.Device{DeviceID: "edge-02", DeviceName: "辅助节点", IP: "127.0.0.2", AgentPort: 9100, MediaPort: 9000, Online: true, Version: "1.0.1", BuildID: "20260419.151900", GitSha: "8eaf213"})
- tasks := service.NewTaskService(cfg, nil, reg)
- taskConfig, err := tasks.CreateTask("config_apply", []string{"edge-01", "edge-02"}, map[string]any{"config": map[string]any{}})
- if err != nil {
- t.Fatalf("CreateTask: %v", err)
- }
- taskService, err := tasks.CreateTask("media_restart", []string{"edge-01", "edge-02"}, nil)
- if err != nil {
- t.Fatalf("CreateTask: %v", err)
- }
- taskConfig.Mu.Lock()
- taskConfig.Status = models.TaskSuccess
- for _, did := range taskConfig.DeviceIDs {
- if ds, ok := taskConfig.Devices[did]; ok && ds != nil {
- ds.Status = models.TaskSuccess
- }
- }
- taskConfig.Mu.Unlock()
- taskService.Mu.Lock()
- taskService.Status = models.TaskSuccess
- for _, did := range taskService.DeviceIDs {
- if ds, ok := taskService.Devices[did]; ok && ds != nil {
- ds.Status = models.TaskSuccess
- }
- }
- taskService.Mu.Unlock()
- ui, err := NewUI(nil, reg, nil, tasks, nil)
- if err != nil {
- t.Fatalf("NewUI: %v", err)
- }
-
- rrAudit := httptest.NewRecorder()
- ui.pageAudit(rrAudit, httptest.NewRequest(http.MethodGet, "/ui/audit", nil))
- for _, want := range []string{"系统设置 / 日志审计 / 审计记录", "审计记录", "批量配置", "批量服务", "设备数", "2 台", "下发设备分配", "重启视频分析服务"} {
- if !strings.Contains(rrAudit.Body.String(), want) {
- t.Fatalf("expected audit HTML to contain %q", want)
- }
- }
- for _, forbidden := range []string{"框架版", "后续", "节点执行情况", `disabled`} {
- if strings.Contains(rrAudit.Body.String(), forbidden) {
- t.Fatalf("audit HTML should not contain placeholder marker %q", forbidden)
- }
- }
- for _, forbidden := range []string{"success", "failed", "running"} {
- if strings.Contains(rrAudit.Body.String(), forbidden) {
- t.Fatalf("audit HTML should not leak raw status enum %q", forbidden)
- }
- }
-
- rrTasks := httptest.NewRecorder()
- ui.pageTasks(rrTasks, httptest.NewRequest(http.MethodGet, "/ui/tasks", nil))
- for _, want := range []string{"执行历史", "设备数"} {
- if !strings.Contains(rrTasks.Body.String(), want) {
- t.Fatalf("expected tasks HTML to contain %q", want)
- }
- }
- for _, forbidden := range []string{"节点执行情况", "创建任务", "目标设备
", "高级参数(JSON)", `name="device_ids"`, `name="payload_json"`} {
- if strings.Contains(rrTasks.Body.String(), forbidden) {
- t.Fatalf("tasks HTML should not contain placeholder marker %q", forbidden)
- }
- }
-
- rrTaskConfig := httptest.NewRecorder()
- reqTask := httptest.NewRequest(http.MethodGet, "/tasks/"+taskConfig.ID, nil)
- rctx := chi.NewRouteContext()
- rctx.URLParams.Add("id", taskConfig.ID)
- reqTask = reqTask.WithContext(context.WithValue(reqTask.Context(), chi.RouteCtxKey, rctx))
- ui.pageTask(rrTaskConfig, reqTask)
- for _, want := range []string{"任务概览", "下发设备分配", "返回任务列表"} {
- if !strings.Contains(rrTaskConfig.Body.String(), want) {
- t.Fatalf("expected task detail HTML to contain %q", want)
- }
- }
- for _, forbidden := range []string{"返回操作审计"} {
- if strings.Contains(rrTaskConfig.Body.String(), forbidden) {
- t.Fatalf("task detail HTML should not contain %q", forbidden)
- }
- }
-
- rrSystem := httptest.NewRecorder()
- ui.pageSystem(rrSystem, httptest.NewRequest(http.MethodGet, "/ui/system", nil))
- for _, want := range []string{"服务状态", "数据备份", "数据恢复", "/health", "/openapi.json"} {
- if !strings.Contains(rrSystem.Body.String(), want) {
- t.Fatalf("expected system HTML to contain %q", want)
- }
- }
- for _, forbidden := range []string{"框架版", "后续", "平台状态与访问入口"} {
- if strings.Contains(rrSystem.Body.String(), forbidden) {
- t.Fatalf("system HTML should not contain placeholder marker %q", forbidden)
- }
- }
-}
-
func TestUI_DeviceAssignmentsPageShowsBoard(t *testing.T) {
ui := newTestUI(t)
store, err := storage.OpenSQLite(filepath.Join(t.TempDir(), "assets.db"))