修复bugagent
This commit is contained in:
parent
d83f2eb749
commit
ce8ae2b448
@ -71,6 +71,28 @@ func (s *Server) handleConfigUIState(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
root, _ := readUIRootConfig(s.agentCfg.ConfigPath)
|
root, _ := readUIRootConfig(s.agentCfg.ConfigPath)
|
||||||
|
|
||||||
|
mode := "instances"
|
||||||
|
warnings := []string{}
|
||||||
|
if st, err := os.Stat(s.agentCfg.ConfigPath); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
warnings = append(warnings, "config file not found: "+s.agentCfg.ConfigPath)
|
||||||
|
} else {
|
||||||
|
warnings = append(warnings, "config stat failed: "+err.Error())
|
||||||
|
}
|
||||||
|
} else if st.IsDir() {
|
||||||
|
warnings = append(warnings, "config path is a directory: "+s.agentCfg.ConfigPath)
|
||||||
|
}
|
||||||
|
instances := root.Instances
|
||||||
|
// If current config is graphs-only, do a best-effort import so GUI can edit.
|
||||||
|
if len(instances) == 0 && len(root.Graphs) > 0 {
|
||||||
|
mode = "graphs_imported"
|
||||||
|
inst, warn := inferInstancesFromGraphs(root.Graphs)
|
||||||
|
if len(inst) > 0 {
|
||||||
|
instances = inst
|
||||||
|
}
|
||||||
|
warnings = append(warnings, warn...)
|
||||||
|
}
|
||||||
|
|
||||||
// Force templates/instances-only view.
|
// Force templates/instances-only view.
|
||||||
// templates = union(current templates, built-in templates)
|
// templates = union(current templates, built-in templates)
|
||||||
tplKeys := map[string]bool{}
|
tplKeys := map[string]bool{}
|
||||||
@ -90,12 +112,412 @@ func (s *Server) handleConfigUIState(w http.ResponseWriter, r *http.Request) {
|
|||||||
"ok": true,
|
"ok": true,
|
||||||
"global": root.Global,
|
"global": root.Global,
|
||||||
"queue": root.Queue,
|
"queue": root.Queue,
|
||||||
"instances": root.Instances,
|
"instances": instances,
|
||||||
"templates": mergedTpls,
|
"templates": mergedTpls,
|
||||||
|
"mode": mode,
|
||||||
|
"warnings": warnings,
|
||||||
}
|
}
|
||||||
writeJSON(w, http.StatusOK, resp)
|
writeJSON(w, http.StatusOK, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func inferInstancesFromGraphs(graphs []map[string]any) ([]UIInstance, []string) {
|
||||||
|
instances := []UIInstance{}
|
||||||
|
warnings := []string{}
|
||||||
|
used := map[string]int{}
|
||||||
|
|
||||||
|
for _, g := range graphs {
|
||||||
|
gname := asString(g["name"])
|
||||||
|
nodes := asSlice(g["nodes"])
|
||||||
|
if len(nodes) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try templates in priority order.
|
||||||
|
if inst, ok := inferFaceDetRecog(gname, nodes); ok {
|
||||||
|
inst.Name = uniquify(inst.Name, used)
|
||||||
|
instances = append(instances, inst)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if inst, ok := inferFaceDet(gname, nodes); ok {
|
||||||
|
inst.Name = uniquify(inst.Name, used)
|
||||||
|
instances = append(instances, inst)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if inst, ok := inferYoloAlarmMinio(gname, nodes); ok {
|
||||||
|
inst.Name = uniquify(inst.Name, used)
|
||||||
|
instances = append(instances, inst)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if inst, ok := inferYolo(gname, nodes); ok {
|
||||||
|
inst.Name = uniquify(inst.Name, used)
|
||||||
|
instances = append(instances, inst)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if inst, ok := inferTranscode(gname, nodes); ok {
|
||||||
|
inst.Name = uniquify(inst.Name, used)
|
||||||
|
instances = append(instances, inst)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if gname == "" {
|
||||||
|
gname = "noname"
|
||||||
|
}
|
||||||
|
warnings = append(warnings, "graph not recognized for templates/instances import: "+gname)
|
||||||
|
}
|
||||||
|
|
||||||
|
return instances, warnings
|
||||||
|
}
|
||||||
|
|
||||||
|
func inferTranscode(graphName string, nodes []any) (UIInstance, bool) {
|
||||||
|
in := findNodeByType(nodes, "input_rtsp")
|
||||||
|
pub := findNodeByType(nodes, "publish")
|
||||||
|
if in == nil || pub == nil {
|
||||||
|
return UIInstance{}, false
|
||||||
|
}
|
||||||
|
if findNodeByType(nodes, "ai_yolo") != nil || findNodeByType(nodes, "ai_face_det") != nil || findNodeByType(nodes, "ai_face_recog") != nil {
|
||||||
|
return UIInstance{}, false
|
||||||
|
}
|
||||||
|
name := deriveNameFromPublish(pub, graphName)
|
||||||
|
params := map[string]any{}
|
||||||
|
params["name"] = name
|
||||||
|
if url := asString(in["url"]); url != "" {
|
||||||
|
params["url"] = url
|
||||||
|
} else {
|
||||||
|
return UIInstance{}, false
|
||||||
|
}
|
||||||
|
params["fps"] = asIntOr(in["fps"], asIntOr(pub["fps"], 25))
|
||||||
|
params["src_w"] = asIntOr(in["width"], 1920)
|
||||||
|
params["src_h"] = asIntOr(in["height"], 1080)
|
||||||
|
params["gop"] = asIntOr(pub["gop"], 50)
|
||||||
|
params["bitrate_kbps"] = asIntOr(pub["bitrate_kbps"], 2000)
|
||||||
|
applyPublishOutputsToParams(pub, params)
|
||||||
|
return UIInstance{Name: name, Template: "transcode_rtsp_hls", Params: params}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func inferYolo(graphName string, nodes []any) (UIInstance, bool) {
|
||||||
|
in := findNodeByType(nodes, "input_rtsp")
|
||||||
|
ai := findNodeByType(nodes, "ai_yolo")
|
||||||
|
pub := findNodeByType(nodes, "publish")
|
||||||
|
if in == nil || ai == nil || pub == nil {
|
||||||
|
return UIInstance{}, false
|
||||||
|
}
|
||||||
|
if findNodeByType(nodes, "alarm") != nil {
|
||||||
|
return UIInstance{}, false
|
||||||
|
}
|
||||||
|
name := deriveNameFromPublish(pub, graphName)
|
||||||
|
params := map[string]any{}
|
||||||
|
params["name"] = name
|
||||||
|
url := asString(in["url"])
|
||||||
|
model := asString(ai["model_path"])
|
||||||
|
if url == "" || model == "" {
|
||||||
|
return UIInstance{}, false
|
||||||
|
}
|
||||||
|
params["url"] = url
|
||||||
|
params["model_path"] = model
|
||||||
|
params["fps"] = asIntOr(in["fps"], asIntOr(pub["fps"], 25))
|
||||||
|
params["src_w"] = asIntOr(in["width"], 1920)
|
||||||
|
params["src_h"] = asIntOr(in["height"], 1080)
|
||||||
|
params["gop"] = asIntOr(pub["gop"], 50)
|
||||||
|
params["bitrate_kbps"] = asIntOr(pub["bitrate_kbps"], 2000)
|
||||||
|
applyPublishOutputsToParams(pub, params)
|
||||||
|
return UIInstance{Name: name, Template: "yolo_rtsp_hls", Params: params}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func inferYoloAlarmMinio(graphName string, nodes []any) (UIInstance, bool) {
|
||||||
|
in := findNodeByType(nodes, "input_rtsp")
|
||||||
|
ai := findNodeByType(nodes, "ai_yolo")
|
||||||
|
alarm := findNodeByType(nodes, "alarm")
|
||||||
|
pub := findNodeByType(nodes, "publish")
|
||||||
|
if in == nil || ai == nil || alarm == nil || pub == nil {
|
||||||
|
return UIInstance{}, false
|
||||||
|
}
|
||||||
|
name := deriveNameFromPublish(pub, graphName)
|
||||||
|
params := map[string]any{}
|
||||||
|
params["name"] = name
|
||||||
|
url := asString(in["url"])
|
||||||
|
model := asString(ai["model_path"])
|
||||||
|
if url == "" || model == "" {
|
||||||
|
return UIInstance{}, false
|
||||||
|
}
|
||||||
|
params["url"] = url
|
||||||
|
params["model_path"] = model
|
||||||
|
params["fps"] = asIntOr(in["fps"], asIntOr(pub["fps"], 25))
|
||||||
|
params["src_w"] = asIntOr(in["width"], 1920)
|
||||||
|
params["src_h"] = asIntOr(in["height"], 1080)
|
||||||
|
params["gop"] = asIntOr(pub["gop"], 50)
|
||||||
|
params["bitrate_kbps"] = asIntOr(pub["bitrate_kbps"], 2000)
|
||||||
|
applyPublishOutputsToParams(pub, params)
|
||||||
|
|
||||||
|
// cooldown
|
||||||
|
if rules := asSlice(alarm["rules"]); len(rules) > 0 {
|
||||||
|
if r0 := asMap(rules[0]); r0 != nil {
|
||||||
|
params["cooldown_ms"] = asIntOr(r0["cooldown_ms"], 3000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// minio from snapshot/clip upload
|
||||||
|
actions := asMap(alarm["actions"])
|
||||||
|
upl := firstMinioUpload(actions)
|
||||||
|
if upl == nil {
|
||||||
|
return UIInstance{}, false
|
||||||
|
}
|
||||||
|
ep := asString(upl["endpoint"])
|
||||||
|
bucket := asString(upl["bucket"])
|
||||||
|
ak := asString(upl["access_key"])
|
||||||
|
sk := asString(upl["secret_key"])
|
||||||
|
if ep == "" || bucket == "" || ak == "" || sk == "" {
|
||||||
|
return UIInstance{}, false
|
||||||
|
}
|
||||||
|
params["minio_endpoint"] = ep
|
||||||
|
params["minio_bucket"] = bucket
|
||||||
|
params["minio_ak"] = ak
|
||||||
|
params["minio_sk"] = sk
|
||||||
|
if region := asString(upl["region"]); region != "" {
|
||||||
|
params["minio_region"] = region
|
||||||
|
} else {
|
||||||
|
params["minio_region"] = "us-east-1"
|
||||||
|
}
|
||||||
|
|
||||||
|
return UIInstance{Name: name, Template: "yolo_alarm_minio", Params: params}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func inferFaceDet(graphName string, nodes []any) (UIInstance, bool) {
|
||||||
|
in := findNodeByType(nodes, "input_rtsp")
|
||||||
|
det := findNodeByType(nodes, "ai_face_det")
|
||||||
|
pub := findNodeByType(nodes, "publish")
|
||||||
|
if in == nil || det == nil || pub == nil {
|
||||||
|
return UIInstance{}, false
|
||||||
|
}
|
||||||
|
if findNodeByType(nodes, "ai_face_recog") != nil {
|
||||||
|
return UIInstance{}, false
|
||||||
|
}
|
||||||
|
name := deriveNameFromPublish(pub, graphName)
|
||||||
|
params := map[string]any{}
|
||||||
|
params["name"] = name
|
||||||
|
url := asString(in["url"])
|
||||||
|
model := asString(det["model_path"])
|
||||||
|
if url == "" || model == "" {
|
||||||
|
return UIInstance{}, false
|
||||||
|
}
|
||||||
|
params["url"] = url
|
||||||
|
params["det_model_path"] = model
|
||||||
|
params["fps"] = asIntOr(in["fps"], asIntOr(pub["fps"], 25))
|
||||||
|
params["src_w"] = asIntOr(in["width"], 1920)
|
||||||
|
params["src_h"] = asIntOr(in["height"], 1080)
|
||||||
|
params["gop"] = asIntOr(pub["gop"], 50)
|
||||||
|
params["bitrate_kbps"] = asIntOr(pub["bitrate_kbps"], 2000)
|
||||||
|
applyPublishOutputsToParams(pub, params)
|
||||||
|
return UIInstance{Name: name, Template: "face_det_rtsp_hls", Params: params}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func inferFaceDetRecog(graphName string, nodes []any) (UIInstance, bool) {
|
||||||
|
in := findNodeByType(nodes, "input_rtsp")
|
||||||
|
det := findNodeByType(nodes, "ai_face_det")
|
||||||
|
rec := findNodeByType(nodes, "ai_face_recog")
|
||||||
|
pub := findNodeByType(nodes, "publish")
|
||||||
|
if in == nil || det == nil || rec == nil || pub == nil {
|
||||||
|
return UIInstance{}, false
|
||||||
|
}
|
||||||
|
name := deriveNameFromPublish(pub, graphName)
|
||||||
|
params := map[string]any{}
|
||||||
|
params["name"] = name
|
||||||
|
url := asString(in["url"])
|
||||||
|
detModel := asString(det["model_path"])
|
||||||
|
recModel := asString(rec["model_path"])
|
||||||
|
if url == "" || detModel == "" || recModel == "" {
|
||||||
|
return UIInstance{}, false
|
||||||
|
}
|
||||||
|
params["url"] = url
|
||||||
|
params["det_model_path"] = detModel
|
||||||
|
params["recog_model_path"] = recModel
|
||||||
|
params["fps"] = asIntOr(in["fps"], asIntOr(pub["fps"], 25))
|
||||||
|
params["src_w"] = asIntOr(in["width"], 1920)
|
||||||
|
params["src_h"] = asIntOr(in["height"], 1080)
|
||||||
|
params["gop"] = asIntOr(pub["gop"], 50)
|
||||||
|
params["bitrate_kbps"] = asIntOr(pub["bitrate_kbps"], 2000)
|
||||||
|
applyPublishOutputsToParams(pub, params)
|
||||||
|
|
||||||
|
if th := asMap(rec["threshold"]); th != nil {
|
||||||
|
params["thr_accept"] = asFloatOr(th["accept"], 0.45)
|
||||||
|
params["thr_margin"] = asFloatOr(th["margin"], 0.05)
|
||||||
|
} else {
|
||||||
|
params["thr_accept"] = 0.45
|
||||||
|
params["thr_margin"] = 0.05
|
||||||
|
}
|
||||||
|
if gal := asMap(rec["gallery"]); gal != nil {
|
||||||
|
if p := asString(gal["path"]); p != "" {
|
||||||
|
params["gallery_path"] = p
|
||||||
|
} else {
|
||||||
|
params["gallery_path"] = "./models/face_gallery.db"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
params["gallery_path"] = "./models/face_gallery.db"
|
||||||
|
}
|
||||||
|
|
||||||
|
return UIInstance{Name: name, Template: "face_det_recog_rtsp_hls", Params: params}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func findNodeByType(nodes []any, typ string) map[string]any {
|
||||||
|
for _, n := range nodes {
|
||||||
|
m := asMap(n)
|
||||||
|
if m == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if asString(m["type"]) == typ {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deriveNameFromPublish(pub map[string]any, fallback string) string {
|
||||||
|
outs := asSlice(pub["outputs"])
|
||||||
|
for _, o := range outs {
|
||||||
|
om := asMap(o)
|
||||||
|
if om == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if asString(om["proto"]) != "rtsp_server" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p := asString(om["path"])
|
||||||
|
if strings.HasPrefix(p, "/live/") {
|
||||||
|
n := strings.TrimPrefix(p, "/live/")
|
||||||
|
n = strings.Trim(n, "/")
|
||||||
|
if n != "" {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(fallback) != "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return "cam"
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyPublishOutputsToParams(pub map[string]any, params map[string]any) {
|
||||||
|
outs := asSlice(pub["outputs"])
|
||||||
|
for _, o := range outs {
|
||||||
|
om := asMap(o)
|
||||||
|
if om == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
proto := asString(om["proto"])
|
||||||
|
if proto == "rtsp_server" {
|
||||||
|
params["rtsp_port"] = asIntOr(om["port"], asIntOr(params["rtsp_port"], 8555))
|
||||||
|
}
|
||||||
|
if proto == "hls" {
|
||||||
|
if p := asString(om["path"]); p != "" {
|
||||||
|
params["hls_path"] = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstMinioUpload(actions map[string]any) map[string]any {
|
||||||
|
if actions == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Prefer snapshot.upload.
|
||||||
|
if snap := asMap(actions["snapshot"]); snap != nil {
|
||||||
|
if up := asMap(snap["upload"]); up != nil {
|
||||||
|
if asString(up["type"]) == "minio" {
|
||||||
|
return up
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if clip := asMap(actions["clip"]); clip != nil {
|
||||||
|
if up := asMap(clip["upload"]); up != nil {
|
||||||
|
if asString(up["type"]) == "minio" {
|
||||||
|
return up
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func uniquify(name string, used map[string]int) string {
|
||||||
|
base := name
|
||||||
|
if base == "" {
|
||||||
|
base = "cam"
|
||||||
|
}
|
||||||
|
if used[base] == 0 {
|
||||||
|
used[base] = 1
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
used[base]++
|
||||||
|
return fmt.Sprintf("%s_%d", base, used[base])
|
||||||
|
}
|
||||||
|
|
||||||
|
func asMap(v any) map[string]any {
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if m, ok := v.(map[string]any); ok {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func asSlice(v any) []any {
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if s, ok := v.([]any); ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func asString(v any) string {
|
||||||
|
if v == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
return strings.TrimSpace(s)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func asIntOr(v any, def int) int {
|
||||||
|
if v == nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
switch x := v.(type) {
|
||||||
|
case float64:
|
||||||
|
return int(x)
|
||||||
|
case int:
|
||||||
|
return x
|
||||||
|
case int32:
|
||||||
|
return int(x)
|
||||||
|
case int64:
|
||||||
|
return int(x)
|
||||||
|
case string:
|
||||||
|
// ignore parse errors
|
||||||
|
return def
|
||||||
|
default:
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func asFloatOr(v any, def float64) float64 {
|
||||||
|
if v == nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
switch x := v.(type) {
|
||||||
|
case float64:
|
||||||
|
return x
|
||||||
|
case int:
|
||||||
|
return float64(x)
|
||||||
|
case int32:
|
||||||
|
return float64(x)
|
||||||
|
case int64:
|
||||||
|
return float64(x)
|
||||||
|
default:
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) handleConfigUIPlan(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleConfigUIPlan(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
errorJSON(w, http.StatusMethodNotAllowed, "method not allowed")
|
errorJSON(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||||
|
|||||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user