Compare commits
No commits in common. "p0-dashboard" and "master" have entirely different histories.
p0-dashboa
...
master
@ -1,173 +0,0 @@
|
|||||||
package httpapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"rk3588sys/agent/internal/files"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Server) alarmsPath() string {
|
|
||||||
return filepath.Join(s.baseDir, "logs", "alarms.jsonl")
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleAlarmReport receives alarm notifications from media-server.
|
|
||||||
// POST /v1/alarms/report
|
|
||||||
func (s *Server) handleAlarmReport(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != http.MethodPost {
|
|
||||||
errorJSON(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Allow localhost requests without token (media-server alarm callback)
|
|
||||||
if !isLocalhost(r) && !s.authorize(r, true) {
|
|
||||||
errorJSON(w, http.StatusUnauthorized, "unauthorized")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
maxBytes := int64(1 << 20) // 1MB max
|
|
||||||
r.Body = http.MaxBytesReader(w, r.Body, maxBytes)
|
|
||||||
|
|
||||||
var alarm map[string]any
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&alarm); err != nil {
|
|
||||||
errorJSON(w, http.StatusBadRequest, "invalid json: "+err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize: extract channel and rule_name from whatever format media-server sends
|
|
||||||
id, _ := alarm["id"].(string)
|
|
||||||
if id == "" {
|
|
||||||
id = fmt.Sprintf("alarm_%s_%s", time.Now().Format("20060102_150405"), randomHex(6))
|
|
||||||
}
|
|
||||||
if _, ok := alarm["timestamp"]; !ok {
|
|
||||||
alarm["timestamp"] = time.Now().Format(time.RFC3339)
|
|
||||||
}
|
|
||||||
alarm["id"] = id
|
|
||||||
alarm["received_at"] = time.Now().Format(time.RFC3339)
|
|
||||||
|
|
||||||
path := s.alarmsPath()
|
|
||||||
dir := filepath.Dir(path)
|
|
||||||
if err := files.EnsureDir(dir, 0o755); err != nil {
|
|
||||||
errorJSON(w, http.StatusInternalServerError, "internal error: "+err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
line, err := json.Marshal(alarm)
|
|
||||||
if err != nil {
|
|
||||||
errorJSON(w, http.StatusInternalServerError, "internal error: "+err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
|
||||||
if err != nil {
|
|
||||||
errorJSON(w, http.StatusInternalServerError, "internal error: "+err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
if _, err := fmt.Fprintf(f, "%s\n", string(line)); err != nil {
|
|
||||||
errorJSON(w, http.StatusInternalServerError, "internal error: "+err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.recordAudit(r, "alarm.report", true, id)
|
|
||||||
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "id": id})
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleAlarmsRecent returns recent alarm records.
|
|
||||||
// GET /v1/alarms/recent?limit=50&since=2026-01-01T00:00:00Z
|
|
||||||
func (s *Server) handleAlarmsRecent(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
errorJSON(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !s.authorize(r, false) {
|
|
||||||
errorJSON(w, http.StatusUnauthorized, "unauthorized")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
limit := 50
|
|
||||||
if v := strings.TrimSpace(r.URL.Query().Get("limit")); v != "" {
|
|
||||||
if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 500 {
|
|
||||||
limit = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
alarms, err := s.readRecentAlarms(limit)
|
|
||||||
if err != nil {
|
|
||||||
errorJSON(w, http.StatusInternalServerError, "internal error: "+err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if alarms == nil {
|
|
||||||
alarms = make([]map[string]any, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
writeJSON(w, http.StatusOK, map[string]any{"alarms": alarms})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) readRecentAlarms(limit int) ([]map[string]any, error) {
|
|
||||||
path := s.alarmsPath()
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
// Read all lines and keep last N
|
|
||||||
var lines []string
|
|
||||||
scanner := bufio.NewScanner(f)
|
|
||||||
scanner.Buffer(make([]byte, 1<<20), 1<<20) // 1MB max line
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
if strings.TrimSpace(line) == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
lines = append(lines, line)
|
|
||||||
// Keep only last 2*limit lines in memory
|
|
||||||
if len(lines) > 2*limit {
|
|
||||||
lines = lines[limit:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := scanner.Err(); err != nil && err != io.EOF {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take last `limit` lines
|
|
||||||
start := 0
|
|
||||||
if len(lines) > limit {
|
|
||||||
start = len(lines) - limit
|
|
||||||
}
|
|
||||||
|
|
||||||
alarms := make([]map[string]any, 0, limit)
|
|
||||||
for i := start; i < len(lines); i++ {
|
|
||||||
var alarm map[string]any
|
|
||||||
if err := json.Unmarshal([]byte(lines[i]), &alarm); err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
alarms = append(alarms, alarm)
|
|
||||||
}
|
|
||||||
return alarms, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func randomHex(n int) string {
|
|
||||||
b := make([]byte, n)
|
|
||||||
rand.Read(b)
|
|
||||||
return hex.EncodeToString(b)[:n]
|
|
||||||
}
|
|
||||||
|
|
||||||
func isLocalhost(r *http.Request) bool {
|
|
||||||
ip := remoteIP(r)
|
|
||||||
return ip == "127.0.0.1" || ip == "::1" || ip == "localhost"
|
|
||||||
}
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
package httpapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type previewChannel struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
HlsURL string `json:"hls_url,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// handlePreviewChannels returns available video preview channels.
|
|
||||||
// GET /v1/preview/channels
|
|
||||||
func (s *Server) handlePreviewChannels(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
errorJSON(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !s.authorize(r, false) {
|
|
||||||
errorJSON(w, http.StatusUnauthorized, "unauthorized")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
channels := s.discoverChannels(r)
|
|
||||||
if channels == nil {
|
|
||||||
channels = make([]previewChannel, 0)
|
|
||||||
}
|
|
||||||
writeJSON(w, http.StatusOK, map[string]any{"channels": channels})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) discoverChannels(r *http.Request) []previewChannel {
|
|
||||||
if s.ms == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, body, err := s.ms.GetGraphs(r.Context())
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var graphs []struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(body, &graphs); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
baseURL := "http://" + s.hostname + ":" + strconv.Itoa(s.mediaPort)
|
|
||||||
var channels []previewChannel
|
|
||||||
for _, g := range graphs {
|
|
||||||
channels = append(channels, previewChannel{
|
|
||||||
Name: g.Name,
|
|
||||||
HlsURL: baseURL + "/hls/" + g.Name + "/index.m3u8",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return channels
|
|
||||||
}
|
|
||||||
@ -132,9 +132,6 @@ func New(agentCfg config.AgentConfig, baseDir string, ms *mediaserver.Client, st
|
|||||||
mux.HandleFunc("/v1/config/ui/apply", s.handleConfigUIApply)
|
mux.HandleFunc("/v1/config/ui/apply", s.handleConfigUIApply)
|
||||||
mux.HandleFunc("/v1/face-gallery", s.handleFaceGallery)
|
mux.HandleFunc("/v1/face-gallery", s.handleFaceGallery)
|
||||||
mux.HandleFunc("/v1/face-gallery/reload", s.handleFaceGalleryReload)
|
mux.HandleFunc("/v1/face-gallery/reload", s.handleFaceGalleryReload)
|
||||||
mux.HandleFunc("/v1/alarms/report", s.handleAlarmReport)
|
|
||||||
mux.HandleFunc("/v1/alarms/recent", s.handleAlarmsRecent)
|
|
||||||
mux.HandleFunc("/v1/preview/channels", s.handlePreviewChannels)
|
|
||||||
mux.HandleFunc("/v1/resources/status", s.handleResourcesStatus)
|
mux.HandleFunc("/v1/resources/status", s.handleResourcesStatus)
|
||||||
mux.HandleFunc("/v1/resources/", s.handleResourceUpload)
|
mux.HandleFunc("/v1/resources/", s.handleResourceUpload)
|
||||||
mux.HandleFunc("/v1/models", s.handleModelsList)
|
mux.HandleFunc("/v1/models", s.handleModelsList)
|
||||||
|
|||||||
Binary file not shown.
@ -1,47 +0,0 @@
|
|||||||
{
|
|
||||||
"description": "降低告警阈值用于测试:缩短冷却时间、降低最小持续时间和命中数。",
|
|
||||||
"instance_overrides": {
|
|
||||||
"*": {
|
|
||||||
"override": {
|
|
||||||
"nodes": {
|
|
||||||
"alarm_violation": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "non_compliant_workshoe",
|
|
||||||
"class_ids": [2],
|
|
||||||
"min_score": 0.1,
|
|
||||||
"min_duration_ms": 100,
|
|
||||||
"min_hits": 1,
|
|
||||||
"hit_window_ms": 5000,
|
|
||||||
"cooldown_ms": 2000
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"face_rules": [
|
|
||||||
{
|
|
||||||
"name": "unknown_face",
|
|
||||||
"type": "unknown",
|
|
||||||
"cooldown_ms": 2000,
|
|
||||||
"min_hits": 1,
|
|
||||||
"hit_window_ms": 5000,
|
|
||||||
"min_face_area_ratio": 0.0001,
|
|
||||||
"min_face_aspect": 0.3,
|
|
||||||
"max_face_aspect": 3.0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "known_person",
|
|
||||||
"type": "person",
|
|
||||||
"cooldown_ms": 2000,
|
|
||||||
"min_sim": 0.3,
|
|
||||||
"min_hits": 1,
|
|
||||||
"hit_window_ms": 5000,
|
|
||||||
"min_face_area_ratio": 0.0001,
|
|
||||||
"min_face_aspect": 0.3,
|
|
||||||
"max_face_aspect": 3.0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -407,12 +407,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"actions": {
|
"actions": {
|
||||||
"http": {
|
|
||||||
"enable": true,
|
|
||||||
"url": "http://127.0.0.1:9100/v1/alarms/report",
|
|
||||||
"timeout_ms": 2000,
|
|
||||||
"include_media_url": true
|
|
||||||
},
|
|
||||||
"log": {
|
"log": {
|
||||||
"enable": true,
|
"enable": true,
|
||||||
"level": "info",
|
"level": "info",
|
||||||
|
|||||||
@ -59,8 +59,6 @@ uv run --with flask scripts/mock_alarm_server.py
|
|||||||
- 运行minio
|
- 运行minio
|
||||||
C:\Users\Tellme\minio\minio.exe server C:\Users\Tellme\minio\myminio --address ":9000" --console-address ":9001"
|
C:\Users\Tellme\minio\minio.exe server C:\Users\Tellme\minio\myminio --address ":9000" --console-address ":9001"
|
||||||
|
|
||||||
用户名密码是:admin/password
|
|
||||||
|
|
||||||
然后在 RK3588 上测试 token 接口:
|
然后在 RK3588 上测试 token 接口:
|
||||||
curl -X POST http://10.0.0.49:8080/api/getToken
|
curl -X POST http://10.0.0.49:8080/api/getToken
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user