fix: console only deploys changed devices; wizard shows display names and pre-checks features

This commit is contained in:
tian 2026-07-21 16:55:22 +08:00
parent c57ca0b5a0
commit a0f22230cb
2 changed files with 79 additions and 6 deletions

View File

@ -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 == "" {

View File

@ -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 = '<div class="card" style="border-left:3px solid var(--green)"><div class="metric-label">'+
'<span class="status-dot ok"></span><strong>部署成功</strong></div>'+
'<div class="muted small" style="margin-top:6px">模板:'+result.template_name+' | 检测通道:'+result.units_created+' 个</div>'+
'<a href="/alarms" class="btn primary" style="margin-top:8px;font-size:11px">查看告警</a></div>';
'<div class="muted small" style="margin-top:6px">模板:'+result.template_name+' | 检测通道:'+result.units_created+' 个</div></div>';
btn.style.display = "none";
currentStep = 3;
document.querySelectorAll(".wizard-step").forEach(function(el){el.classList.add("done")});