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