diff --git a/internal/web/ui.go b/internal/web/ui.go
index e09ad3e..7e4d438 100644
--- a/internal/web/ui.go
+++ b/internal/web/ui.go
@@ -773,6 +773,7 @@ func (u *UI) Routes() (chi.Router, error) {
r.Post("/wizard/create-source", u.actionWizardCreateSource)
r.Get("/devices", u.pageDevices)
r.Post("/devices/cleanup-offline", u.actionDevicesCleanupOffline)
+ r.Get("/devices/export-channels", u.actionDevicesExportChannels)
r.Get("/devices/{id}/control", u.pageDeviceControl)
r.Get("/plans", u.redirectPlansToSceneTemplates)
r.Get("/plans/{name}", u.redirectPlanToSceneTemplate)
@@ -1667,6 +1668,52 @@ func (u *UI) actionDevicesCleanupOffline(w http.ResponseWriter, r *http.Request)
http.Redirect(w, r, "/devices?msg="+urlQueryEscape(fmt.Sprintf("已清理 %d 台离线设备", deleted)), http.StatusFound)
}
+func (u *UI) actionDevicesExportChannels(w http.ResponseWriter, r *http.Request) {
+ u.ensureDevicesLoaded()
+ devices := u.registry.GetDevices()
+
+ w.Header().Set("Content-Type", "text/csv; charset=utf-8")
+ w.Header().Set("Content-Disposition", "attachment; filename=video_channels.csv")
+ w.Write([]byte("\xef\xbb\xbf"))
+ w.Write([]byte("设备名,IP,通道名,视频源,RTSP地址,HLS地址\n"))
+
+ if u.preview == nil {
+ return
+ }
+ units, _ := u.preview.ListRecognitionUnits()
+ for _, unit := range units {
+ // Find which device this unit is assigned to
+ devName := ""
+ devIP := ""
+ for _, dev := range devices {
+ if dev == nil {
+ continue
+ }
+ if assignment, err := u.preview.GetDeviceAssignment(dev.DeviceID); err == nil && assignment != nil {
+ for _, ref := range assignment.RecognitionUnits {
+ if ref == unit.Ref {
+ devName = dev.DisplayName()
+ devIP = dev.IP
+ break
+ }
+ }
+ }
+ if devName != "" {
+ break
+ }
+ }
+ rtspPort := unit.RTSPPort
+ if rtspPort == "" {
+ rtspPort = "8555"
+ }
+ rtsp := fmt.Sprintf("rtsp://%s:%s/live/%s", devIP, rtspPort, unit.Name)
+ hls := fmt.Sprintf("http://%s:9000/hls/%s/index.m3u8", devIP, unit.Name)
+ fmt.Fprintf(w, "%s,%s,%s,%s,%s,%s\n",
+ escapeCSV(devName), escapeCSV(devIP), escapeCSV(unit.Name),
+ escapeCSV(unit.VideoSourceRef), escapeCSV(rtsp), escapeCSV(hls))
+ }
+}
+
func (u *UI) pageDeviceAdd(w http.ResponseWriter, r *http.Request) {
u.render(w, r, "device_add", PageData{Title: "新增设备"})
}
diff --git a/internal/web/ui/templates/devices.html b/internal/web/ui/templates/devices.html
index e31639d..9341407 100644
--- a/internal/web/ui/templates/devices.html
+++ b/internal/web/ui/templates/devices.html
@@ -49,6 +49,7 @@
{{end}}
下发设备分配
+ 导出视频地址
清空选择
diff --git a/safesightd-linux-arm64 b/safesightd-linux-arm64
index 132cae0..36a7b4a 100644
Binary files a/safesightd-linux-arm64 and b/safesightd-linux-arm64 differ