diff --git a/internal/web/ui.go b/internal/web/ui.go index 759b9d8..00685dd 100644 --- a/internal/web/ui.go +++ b/internal/web/ui.go @@ -49,6 +49,12 @@ type PageData struct { ConfigStatusErr string ConfigSources service.ConfigPreviewSources ConfigPreview *service.ConfigPreviewResult + ResultTitle string + SelectedTemplate string + SelectedProfile string + SelectedOverlays []string + SelectedConfigID string + SelectedVersion string Tasks []models.Task Task *models.Task Templates []service.Template @@ -107,6 +113,14 @@ func NewUI(discovery *service.DiscoveryService, registry *service.RegistryServic b, _ := json.MarshalIndent(v, "", " ") return string(b) }, + "hasString": func(items []string, want string) bool { + for _, item := range items { + if item == want { + return true + } + } + return false + }, "ago": func(ms int64) string { if ms <= 0 { return "-" @@ -873,6 +887,11 @@ func (u *UI) actionDeviceConfigPreview(w http.ResponseWriter, r *http.Request) { preview, err := u.preview.Render(req) data := u.configPreviewPageData(dev) data.ConfigPreview = preview + data.SelectedTemplate = req.Template + data.SelectedProfile = req.Profile + data.SelectedOverlays = append([]string(nil), req.Overlays...) + data.SelectedConfigID = req.ConfigID + data.SelectedVersion = req.ConfigVersion if err != nil { data.Error = err.Error() } @@ -900,9 +919,11 @@ func (u *UI) actionDeviceConfigCandidate(w http.ResponseWriter, r *http.Request) return } data.ConfigPreview = previewResultFromJSON(raw) + populateSelectionsFromPreview(&data) body, code, err := u.agent.Do("PUT", dev.IP, dev.AgentPort, "/v1/config/candidate", []byte(raw)) data.Message = fmt.Sprintf("PUT /v1/config/candidate -> %d", code) data.RawText = prettyJSON(body) + data.ResultTitle = "候选配置结果" if err != nil { data.Error = err.Error() } @@ -920,10 +941,12 @@ func (u *UI) actionDeviceConfigCandidateApply(w http.ResponseWriter, r *http.Req raw := strings.TrimSpace(r.FormValue("json")) if raw != "" { data.ConfigPreview = previewResultFromJSON(raw) + populateSelectionsFromPreview(&data) } 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) + data.ResultTitle = "应用候选配置结果" if err != nil { data.Error = err.Error() } @@ -932,7 +955,15 @@ func (u *UI) actionDeviceConfigCandidateApply(w http.ResponseWriter, r *http.Req func (u *UI) configPreviewPageData(dev *models.Device) PageData { sources, err := u.preview.ListSources() - data := PageData{Title: "配置预览", Device: dev, ConfigSources: sources} + data := PageData{ + Title: "配置预览", + Device: dev, + ConfigSources: sources, + SelectedTemplate: "workshop_face_shoe_alarm", + SelectedProfile: "local_3588_test", + SelectedOverlays: []string{"face_debug"}, + SelectedConfigID: "preview_" + dev.DeviceID, + } if err != nil { data.Error = err.Error() } @@ -972,6 +1003,37 @@ func previewResultFromJSON(raw string) *service.ConfigPreviewResult { } } +func populateSelectionsFromPreview(data *PageData) { + if data == nil || data.ConfigPreview == nil { + return + } + if metadata := data.ConfigPreview.Metadata; metadata != nil { + if v, _ := metadata["template"].(string); strings.TrimSpace(v) != "" { + data.SelectedTemplate = v + } + if v, _ := metadata["profile"].(string); strings.TrimSpace(v) != "" { + data.SelectedProfile = v + } + if v, _ := metadata["config_id"].(string); strings.TrimSpace(v) != "" { + data.SelectedConfigID = v + } + if v, _ := metadata["config_version"].(string); strings.TrimSpace(v) != "" { + data.SelectedVersion = v + } + if items, ok := metadata["overlays"].([]any); ok { + overlays := make([]string, 0, len(items)) + for _, item := range items { + if s, ok := item.(string); ok && strings.TrimSpace(s) != "" { + overlays = append(overlays, s) + } + } + if len(overlays) > 0 { + data.SelectedOverlays = overlays + } + } + } +} + func (u *UI) actionDeviceConfigUIPlan(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") dev, ok := u.findDevice(id) diff --git a/internal/web/ui/templates/config_preview.html b/internal/web/ui/templates/config_preview.html index 97b8daf..3bba19d 100644 --- a/internal/web/ui/templates/config_preview.html +++ b/internal/web/ui/templates/config_preview.html @@ -19,7 +19,7 @@
模板
@@ -27,17 +27,17 @@
Profile
config_id
- +
config_version
- +
@@ -45,7 +45,7 @@
{{range .ConfigSources.Overlays}} {{end}} @@ -55,9 +55,7 @@ {{if and .ConfigStatus .ConfigStatus.Candidate .ConfigStatus.Candidate.Exists}} -
- -
+ {{else}} {{end}} @@ -90,13 +88,23 @@ 查看当前运行配置 -
{{.ConfigPreview.JSON}}
+ {{if .RawText}} +
+ 展开完整 JSON +
{{.ConfigPreview.JSON}}
+
+ {{else}} +
+ 展开完整 JSON +
{{.ConfigPreview.JSON}}
+
+ {{end}}
{{end}} {{if .RawText}}
-

上传结果

+

{{if .ResultTitle}}{{.ResultTitle}}{{else}}执行结果{{end}}

{{.RawText}}
{{end}} diff --git a/internal/web/ui_test.go b/internal/web/ui_test.go index b8bb755..82373bc 100644 --- a/internal/web/ui_test.go +++ b/internal/web/ui_test.go @@ -364,12 +364,13 @@ func TestUI_ConfigPreviewPageKeepsApplyActionAfterUploadResult(t *testing.T) { }, Size: 123, }, - RawText: `{"ok":true}`, + RawText: `{"ok":true}`, + ResultTitle: "应用候选配置结果", }) body := rr.Body.String() for _, want := range []string{ - "上传结果", + "应用候选配置结果", "上传为候选配置", "应用候选配置", `formaction="/ui/devices/edge-01/config-candidate/apply"`, @@ -428,7 +429,7 @@ func TestUI_ActionDeviceConfigCandidateKeepsPreviewApplyAction(t *testing.T) { body := rr.Body.String() for _, want := range []string{ - "上传结果", + "候选配置结果", "应用候选配置", `formaction="/ui/devices/edge-01/config-candidate/apply"`, } { @@ -438,6 +439,72 @@ func TestUI_ActionDeviceConfigCandidateKeepsPreviewApplyAction(t *testing.T) { } } +func TestUI_ConfigPreviewKeepsSelectedOverlayAfterPreview(t *testing.T) { + ui := newTestUI(t) + req := httptest.NewRequest(http.MethodGet, "/ui/devices/edge-01/config-preview", nil) + rr := httptest.NewRecorder() + + ui.render(rr, req, "config_preview", PageData{ + Title: "配置预览", + Device: &models.Device{DeviceID: "edge-01", DeviceName: "入口识别节点", IP: "127.0.0.1", AgentPort: 9100}, + ConfigSources: service.ConfigPreviewSources{Templates: []service.ConfigSource{{Name: "workshop_face_shoe_alarm"}}, Profiles: []service.ConfigSource{{Name: "local_3588_test"}}, Overlays: []service.ConfigSource{{Name: "face_debug"}, {Name: "face_test_sensitive"}}}, + SelectedTemplate: "workshop_face_shoe_alarm", + SelectedProfile: "local_3588_test", + SelectedOverlays: []string{"face_test_sensitive"}, + SelectedConfigID: "preview_edge-01", + ConfigPreview: &service.ConfigPreviewResult{ + JSON: `{"templates":{"tpl":{"nodes":[],"edges":[]}},"instances":[],"metadata":{"config_id":"preview_edge-01","config_version":"v1","template":"workshop_face_shoe_alarm","profile":"local_3588_test","overlays":["face_test_sensitive"]}}`, + Metadata: map[string]any{ + "config_id": "preview_edge-01", + "config_version": "v1", + "template": "workshop_face_shoe_alarm", + "profile": "local_3588_test", + "overlays": []any{"face_test_sensitive"}, + }, + Size: 123, + }, + }) + + body := rr.Body.String() + if !strings.Contains(body, `value="face_test_sensitive" checked`) { + t.Fatalf("expected selected overlay to remain checked, got:\n%s", body) + } + if strings.Contains(body, `value="face_debug" checked`) { + t.Fatalf("did not expect default overlay to stay checked, got:\n%s", body) + } +} + +func TestUI_ConfigPreviewCollapsesJSONAfterActionResult(t *testing.T) { + ui := newTestUI(t) + req := httptest.NewRequest(http.MethodGet, "/ui/devices/edge-01/config-preview", nil) + rr := httptest.NewRecorder() + + ui.render(rr, req, "config_preview", PageData{ + Title: "配置预览", + Device: &models.Device{DeviceID: "edge-01", DeviceName: "入口识别节点", IP: "127.0.0.1", AgentPort: 9100}, + ConfigSources: service.ConfigPreviewSources{Templates: []service.ConfigSource{{Name: "workshop_face_shoe_alarm"}}, Profiles: []service.ConfigSource{{Name: "local_3588_test"}}, Overlays: []service.ConfigSource{{Name: "face_debug"}}}, + SelectedTemplate: "workshop_face_shoe_alarm", + SelectedProfile: "local_3588_test", + SelectedOverlays: []string{"face_debug"}, + SelectedConfigID: "preview_edge-01", + ConfigPreview: &service.ConfigPreviewResult{ + JSON: `{"templates":{"tpl":{"nodes":[],"edges":[]}},"instances":[]}`, + Metadata: map[string]any{"config_id": "preview_edge-01"}, + Size: 64, + }, + ResultTitle: "候选配置结果", + RawText: `{"ok":true}`, + }) + + body := rr.Body.String() + if !strings.Contains(body, "") { + t.Fatalf("expected json panel to be collapsed after action result, got:\n%s", body) + } +} + func TestUI_ModelDeploymentPageRendersDeviceActions(t *testing.T) { ui := newTestUI(t) req := httptest.NewRequest(http.MethodGet, "/ui/models", nil)