From b4ef331be8c69dfde33a4aa5af57d0c8e74d7616 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Sun, 26 Jul 2026 11:52:21 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20alarmChannelSource=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E5=A4=84=E7=90=86=E9=80=9A=E9=81=93=E5=90=8E=E7=BC=80=EF=BC=8C?= =?UTF-8?q?CSV=E5=92=8C=E5=88=97=E8=A1=A8=E5=85=B1=E7=94=A8=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/web/ui.go | 50 ++++++++++++++++---------- internal/web/ui/templates/alarms.html | 2 +- safesightd-linux-arm64 | Bin 15270050 -> 15270050 bytes 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/internal/web/ui.go b/internal/web/ui.go index 01f3e83..f564f20 100644 --- a/internal/web/ui.go +++ b/internal/web/ui.go @@ -595,6 +595,7 @@ func NewUI(discovery *service.DiscoveryService, registry *service.RegistryServic return "" } }, + "alarmChannelSource": alarmChannelSource, "statusLabel": func(v string) string { switch strings.TrimSpace(v) { case "unacknowledged": @@ -2734,13 +2735,26 @@ func (u *UI) apiAlarmExport(w http.ResponseWriter, r *http.Request) { } records := u.alarmCollector.ExportFiltered(from, to, deviceID, ruleType) - // Lookup device info for names and IPs + // Device name/IP lookup deviceMap := map[string]*models.Device{} for _, d := range u.registry.GetDevices() { if d != nil { deviceMap[d.DeviceID] = d } } + // Channel→source lookup (channel = recognition unit name) + channelSources := map[string]string{} + if u.preview != nil { + if assignments, _ := u.preview.ListDeviceAssignments(); len(assignments) > 0 { + for _, a := range assignments { + for _, ref := range a.RecognitionUnits { + if unit, err := u.preview.GetRecognitionUnit(ref); err == nil && unit != nil && unit.VideoSourceRef != "" { + channelSources[unit.Name] = unit.VideoSourceRef + } + } + } + } + } w.Header().Set("Content-Type", "text/csv; charset=utf-8") w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="alarms_%s_%s.csv"`, from, to)) @@ -2750,27 +2764,11 @@ func (u *UI) apiAlarmExport(w http.ResponseWriter, r *http.Request) { for _, a := range records { devName := a.DeviceID devIP := "" - sourceName := a.Channel if dev, ok := deviceMap[a.DeviceID]; ok { devName = dev.DisplayName() devIP = dev.IP } - // Resolve channel to video source name - if u.preview != nil { - if assignment, err := u.preview.GetDeviceAssignment(a.DeviceID); err == nil && assignment != nil { - for _, ref := range assignment.RecognitionUnits { - if unit, err := u.preview.GetRecognitionUnit(ref); err == nil && unit != nil { - if unit.Name == a.Channel && unit.VideoSourceRef != "" { - sourceName = unit.VideoSourceRef - break - } - } - } - } - } - if sourceName == "" { - sourceName = a.Channel - } + sourceName := alarmChannelSource(a.Channel, channelSources) fmt.Fprintf(w, "%s,%s,%s,%s,%s,%s,%s\n", escapeCSV(formatAlarmTime(a.Timestamp)), escapeCSV(devName), escapeCSV(devIP), escapeCSV(sourceName), escapeCSV(alarmRuleLabel(a.RuleName)), escapeCSV(a.ObjectLabel), @@ -2817,6 +2815,22 @@ func alarmStatusLabel(v string) string { } } +// alarmChannelSource resolves an alarm channel (e.g. "cam1_alarm_violation") to its video source name. +// The channel format is {instanceName}_alarm_{type}, where instanceName matches the recognition unit. +func alarmChannelSource(channel string, sources map[string]string) string { + // Try exact match first + if src, ok := sources[channel]; ok { + return src + } + // Strip suffix: cam1_alarm_violation → cam1 + if idx := strings.LastIndex(channel, "_alarm_"); idx > 0 { + if src, ok := sources[channel[:idx]]; ok { + return src + } + } + return channel +} + func formatAlarmTime(t string) string { // 2026-07-22T13:03:20+08:00 → 2026-07-22 13:03:20 s := strings.TrimSpace(t) diff --git a/internal/web/ui/templates/alarms.html b/internal/web/ui/templates/alarms.html index 8f6fb9d..c3838e0 100644 --- a/internal/web/ui/templates/alarms.html +++ b/internal/web/ui/templates/alarms.html @@ -62,7 +62,7 @@ {{index $.AlarmDeviceNames .DeviceID}}
vYb%8GRaYaMtiplXcMbFQr0T0B&2v-n%R=%Z@Z+-sp^y |p%nu8v+88r0r
zGIa6gKOT26Q()tR$$K$suy)qFIPmEj&`MM0+;}PwG`XNz{Fno+{JLQBTf$<^g`NLP
zi9$qK*CfhWf?>A(9j&`B#X$CZx&_oym+2Znhfxcw?M%m)Vv%gW^sx{$7IsOLxKwmh
zC-BXBx)RX9W&}`~ppg c_^UVFdkMmLZ&6<
zk89a!ujM2f1aox|039!XT+92w$3Ica{c;07( Wa%j5URri*zP@fnPrc
z3Wr~im_`F)DSaI3#h!lpI1^rC45!j@rjhJUJl!7$8RPpZ0pm?!4qf>M%nN0lt=Et8
zU1<1t%&r?wR50Fzmx)`^w(%&B*LYoC0UXkZd&M+Ce2}B`6|@5#Iw|1Lox87?tPBPQ
zUw2KPWIA22MxR@2QMG8|Q?RHBw1fVCXO##z6z3DbbA_P0;r|N2j9($(f!5|qz#%(v
zG@~ioQUQldw>r9NSCa*RyX66gxq~R+x0kSL;r}nfe}wd^=_^);PjpW D))qh8mxE7t#n3mZbYNWcR{Q>~t6=`eTg40l!WXxtU>v>CS@
zXj>VNv3RInoBQIgji9QhF+AN6Njskg$6Yvg^0av!%xhG(+C05{i)N-yVCnK57p5Q`
zM6epFDL2o>GI$OaAYm?exCW14aI8hQXz)f|qn7_52G7JnZ4C}jHq8v^&)`@fJ_mR^
zyS?vI&8|;3RGEhuuo Sao;V>Sw842vORC)o9Me3TGdT}s
zNs1T7<4NZ`<3VCG3B!a#_?k6bh@hW`33uolRQxyL85DT!;GKt0&8`|Ppv4J7JThIE
zAjI4T_E-V$$aP*?QIQ?F6&3NsggYV*n(yTK%+J%}@7nLYBoEap iIm+9mo7w{f^e
zJG5K`&9I5l!Do%imuGwE*&I(Hsj`U?!Fb2%BgC!rira4!b6|26{lspj9rO+q*_>d&
z8dwZdxA=<>GiYQWhdxa9KrxIy2o&!IFeykphh6~D>asgRC_Y%6e)&)Yrg9vLpvqt|
zIq~u#3QZjELUrK~g>nYUHtQ05S+(LYML{RVgwb~hf%HePI8i@>QbWXK_QqaGhzS2;
z9o{M&BreE$!AE%l0}da6kPEXhJawjrM&baot6#9?gz^x^=LdTMV{gxDPU~G*J1Z%B
z= f$
zX4SC5FJq*0_+Gg`br0sP+1i?g+K@T=O)m?K6!{W@iIn*g81tb5_J#MmSYV)97L23a
zh#Qijaf{>hecX-!%{y7Sbf~nKq(oTIBX{t^m+X+jFjYUsEZz=0>$5|efhsw@gI7>w
zy_5CxEi23Et%TnKn
zCL!?|;r4`dsD@FqBThlrt%bl9K^N9V6-B&+@8j5jlW^e11*gu3#U$GIjF1|0g{(B_aOu;S
ue5(7SWZc-CrZ`d2&Zr1=VYCbdDNQYpk@HyC5-a0OV;aT8$rte%84u1877mkf
z9Y1i
zzu?Qa=F#&edT9*LFfX}3!yguh3gnmz9
cX5u!z3u>*Z2+O+1yoiXl#*=2xY}DC{lw(izL*HB2!UcRCP=M2GO+^5L0%
z=^9h|6P=m<8b%~Dg7ROJD){U@(U*18xvXyPN1P+1zAmNux)p_PcwK6AJci}{4Rq^O
zDwV$>eP)@a3ie75vt_Q}QA`u;^B2Jw=)Mo0e_qR6p|F8XH1d${=Cp|#CBG@9_L2<0
zo{pWOyh1F=Z%WTu1e4y9_B;Gwi+)?$;;Vse9F@N<%|{=#zbzd^Qw~-`DZ)f*u9lWD
z^4uEf4X%af5vvxMKCPW=fxZN%Pc$YXUDRS-7$v?SXpWttZX?-CA&Sf0tT1VP&0`o?^yy
zV(0H>#(YkNg
<$1_v%Sg6@U`8|)
zdrtcU4qs%$l3<