feat(control): 设备列表视频源离线警告

- models.Device 增加视频源统计字段(内存/UI用)
- registry graph 轮询(30s)顺带解析 input_rtsp 在线/离线数
- 设备列表状态列: 视频源 N/M 离线 红色警告 pill(悬停显示明细)
This commit is contained in:
tian 2026-08-02 12:21:38 +08:00
parent c92ad41bd1
commit db7284d2e5
3 changed files with 45 additions and 0 deletions

View File

@ -19,6 +19,10 @@ type Device struct {
LastSeenMs int64 `json:"last_seen_ms"`
Online bool `json:"online"`
Graphs interface{} `json:"graphs,omitempty"` // 摘要或详情
// 视频源状态统计(由 registry graph 轮询更新,仅内存/UI 用)
VideoSourceTotal int `json:"-"`
VideoSourceOnline int `json:"-"`
VideoSourceDown int `json:"-"`
}
type DeviceRegistry struct {

View File

@ -101,6 +101,8 @@ func (s *RegistryService) startGraphPolling() {
if err := json.Unmarshal(data, &graphs); err == nil {
s.mu.Lock()
dev.Graphs = graphs
total, online, down := parseVideoSourceStats(graphs)
dev.VideoSourceTotal, dev.VideoSourceOnline, dev.VideoSourceDown = total, online, down
s.mu.Unlock()
s.persistDevice(dev)
}
@ -109,6 +111,44 @@ func (s *RegistryService) startGraphPolling() {
}
}
// parseVideoSourceStats 从 /v1/graphs 响应统计 input_rtsp 视频源在线/离线数。
func parseVideoSourceStats(graphs any) (total, online, down int) {
arr, ok := graphs.([]any)
if !ok {
return 0, 0, 0
}
for _, g := range arr {
gm, ok := g.(map[string]any)
if !ok {
continue
}
nodes, ok := gm["nodes"].([]any)
if !ok {
continue
}
for _, n := range nodes {
nm, ok := n.(map[string]any)
if !ok {
continue
}
if typ, _ := nm["type"].(string); typ != "input_rtsp" {
continue
}
total++
cm, ok := nm["custom_metrics"].(map[string]any)
if !ok {
continue
}
if streaming, _ := cm["streaming"].(bool); streaming {
online++
} else {
down++
}
}
}
return total, online, down
}
func (s *RegistryService) UpdateDevice(dev *models.Device) {
s.mu.Lock()
defer s.mu.Unlock()

View File

@ -96,6 +96,7 @@
<span class="pill bad">未知</span>
{{end}}
{{if .IntegrationStale}}<span class="pill warn">需重新下发</span>{{end}}
{{if gt .Device.VideoSourceDown 0}}<span class="pill bad" title="{{.Device.VideoSourceDown}}/{{.Device.VideoSourceTotal}} 个视频源离线">视频源 {{.Device.VideoSourceDown}}/{{.Device.VideoSourceTotal}} 离线</span>{{end}}
{{if gt .TimeDriftSec $.TimeSyncThreshold}}<span class="pill bad">⏱ 偏差{{.TimeDriftSec}}秒</span>{{end}}
</div>
</div>