diff --git a/internal/service/auto_config.go b/internal/service/auto_config.go index 73bfff1..df16301 100644 --- a/internal/service/auto_config.go +++ b/internal/service/auto_config.go @@ -19,6 +19,9 @@ type FeatureDef struct { // FeatureRegistry centralises which templates cover which detection features. type FeatureRegistry struct { features map[string]*FeatureDef + // nodeTypeRequirements maps feature key → required media-server node types. + // If a device lacks any required node type, the feature is unavailable. + nodeTypeRequirements map[string][]string } // NewFeatureRegistry creates a registry with the built-in feature definitions. @@ -50,7 +53,16 @@ func NewFeatureRegistry() *FeatureRegistry { Templates: []string{}, // 模板就绪后再补充 }, } - r := &FeatureRegistry{features: make(map[string]*FeatureDef, len(defs))} + r := &FeatureRegistry{ + features: make(map[string]*FeatureDef, len(defs)), + nodeTypeRequirements: map[string][]string{ + "face": {"ai_face_recog"}, // face recognition plugin + "shoe": {"ai_shoe_det"}, // shoe detection plugin + "helmet": {"ai_yolo"}, // YOLO for helmet (model pending) + "smoking": {"action_recog"}, // action recognition for smoking + "intrusion": {"region_event"}, // region-based intrusion + }, + } for i := range defs { r.features[defs[i].Key] = &defs[i] } @@ -68,6 +80,41 @@ func (r *FeatureRegistry) FeatureKeys() []string { } // Feature returns the definition for a feature key, or nil. +// AvailableFeatures returns the subset of registered features that are supported +// by the given device node types (from /v1/graph-node-types). +// If nodeTypes is nil/empty, all features are considered available (fallback for +// old agents that don't expose node types). +func (r *FeatureRegistry) AvailableFeatures(nodeTypes []string) []FeatureDef { + all := r.AllFeatures() + if len(nodeTypes) == 0 { + return all // no info → assume all available + } + typeSet := make(map[string]bool, len(nodeTypes)) + for _, t := range nodeTypes { + typeSet[t] = true + } + out := make([]FeatureDef, 0, len(all)) + for _, f := range all { + if r.featureAvailable(f.Key, typeSet) { + out = append(out, f) + } + } + return out +} + +func (r *FeatureRegistry) featureAvailable(key string, nodeTypes map[string]bool) bool { + required, ok := r.nodeTypeRequirements[key] + if !ok || len(required) == 0 { + return true // no specific plugin requirement → always available + } + for _, t := range required { + if !nodeTypes[t] { + return false + } + } + return true +} + func (r *FeatureRegistry) Feature(key string) *FeatureDef { return r.features[key] } diff --git a/internal/web/ui.go b/internal/web/ui.go index 9b8a8a8..32388f1 100644 --- a/internal/web/ui.go +++ b/internal/web/ui.go @@ -909,13 +909,36 @@ func (u *UI) pageConsole(w http.ResponseWriter, r *http.Request) { sourceNames[s.Name] = s.Name } - // Feature definitions - featureDefs := []ConsoleFeature{ - {Key: "face", Label: "人脸识别"}, - {Key: "shoe", Label: "劳保鞋检测"}, - {Key: "helmet", Label: "安全帽检测"}, - {Key: "smoking", Label: "抽烟检测"}, - {Key: "intrusion", Label: "区域入侵"}, + // Query each device's capabilities from agent to filter available features. + deviceAvailableFeatures := map[string][]ConsoleFeature{} + if u.agent != nil { + for _, dev := range devices { + if dev == nil || !dev.Online || dev.DeviceID == "" { + continue + } + body, code, err := u.agent.Do("GET", dev.IP, dev.AgentPort, "/v1/capabilities", nil) + if err != nil || code != 200 { + continue + } + var capsResp struct { + Capabilities []struct { + Key string `json:"key"` + Label string `json:"label"` + Available bool `json:"available"` + Description string `json:"description"` + } `json:"capabilities"` + } + if json.Unmarshal(body, &capsResp) != nil { + continue + } + features := make([]ConsoleFeature, 0, len(capsResp.Capabilities)) + for _, c := range capsResp.Capabilities { + if c.Available { + features = append(features, ConsoleFeature{Key: c.Key, Label: c.Label, Description: c.Description, Enabled: false}) + } + } + deviceAvailableFeatures[dev.DeviceID] = features + } } // Load persisted device features as the source of truth. @@ -1015,10 +1038,20 @@ func (u *UI) pageConsole(w http.ResponseWriter, r *http.Request) { activeKeys = map[string]bool{} } + // Features from device capabilities (filtered by agent /v1/capabilities). + // Fall back to persisted keys if agent doesn't respond. + available := deviceAvailableFeatures[dev.DeviceID] var features []ConsoleFeature - for _, fd := range featureDefs { - f := ConsoleFeature{Key: fd.Key, Label: fd.Label, Enabled: activeKeys[fd.Key]} - features = append(features, f) + if len(available) > 0 { + for _, f := range available { + f.Enabled = activeKeys[f.Key] + features = append(features, f) + } + } else { + // Old agent: use persisted keys to show at least what's configured. + for key := range activeKeys { + features = append(features, ConsoleFeature{Key: key, Label: key, Enabled: true}) + } } npu, diskUsage, temp, cpuUsage, memUsage := 0.0, 0.0, 0.0, 0.0, 0.0 @@ -4695,7 +4728,8 @@ type ConsoleChannel struct { } type ConsoleFeature struct { - Key string - Label string - Enabled bool + Key string + Label string + Description string + Enabled bool } diff --git a/internal/web/ui/templates/console.html b/internal/web/ui/templates/console.html index 896daf0..231e124 100644 --- a/internal/web/ui/templates/console.html +++ b/internal/web/ui/templates/console.html @@ -62,7 +62,7 @@ {{range $f := $cd.Features}} - {{$f.Label}} + {{$f.Label}} {{end}}