@calebcall/camera-ui-notify 0.5.6 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +50 -3
- package/bundle.zip +0 -0
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,62 @@ All notable changes to **Notify** are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.6.1] - 2026-08-02
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Grafana IRM mode now renders correctly on a `grafana_alerting` integration** ([#31](https://github.com/calebcall/camera-ui-notify/issues/31)).
|
|
13
|
+
0.6.0 sent only Grafana OnCall's formatted-webhook field set. IRM renders each alert group
|
|
14
|
+
through Jinja2 templates chosen by the integration's *type*, and a `grafana_alerting`
|
|
15
|
+
integration's templates read `payload.status` and `payload.alerts[]` — neither of which we sent.
|
|
16
|
+
The result was an alert group that arrived intact but displayed as
|
|
17
|
+
`Status: Unknown ⚠️ (Template Warning: 'dict object' has no attribute 'alerts')`, with
|
|
18
|
+
`numFiring`/`numResolved` showing IRM's zero defaults for the absent `alerts` array.
|
|
19
|
+
|
|
20
|
+
The payload is now a union of both shapes: Grafana Alerting's documented webhook envelope
|
|
21
|
+
(`receiver`, `status`, `alerts[]` carrying labels, annotations, `startsAt`/`endsAt`,
|
|
22
|
+
`generatorURL`, `fingerprint` and `imageURL`, plus `groupLabels`, `commonLabels`,
|
|
23
|
+
`commonAnnotations`, `externalURL`, `version`, `groupKey`, `truncatedAlerts`) alongside the
|
|
24
|
+
formatted-webhook fields 0.6.0 already sent. `title`, `message` and `state` are read by both and
|
|
25
|
+
are unchanged, so a **Webhook**-type integration configured against 0.6.0 keeps working exactly
|
|
26
|
+
as before — this is additive, not a replacement.
|
|
27
|
+
|
|
28
|
+
This was a design-time error rather than an implementation one: the spec specified the wrong
|
|
29
|
+
payload shape, and the unit tests could not catch it because they assert our body against a test
|
|
30
|
+
server that accepts anything. The envelope now follows the schema Grafana documents for its
|
|
31
|
+
webhook contact point.
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **Per-camera grouping for IRM alert groups.** `groupKey` is `camera.ui:<cameraId>`, falling back
|
|
36
|
+
to `camera.ui` when a notification names no camera, so a busy camera cannot bury a quiet one.
|
|
37
|
+
Each event keeps a distinct `fingerprint` within its group. IRM groups still do not auto-resolve
|
|
38
|
+
— that remains deliberate, since a follow-up `state: "ok"` would need a background timer.
|
|
39
|
+
- **The snapshot now reaches IRM through the documented `imageURL` alert field** as well as
|
|
40
|
+
`image_url`, so it renders under either integration type. Still only when the publisher supplied
|
|
41
|
+
a hosted `ImageURL`.
|
|
42
|
+
- **`externalURL`** is derived from the absolute deep link's scheme and host, so it needs no new
|
|
43
|
+
configuration; it is omitted when `base_url` is unset and the deep link is therefore relative.
|
|
44
|
+
|
|
45
|
+
## [0.6.0] - 2026-08-01
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- **Grafana backend** with three delivery modes selected by a **Mode** field:
|
|
50
|
+
- **Annotations** — a point-in-time, organization-wide annotation via `POST /api/annotations`,
|
|
51
|
+
tagged `camera.ui` / `camera:<id>` / `severity:<level>` plus any extra tags, so dashboards can
|
|
52
|
+
surface camera events through a tag-filtered annotation query.
|
|
53
|
+
- **Alerts** — a firing alert via `POST /api/alertmanager/grafana/api/v2/alerts`, routed by your
|
|
54
|
+
existing notification policies. `endsAt` is `startsAt + grafana_ttl` (default 300s) so Grafana
|
|
55
|
+
auto-resolves it with no second request, and a unique `event_id` label keeps Alertmanager from
|
|
56
|
+
deduplicating two detections on the same camera into one alert.
|
|
57
|
+
- **IRM** — one alert group per event via a Grafana IRM / OnCall inbound webhook, using the
|
|
58
|
+
formatted-webhook field set. The only Grafana mode that renders the snapshot, and only when the
|
|
59
|
+
publisher supplied a hosted `ImageURL`.
|
|
60
|
+
|
|
61
|
+
The integration URL for IRM embeds its own token, so it is a masked field and is stripped from
|
|
62
|
+
every error message, including transport failures.
|
|
63
|
+
|
|
8
64
|
## [0.5.6] - 2026-08-01
|
|
9
65
|
|
|
10
66
|
### Changed
|
package/README.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
A fully-local, multi-backend <a href="https://github.com/seydx/camera.ui">camera.ui</a> notifier
|
|
5
5
|
plugin — delivers notifications to a service you control (<a href="https://ntfy.sh">ntfy</a>,
|
|
6
|
-
<a href="https://gotify.net">Gotify</a>, Pushover, Telegram, Discord,
|
|
6
|
+
<a href="https://gotify.net">Gotify</a>, Pushover, Telegram, Discord,
|
|
7
|
+
<a href="https://grafana.com">Grafana</a>, or a generic webhook).
|
|
7
8
|
</p>
|
|
8
9
|
|
|
9
10
|
---
|
|
@@ -32,7 +33,7 @@ Adding a new backend later is **one new file** — `src/backend/<name>.go` imple
|
|
|
32
33
|
|
|
33
34
|
## Backends (v1)
|
|
34
35
|
|
|
35
|
-
Severity is mapped consistently across backends via `backend.PriorityScale`, which spreads camera.ui's four severity levels (`info` → `warn` → `error` → `critical`) evenly across each backend's native priority range, `info` at the low end and `critical` at the high end.
|
|
36
|
+
Severity is mapped consistently across backends via `backend.PriorityScale`, which spreads camera.ui's four severity levels (`info` → `warn` → `error` → `critical`) evenly across each backend's native priority range, `info` at the low end and `critical` at the high end. Grafana is the exception: no Grafana surface has a numeric priority, so severity travels verbatim as a label/tag.
|
|
36
37
|
|
|
37
38
|
### ntfy
|
|
38
39
|
|
|
@@ -102,8 +103,54 @@ Delivers to a channel via a Discord [webhook](https://support.discord.com/hc/en-
|
|
|
102
103
|
|
|
103
104
|
Delivery: a rich embed (title, body, severity color — blue/yellow/red) with the snapshot **image** attached; an absolute deep link makes the title a link.
|
|
104
105
|
|
|
106
|
+
### Grafana
|
|
107
|
+
|
|
108
|
+
Delivers to a [Grafana](https://grafana.com) instance. Grafana is not one ingest endpoint, so a
|
|
109
|
+
**Mode** field selects which surface receives the notification.
|
|
110
|
+
|
|
111
|
+
| Field | Required | Mode | Notes |
|
|
112
|
+
| ----------------------- | -------- | ----------------------- | --------------------------------------------------------- |
|
|
113
|
+
| `grafana_mode` | yes | — | `annotations`, `alerts`, or `irm`. Defaults to `annotations`. |
|
|
114
|
+
| `grafana_server` | yes | annotations, alerts | Base URL of the Grafana instance. Trailing `/` trimmed. |
|
|
115
|
+
| `grafana_token` | yes | annotations, alerts | Service-account token, sent as `Authorization: Bearer <token>`. |
|
|
116
|
+
| `grafana_tags` | no | annotations | Comma-separated extra tags. |
|
|
117
|
+
| `grafana_alertname` | no | alerts | `alertname` label. Defaults to `CameraUINotification`. |
|
|
118
|
+
| `grafana_ttl` | no | alerts | Seconds before Grafana auto-resolves the alert. Default `300`, minimum `30`. |
|
|
119
|
+
| `grafana_irm_url` | yes | irm | Inbound webhook URL of an IRM / OnCall integration. The token is in the URL, so it is masked and kept out of every error message. |
|
|
120
|
+
|
|
121
|
+
**Annotations** — `POST {server}/api/annotations` with a point-in-time, organization-wide
|
|
122
|
+
annotation tagged `camera.ui`, `camera:<id>`, `severity:<level>`, plus your extra tags. Surface it
|
|
123
|
+
on a dashboard with an annotation query filtered on the `camera.ui` tag; that survives dashboard
|
|
124
|
+
renames, which pinning to a dashboard UID would not. The tooltip text carries the title, the body,
|
|
125
|
+
and — when `base_url` is set — a link back to camera.ui.
|
|
126
|
+
|
|
127
|
+
**Alerts** — `POST {server}/api/alertmanager/grafana/api/v2/alerts`, so your existing notification
|
|
128
|
+
policies route the event. `endsAt` is `startsAt + grafana_ttl`, which lets Grafana auto-resolve the
|
|
129
|
+
alert without a second request. Labels are `alertname`, `source=camera.ui`, `severity` (camera.ui's
|
|
130
|
+
own four levels, verbatim), `camera`, and a unique `event_id` — the last of these matters, because
|
|
131
|
+
Alertmanager deduplicates on the label set and without it two detections on one camera inside the
|
|
132
|
+
TTL window would collapse into a single alert. The absolute deep link becomes `generatorURL`,
|
|
133
|
+
which Grafana shows as **Source**.
|
|
134
|
+
|
|
135
|
+
**IRM** — `POST {integration URL}`. IRM renders each alert group through templates chosen by the
|
|
136
|
+
integration's *type*, and the two types people actually create read different bodies, so the
|
|
137
|
+
payload carries both: Grafana Alerting's webhook envelope (`status`, `alerts[]` with
|
|
138
|
+
labels/annotations/`generatorURL`/`imageURL`, `groupKey`, `commonLabels`, `externalURL`) for a
|
|
139
|
+
**Grafana Alerting** integration, and OnCall's formatted-webhook fields (`alert_uid`, `image_url`,
|
|
140
|
+
`link_to_upstream_details`) for a **Webhook** integration. `title`, `message`, and
|
|
141
|
+
`state=alerting` are read by both. One body, correct under either type, nothing to configure.
|
|
142
|
+
|
|
143
|
+
Alert groups are keyed **per camera** — `camera.ui:<cameraId>`, falling back to `camera.ui` for a
|
|
144
|
+
notification that names no camera — so one busy camera can't bury a quiet one. Within a group each
|
|
145
|
+
event keeps its own `fingerprint`, so detections stay individually visible. Unlike Alerts mode,
|
|
146
|
+
IRM groups do **not** auto-resolve: there is no TTL and no follow-up `state: "ok"` request, so they
|
|
147
|
+
stay open until you resolve them.
|
|
148
|
+
|
|
105
149
|
> **Images:** ntfy, Pushover, Telegram, and Discord all render the detection snapshot. Gotify is
|
|
106
150
|
> text + link only (it needs a hosted image URL, which this fully-local plugin doesn't provide).
|
|
151
|
+
> Grafana renders one only in IRM mode, and only when the publisher supplied a hosted `ImageURL` —
|
|
152
|
+
> annotations have no image field at all, and alerts carry the URL as an `image_url` annotation
|
|
153
|
+
> that Grafana itself won't render but downstream notification templates can use.
|
|
107
154
|
|
|
108
155
|
> **Secrets in logs:** transport failures never log the bot token / webhook URL / other
|
|
109
156
|
> URL-embedded secret — request URLs are redacted from delivery errors.
|
|
@@ -113,7 +160,7 @@ Delivery: a rich embed (title, body, severity color — blue/yellow/red) with th
|
|
|
113
160
|
There is no "add device" flow. Instead, configure the plugin itself:
|
|
114
161
|
|
|
115
162
|
1. Open the **Notify** plugin's page in camera.ui (Plugins → Notify).
|
|
116
|
-
2. In its settings, pick a **Service** (`ntfy`, `Gotify`, `Generic webhook`, `Pushover`, `Telegram`, or `
|
|
163
|
+
2. In its settings, pick a **Service** (`ntfy`, `Gotify`, `Generic webhook`, `Pushover`, `Telegram`, `Discord`, or `Grafana`) from the dropdown built from the registered backends.
|
|
117
164
|
3. Fill in that service's fields — only the selected service's fields are shown; the rest are condition-gated out.
|
|
118
165
|
4. Save. The config is validated (`ParseTarget`) the next time a notification is dispatched; `getDevices` then synthesizes one delivery target from it, and notifications from any publisher are delivered there.
|
|
119
166
|
|
package/bundle.zip
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "Notify",
|
|
3
3
|
"name": "@calebcall/camera-ui-notify",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.1",
|
|
5
5
|
"description": "A fully-local, multi-backend camera.ui notifier plugin: delivers notifications to ntfy, Gotify, or a generic webhook, entirely on your own hardware with no cloud dependency.",
|
|
6
6
|
"author": "calebcall (https://github.com/calebcall/camera-ui-notify)",
|
|
7
7
|
"main": "./main.go",
|
|
@@ -48,14 +48,14 @@
|
|
|
48
48
|
"protocolLevel": 1
|
|
49
49
|
},
|
|
50
50
|
"optionalDependencies": {
|
|
51
|
-
"@calebcall/camera-ui-notify-darwin-arm64": "0.
|
|
52
|
-
"@calebcall/camera-ui-notify-darwin-amd64": "0.
|
|
53
|
-
"@calebcall/camera-ui-notify-linux-amd64": "0.
|
|
54
|
-
"@calebcall/camera-ui-notify-linux-arm64": "0.
|
|
55
|
-
"@calebcall/camera-ui-notify-windows-amd64": "0.
|
|
56
|
-
"@calebcall/camera-ui-notify-windows-arm64": "0.
|
|
57
|
-
"@calebcall/camera-ui-notify-linux-amd64-musl": "0.
|
|
58
|
-
"@calebcall/camera-ui-notify-linux-arm64-musl": "0.
|
|
51
|
+
"@calebcall/camera-ui-notify-darwin-arm64": "0.6.1",
|
|
52
|
+
"@calebcall/camera-ui-notify-darwin-amd64": "0.6.1",
|
|
53
|
+
"@calebcall/camera-ui-notify-linux-amd64": "0.6.1",
|
|
54
|
+
"@calebcall/camera-ui-notify-linux-arm64": "0.6.1",
|
|
55
|
+
"@calebcall/camera-ui-notify-windows-amd64": "0.6.1",
|
|
56
|
+
"@calebcall/camera-ui-notify-windows-arm64": "0.6.1",
|
|
57
|
+
"@calebcall/camera-ui-notify-linux-amd64-musl": "0.6.1",
|
|
58
|
+
"@calebcall/camera-ui-notify-linux-arm64-musl": "0.6.1"
|
|
59
59
|
},
|
|
60
60
|
"files": [
|
|
61
61
|
"bundle.zip",
|