@camstack/system 1.2.231 → 1.2.233
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/dist/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/addon-utils.d.ts +1 -0
- package/dist/addon-utils.js +6 -1
- package/dist/addon-utils.mjs +3 -2
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +17 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +17 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-config-contribution.d.ts +1 -0
- package/dist/builtins/device-manager/device-manager.addon.js +46 -3
- package/dist/builtins/device-manager/device-manager.addon.mjs +46 -3
- package/dist/builtins/device-manager/native-object-detection-config-schema.d.ts +3 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +5 -140
- package/dist/builtins/snapshot/index.mjs +2 -137
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{custom-action-registry-CM03Xda0.mjs → custom-action-registry-CFHYDHAu.mjs} +1 -1
- package/dist/{custom-action-registry-DAFhWd0h.js → custom-action-registry-CWFK5UDj.js} +1 -1
- package/dist/{dist-Zhe9cTnK.mjs → dist-BIvXRexm.mjs} +291 -18
- package/dist/{dist-DUSfUAgz.js → dist-D3lJV9M7.js} +291 -18
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{retired-settings-keys-BK_geSOq.mjs → retired-settings-keys-CV3ZLGnJ.mjs} +1 -1
- package/dist/{retired-settings-keys-D61PxN8L.js → retired-settings-keys-YPsd2Exa.js} +1 -1
- package/dist/snapshot-courtesy-LoJNgHHZ.mjs +140 -0
- package/dist/snapshot-courtesy-oeTWCXPv.js +165 -0
- package/package.json +1 -1
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
2
|
+
let sharp = require("sharp");
|
|
3
|
+
sharp = require_chunk.__toESM(sharp);
|
|
4
|
+
//#region src/builtins/snapshot/snapshot-courtesy.ts
|
|
5
|
+
/**
|
|
6
|
+
* State treatment for a camera that cannot produce a current frame.
|
|
7
|
+
*
|
|
8
|
+
* ── Why this exists ───────────────────────────────────────────────────────
|
|
9
|
+
* A disabled, offline or sleeping camera has no frame, and until 2026-08-11 the
|
|
10
|
+
* snapshot service simply had nothing to say about it: the media route answered
|
|
11
|
+
* **404** and the client painted "Unavailable". Measured on the live grid that
|
|
12
|
+
* day, 10 of 26 tiles were 404s — one genuinely disabled camera plus nine dead
|
|
13
|
+
* legacy rows. A 404 is indistinguishable from a broken camera, so an operator
|
|
14
|
+
* who deliberately switched a camera off saw the same thing as a fault ([D62]:
|
|
15
|
+
* "an off switch is REPORTED off; disabled must never look like broken").
|
|
16
|
+
*
|
|
17
|
+
* So the service answers with an image that SAYS what is going on, carrying
|
|
18
|
+
* the camera's own name. When a real cached frame exists it remains visible
|
|
19
|
+
* under the treatment; otherwise the generated courtesy background is used.
|
|
20
|
+
*
|
|
21
|
+
* ── Why sharp and not ffmpeg ──────────────────────────────────────────────
|
|
22
|
+
* The first draft of this file shelled out to `ffmpeg` with a `drawtext`
|
|
23
|
+
* filter. That was written before the resize path was measured, and it was the
|
|
24
|
+
* wrong call for the same reason: this runs in the snapshot wrapper, a system
|
|
25
|
+
* builtin loaded in the hub's ROOT process, so every render was a fork + exec
|
|
26
|
+
* on the loop that serves the API — 52 ms against sharp's 4.8 ms in that
|
|
27
|
+
* container, to draw two lines of text.
|
|
28
|
+
*
|
|
29
|
+
* `terminal-frame-renderer.ts` already renders text this way (SVG → sharp), so
|
|
30
|
+
* this is the house pattern rather than a new one. It also drops the font-PATH
|
|
31
|
+
* probing the ffmpeg version needed: an SVG names a font FAMILY and fontconfig
|
|
32
|
+
* resolves it — verified in the hub image, where `fc-match "DejaVu Sans Mono"`
|
|
33
|
+
* answers with the real file.
|
|
34
|
+
*
|
|
35
|
+
* Rendering is pure input → bytes with no I/O of its own, and the geometry and
|
|
36
|
+
* escaping are separated out so they stay testable without rasterizing.
|
|
37
|
+
*/
|
|
38
|
+
/** Default tile size when the caller asks for no particular width. */
|
|
39
|
+
var COURTESY_DEFAULT_WIDTH = 640;
|
|
40
|
+
/**
|
|
41
|
+
* Font stack for the rendered text. Family names, not paths — librsvg resolves
|
|
42
|
+
* them through fontconfig, and the trailing generics keep a host without DejaVu
|
|
43
|
+
* rendering something legible instead of nothing.
|
|
44
|
+
*/
|
|
45
|
+
var COURTESY_FONT_STACK = "DejaVu Sans,DejaVu Sans Mono,Helvetica,Arial,sans-serif";
|
|
46
|
+
/** The word the frame carries. Deliberately the operator's vocabulary. */
|
|
47
|
+
function courtesyLabel(reason) {
|
|
48
|
+
switch (reason) {
|
|
49
|
+
case "disabled": return "Disabled";
|
|
50
|
+
case "offline": return "Offline";
|
|
51
|
+
case "sleeping": return "Sleeping";
|
|
52
|
+
case "unreachable": return "Unreachable";
|
|
53
|
+
case "waking": return "Waking";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Background per reason. A disabled camera is a DELIBERATE state and must not
|
|
58
|
+
* read as an alarm, so it is neutral grey; offline is a fault and is warmer.
|
|
59
|
+
*/
|
|
60
|
+
function courtesyBackground(reason) {
|
|
61
|
+
switch (reason) {
|
|
62
|
+
case "disabled": return "#2b2b31";
|
|
63
|
+
case "offline": return "#3a2b2b";
|
|
64
|
+
case "sleeping": return "#232b3a";
|
|
65
|
+
case "waking": return "#243247";
|
|
66
|
+
case "unreachable": return "#3a2b2b";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** XML escaping for text placed inside an SVG `<text>` node. */
|
|
70
|
+
function escapeCourtesyText(value) {
|
|
71
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The SVG the frame is rasterized from: the state large and centred, the
|
|
75
|
+
* camera's name under it, smaller and dimmer.
|
|
76
|
+
*
|
|
77
|
+
* Sizes derive from the width so a 240 px grid tile and a 1920 px full-bleed
|
|
78
|
+
* frame read the same, with a floor so a thumbnail stays legible.
|
|
79
|
+
*/
|
|
80
|
+
function buildCourtesySvg(spec) {
|
|
81
|
+
return buildStateSvg(spec, false);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The same state treatment as a courtesy frame, but translucent so the last
|
|
85
|
+
* real camera frame remains visible underneath it.
|
|
86
|
+
*/
|
|
87
|
+
function buildCourtesyOverlaySvg(spec) {
|
|
88
|
+
return buildStateSvg(spec, true);
|
|
89
|
+
}
|
|
90
|
+
function buildStateSvg(spec, overlay) {
|
|
91
|
+
const stateSize = Math.max(12, Math.round(spec.width / 12));
|
|
92
|
+
const nameSize = Math.max(9, Math.round(spec.width / 26));
|
|
93
|
+
const state = escapeCourtesyText(courtesyLabel(spec.reason));
|
|
94
|
+
const name = escapeCourtesyText(spec.deviceName);
|
|
95
|
+
const midY = spec.height / 2;
|
|
96
|
+
const washOpacity = overlay ? "0.62" : "1";
|
|
97
|
+
const textStroke = Math.max(1, Math.round(spec.width / 320));
|
|
98
|
+
return [
|
|
99
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${String(spec.width)}" height="${String(spec.height)}">`,
|
|
100
|
+
`<rect width="100%" height="100%" fill="${courtesyBackground(spec.reason)}" fill-opacity="${washOpacity}"/>`,
|
|
101
|
+
`<g font-family="${COURTESY_FONT_STACK}" text-anchor="middle" paint-order="stroke" stroke="#111118" stroke-width="${String(textStroke)}">`,
|
|
102
|
+
`<text x="50%" y="${String(Math.round(midY))}" font-size="${String(stateSize)}" fill="#e8e8ee">${state}</text>`,
|
|
103
|
+
`<text x="50%" y="${String(Math.round(midY + stateSize))}" font-size="${String(nameSize)}" fill="#9a9aa8">${name}</text>`,
|
|
104
|
+
`</g></svg>`
|
|
105
|
+
].join("");
|
|
106
|
+
}
|
|
107
|
+
/** Cache key — a courtesy frame is a pure function of these four. */
|
|
108
|
+
function courtesyCacheKey(spec) {
|
|
109
|
+
return `${spec.reason}:${String(spec.width)}x${String(spec.height)}:${spec.deviceName}`;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Render the frame.
|
|
113
|
+
*
|
|
114
|
+
* Rejects on failure — the caller decides what to do, exactly as `resizeJpeg`
|
|
115
|
+
* does, so a broken courtesy path is never mistaken for a broken camera.
|
|
116
|
+
*/
|
|
117
|
+
function renderCourtesyJpeg(spec) {
|
|
118
|
+
return (0, sharp.default)(Buffer.from(buildCourtesySvg(spec))).jpeg({ quality: 82 }).toBuffer().then((bytes) => {
|
|
119
|
+
if (bytes.length === 0) throw new Error("courtesy frame produced no bytes");
|
|
120
|
+
return bytes;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Paint the state treatment over a real cached frame.
|
|
125
|
+
*
|
|
126
|
+
* The source dimensions are authoritative: overlays preserve the original
|
|
127
|
+
* frame rather than resizing it to the generated courtesy-frame default.
|
|
128
|
+
*/
|
|
129
|
+
async function renderCourtesyOverlayJpeg(background, spec) {
|
|
130
|
+
const metadata = await (0, sharp.default)(background).metadata();
|
|
131
|
+
if (!metadata.width || !metadata.height) throw new Error("cached snapshot has no renderable dimensions");
|
|
132
|
+
const overlay = Buffer.from(buildCourtesyOverlaySvg({
|
|
133
|
+
...spec,
|
|
134
|
+
width: metadata.width,
|
|
135
|
+
height: metadata.height
|
|
136
|
+
}));
|
|
137
|
+
const bytes = await (0, sharp.default)(background).composite([{ input: overlay }]).jpeg({ quality: 82 }).toBuffer();
|
|
138
|
+
if (bytes.length === 0) throw new Error("courtesy overlay produced no bytes");
|
|
139
|
+
return bytes;
|
|
140
|
+
}
|
|
141
|
+
//#endregion
|
|
142
|
+
Object.defineProperty(exports, "COURTESY_DEFAULT_WIDTH", {
|
|
143
|
+
enumerable: true,
|
|
144
|
+
get: function() {
|
|
145
|
+
return COURTESY_DEFAULT_WIDTH;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
Object.defineProperty(exports, "courtesyCacheKey", {
|
|
149
|
+
enumerable: true,
|
|
150
|
+
get: function() {
|
|
151
|
+
return courtesyCacheKey;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
Object.defineProperty(exports, "renderCourtesyJpeg", {
|
|
155
|
+
enumerable: true,
|
|
156
|
+
get: function() {
|
|
157
|
+
return renderCourtesyJpeg;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
Object.defineProperty(exports, "renderCourtesyOverlayJpeg", {
|
|
161
|
+
enumerable: true,
|
|
162
|
+
get: function() {
|
|
163
|
+
return renderCourtesyOverlayJpeg;
|
|
164
|
+
}
|
|
165
|
+
});
|