From a0f22230cbaefca27f7adc174c150f37f666bf47 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Tue, 21 Jul 2026 16:55:22 +0800 Subject: [PATCH] fix: console only deploys changed devices; wizard shows display names and pre-checks features --- internal/web/ui.go | 77 +++++++++++++++++++++++++-- internal/web/ui/templates/wizard.html | 8 +-- 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/internal/web/ui.go b/internal/web/ui.go index 83533cb..259d820 100644 --- a/internal/web/ui.go +++ b/internal/web/ui.go @@ -1276,14 +1276,22 @@ func (u *UI) actionConsoleSave(w http.ResponseWriter, r *http.Request) { } } - // Step 2: build requests using form features (user intent) and - // existing recognition unit video sources. + // Step 2: build requests only for devices whose features actually changed. var requests []service.AutoConfigRequest for _, dev := range devices { if dev == nil || dev.DeviceID == "" { continue } features := formFeatures[dev.DeviceID] + if len(features) == 0 { + continue + } + // Skip if features haven't changed from what's already saved. + if saved, err := u.preview.GetDeviceFeatures(dev.DeviceID); err == nil { + if stringSlicesEqual(sortedCopy(features), sortedCopy(saved)) { + continue + } + } var sources []string if assignment, err := u.preview.GetDeviceAssignment(dev.DeviceID); err == nil && assignment != nil { for _, ref := range assignment.RecognitionUnits { @@ -1451,15 +1459,56 @@ func (u *UI) apiWizardDeviceInfo(w http.ResponseWriter, r *http.Request) { } if u.agent != nil && dev.Online { - channels := queryAgentChannels(u.agent, dev) + // Channels: use management DB for friendly names and RTSP URLs + channels := queryDeviceChannelsFromDB(u.preview, deviceID) resp["channels"] = channels caps := queryAgentCapabilities(u.agent, dev) resp["capabilities"] = caps + + // Include currently enabled features from management DB + if u.preview != nil { + if enabled, err := u.preview.GetDeviceFeatures(deviceID); err == nil { + resp["enabled_features"] = enabled + } + } } json.NewEncoder(w).Encode(resp) } +func queryDeviceChannelsFromDB(preview *service.ConfigPreviewService, deviceID string) []map[string]string { + if preview == nil { + return nil + } + assignment, err := preview.GetDeviceAssignment(deviceID) + if err != nil || assignment == nil { + return nil + } + var channels []map[string]string + for _, ref := range assignment.RecognitionUnits { + unit, err := preview.GetRecognitionUnit(ref) + if err != nil || unit == nil { + continue + } + ch := map[string]string{ + "name": unit.Name, + } + if unit.DisplayName != "" { + ch["display"] = unit.DisplayName + } else { + ch["display"] = unit.Name + } + if unit.VideoSourceRef != "" { + src, err := preview.GetVideoSource(unit.VideoSourceRef) + if err == nil && src != nil && src.Config.URL != "" { + ch["rtsp_url"] = src.Config.URL + } + } + channels = append(channels, ch) + } + return channels +} + func queryAgentChannels(agent *service.AgentClient, dev *models.Device) []map[string]string { body, code, err := agent.Do("GET", dev.IP, dev.AgentPort, "/v1/preview/channels", nil) if err != nil || code != 200 { @@ -4488,6 +4537,28 @@ func cleanFormList(values []string) []string { return out } +func sortedCopy(s []string) []string { + if s == nil { + return nil + } + c := make([]string, len(s)) + copy(c, s) + sort.Strings(c) + return c +} + +func stringSlicesEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + func parseDeviceAssignmentBoardState(raw string) (map[string][]string, error) { raw = strings.TrimSpace(raw) if raw == "" { diff --git a/internal/web/ui/templates/wizard.html b/internal/web/ui/templates/wizard.html index 2306ffc..045d06b 100644 --- a/internal/web/ui/templates/wizard.html +++ b/internal/web/ui/templates/wizard.html @@ -200,11 +200,14 @@ .then(function(r){return r.json()}) .then(function(info){ var channels = info.channels || []; - step2Assigned = channels.map(function(ch){return {name: ch.name, url: ch.hls_url || ""}}); + step2Assigned = channels.map(function(ch){return {name: ch.display || ch.name, url: ch.rtsp_url || ""}}); renderAssignedSources(); step2Features = []; var caps = (info.capabilities || []).filter(function(c){return c.available}); + // Pre-check features already enabled on this device + var enabled = info.enabled_features || []; + caps.forEach(function(c){ if (enabled.indexOf(c.key) >= 0) step2Features.push(c.key); }); document.getElementById("feature-placeholder").style.display = "none"; renderFeatures(caps); }) @@ -360,8 +363,7 @@ } else { resultDiv.innerHTML = '
'; + '