fix: console only deploys changed devices; wizard shows display names and pre-checks features
This commit is contained in:
parent
c57ca0b5a0
commit
a0f22230cb
@ -1276,14 +1276,22 @@ func (u *UI) actionConsoleSave(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: build requests using form features (user intent) and
|
// Step 2: build requests only for devices whose features actually changed.
|
||||||
// existing recognition unit video sources.
|
|
||||||
var requests []service.AutoConfigRequest
|
var requests []service.AutoConfigRequest
|
||||||
for _, dev := range devices {
|
for _, dev := range devices {
|
||||||
if dev == nil || dev.DeviceID == "" {
|
if dev == nil || dev.DeviceID == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
features := formFeatures[dev.DeviceID]
|
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
|
var sources []string
|
||||||
if assignment, err := u.preview.GetDeviceAssignment(dev.DeviceID); err == nil && assignment != nil {
|
if assignment, err := u.preview.GetDeviceAssignment(dev.DeviceID); err == nil && assignment != nil {
|
||||||
for _, ref := range assignment.RecognitionUnits {
|
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 {
|
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
|
resp["channels"] = channels
|
||||||
|
|
||||||
caps := queryAgentCapabilities(u.agent, dev)
|
caps := queryAgentCapabilities(u.agent, dev)
|
||||||
resp["capabilities"] = caps
|
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)
|
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 {
|
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)
|
body, code, err := agent.Do("GET", dev.IP, dev.AgentPort, "/v1/preview/channels", nil)
|
||||||
if err != nil || code != 200 {
|
if err != nil || code != 200 {
|
||||||
@ -4488,6 +4537,28 @@ func cleanFormList(values []string) []string {
|
|||||||
return out
|
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) {
|
func parseDeviceAssignmentBoardState(raw string) (map[string][]string, error) {
|
||||||
raw = strings.TrimSpace(raw)
|
raw = strings.TrimSpace(raw)
|
||||||
if raw == "" {
|
if raw == "" {
|
||||||
|
|||||||
@ -200,11 +200,14 @@
|
|||||||
.then(function(r){return r.json()})
|
.then(function(r){return r.json()})
|
||||||
.then(function(info){
|
.then(function(info){
|
||||||
var channels = info.channels || [];
|
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();
|
renderAssignedSources();
|
||||||
|
|
||||||
step2Features = [];
|
step2Features = [];
|
||||||
var caps = (info.capabilities || []).filter(function(c){return c.available});
|
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";
|
document.getElementById("feature-placeholder").style.display = "none";
|
||||||
renderFeatures(caps);
|
renderFeatures(caps);
|
||||||
})
|
})
|
||||||
@ -360,8 +363,7 @@
|
|||||||
} else {
|
} else {
|
||||||
resultDiv.innerHTML = '<div class="card" style="border-left:3px solid var(--green)"><div class="metric-label">'+
|
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>'+
|
'<span class="status-dot ok"></span><strong>部署成功</strong></div>'+
|
||||||
'<div class="muted small" style="margin-top:6px">模板:'+result.template_name+' | 检测通道:'+result.units_created+' 个</div>'+
|
'<div class="muted small" style="margin-top:6px">模板:'+result.template_name+' | 检测通道:'+result.units_created+' 个</div></div>';
|
||||||
'<a href="/alarms" class="btn primary" style="margin-top:8px;font-size:11px">查看告警</a></div>';
|
|
||||||
btn.style.display = "none";
|
btn.style.display = "none";
|
||||||
currentStep = 3;
|
currentStep = 3;
|
||||||
document.querySelectorAll(".wizard-step").forEach(function(el){el.classList.add("done")});
|
document.querySelectorAll(".wizard-step").forEach(function(el){el.classList.add("done")});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user