feat: device capabilities from agent /v1/capabilities
Agent 新增 /v1/capabilities 接口,返回设备检测能力列表。 Managerd 控制台查询此接口,仅显示设备真实支持的功能。 - Agent: capabilities.go — 根据插件节点类型映射能力 - Managerd: pageConsole 查询 /v1/capabilities 过滤可选功能 - ConsoleFeature 增加 Description 字段,hover 显示说明 - 旧 agent 无此接口时回退为显示已持久化的功能
This commit is contained in:
parent
286dcaa945
commit
700d964524
@ -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]
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -62,7 +62,7 @@
|
||||
{{range $f := $cd.Features}}
|
||||
<tr>
|
||||
<td class="console-feature-check"><input type="checkbox" name="device_{{$cd.Device.DeviceID}}_feature" value="{{$f.Key}}"{{if $f.Enabled}} checked{{end}}></td>
|
||||
<td class="console-feature-label">{{$f.Label}}</td>
|
||||
<td class="console-feature-label" title="{{$f.Description}}">{{$f.Label}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user