refactor: rebrand to SafeSight Control (merge p0-dashboard)

This commit is contained in:
tian 2026-07-17 11:08:47 +08:00
parent 2921c180cb
commit d422ffd48e
42 changed files with 653 additions and 653 deletions

View File

@ -1,6 +1,6 @@
# managerd
# safesightd
`managerd` 是 RK3588 管理端后端服务,负责设备发现、设备注册表维护、代理访问设备端 `rk3588-agent`,并提供内嵌 Web UI、OpenAPI 页面和 HTTP API。
`safesightd` 是 SafeSight Control 管理端后端服务,负责设备发现、设备注册表维护、代理访问设备端 `rk3588-agent`,并提供内嵌 Web UI、OpenAPI 页面和 HTTP API。
## 当前状态
@ -16,37 +16,37 @@
默认通过单个可执行文件运行:
```bash
managerd
safesightd
```
也可以指定配置文件路径:
```bash
managerd path/to/managerd.json
safesightd path/to/safesightd.json
```
在当前仓库的 Windows 开发环境中,推荐使用统一脚本入口:
```bat
scripts\managerd.bat build
scripts\managerd.bat start
scripts\managerd.bat stop
scripts\managerd.bat restart
scripts\managerd.bat status
scripts\safesightd.bat build
scripts\safesightd.bat start
scripts\safesightd.bat stop
scripts\safesightd.bat restart
scripts\safesightd.bat status
```
各动作说明:
- `build`: 编译 `.\cmd\managerd`,生成根目录下的 `managerd.exe`
- `start`: 启动当前仓库中的 `managerd.exe`,并检查 `/health`
- `stop`: 停止当前仓库对应的 `managerd.exe`
- `build`: 编译 `.\cmd\safesightd`,生成根目录下的 `safesightd.exe`
- `start`: 启动当前仓库中的 `safesightd.exe`,并检查 `/health`
- `stop`: 停止当前仓库对应的 `safesightd.exe`
- `restart`: 先停止再启动
- `status`: 查看进程状态与健康检查结果
脚本实际入口文件:
- `scripts/managerd.bat`
- `scripts/managerd.ps1`
- `scripts/safesightd.bat`
- `scripts/safesightd.ps1`
程序启动后:
@ -192,7 +192,7 @@ Response:
### 设备代理接口
以下接口由 managerd 转发到设备端 agent
以下接口由 safesightd 转发到设备端 agent
- `GET /api/devices/{id}/info` -> `GET /v1/info`
- `POST /api/devices/{id}/reload` -> `POST /v1/media-server/reload`
@ -233,7 +233,7 @@ Response:
- `name`: 模型名
- `file`: 二进制文件
行为:managerd 读取上传文件,并转发为设备端 agent 的 `PUT /v1/models/{name}`
行为:safesightd 读取上传文件,并转发为设备端 agent 的 `PUT /v1/models/{name}`
### Templates
@ -288,7 +288,7 @@ SSE 事件名为 `device_update`,数据格式:
## 配置文件
`managerd.json` 示例:
`safesightd.json` 示例:
```json
{
@ -303,7 +303,7 @@ SSE 事件名为 `device_update`,数据格式:
字段说明:
- `listen`: managerd 监听地址
- `listen`: safesightd 监听地址
- `discovery_port`: 设备发现广播端口
- `discovery_timeout_ms`: 搜索超时
- `offline_after_ms`: 多久未更新则视为离线
@ -312,7 +312,7 @@ SSE 事件名为 `device_update`,数据格式:
## 目录说明
- `cmd/managerd`: 程序入口
- `cmd/safesightd`: 程序入口
- `internal/api`: API 路由与处理器
- `internal/service`: discovery、registry、task、template、agent client
- `internal/web`: 内嵌 Web UI

View File

@ -7,11 +7,11 @@ import (
"os"
"path/filepath"
"3588AdminBackend/internal/api"
"3588AdminBackend/internal/config"
"3588AdminBackend/internal/service"
"3588AdminBackend/internal/storage"
"3588AdminBackend/internal/web"
"safesight-control/internal/api"
"safesight-control/internal/config"
"safesight-control/internal/service"
"safesight-control/internal/storage"
"safesight-control/internal/web"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
@ -19,10 +19,10 @@ import (
)
func main() {
cfgPath := "managerd.json"
if len(os.Args) > 1 {
cfgPath = os.Args[1]
}
cfgPath := "safesightd.json"
if len(os.Args) > 1 {
cfgPath = os.Args[1]
}
cfg, err := config.LoadConfig(cfgPath)
if err != nil {
@ -157,8 +157,8 @@ func main() {
r.Post("/devices/{id}/models/upload", h.UploadModel)
})
fmt.Printf("Starting managerd on %s\n", cfg.Listen)
if err := http.ListenAndServe(cfg.Listen, r); err != nil {
log.Fatalf("failed to start server: %v", err)
}
fmt.Printf("Starting safesightd on %s\n", cfg.Listen)
if err := http.ListenAndServe(cfg.Listen, r); err != nil {
log.Fatalf("failed to start server: %v", err)
}
}

View File

@ -286,4 +286,4 @@ func (u *UI) pageDashboard(w http.ResponseWriter, r *http.Request) {
| 后端 | `internal/web/ui/templates/monitor.html` | 新建:视频监控页面 |
| 后端 | `internal/web/ui/templates/dashboard.html` | 修改:实时化改造 |
| 后端 | `internal/web/ui.go` | 修改:新页面 handler + 路由 |
| 后端 | `cmd/managerd/main.go` | 修改:注入新服务 |
| 后端 | `cmd/safesightd/main.go` | 修改:注入新服务 |

View File

@ -1,7 +1,7 @@
# 产品化评估与后续规划
> 评估日期2026-05-06
> 范围视觉识别服务OrangePi3588Media+ 管理后台(3588AdminBackend
> 范围视觉识别服务OrangePi3588Media+ 管理后台(SafeSight Control
---
@ -75,7 +75,7 @@
两个项目的架构基础扎实:
- **media-server**:插件化 DAG 流水线JSON 配置驱动,热更新/回滚,硬件解耦抽象清晰
- **managerd**分层架构API→Service→StorageRESTfulHTML/template 服务端渲染,无框架依赖
- **safesightd**分层架构API→Service→StorageRESTfulHTML/template 服务端渲染,无框架依赖
从产品化角度看,两边都有显著缺口,下面按优先级列出。
@ -107,7 +107,7 @@
---
## 三、管理后台(managerd
## 三、管理后台(safesightd
### ✅ 优势
@ -249,7 +249,7 @@
| 项目 | 说明 |
|------|------|
| `cmd/managerd` 目录名歧义 | 既是源码目录又被 gitignore 匹配为二进制 |
| `cmd/safesightd` 目录名歧义 | 既是源码目录又被 gitignore 匹配为二进制 |
| 模板函数分散 | `modelTypeLabel`、`resourceTypeLabel` 等散落在 NewUI 中,应集中管理 |
| agent 配置示例过时 | `agent.config.example.json` 路径与生产环境不一致 |
| deploy.sh 交互选择 | 应支持完全无交互部署CI/CD 友好) |

View File

@ -1,4 +1,4 @@
# 3588AdminBackend 离线部署文档
# SafeSight Control 离线部署文档
> **目标环境**: Ubuntu 22.04.5 LTS (离线环境,无法连接互联网)
> **部署方式**: 在 Windows 上交叉编译,上传安装包到 Ubuntu 进行离线部署
@ -21,7 +21,7 @@
### 1.1 项目简介
3588AdminBackend 是 Orange Pi 3588 媒体服务器的后台管理端,提供以下功能:
SafeSight Control 是 Orange Pi 3588 媒体服务器的后台管理端,提供以下功能:
- 设备发现与注册管理
- 设备配置下发与批量任务管理
@ -47,7 +47,7 @@
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 1. 安装 Go 1.23+ │ │
│ │ 2. 运行 build-windows.ps1 │ │
│ │ 3. 生成 3588AdminBackend-离线部署包-YYYYMMDD.tar.gz │ │
│ │ 3. 生成 SafeSight Control-离线部署包-YYYYMMDD.tar.gz │ │
│ └──────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
@ -56,7 +56,7 @@
│ Ubuntu生产环境
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 无需 Go 运行时!直接运行编译好的静态二进制文件 │ │
│ │ - tar -xzf 3588AdminBackend-离线部署包-*.tar.gz │ │
│ │ - tar -xzf SafeSight Control-离线部署包-*.tar.gz │ │
│ │ - sudo ./scripts/3588admin install │ │
│ │ - sudo ./scripts/3588admin start │ │
│ └──────────────────────────────────────────────────────────────┘ │
@ -96,14 +96,14 @@
将项目源码复制到 Windows 机器上,例如:
```
C:\Users\YourName\3588AdminBackend\
C:\Users\YourName\SafeSight Control\
```
目录结构应包含:
```
3588AdminBackend/
SafeSight Control/
├── cmd/
│ └── managerd/
│ └── safesightd/
├── internal/
├── scripts/
│ └── deploy/
@ -125,7 +125,7 @@ C:\Users\YourName\3588AdminBackend\
```powershell
# 进入项目目录
cd C:\Users\YourName\3588AdminBackend\scripts\deploy
cd C:\Users\YourName\SafeSight Control\scripts\deploy
# 执行构建脚本
.\build-windows.ps1
@ -133,21 +133,21 @@ cd C:\Users\YourName\3588AdminBackend\scripts\deploy
或一行命令:
```powershell
& "C:<Users>YourName<3588AdminBackend<scripts>deploy<build-windows.ps1"
& "C:<Users>YourName<SafeSight Control<scripts>deploy<build-windows.ps1"
```
### 3.2 构建输出
构建完成后,会在 `build\` 目录下生成:
```
3588AdminBackend-离线部署包-YYYYMMDD.tar.gz
SafeSight Control-离线部署包-YYYYMMDD.tar.gz
```
构建输出示例:
```
========== 3588AdminBackend Windows 构建 ==========
项目目录: C:<Users>YourName<3588AdminBackend
构建目录: C:<Users>YourName<3588AdminBackend<build
========== SafeSight Control Windows 构建 ==========
项目目录: C:<Users>YourName<SafeSight Control
构建目录: C:<Users>YourName<SafeSight Control<build
Go 版本: go version go1.23.3 windows/amd64
[1/6] 清理旧构建...
@ -163,12 +163,12 @@ Go 版本: go version go1.23.3 windows/amd64
========== 构建完成 ==========
输出文件:
3588AdminBackend-离线部署包-20260225.tar.gz (12.34 MB)
SafeSight Control-离线部署包-20260225.tar.gz (12.34 MB)
部署包位置: C:<Users>YourName<3588AdminBackend<build<3588AdminBackend-离线部署包-20260225.tar.gz
部署包位置: C:<Users>YourName<SafeSight Control<build<SafeSight Control-离线部署包-20260225.tar.gz
使用说明:
1. 将 3588AdminBackend-离线部署包-20260225.tar.gz 上传到 Ubuntu 服务器
1. 将 SafeSight Control-离线部署包-20260225.tar.gz 上传到 Ubuntu 服务器
2. 解压并运行: sudo ./scripts/3588admin install
```
@ -178,12 +178,12 @@ Go 版本: go version go1.23.3 windows/amd64
```powershell
# 1. 进入项目目录
cd C:<Users>YourName<3588AdminBackend
cd C:<Users>YourName<SafeSight Control
# 2. 交叉编译 Linux 版本
$env:GOOS = "linux"
$env:GOARCH = "amd64"
go build -ldflags="-s -w" -o managerd-linux-amd64 .\cmd\managerd
go build -ldflags="-s -w" -o safesightd-linux-amd64 .\cmd\safesightd
# 3. 创建部署目录
mkdir build<3588admin-deploy<bin
@ -191,14 +191,14 @@ mkdir build<3588admin-deploy<config
mkdir build<3588admin-deploy<scripts
# 4. 复制文件
copy managerd-linux-amd64 build<3588admin-deploy<bin<managerd
copy safesightd-linux-amd64 build<3588admin-deploy<bin<safesightd
copy scripts>deploy<3588admin build<3588admin-deploy<scripts<
copy scripts>deploy<3588admin.service build<3588admin-deploy<scripts<
copy managerd.json build<3588admin-deploy<config<managerd.json.example
copy safesightd.json build<3588admin-deploy<config<safesightd.json.example
# 5. 打包
cd build
tar -czf 3588AdminBackend-离线部署包-$(Get-Date -Format 'yyyyMMdd').tar.gz 3588admin-deploy<
tar -czf SafeSight Control-离线部署包-$(Get-Date -Format 'yyyyMMdd').tar.gz 3588admin-deploy<
```
### 3.4 上传到 Ubuntu 服务器
@ -207,7 +207,7 @@ tar -czf 3588AdminBackend-离线部署包-$(Get-Date -Format 'yyyyMMdd').tar.gz
```powershell
# 使用 scp如果 Windows 有 OpenSSH
scp .\build<3588AdminBackend-离线部署包-*.tar.gz user@ubuntu-server:/tmp/
scp .\build<SafeSight Control-离线部署包-*.tar.gz user@ubuntu-server:/tmp/
# 或使用 WinSCP、FileZilla 等图形化工具
```
@ -225,10 +225,10 @@ scp .\build<3588AdminBackend-离线部署包-*.tar.gz user@ubuntu-server:/tmp/
cd /tmp
# 解压
tar -xzf 3588AdminBackend-离线部署包-*.tar.gz
tar -xzf SafeSight Control-离线部署包-*.tar.gz
# 进入部署目录
cd 3588AdminBackend-离线部署包-*/
cd SafeSight Control-离线部署包-*/
# 查看内容
ls -la
@ -283,7 +283,7 @@ sudo systemctl status 3588admin
```bash
# 编辑配置文件
sudo nano /opt/3588admin/config/managerd.json
sudo nano /opt/3588admin/config/safesightd.json
```
**关键配置项说明**
@ -346,7 +346,7 @@ sudo systemctl status 3588admin
```bash
# 检查进程
ps aux | grep managerd
ps aux | grep safesightd
# 检查端口监听
sudo netstat -tlnp | grep 18080
@ -357,7 +357,7 @@ sudo ss -tlnp | grep 18080
curl http://127.0.0.1:18080/api/devices
# 查看日志
tail -f /opt/3588admin/logs/managerd.log
tail -f /opt/3588admin/logs/safesightd.log
```
### 5.4 防火墙配置
@ -403,14 +403,14 @@ Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
```bash
# 1. 检查二进制文件架构是否匹配
file /opt/3588admin/bin/managerd
file /opt/3588admin/bin/safesightd
# 应输出: ELF 64-bit LSB executable, x86-64
# 2. 直接运行查看错误信息
/opt/3588admin/bin/managerd -config /opt/3588admin/config/managerd.json
/opt/3588admin/bin/safesightd -config /opt/3588admin/config/safesightd.json
# 3. 检查配置文件格式
python3 -m json.tool /opt/3588admin/config/managerd.json
python3 -m json.tool /opt/3588admin/config/safesightd.json
```
### 6.3 端口被占用
@ -426,7 +426,7 @@ sudo lsof -i :18080
sudo netstat -tlnp | grep 18080
# 修改配置文件使用其他端口
sudo nano /opt/3588admin/config/managerd.json
sudo nano /opt/3588admin/config/safesightd.json
# 修改 "listen" 为 "127.0.0.1:18081" 或其他端口
```
@ -457,7 +457,7 @@ sudo ufw status | grep 35688
```bash
# 修复权限
sudo chmod +x /opt/3588admin/bin/managerd
sudo chmod +x /opt/3588admin/bin/safesightd
sudo chmod +x /opt/3588admin/scripts/3588admin
# 确保以 root 运行
@ -468,13 +468,13 @@ sudo 3588admin start
```bash
# 实时查看日志
tail -f /opt/3588admin/logs/managerd.log
tail -f /opt/3588admin/logs/safesightd.log
# 查看最近 100 行
tail -n 100 /opt/3588admin/logs/managerd.log
tail -n 100 /opt/3588admin/logs/safesightd.log
# 搜索错误信息
grep -i error /opt/3588admin/logs/managerd.log
grep -i error /opt/3588admin/logs/safesightd.log
```
---
@ -485,11 +485,11 @@ grep -i error /opt/3588admin/logs/managerd.log
**Windows 构建机**
```
C:<Users>YourName<3588AdminBackend<
C:<Users>YourName<SafeSight Control<
├── build< # 构建输出
│ └── 3588AdminBackend-离线部署包-20260225<
│ └── SafeSight Control-离线部署包-20260225<
│ ├── bin<
│ │ └── managerd # Linux 静态二进制文件
│ │ └── safesightd # Linux 静态二进制文件
│ ├── config<
│ ├── scripts<
│ │ └── 3588admin # 管理脚本
@ -508,11 +508,11 @@ C:<Users>YourName<3588AdminBackend<
```
/opt/3588admin/
├── bin/
│ └── managerd # 主程序Windows 交叉编译生成)
│ └── safesightd # 主程序Windows 交叉编译生成)
├── config/
│ └── managerd.json # 配置文件
│ └── safesightd.json # 配置文件
├── logs/
│ └── managerd.log # 运行日志
│ └── safesightd.log # 运行日志
├── scripts/
│ ├── 3588admin # 统一管理脚本
│ └── 3588admin.service # 服务文件(复制用)
@ -555,7 +555,7 @@ sudo 3588admin stop
sudo systemctl stop 3588admin
# 2. 备份配置
cp /opt/3588admin/config/managerd.json ~/managerd.json.bak
cp /opt/3588admin/config/safesightd.json ~/safesightd.json.bak
# 3. 在 Windows 上重新构建新版本
# 运行 build-windows.ps1
@ -589,4 +589,4 @@ echo "卸载完成"
**文档版本**: 2.1 (Windows 构建版)
**最后更新**: 2026-02-25
**适用版本**: 3588AdminBackend V1.0+
**适用版本**: SafeSight Control V1.0+

2
go.mod
View File

@ -1,4 +1,4 @@
module 3588AdminBackend
module safesight-control
go 1.23.3

View File

@ -8,9 +8,9 @@ import (
"strings"
"time"
"3588AdminBackend/internal/models"
"3588AdminBackend/internal/service"
"github.com/go-chi/chi/v5"
"safesight-control/internal/models"
"safesight-control/internal/service"
"github.com/go-chi/chi/v5"
)
type Handler struct {

View File

@ -1,18 +1,18 @@
package api
import (
"3588AdminBackend/internal/config"
"3588AdminBackend/internal/models"
"3588AdminBackend/internal/service"
"bytes"
"encoding/json"
"net"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
"safesight-control/internal/config"
"safesight-control/internal/models"
"safesight-control/internal/service"
"bytes"
"encoding/json"
"net"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
"github.com/go-chi/chi/v5"
)

View File

@ -6,281 +6,281 @@ import (
)
func OpenAPI(w http.ResponseWriter, r *http.Request) {
spec := map[string]any{
"openapi": "3.0.3",
"info": map[string]any{
"title": "managerd API",
"version": "v1",
},
"paths": map[string]any{
"/health": map[string]any{
"get": map[string]any{
"responses": map[string]any{
"200": map[string]any{
"description": "ok",
"content": map[string]any{"text/plain": map[string]any{}},
},
},
},
},
"/api/discovery/search": map[string]any{
"post": map[string]any{
"requestBody": map[string]any{
"required": true,
"content": map[string]any{
"application/json": map[string]any{
"schema": map[string]any{
"type": "object",
"properties": map[string]any{"timeout_ms": map[string]any{"type": "integer"}},
},
},
},
},
"responses": map[string]any{
"200": map[string]any{
"description": "devices",
"content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}},
},
},
},
},
"/api/devices": map[string]any{
"get": map[string]any{
"responses": map[string]any{
"200": map[string]any{
"description": "devices",
"content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}},
},
},
},
"post": map[string]any{
"requestBody": map[string]any{
"required": true,
"content": map[string]any{
"application/json": map[string]any{
"schema": map[string]any{
"type": "object",
"properties": map[string]any{
"device_id": map[string]any{"type": "string"},
"device_name": map[string]any{"type": "string"},
"ip": map[string]any{"type": "string"},
"agent_port": map[string]any{"type": "integer"},
"media_port": map[string]any{"type": "integer"},
},
"required": []any{"device_id", "ip"},
},
},
},
},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "device", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/info": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "agent info", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/reload": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/rollback": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/media-server/start": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/media-server/restart": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/media-server/stop": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/media-server/status": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "status", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/graphs": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "graphs", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/graphs/{name}": map[string]any{
"get": map[string]any{
"parameters": []any{
map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}},
map[string]any{"name": "name", "in": "path", "required": true, "schema": map[string]any{"type": "string"}},
},
"responses": map[string]any{"200": map[string]any{"description": "graph", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/logs": map[string]any{
"get": map[string]any{
"parameters": []any{
map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}},
map[string]any{"name": "limit", "in": "query", "required": false, "schema": map[string]any{"type": "integer"}},
},
"responses": map[string]any{"200": map[string]any{"description": "logs", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/config/apply": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}},
},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/models": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "models", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/models/upload": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{
"multipart/form-data": map[string]any{
"schema": map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]any{"type": "string"},
"file": map[string]any{"type": "string", "format": "binary"},
},
"required": []any{"name", "file"},
},
},
},
},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/v1/config": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "config", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
"put": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}},
},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/v1/config/ui/schema": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "schema", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/v1/config/ui/state": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "state", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/v1/config/ui/plan": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}},
},
"responses": map[string]any{"200": map[string]any{"description": "plan", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/v1/config/ui/apply": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}},
},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/v1/face-gallery": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "face gallery info", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
"put": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{"application/octet-stream": map[string]any{"schema": map[string]any{"type": "string", "format": "binary"}}},
},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/v1/face-gallery/reload": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/templates": map[string]any{
"get": map[string]any{
"responses": map[string]any{"200": map[string]any{"description": "templates", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "array"}}}}},
},
},
"/api/templates/{name}": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "name", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "template", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/tasks": map[string]any{
"get": map[string]any{
"responses": map[string]any{"200": map[string]any{"description": "tasks", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
"post": map[string]any{
"requestBody": map[string]any{
"required": true,
"content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}},
},
"responses": map[string]any{"200": map[string]any{"description": "task_id", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/tasks/{id}/events": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "SSE", "content": map[string]any{"text/event-stream": map[string]any{}}}},
},
},
},
}
spec := map[string]any{
"openapi": "3.0.3",
"info": map[string]any{
"title": "SafeSight Control API",
"version": "v1",
},
"paths": map[string]any{
"/health": map[string]any{
"get": map[string]any{
"responses": map[string]any{
"200": map[string]any{
"description": "ok",
"content": map[string]any{"text/plain": map[string]any{}},
},
},
},
},
"/api/discovery/search": map[string]any{
"post": map[string]any{
"requestBody": map[string]any{
"required": true,
"content": map[string]any{
"application/json": map[string]any{
"schema": map[string]any{
"type": "object",
"properties": map[string]any{"timeout_ms": map[string]any{"type": "integer"}},
},
},
},
},
"responses": map[string]any{
"200": map[string]any{
"description": "devices",
"content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}},
},
},
},
},
"/api/devices": map[string]any{
"get": map[string]any{
"responses": map[string]any{
"200": map[string]any{
"description": "devices",
"content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}},
},
},
},
"post": map[string]any{
"requestBody": map[string]any{
"required": true,
"content": map[string]any{
"application/json": map[string]any{
"schema": map[string]any{
"type": "object",
"properties": map[string]any{
"device_id": map[string]any{"type": "string"},
"device_name": map[string]any{"type": "string"},
"ip": map[string]any{"type": "string"},
"agent_port": map[string]any{"type": "integer"},
"media_port": map[string]any{"type": "integer"},
},
"required": []any{"device_id", "ip"},
},
},
},
},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "device", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/info": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "agent info", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/reload": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/rollback": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/media-server/start": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/media-server/restart": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/media-server/stop": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/media-server/status": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "status", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/graphs": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "graphs", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/graphs/{name}": map[string]any{
"get": map[string]any{
"parameters": []any{
map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}},
map[string]any{"name": "name", "in": "path", "required": true, "schema": map[string]any{"type": "string"}},
},
"responses": map[string]any{"200": map[string]any{"description": "graph", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/logs": map[string]any{
"get": map[string]any{
"parameters": []any{
map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}},
map[string]any{"name": "limit", "in": "query", "required": false, "schema": map[string]any{"type": "integer"}},
},
"responses": map[string]any{"200": map[string]any{"description": "logs", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/config/apply": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}},
},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/models": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "models", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/models/upload": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{
"multipart/form-data": map[string]any{
"schema": map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]any{"type": "string"},
"file": map[string]any{"type": "string", "format": "binary"},
},
"required": []any{"name", "file"},
},
},
},
},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/v1/config": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "config", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
"put": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}},
},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/v1/config/ui/schema": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "schema", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/v1/config/ui/state": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "state", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/v1/config/ui/plan": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}},
},
"responses": map[string]any{"200": map[string]any{"description": "plan", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/devices/{id}/v1/config/ui/apply": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}},
},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/v1/face-gallery": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "face gallery info", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
"put": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"requestBody": map[string]any{
"required": true,
"content": map[string]any{"application/octet-stream": map[string]any{"schema": map[string]any{"type": "string", "format": "binary"}}},
},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/devices/{id}/v1/face-gallery/reload": map[string]any{
"post": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "ok"}},
},
},
"/api/templates": map[string]any{
"get": map[string]any{
"responses": map[string]any{"200": map[string]any{"description": "templates", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "array"}}}}},
},
},
"/api/templates/{name}": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "name", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "template", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/tasks": map[string]any{
"get": map[string]any{
"responses": map[string]any{"200": map[string]any{"description": "tasks", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
"post": map[string]any{
"requestBody": map[string]any{
"required": true,
"content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}},
},
"responses": map[string]any{"200": map[string]any{"description": "task_id", "content": map[string]any{"application/json": map[string]any{"schema": map[string]any{"type": "object"}}}}},
},
},
"/api/tasks/{id}/events": map[string]any{
"get": map[string]any{
"parameters": []any{map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}},
"responses": map[string]any{"200": map[string]any{"description": "SSE", "content": map[string]any{"text/event-stream": map[string]any{}}}},
},
},
},
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(spec)

View File

@ -9,7 +9,7 @@ import (
"strings"
"time"
"3588AdminBackend/internal/config"
"safesight-control/internal/config"
)
type AgentClient struct {

View File

@ -8,8 +8,8 @@ import (
"sort"
"strings"
"3588AdminBackend/internal/models"
"3588AdminBackend/internal/storage"
"safesight-control/internal/models"
"safesight-control/internal/storage"
)
var legacyBuiltinTemplateAliases = map[string]string{

View File

@ -7,9 +7,9 @@ import (
"strings"
"testing"
"3588AdminBackend/internal/config"
"3588AdminBackend/internal/models"
"3588AdminBackend/internal/storage"
"safesight-control/internal/config"
"safesight-control/internal/models"
"safesight-control/internal/storage"
)
func mustSaveTemplateRecord(t *testing.T, repo *storage.AssetsRepo, name string, description string, body string) {

View File

@ -12,8 +12,8 @@ import (
"strings"
"time"
"3588AdminBackend/internal/config"
"3588AdminBackend/internal/storage"
"safesight-control/internal/config"
"safesight-control/internal/storage"
)
var safeConfigName = regexp.MustCompile(`^[A-Za-z0-9_.-]+$`)
@ -251,56 +251,56 @@ func profileOverlayNames(raw map[string]any) []string {
}
func (s *ConfigPreviewService) renderFromAssets(req ConfigPreviewRequest, templateRaw map[string]any, templatePath string, profileRaw map[string]any, profilePath string, overlays []runtimeOverlayInput) (*ConfigPreviewResult, error) {
if err := validateConfigName(req.Template); err != nil {
return nil, fmt.Errorf("invalid template: %w", err)
}
if strings.TrimSpace(req.Profile) != "" {
if err := validateConfigName(req.Profile); err != nil {
return nil, fmt.Errorf("invalid profile: %w", err)
}
}
for _, overlay := range req.Overlays {
if err := validateConfigName(overlay); err != nil {
return nil, fmt.Errorf("invalid overlay %q: %w", overlay, err)
}
}
if req.ConfigID == "" {
req.ConfigID = "preview_" + time.Now().Format("20060102.150405")
}
if req.ConfigVersion == "" {
req.ConfigVersion = time.Now().Format("20060102.150405")
}
resolvedProfileRaw, err := s.resolveSceneBindings(profileRaw)
if err != nil {
return nil, err
}
metadata := map[string]any{
"config_id": req.ConfigID,
"config_version": req.ConfigVersion,
"rendered_at": time.Now().Format(time.RFC3339),
"rendered_by": "managerd",
}
if strings.TrimSpace(req.DeviceID) != "" {
metadata["device_id"] = strings.TrimSpace(req.DeviceID)
}
doc, err := renderRuntimeConfig(templateRaw, templatePath, resolvedProfileRaw, profilePath, overlays, metadata)
if err != nil {
return nil, err
}
body, err := marshalConfigJSON(doc)
if err != nil {
return nil, err
}
renderedMetadata, _ := doc["metadata"].(map[string]any)
sum := sha256.Sum256(body)
return &ConfigPreviewResult{
Request: req,
Root: previewRenderRoot(templatePath, profilePath),
Sha256: hex.EncodeToString(sum[:]),
Size: len(body),
Metadata: renderedMetadata,
JSON: string(body),
}, nil
if err := validateConfigName(req.Template); err != nil {
return nil, fmt.Errorf("invalid template: %w", err)
}
if strings.TrimSpace(req.Profile) != "" {
if err := validateConfigName(req.Profile); err != nil {
return nil, fmt.Errorf("invalid profile: %w", err)
}
}
for _, overlay := range req.Overlays {
if err := validateConfigName(overlay); err != nil {
return nil, fmt.Errorf("invalid overlay %q: %w", overlay, err)
}
}
if req.ConfigID == "" {
req.ConfigID = "preview_" + time.Now().Format("20060102.150405")
}
if req.ConfigVersion == "" {
req.ConfigVersion = time.Now().Format("20060102.150405")
}
resolvedProfileRaw, err := s.resolveSceneBindings(profileRaw)
if err != nil {
return nil, err
}
metadata := map[string]any{
"config_id": req.ConfigID,
"config_version": req.ConfigVersion,
"rendered_at": time.Now().Format(time.RFC3339),
"rendered_by": "safesightd",
}
if strings.TrimSpace(req.DeviceID) != "" {
metadata["device_id"] = strings.TrimSpace(req.DeviceID)
}
doc, err := renderRuntimeConfig(templateRaw, templatePath, resolvedProfileRaw, profilePath, overlays, metadata)
if err != nil {
return nil, err
}
body, err := marshalConfigJSON(doc)
if err != nil {
return nil, err
}
renderedMetadata, _ := doc["metadata"].(map[string]any)
sum := sha256.Sum256(body)
return &ConfigPreviewResult{
Request: req,
Root: previewRenderRoot(templatePath, profilePath),
Sha256: hex.EncodeToString(sum[:]),
Size: len(body),
Metadata: renderedMetadata,
JSON: string(body),
}, nil
}
func (s *ConfigPreviewService) resolveSceneBindings(raw map[string]any) (map[string]any, error) {
@ -427,13 +427,13 @@ func resolvedServiceBinding(asset *ConfigIntegrationServiceAsset) map[string]any
}
func previewRenderRoot(templatePath string, profilePath string) string {
if strings.HasPrefix(templatePath, "sqlite:") || strings.HasPrefix(profilePath, "sqlite:") {
return "SQLite"
}
if dir := filepath.Dir(templatePath); strings.TrimSpace(dir) != "" && dir != "." {
return dir
}
return "managerd"
if strings.HasPrefix(templatePath, "sqlite:") || strings.HasPrefix(profilePath, "sqlite:") {
return "SQLite"
}
if dir := filepath.Dir(templatePath); strings.TrimSpace(dir) != "" && dir != "." {
return dir
}
return "safesightd"
}
func writeResolvedConfigFile(pattern string, raw map[string]any) (string, error) {

View File

@ -7,8 +7,8 @@ import (
"strings"
"testing"
"3588AdminBackend/internal/config"
"3588AdminBackend/internal/storage"
"safesight-control/internal/config"
"safesight-control/internal/storage"
)
func mustImportAssetsFromMediaRepo(t *testing.T, svc *ConfigPreviewService) {
@ -218,27 +218,27 @@ func TestConfigPreviewServiceRenderProfileEditorWritesResolvedBindings(t *testin
t.Fatalf("RenderProfileEditor: %v", err)
}
var doc map[string]any
if err := json.Unmarshal([]byte(result.JSON), &doc); err != nil {
t.Fatalf("unmarshal render result: %v", err)
}
templates, _ := doc["templates"].(map[string]any)
renderedTemplate, _ := templates["std_workshop_face_recognition_shoe_alarm__cam1"].(map[string]any)
nodes, _ := renderedTemplate["nodes"].([]any)
inputNode, _ := nodes[0].(map[string]any)
if got := stringValue(inputNode["url"]); got != "rtsp://10.0.0.1/live" {
t.Fatalf("expected expanded input url, got %#v", inputNode)
}
publishNode, _ := nodes[1].(map[string]any)
outputs, _ := publishNode["outputs"].([]any)
output, _ := outputs[0].(map[string]any)
if got := stringValue(output["path"]); got != "./web/hls/cam1/index.m3u8" {
t.Fatalf("expected expanded output path, got %#v", output)
}
metadata, _ := doc["metadata"].(map[string]any)
if got := stringValue(metadata["rendered_by"]); got != "managerd" {
t.Fatalf("expected managerd renderer metadata, got %#v", metadata)
}
var doc map[string]any
if err := json.Unmarshal([]byte(result.JSON), &doc); err != nil {
t.Fatalf("unmarshal render result: %v", err)
}
templates, _ := doc["templates"].(map[string]any)
renderedTemplate, _ := templates["std_workshop_face_recognition_shoe_alarm__cam1"].(map[string]any)
nodes, _ := renderedTemplate["nodes"].([]any)
inputNode, _ := nodes[0].(map[string]any)
if got := stringValue(inputNode["url"]); got != "rtsp://10.0.0.1/live" {
t.Fatalf("expected expanded input url, got %#v", inputNode)
}
publishNode, _ := nodes[1].(map[string]any)
outputs, _ := publishNode["outputs"].([]any)
output, _ := outputs[0].(map[string]any)
if got := stringValue(output["path"]); got != "./web/hls/cam1/index.m3u8" {
t.Fatalf("expected expanded output path, got %#v", output)
}
metadata, _ := doc["metadata"].(map[string]any)
if got := stringValue(metadata["rendered_by"]); got != "safesightd" {
t.Fatalf("expected safesightd renderer metadata, got %#v", metadata)
}
}
func TestConfigPreviewServiceRenderProfileEditorAllowsUnboundOptionalServiceSlot(t *testing.T) {

View File

@ -6,9 +6,9 @@ import (
"strings"
"time"
"3588AdminBackend/internal/config"
"3588AdminBackend/internal/models"
"github.com/google/uuid"
"safesight-control/internal/config"
"safesight-control/internal/models"
"github.com/google/uuid"
)
const discoveryMagicV1 = "RK3588SYS_DISCOVERY_V1"

View File

@ -11,8 +11,8 @@ import (
"sort"
"strings"
"3588AdminBackend/internal/models"
"3588AdminBackend/internal/storage"
"safesight-control/internal/models"
"safesight-control/internal/storage"
)
type ModelManagementService struct {

View File

@ -5,8 +5,8 @@ import (
"path/filepath"
"testing"
"3588AdminBackend/internal/models"
"3588AdminBackend/internal/storage"
"safesight-control/internal/models"
"safesight-control/internal/storage"
)
func TestSyncStandardModelsFromDirectory(t *testing.T) {

View File

@ -6,8 +6,8 @@ import (
"sync"
"time"
"3588AdminBackend/internal/config"
"3588AdminBackend/internal/models"
"safesight-control/internal/config"
"safesight-control/internal/models"
)
type RegistryService struct {

View File

@ -1,12 +1,12 @@
package service
import (
"3588AdminBackend/internal/config"
"3588AdminBackend/internal/models"
"3588AdminBackend/internal/storage"
"path/filepath"
"testing"
"time"
"safesight-control/internal/config"
"safesight-control/internal/models"
"safesight-control/internal/storage"
"path/filepath"
"testing"
"time"
)
func TestRegistryService_UpdateAndGet(t *testing.T) {

View File

@ -10,8 +10,8 @@ import (
"sync"
"time"
"3588AdminBackend/internal/models"
"3588AdminBackend/internal/storage"
"safesight-control/internal/models"
"safesight-control/internal/storage"
)
type ResourceManagementService struct {

View File

@ -5,8 +5,8 @@ import (
"path/filepath"
"testing"
"3588AdminBackend/internal/models"
"3588AdminBackend/internal/storage"
"safesight-control/internal/models"
"safesight-control/internal/storage"
)
func TestSyncStandardResourcesFromDirectory(t *testing.T) {

View File

@ -7,7 +7,7 @@ import (
"path/filepath"
"strings"
"3588AdminBackend/internal/storage"
"safesight-control/internal/storage"
)
func ImportStandardTemplatesFromDir(repo *storage.AssetsRepo, dir string) (int, error) {

View File

@ -10,10 +10,10 @@ import (
"strings"
"sync"
"3588AdminBackend/internal/config"
"3588AdminBackend/internal/models"
"3588AdminBackend/internal/storage"
"github.com/google/uuid"
"safesight-control/internal/config"
"safesight-control/internal/models"
"safesight-control/internal/storage"
"github.com/google/uuid"
)
type TaskRepository interface {

View File

@ -1,23 +1,23 @@
package service
import (
"3588AdminBackend/internal/config"
"3588AdminBackend/internal/models"
"3588AdminBackend/internal/storage"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
"safesight-control/internal/config"
"safesight-control/internal/models"
"safesight-control/internal/storage"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
)
func waitForTaskDone(t *testing.T, task *models.Task, timeout time.Duration) models.TaskStatus {

View File

@ -5,7 +5,7 @@ import (
"os"
"path/filepath"
"3588AdminBackend/internal/config"
"safesight-control/internal/config"
)
type Template struct {

View File

@ -1,9 +1,9 @@
package service
import (
"3588AdminBackend/internal/config"
"os"
"testing"
"safesight-control/internal/config"
"os"
"testing"
)
func TestTemplateService(t *testing.T) {

View File

@ -5,7 +5,7 @@ import (
"encoding/json"
"time"
"3588AdminBackend/internal/models"
"safesight-control/internal/models"
)
type DevicesRepo struct {

View File

@ -3,7 +3,7 @@ package storage
import (
"testing"
"3588AdminBackend/internal/models"
"safesight-control/internal/models"
)
func TestDevicesRepoUpsertsRuntimeSnapshot(t *testing.T) {

View File

@ -5,7 +5,7 @@ import (
"encoding/json"
"time"
"3588AdminBackend/internal/models"
"safesight-control/internal/models"
)
type TasksRepo struct {

View File

@ -4,7 +4,7 @@ import (
"path/filepath"
"testing"
"3588AdminBackend/internal/models"
"safesight-control/internal/models"
)
func openTestStore(t *testing.T) *Store {

View File

@ -18,10 +18,10 @@ import (
"strings"
"time"
"3588AdminBackend/internal/models"
"3588AdminBackend/internal/service"
"3588AdminBackend/internal/storage"
"github.com/go-chi/chi/v5"
"safesight-control/internal/models"
"safesight-control/internal/service"
"safesight-control/internal/storage"
"github.com/go-chi/chi/v5"
)
type UI struct {

View File

@ -1,25 +1,25 @@
package web
import (
"3588AdminBackend/internal/config"
"3588AdminBackend/internal/models"
"3588AdminBackend/internal/service"
"3588AdminBackend/internal/storage"
"bytes"
"context"
"encoding/json"
"fmt"
"mime/multipart"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
"safesight-control/internal/config"
"safesight-control/internal/models"
"safesight-control/internal/service"
"safesight-control/internal/storage"
"bytes"
"context"
"encoding/json"
"fmt"
"mime/multipart"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
"github.com/go-chi/chi/v5"
)

View File

@ -1,5 +1,5 @@
{
"name": "3588adminbackend",
"name": "safesight-control",
"version": "1.0.0",
"description": "`managerd` 是 RK3588 管理端后端服务,负责设备发现、设备注册表维护、代理访问设备端 `rk3588-agent`,并提供内嵌 Web UI、OpenAPI 页面和 HTTP API。",
"main": "index.js",
@ -11,7 +11,7 @@
},
"repository": {
"type": "git",
"url": "http://10.0.0.99:4000/Doni/3588AdminBackend.git"
"url": "http://10.0.0.99:4000/Doni/safesight-control.git"
},
"keywords": [],
"author": "",

View File

@ -50,24 +50,24 @@ echo "[3/7] 编译项目..."
cd "$PROJECT_DIR"
echo " 编译 Linux AMD64 版本..."
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o "$PACKAGE_DIR/bin/managerd" ./cmd/managerd
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o "$PACKAGE_DIR/bin/safesightd" ./cmd/safesightd
echo " 验证编译结果..."
if [ ! -f "$PACKAGE_DIR/bin/managerd" ]; then
if [ ! -f "$PACKAGE_DIR/bin/safesightd" ]; then
echo -e "${RED}错误: 编译失败${NC}"
exit 1
fi
FILE_INFO=$(file "$PACKAGE_DIR/bin/managerd")
FILE_INFO=$(file "$PACKAGE_DIR/bin/safesightd")
echo " $FILE_INFO"
# 复制配置文件
echo "[4/7] 复制配置文件..."
if [ -f "$PROJECT_DIR/managerd.json" ]; then
cp "$PROJECT_DIR/managerd.json" "$PACKAGE_DIR/config/managerd.json.example"
if [ -f "$PROJECT_DIR/safesightd.json" ]; then
cp "$PROJECT_DIR/safesightd.json" "$PACKAGE_DIR/config/safesightd.json.example"
else
# 创建默认配置示例
cat > "$PACKAGE_DIR/config/managerd.json.example" << 'EOF'
cat > "$PACKAGE_DIR/config/safesightd.json.example" << 'EOF'
{
"listen": "0.0.0.0:18080",
"discovery_port": 35688,
@ -141,7 +141,7 @@ cat > "$PACKAGE_DIR/README-离线安装.txt" << 'EOF'
sudo ./scripts/3588admin install /usr/local/3588admin
3. 修改配置
sudo nano /opt/3588admin/config/managerd.json
sudo nano /opt/3588admin/config/safesightd.json
重点修改:
- agent_token: 修改为安全的随机字符串
@ -174,13 +174,13 @@ cat > "$PACKAGE_DIR/README-离线安装.txt" << 'EOF'
【文件位置】
安装目录: /opt/3588admin
配置文件: /opt/3588admin/config/managerd.json
日志文件: /opt/3588admin/logs/managerd.log
配置文件: /opt/3588admin/config/safesightd.json
日志文件: /opt/3588admin/logs/safesightd.log
【问题排查】
1. 查看日志: tail -f /opt/3588admin/logs/managerd.log
2. 检查配置: python3 -m json.tool /opt/3588admin/config/managerd.json
3. 检查端口: sudo netstat -tlnp | grep managerd
1. 查看日志: tail -f /opt/3588admin/logs/safesightd.log
2. 检查配置: python3 -m json.tool /opt/3588admin/config/safesightd.json
3. 检查端口: sudo netstat -tlnp | grep safesightd
【防火墙】
sudo ufw allow 18080/tcp # HTTP API

View File

@ -1,14 +1,14 @@
# 3588AdminBackend Windows 构建脚本
# SafeSight Control Windows 构建脚本
# 在 Windows 上交叉编译 Linux 版本并打包
# 配置
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$ProjectDir = Split-Path -Parent (Split-Path -Parent $ScriptDir)
$BuildDir = "$ProjectDir\build"
$PackageName = "3588AdminBackend-离线部署包-$(Get-Date -Format 'yyyyMMdd')"
$PackageName = "SafeSight Control-离线部署包-$(Get-Date -Format 'yyyyMMdd')"
$PackageDir = "$BuildDir\$PackageName"
Write-Host "========== 3588AdminBackend Windows 构建 ==========" -ForegroundColor Green
Write-Host "========== SafeSight Control Windows 构建 ==========" -ForegroundColor Green
Write-Host "项目目录: $ProjectDir"
Write-Host "构建目录: $BuildDir"
Write-Host ""
@ -45,9 +45,9 @@ $env:GOOS = "linux"
$env:GOARCH = "amd64"
$env:GOTOOLCHAIN = "local"
go build -ldflags="-s -w" -o "$PackageDir\bin\managerd" ./cmd/managerd
go build -ldflags="-s -w" -o "$PackageDir\bin\safesightd" ./cmd/safesightd
if ($LASTEXITCODE -ne 0 -or -not (Test-Path "$PackageDir\bin\managerd")) {
if ($LASTEXITCODE -ne 0 -or -not (Test-Path "$PackageDir\bin\safesightd")) {
Write-Host "错误: 编译失败" -ForegroundColor Red
exit 1
}
@ -56,8 +56,8 @@ Write-Host " 编译成功" -ForegroundColor Green
# 复制配置文件
Write-Host "[4/6] 复制配置文件..."
if (Test-Path "$ProjectDir\managerd.json") {
Copy-Item "$ProjectDir\managerd.json" "$PackageDir\config\managerd.json.example"
if (Test-Path "$ProjectDir\safesightd.json") {
Copy-Item "$ProjectDir\safesightd.json" "$PackageDir\config\safesightd.json.example"
} else {
@"
{
@ -68,7 +68,7 @@ if (Test-Path "$ProjectDir\managerd.json") {
"agent_token": "CHANGE_ME",
"concurrency": 5
}
"@ | Out-File -FilePath "$PackageDir\config\managerd.json.example" -Encoding UTF8
"@ | Out-File -FilePath "$PackageDir\config\safesightd.json.example" -Encoding UTF8
}
# 复制模板文件(如果有)
@ -79,17 +79,17 @@ if (Test-Path "$ProjectDir\templates") {
# 复制脚本
Write-Host "[5/6] 复制部署脚本..."
Copy-Item "$ScriptDir\3588admin" "$PackageDir\scripts\"
Copy-Item "$ScriptDir\3588admin.service" "$PackageDir\systemd\"
Copy-Item "$ScriptDir\safesightd" "$PackageDir\scripts\"
Copy-Item "$ScriptDir\safesightd.service" "$PackageDir\systemd\"
# 修复换行符为 LFUnix 格式)
$scriptContent = Get-Content "$PackageDir\scripts\3588admin" -Raw
$scriptContent = Get-Content "$PackageDir\scripts\safesightd" -Raw
$scriptContent = $scriptContent -replace "`r`n", "`n"
Set-Content -Path "$PackageDir\scripts\3588admin" -Value $scriptContent -NoNewLine
Set-Content -Path "$PackageDir\scripts\safesightd" -Value $scriptContent -NoNewLine
$serviceContent = Get-Content "$PackageDir\systemd\3588admin.service" -Raw
$serviceContent = Get-Content "$PackageDir\systemd\safesightd.service" -Raw
$serviceContent = $serviceContent -replace "`r`n", "`n"
Set-Content -Path "$PackageDir\systemd\3588admin.service" -Value $serviceContent -NoNewLine
Set-Content -Path "$PackageDir\systemd\safesightd.service" -Value $serviceContent -NoNewLine
# 复制文档
Write-Host "[6/6] 复制文档..."
@ -107,7 +107,7 @@ if (Test-Path "$ProjectDir\API_Device_RemoteMgmt_InterfaceTable.md") {
# 创建版本信息
@"
3588AdminBackend 离线部署包
SafeSight Control 离线部署包
版本: 1.0.0
构建时间: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
Go版本: $GoVersion
@ -118,7 +118,7 @@ Go版本: $GoVersion
# 创建快速安装说明
@"
3588AdminBackend 离线部署包 - 快速安装指南
SafeSight Control 离线部署包 - 快速安装指南
系统要求
@ -141,44 +141,44 @@ Go版本: $GoVersion
cd $PackageName/
3. 执行安装
sudo ./scripts/3588admin install
sudo ./scripts/safesightd install
4. 修改配置
sudo nano /opt/3588admin/config/managerd.json
sudo nano /opt/safesightd/config/safesightd.json
重点修改:
- agent_token: 修改为安全的随机字符串
- listen: 如需外部访问改为 "0.0.0.0:18080"
5. 启动服务
sudo 3588admin start
sudo safesightd start
6. 验证安装
sudo 3588admin status
sudo safesightd status
常用命令
3588admin start - 启动服务
3588admin stop - 停止服务
3588admin status - 查看状态
3588admin restart - 重启服务
3588admin upgrade - 升级服务
3588admin uninstall - 卸载服务
3588admin check - 检查环境
safesightd start - 启动服务
safesightd stop - 停止服务
safesightd status - 查看状态
safesightd restart - 重启服务
safesightd upgrade - 升级服务
safesightd uninstall - 卸载服务
safesightd check - 检查环境
快捷命令
3588admin-start - 启动服务
3588admin-stop - 停止服务
3588admin-status - 查看状态
safesightd-start - 启动服务
safesightd-stop - 停止服务
safesightd-status - 查看状态
文件位置
安装目录: /opt/3588admin
配置文件: /opt/3588admin/config/managerd.json
日志文件: /opt/3588admin/logs/managerd.log
安装目录: /opt/safesightd
配置文件: /opt/safesightd/config/safesightd.json
日志文件: /opt/safesightd/logs/safesightd.log
问题排查
1. 查看日志: tail -f /opt/3588admin/logs/managerd.log
2. 检查配置: python3 -m json.tool /opt/3588admin/config/managerd.json
3. 检查端口: sudo netstat -tlnp | grep managerd
1. 查看日志: tail -f /opt/safesightd/logs/safesightd.log
2. 检查配置: python3 -m json.tool /opt/safesightd/config/safesightd.json
3. 检查端口: sudo netstat -tlnp | grep safesightd
防火墙
sudo ufw allow 18080/tcp # HTTP API
@ -209,7 +209,7 @@ if ($LASTEXITCODE -eq 0 -and (Test-Path "$PackageName.tar.gz")) {
Write-Host ""
Write-Host "使用说明:"
Write-Host "1. 将 $PackageName.tar.gz 上传到 Ubuntu 服务器"
Write-Host "2. 解压并运行: sudo ./scripts/3588admin install"
Write-Host "2. 解压并运行: sudo ./scripts/safesightd install"
Write-Host ""
} else {
Write-Host "错误: 打包失败" -ForegroundColor Red

View File

@ -1,5 +1,5 @@
#!/bin/bash
# 3588AdminBackend 统一管理脚本
# SafeSight Control 统一管理脚本
# 提供安装、状态、升级、卸载等功能
set -e
@ -12,21 +12,21 @@ BLUE='\033[0;34m'
NC='\033[0m'
# 配置
APP_NAME="managerd"
APP_NAME="safesightd"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
DEFAULT_INSTALL_DIR="/opt/3588admin"
DEFAULT_INSTALL_DIR="/opt/safesightd"
INSTALL_DIR="${DEFAULT_INSTALL_DIR}"
PID_FILE="$INSTALL_DIR/$APP_NAME.pid"
# 显示帮助
cmd_help() {
cat << EOF
3588AdminBackend 管理脚本
SafeSight Control 管理脚本
用法: $0 <命令> [选项]
命令:
install [目录] 安装服务 (默认: /opt/3588admin)
install [目录] 安装服务 (默认: /opt/safesightd)
status 查看服务状态
start 启动服务
stop 停止服务
@ -69,7 +69,7 @@ get_install_dir() {
# 环境检查
cmd_check() {
echo "========== 3588AdminBackend 环境检查 =========="
echo "========== SafeSight Control 环境检查 =========="
echo ""
PASS=0
@ -233,7 +233,7 @@ cmd_install() {
local CURRENT_DIR="$SCRIPT_DIR"
local BASE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
echo "========== 3588AdminBackend 离线安装 =========="
echo "========== SafeSight Control 离线安装 =========="
echo "安装目录: $INSTALL_DIR"
echo ""
@ -247,10 +247,10 @@ cmd_install() {
exit 1
fi
# 备份现有配置
if [ -f "$INSTALL_DIR/config/managerd.json" ]; then
local BACKUP_FILE="$INSTALL_DIR/config/managerd.json.bak.$(date +%Y%m%d%H%M%S)"
if [ -f "$INSTALL_DIR/config/safesightd.json" ]; then
local BACKUP_FILE="$INSTALL_DIR/config/safesightd.json.bak.$(date +%Y%m%d%H%M%S)"
echo "备份现有配置到: $BACKUP_FILE"
cp "$INSTALL_DIR/config/managerd.json" "$BACKUP_FILE"
cp "$INSTALL_DIR/config/safesightd.json" "$BACKUP_FILE"
fi
fi
@ -273,18 +273,18 @@ cmd_install() {
echo " 复制二进制文件..."
mkdir -p "$INSTALL_DIR/bin"
cp "$BASE_DIR/bin/managerd" "$INSTALL_DIR/bin/" 2>/dev/null || {
echo -e "${RED}错误: 未找到 managerd 二进制文件${NC}"
cp "$BASE_DIR/bin/safesightd" "$INSTALL_DIR/bin/" 2>/dev/null || {
echo -e "${RED}错误: 未找到 safesightd 二进制文件${NC}"
echo "请确保在打包前已编译"
exit 1
}
echo " 复制配置文件..."
mkdir -p "$INSTALL_DIR/config"
if [ -f "$BASE_DIR/config/managerd.json" ]; then
cp "$BASE_DIR/config/managerd.json" "$INSTALL_DIR/config/"
elif [ -f "$BASE_DIR/config/managerd.json.example" ]; then
cp "$BASE_DIR/config/managerd.json.example" "$INSTALL_DIR/config/"
if [ -f "$BASE_DIR/config/safesightd.json" ]; then
cp "$BASE_DIR/config/safesightd.json" "$INSTALL_DIR/config/"
elif [ -f "$BASE_DIR/config/safesightd.json.example" ]; then
cp "$BASE_DIR/config/safesightd.json.example" "$INSTALL_DIR/config/"
fi
# 复制模板文件
@ -295,7 +295,7 @@ cmd_install() {
# 3. 设置权限
echo "[3/5] 设置权限..."
chmod +x "$INSTALL_DIR/bin/managerd"
chmod +x "$INSTALL_DIR/bin/safesightd"
mkdir -p "$INSTALL_DIR/logs" || {
echo -e "${RED}错误: 无法创建日志目录 $INSTALL_DIR/logs${NC}"
exit 1
@ -307,8 +307,8 @@ cmd_install() {
# 4. 初始化配置文件
echo "[4/5] 初始化配置文件..."
if [ ! -f "$INSTALL_DIR/config/managerd.json" ]; then
cat > "$INSTALL_DIR/config/managerd.json" << 'EOF'
if [ ! -f "$INSTALL_DIR/config/safesightd.json" ]; then
cat > "$INSTALL_DIR/config/safesightd.json" << 'EOF'
{
"listen": "0.0.0.0:18080",
"discovery_port": 35688,
@ -319,7 +319,7 @@ cmd_install() {
}
EOF
echo -e " ${GREEN}已创建默认配置文件${NC}"
echo -e " ${YELLOW}⚠️ 请编辑 $INSTALL_DIR/config/managerd.json 修改配置(特别是 agent_token${NC}"
echo -e " ${YELLOW}⚠️ 请编辑 $INSTALL_DIR/config/safesightd.json 修改配置(特别是 agent_token${NC}"
else
echo " 配置文件已存在,保留现有配置"
fi
@ -340,7 +340,7 @@ EOF
if [ -f "$CURRENT_DIR/3588admin.service" ]; then
echo "[可选] 安装 Systemd 服务..."
cp "$CURRENT_DIR/3588admin.service" /etc/systemd/system/
sed -i "s|/opt/3588admin|$INSTALL_DIR|g" /etc/systemd/system/3588admin.service
sed -i "s|/opt/safesightd|$INSTALL_DIR|g" /etc/systemd/system/3588admin.service
systemctl daemon-reload
echo " 已安装 Systemd 服务"
fi
@ -358,7 +358,7 @@ EOF
echo " 3588admin uninstall - 卸载服务"
echo ""
echo -e "${YELLOW}重要提示:${NC}"
echo " 1. 请先编辑配置文件: $INSTALL_DIR/config/managerd.json"
echo " 1. 请先编辑配置文件: $INSTALL_DIR/config/safesightd.json"
echo " 2. 务必修改 agent_token 为安全的随机字符串"
echo ""
echo -e "${YELLOW}防火墙配置:${NC}"
@ -373,10 +373,10 @@ cmd_start() {
get_install_dir "$1"
local BIN_PATH="$INSTALL_DIR/bin/$APP_NAME"
local CONFIG_PATH="$INSTALL_DIR/config/managerd.json"
local CONFIG_PATH="$INSTALL_DIR/config/safesightd.json"
local LOG_DIR="$INSTALL_DIR/logs"
echo "========== 启动 3588AdminBackend =========="
echo "========== 启动 SafeSight Control =========="
echo "应用目录: $INSTALL_DIR"
echo "配置文件: $CONFIG_PATH"
echo ""
@ -468,7 +468,7 @@ cmd_start() {
cmd_stop() {
get_install_dir "$1"
echo "========== 停止 3588AdminBackend =========="
echo "========== 停止 SafeSight Control =========="
if [ -f "$PID_FILE" ]; then
local PID=$(cat "$PID_FILE")
@ -528,10 +528,10 @@ cmd_status() {
get_install_dir "$1"
local BIN_PATH="$INSTALL_DIR/bin/$APP_NAME"
local CONFIG_PATH="$INSTALL_DIR/config/managerd.json"
local CONFIG_PATH="$INSTALL_DIR/config/safesightd.json"
local LOG_DIR="$INSTALL_DIR/logs"
echo "========== 3588AdminBackend 状态 =========="
echo "========== SafeSight Control 状态 =========="
echo ""
echo "基本信息:"
echo " 应用目录: $INSTALL_DIR"
@ -640,7 +640,7 @@ cmd_upgrade() {
local SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
local BACKUP_DIR="/root/3588admin-backup-$(date +%Y%m%d%H%M%S)"
echo "========== 3588AdminBackend 升级 =========="
echo "========== SafeSight Control 升级 =========="
echo "安装目录: $INSTALL_DIR"
echo "备份目录: $BACKUP_DIR"
echo ""
@ -653,7 +653,7 @@ cmd_upgrade() {
fi
# 确认
read -p "确定要升级 3588AdminBackend? (y/N): " -n 1 -r
read -p "确定要升级 SafeSight Control? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "升级已取消"
@ -669,8 +669,8 @@ cmd_upgrade() {
echo "[2/5] 备份现有数据..."
mkdir -p "$BACKUP_DIR"
if [ -f "$INSTALL_DIR/config/managerd.json" ]; then
cp "$INSTALL_DIR/config/managerd.json" "$BACKUP_DIR/"
if [ -f "$INSTALL_DIR/config/safesightd.json" ]; then
cp "$INSTALL_DIR/config/safesightd.json" "$BACKUP_DIR/"
echo " 已备份配置文件"
fi
@ -679,17 +679,17 @@ cmd_upgrade() {
echo " 已备份日志文件"
fi
if [ -f "$INSTALL_DIR/bin/managerd" ]; then
cp "$INSTALL_DIR/bin/managerd" "$BACKUP_DIR/managerd.old"
if [ -f "$INSTALL_DIR/bin/safesightd" ]; then
cp "$INSTALL_DIR/bin/safesightd" "$BACKUP_DIR/safesightd.old"
echo " 已备份旧版本二进制"
fi
# 3. 更新二进制文件
echo "[3/5] 更新二进制文件..."
local BASE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
if [ -f "$BASE_DIR/bin/managerd" ]; then
cp "$BASE_DIR/bin/managerd" "$INSTALL_DIR/bin/managerd"
chmod +x "$INSTALL_DIR/bin/managerd"
if [ -f "$BASE_DIR/bin/safesightd" ]; then
cp "$BASE_DIR/bin/safesightd" "$INSTALL_DIR/bin/safesightd"
chmod +x "$INSTALL_DIR/bin/safesightd"
echo -e " ${GREEN}已更新二进制文件${NC}"
else
echo -e "${YELLOW}警告: 未找到新的二进制文件${NC}"
@ -715,12 +715,12 @@ cmd_uninstall() {
check_root
get_install_dir "$1"
echo "========== 3588AdminBackend 卸载 =========="
echo "========== SafeSight Control 卸载 =========="
echo "安装目录: $INSTALL_DIR"
echo ""
# 确认
read -p "确定要完全卸载 3588AdminBackend? 此操作不可恢复! (y/N): " -n 1 -r
read -p "确定要完全卸载 SafeSight Control? 此操作不可恢复! (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "卸载已取消"
@ -759,10 +759,10 @@ cmd_uninstall() {
echo "[4/4] 删除安装文件..."
local BACKUP_DIR=""
if [ -d "$INSTALL_DIR" ]; then
if [ -f "$INSTALL_DIR/config/managerd.json" ]; then
if [ -f "$INSTALL_DIR/config/safesightd.json" ]; then
BACKUP_DIR="/root/3588admin-backup-$(date +%Y%m%d%H%M%S)"
mkdir -p "$BACKUP_DIR"
cp "$INSTALL_DIR/config/managerd.json" "$BACKUP_DIR/"
cp "$INSTALL_DIR/config/safesightd.json" "$BACKUP_DIR/"
echo " 配置已备份到: $BACKUP_DIR/"
fi
@ -773,7 +773,7 @@ cmd_uninstall() {
echo ""
echo -e "${GREEN}========== 卸载完成 ==========${NC}"
if [ -n "$BACKUP_DIR" ]; then
echo "配置文件备份: $BACKUP_DIR/managerd.json"
echo "配置文件备份: $BACKUP_DIR/safesightd.json"
fi
}

View File

@ -1,6 +1,6 @@
[Unit]
Description=3588AdminBackend Service
Documentation=https://github.com/your-org/3588AdminBackend
Description=SafeSight Control Service
Documentation=https://github.com/your-org/SafeSight Control
After=network.target network-online.target
Wants=network-online.target
@ -8,8 +8,8 @@ Wants=network-online.target
Type=simple
User=root
Group=root
WorkingDirectory=/opt/3588admin
ExecStart=/opt/3588admin/bin/managerd /opt/3588admin/config/managerd.json
WorkingDirectory=/opt/safesightd
ExecStart=/opt/safesightd/bin/safesightd /opt/safesightd/config/safesightd.json
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
KillSignal=SIGTERM

View File

@ -6,7 +6,7 @@ set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
CONFIG_FILE="$PROJECT_DIR/managerd.json"
CONFIG_FILE="$PROJECT_DIR/safesightd.json"
echo "========== 3588AdminBackend 开发运行 =========="
echo "项目目录: $PROJECT_DIR"
@ -53,4 +53,4 @@ echo "按 Ctrl+C 停止服务"
echo ""
# 使用 go run 直接运行
go run ./cmd/managerd -config "$CONFIG_FILE"
go run ./cmd/safesightd -config "$CONFIG_FILE"

View File

@ -1,5 +0,0 @@
@echo off
setlocal
chcp 65001 >nul
pwsh -NoProfile -ExecutionPolicy Bypass -File "%~dp0managerd.ps1" %*
exit /b %ERRORLEVEL%

5
scripts/safesightd.bat Normal file
View File

@ -0,0 +1,5 @@
@echo off
setlocal
chcp 65001 >nul
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0safesightd.ps1" %*
exit /b %ERRORLEVEL%

View File

@ -7,18 +7,18 @@ $ErrorActionPreference = "Stop"
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$rootDir = (Resolve-Path (Join-Path $scriptDir "..")).Path
$exePath = Join-Path $rootDir "managerd.exe"
$configPath = Join-Path $rootDir "managerd.json"
$logPath = Join-Path $rootDir "managerd.local.log"
$errLogPath = Join-Path $rootDir "managerd.local.err.log"
$exePath = Join-Path $rootDir "safesightd.exe"
$configPath = Join-Path $rootDir "safesightd.json"
$logPath = Join-Path $rootDir "safesightd.local.log"
$errLogPath = Join-Path $rootDir "safesightd.local.err.log"
function Write-Info {
param([string]$Message)
Write-Host "[managerd] $Message"
Write-Host "[safesightd] $Message"
}
function Get-ManagerdProcess {
return Get-Process -Name "managerd" -ErrorAction SilentlyContinue | Select-Object -First 1
function Get-SafesightdProcess {
return Get-Process -Name "safesightd" -ErrorAction SilentlyContinue | Select-Object -First 1
}
function Get-HealthInfo {
@ -64,7 +64,7 @@ function Wait-ForHealthOk {
function Wait-ForStop {
param([int]$Retries = 8)
for ($i = 0; $i -lt $Retries; $i++) {
if (-not (Get-ManagerdProcess)) {
if (-not (Get-SafesightdProcess)) {
return $true
}
Start-Sleep -Seconds 1
@ -82,7 +82,7 @@ function Show-Health {
}
function Invoke-Build {
Write-Info "编译 managerd.exe ..."
Write-Info "编译 safesightd.exe ..."
Push-Location $rootDir
try {
& go build -o $exePath .\cmd\managerd
@ -97,17 +97,17 @@ function Invoke-Build {
function Invoke-Start {
if (-not (Test-Path $exePath)) {
throw "未找到 managerd.exe请先执行 build"
throw "未找到 safesightd.exe请先执行 build"
}
$proc = Get-ManagerdProcess
$proc = Get-SafesightdProcess
if ($proc) {
Write-Info ("已在运行PID={0}" -f $proc.Id)
Show-Health
return
}
Write-Info "启动 managerd.exe ..."
Write-Info "启动 safesightd.exe ..."
Start-Process -FilePath $exePath `
-ArgumentList $configPath `
-WorkingDirectory $rootDir `
@ -116,7 +116,7 @@ function Invoke-Start {
-WindowStyle Hidden
$null = Wait-ForHealthOk
$proc = Get-ManagerdProcess
$proc = Get-SafesightdProcess
if (-not $proc) {
throw "进程未启动成功"
}
@ -126,13 +126,13 @@ function Invoke-Start {
}
function Invoke-Stop {
$proc = Get-ManagerdProcess
$proc = Get-SafesightdProcess
if (-not $proc) {
Write-Info "当前未运行"
return
}
Write-Info ("停止 managerd.exePID={0} ..." -f $proc.Id)
Write-Info ("停止 safesightd.exePID={0} ..." -f $proc.Id)
Stop-Process -Id $proc.Id -Force
if (-not (Wait-ForStop)) {
throw "进程停止超时"
@ -146,7 +146,7 @@ function Invoke-Restart {
}
function Show-Status {
$proc = Get-ManagerdProcess
$proc = Get-SafesightdProcess
if ($proc) {
Write-Info ("进程状态: running (PID={0})" -f $proc.Id)
} else {
@ -166,9 +166,9 @@ function Show-Usage {
" managerd.bat help"
""
"说明:"
" build 编译 .\cmd\managerd 到 managerd.exe"
" start 启动现有 managerd.exe并做健康检查"
" stop 停止当前仓库对应的 managerd.exe"
" build 编译 .\cmd\managerd 到 safesightd.exe"
" start 启动现有 safesightd.exe并做健康检查"
" stop 停止当前仓库对应的 safesightd.exe"
" restart 先 stop 再 start"
" status 查看进程状态与 /health"
" help 显示帮助"