@camstack/system 1.2.287 → 1.2.288
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/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/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -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 +472 -3
- package/dist/builtins/device-manager/device-manager.addon.mjs +472 -3
- package/dist/builtins/device-manager/recording-onboard-config-schema.d.ts +41 -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 +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- 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/{dist-C51Z6L6t.js → dist-BYnHJU96.js} +563 -2
- package/dist/{dist-jv1aoo9E.mjs → dist-DKFb2936.mjs} +546 -3
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{retired-settings-keys-BFeJh5E7.js → retired-settings-keys-C6rFqHR7.js} +1 -1
- package/dist/{retired-settings-keys-Bulcwqkz.mjs → retired-settings-keys-tuNxNNpR.mjs} +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-
|
|
1
|
+
import { Kt as runtimeStatePolicyFor, Ln as sleep$1, Lt as parseStreamParamsFormPatch, Pt as normalizeUnit, Rn as EventCategory, Tt as locationIconKey, Ut as resolveContainerPrimaryChild, W as buildStreamParamsConfigSchema, Zt as siteZoneKey, _n as DeviceType, at as enumerateItemArrayFields, bn as WELL_KNOWN_TAB_MAP, d as DAY_NAMES, dn as BaseAddon, et as deviceManagerCapability, ft as formatMinutes, gn as DeviceRole, h as DeviceStatusSchema, hn as DeviceFeature, k as STREAM_PROFILE_META, kn as isDeviceConfigCap, ln as errMsg, nt as deviceStatusCapability, o as CAMERA_SWITCH_ORDER, ot as enumerateSchemaFields, s as CAP_NAMES_WITH_STATUS, t as ALL_CAPABILITY_DEFINITIONS, tt as deviceStateCapability, x as MINUTES_PER_DAY, y as LocationIconIdSchema } from "../../dist-DKFb2936.mjs";
|
|
2
|
+
import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-tuNxNNpR.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { canonicalDeviceFingerprint } from "@camstack/types/node";
|
|
5
5
|
//#region src/builtins/device-manager/adoption-job-engine.ts
|
|
@@ -1286,6 +1286,468 @@ function parseImageSettingsFormPatch(patch) {
|
|
|
1286
1286
|
return out;
|
|
1287
1287
|
}
|
|
1288
1288
|
//#endregion
|
|
1289
|
+
//#region src/builtins/device-manager/recording-onboard-config-schema.ts
|
|
1290
|
+
var KEY_PREFIX = "recordingOnboard_";
|
|
1291
|
+
var RECORDING_ONBOARD_KEYS = {
|
|
1292
|
+
storageBanner: `${KEY_PREFIX}storageBanner`,
|
|
1293
|
+
volumes: `${KEY_PREFIX}volumes`,
|
|
1294
|
+
tracks: `${KEY_PREFIX}tracks`,
|
|
1295
|
+
enabled: `${KEY_PREFIX}enabled`,
|
|
1296
|
+
overwriteWhenFull: `${KEY_PREFIX}overwriteWhenFull`,
|
|
1297
|
+
preRecordSec: `${KEY_PREFIX}preRecordSec`,
|
|
1298
|
+
postRecordSec: `${KEY_PREFIX}postRecordSec`,
|
|
1299
|
+
segmentMinutes: `${KEY_PREFIX}segmentMinutes`,
|
|
1300
|
+
windows: `${KEY_PREFIX}windows`
|
|
1301
|
+
};
|
|
1302
|
+
/** Row keys inside the windows `editable-array`. Local, not prefixed. */
|
|
1303
|
+
var WINDOW_ROW_KEYS = {
|
|
1304
|
+
trigger: "trigger",
|
|
1305
|
+
day: "day",
|
|
1306
|
+
startMinute: "startMinute",
|
|
1307
|
+
endMinute: "endMinute"
|
|
1308
|
+
};
|
|
1309
|
+
var TRIGGER_LABELS = {
|
|
1310
|
+
continuous: "Continuous",
|
|
1311
|
+
motion: "Motion",
|
|
1312
|
+
person: "Person",
|
|
1313
|
+
vehicle: "Vehicle",
|
|
1314
|
+
animal: "Animal",
|
|
1315
|
+
lineCrossing: "Line crossing",
|
|
1316
|
+
intrusion: "Intrusion",
|
|
1317
|
+
loitering: "Loitering",
|
|
1318
|
+
alarmInput: "Alarm input"
|
|
1319
|
+
};
|
|
1320
|
+
/**
|
|
1321
|
+
* Append a support reason to a description, so a disabled control always
|
|
1322
|
+
* says WHY. Returns undefined when there is nothing to say, because an
|
|
1323
|
+
* empty description renders as a stray blank line.
|
|
1324
|
+
*/
|
|
1325
|
+
function describeSupport(base, support) {
|
|
1326
|
+
if (support.writable) return base.length > 0 ? base : void 0;
|
|
1327
|
+
const suffix = `Read-only on this camera: ${support.reason ?? "this camera does not expose it"}`;
|
|
1328
|
+
return base.length > 0 ? `${base} ${suffix}` : suffix;
|
|
1329
|
+
}
|
|
1330
|
+
function storageBanner(status) {
|
|
1331
|
+
if (!status) return {
|
|
1332
|
+
type: "info",
|
|
1333
|
+
key: RECORDING_ONBOARD_KEYS.storageBanner,
|
|
1334
|
+
label: "Onboard storage",
|
|
1335
|
+
variant: "info",
|
|
1336
|
+
content: "Not read from the camera yet."
|
|
1337
|
+
};
|
|
1338
|
+
if (status.recordingToNowhere) return {
|
|
1339
|
+
type: "info",
|
|
1340
|
+
key: RECORDING_ONBOARD_KEYS.storageBanner,
|
|
1341
|
+
label: "Onboard storage",
|
|
1342
|
+
variant: "danger",
|
|
1343
|
+
content: "This camera is scheduled to record and has no usable storage. Everything it records is discarded."
|
|
1344
|
+
};
|
|
1345
|
+
if (status.storage.kind === "absent") return {
|
|
1346
|
+
type: "info",
|
|
1347
|
+
key: RECORDING_ONBOARD_KEYS.storageBanner,
|
|
1348
|
+
label: "Onboard storage",
|
|
1349
|
+
variant: "warning",
|
|
1350
|
+
content: `No usable storage. ${status.storage.reason}`
|
|
1351
|
+
};
|
|
1352
|
+
if (status.storage.kind === "unknown") return {
|
|
1353
|
+
type: "info",
|
|
1354
|
+
key: RECORDING_ONBOARD_KEYS.storageBanner,
|
|
1355
|
+
label: "Onboard storage",
|
|
1356
|
+
variant: "warning",
|
|
1357
|
+
content: `CamStack could not read this camera's storage, so its state is unknown — not empty. ${status.storage.reason}`
|
|
1358
|
+
};
|
|
1359
|
+
return {
|
|
1360
|
+
type: "info",
|
|
1361
|
+
key: RECORDING_ONBOARD_KEYS.storageBanner,
|
|
1362
|
+
label: "Onboard storage",
|
|
1363
|
+
variant: "info",
|
|
1364
|
+
content: "Free space is not a measure of how much footage is held: a card that has wrapped once sits at a fixed reserve forever."
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
function volumesField(status) {
|
|
1368
|
+
const rows = status && status.storage.kind === "present" ? status.storage.volumes.map((volume) => ({
|
|
1369
|
+
id: volume.id,
|
|
1370
|
+
label: volume.label ?? null,
|
|
1371
|
+
status: volume.status === "ok" ? "ok" : "warn",
|
|
1372
|
+
capacity: volume.capacityMb === null ? null : `${String(volume.capacityMb)} MB`,
|
|
1373
|
+
free: volume.freeMb === null ? null : `${String(volume.freeMb)} MB`
|
|
1374
|
+
})) : [];
|
|
1375
|
+
return {
|
|
1376
|
+
type: "object-array",
|
|
1377
|
+
key: RECORDING_ONBOARD_KEYS.volumes,
|
|
1378
|
+
label: "Cards",
|
|
1379
|
+
columns: [
|
|
1380
|
+
{
|
|
1381
|
+
key: "id",
|
|
1382
|
+
label: "Id",
|
|
1383
|
+
kind: "monospace",
|
|
1384
|
+
width: "70px"
|
|
1385
|
+
},
|
|
1386
|
+
{
|
|
1387
|
+
key: "label",
|
|
1388
|
+
label: "Name",
|
|
1389
|
+
kind: "monospace"
|
|
1390
|
+
},
|
|
1391
|
+
{
|
|
1392
|
+
key: "status",
|
|
1393
|
+
label: "Status",
|
|
1394
|
+
kind: "status"
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
key: "capacity",
|
|
1398
|
+
label: "Capacity"
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
key: "free",
|
|
1402
|
+
label: "Free"
|
|
1403
|
+
}
|
|
1404
|
+
],
|
|
1405
|
+
value: rows,
|
|
1406
|
+
emptyMessage: "The camera reports no storage."
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
function tracksField(status) {
|
|
1410
|
+
const rows = (status?.tracks ?? []).map((track) => ({
|
|
1411
|
+
id: track.id,
|
|
1412
|
+
primary: track.id === status?.primaryTrackId ? "yes" : "",
|
|
1413
|
+
enabled: track.enabled ? "ok" : "idle",
|
|
1414
|
+
codec: track.codec,
|
|
1415
|
+
resolution: track.resolution
|
|
1416
|
+
}));
|
|
1417
|
+
return {
|
|
1418
|
+
type: "object-array",
|
|
1419
|
+
key: RECORDING_ONBOARD_KEYS.tracks,
|
|
1420
|
+
label: "Recording tracks",
|
|
1421
|
+
description: "Some cameras keep several tracks with independent schedules. Settings below apply to the primary one. Frame rate is deliberately absent — the value these cameras declare for it is wrong.",
|
|
1422
|
+
columns: [
|
|
1423
|
+
{
|
|
1424
|
+
key: "id",
|
|
1425
|
+
label: "Track",
|
|
1426
|
+
kind: "monospace",
|
|
1427
|
+
width: "80px"
|
|
1428
|
+
},
|
|
1429
|
+
{
|
|
1430
|
+
key: "primary",
|
|
1431
|
+
label: "Primary",
|
|
1432
|
+
width: "80px"
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
key: "enabled",
|
|
1436
|
+
label: "Enabled",
|
|
1437
|
+
kind: "status",
|
|
1438
|
+
width: "90px"
|
|
1439
|
+
},
|
|
1440
|
+
{
|
|
1441
|
+
key: "codec",
|
|
1442
|
+
label: "Codec"
|
|
1443
|
+
},
|
|
1444
|
+
{
|
|
1445
|
+
key: "resolution",
|
|
1446
|
+
label: "Resolution"
|
|
1447
|
+
}
|
|
1448
|
+
],
|
|
1449
|
+
value: rows,
|
|
1450
|
+
emptyMessage: "The camera reports no recording track."
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
function booleanField(key, label, description, support, current) {
|
|
1454
|
+
const field = {
|
|
1455
|
+
type: "boolean",
|
|
1456
|
+
key,
|
|
1457
|
+
label,
|
|
1458
|
+
default: current ?? false,
|
|
1459
|
+
disabled: !support.writable
|
|
1460
|
+
};
|
|
1461
|
+
const text = describeSupport(description, support);
|
|
1462
|
+
return text === void 0 ? field : {
|
|
1463
|
+
...field,
|
|
1464
|
+
description: text
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Label one member of an allowed SET.
|
|
1469
|
+
*
|
|
1470
|
+
* Zero is "Off" in every field that carries a set here — no pre-roll and
|
|
1471
|
+
* no post-roll are both the feature switched off, and "0 s" is a
|
|
1472
|
+
* duration an operator would read as a very short one. A sentinel is
|
|
1473
|
+
* named by its meaning rather than printed: Hikvision's no-limit member
|
|
1474
|
+
* is `2147483647`, which as a number on a form is nonsense.
|
|
1475
|
+
*
|
|
1476
|
+
* Whole minutes are shown as minutes because that is how the vendor's
|
|
1477
|
+
* own UI offers them — 120, 300 and 600 read back to the operator as
|
|
1478
|
+
* "2 min", "5 min", "10 min", which is the list they picked from.
|
|
1479
|
+
*/
|
|
1480
|
+
function describeChoice(value, unit, allowed) {
|
|
1481
|
+
const sentinel = allowed.sentinel;
|
|
1482
|
+
if (sentinel !== void 0 && sentinel.value === value) return sentinel.meaning === "no-limit" ? "No limit" : "Off";
|
|
1483
|
+
if (value === 0) return "Off";
|
|
1484
|
+
if (unit === "s" && value >= 60 && value % 60 === 0) return `${String(value / 60)} min`;
|
|
1485
|
+
return `${String(value)} ${unit}`;
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* A numeric camera setting.
|
|
1489
|
+
*
|
|
1490
|
+
* An allowed SET is preferred over a range whenever the camera stated
|
|
1491
|
+
* one, and it renders a SELECT rather than a number input. A spinner
|
|
1492
|
+
* over a set is a control that offers values the camera answers 200 to
|
|
1493
|
+
* and then discards — measured on both vendors, and the reason
|
|
1494
|
+
* `*Allowed` exists at all. The cap says the set wins; before this, the
|
|
1495
|
+
* builder read only the range and the set was declared and rendered by
|
|
1496
|
+
* nobody.
|
|
1497
|
+
*/
|
|
1498
|
+
function numberField(key, label, unit, description, support, range, current, allowed) {
|
|
1499
|
+
const text = describeSupport(description, support);
|
|
1500
|
+
if (allowed !== void 0) {
|
|
1501
|
+
const options = allowed.values.map((value) => ({
|
|
1502
|
+
value: String(value),
|
|
1503
|
+
label: describeChoice(value, unit, allowed)
|
|
1504
|
+
}));
|
|
1505
|
+
const fallback = allowed.values[0];
|
|
1506
|
+
const selected = current !== null && allowed.values.includes(current) ? current : current ?? fallback;
|
|
1507
|
+
const field = {
|
|
1508
|
+
type: "select",
|
|
1509
|
+
key,
|
|
1510
|
+
label,
|
|
1511
|
+
options: current !== null && !allowed.values.includes(current) ? [...options, {
|
|
1512
|
+
value: String(current),
|
|
1513
|
+
label: describeChoice(current, unit, allowed)
|
|
1514
|
+
}] : options,
|
|
1515
|
+
default: String(selected),
|
|
1516
|
+
disabled: !support.writable
|
|
1517
|
+
};
|
|
1518
|
+
return text === void 0 ? field : {
|
|
1519
|
+
...field,
|
|
1520
|
+
description: text
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
const field = {
|
|
1524
|
+
type: "number",
|
|
1525
|
+
key,
|
|
1526
|
+
label,
|
|
1527
|
+
unit,
|
|
1528
|
+
default: current ?? range?.min ?? 0,
|
|
1529
|
+
disabled: !support.writable,
|
|
1530
|
+
...range ? {
|
|
1531
|
+
min: range.min,
|
|
1532
|
+
max: range.max,
|
|
1533
|
+
step: range.step
|
|
1534
|
+
} : {}
|
|
1535
|
+
};
|
|
1536
|
+
return text === void 0 ? field : {
|
|
1537
|
+
...field,
|
|
1538
|
+
description: text
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* The time-of-day offer for a window edge.
|
|
1543
|
+
*
|
|
1544
|
+
* Stepped at the camera's own granularity when that is coarse (Reolink's
|
|
1545
|
+
* 60), and at 15 minutes when it is fine (Hikvision's 1) — a 1-minute
|
|
1546
|
+
* offer would be 1 441 options. 15 is a multiple of 1, so every value
|
|
1547
|
+
* offered is one the camera accepts; the converse is not true, and a
|
|
1548
|
+
* window the camera already holds that falls between the offered values
|
|
1549
|
+
* still round-trips because it is carried in the row's value, not
|
|
1550
|
+
* re-derived from the offer.
|
|
1551
|
+
*/
|
|
1552
|
+
function timeOptions(granularityMinutes) {
|
|
1553
|
+
const step = granularityMinutes >= 30 ? granularityMinutes : 15;
|
|
1554
|
+
const out = [];
|
|
1555
|
+
for (let minute = 0; minute < MINUTES_PER_DAY; minute += step) out.push({
|
|
1556
|
+
value: String(minute),
|
|
1557
|
+
label: formatMinutes(minute)
|
|
1558
|
+
});
|
|
1559
|
+
out.push({
|
|
1560
|
+
value: String(MINUTES_PER_DAY),
|
|
1561
|
+
label: "24:00"
|
|
1562
|
+
});
|
|
1563
|
+
return out;
|
|
1564
|
+
}
|
|
1565
|
+
function windowsField(options, status) {
|
|
1566
|
+
const schedule = options.schedule;
|
|
1567
|
+
const triggerOptions = schedule.triggers.map((trigger) => ({
|
|
1568
|
+
value: trigger,
|
|
1569
|
+
label: TRIGGER_LABELS[trigger]
|
|
1570
|
+
}));
|
|
1571
|
+
const dayOptions = DAY_NAMES.map((name, index) => ({
|
|
1572
|
+
value: String(index),
|
|
1573
|
+
label: name
|
|
1574
|
+
}));
|
|
1575
|
+
const times = timeOptions(schedule.granularityMinutes);
|
|
1576
|
+
const itemFields = [
|
|
1577
|
+
{
|
|
1578
|
+
type: "select",
|
|
1579
|
+
key: WINDOW_ROW_KEYS.trigger,
|
|
1580
|
+
label: "Record on",
|
|
1581
|
+
options: triggerOptions,
|
|
1582
|
+
default: schedule.triggers[0] ?? ""
|
|
1583
|
+
},
|
|
1584
|
+
{
|
|
1585
|
+
type: "select",
|
|
1586
|
+
key: WINDOW_ROW_KEYS.day,
|
|
1587
|
+
label: "Day",
|
|
1588
|
+
options: dayOptions,
|
|
1589
|
+
default: "0"
|
|
1590
|
+
},
|
|
1591
|
+
{
|
|
1592
|
+
type: "select",
|
|
1593
|
+
key: WINDOW_ROW_KEYS.startMinute,
|
|
1594
|
+
label: "From",
|
|
1595
|
+
options: times,
|
|
1596
|
+
default: "0"
|
|
1597
|
+
},
|
|
1598
|
+
{
|
|
1599
|
+
type: "select",
|
|
1600
|
+
key: WINDOW_ROW_KEYS.endMinute,
|
|
1601
|
+
label: "To",
|
|
1602
|
+
options: times,
|
|
1603
|
+
default: String(MINUTES_PER_DAY)
|
|
1604
|
+
}
|
|
1605
|
+
];
|
|
1606
|
+
const rows = (status?.windows ?? []).map((window) => ({
|
|
1607
|
+
[WINDOW_ROW_KEYS.trigger]: window.trigger,
|
|
1608
|
+
[WINDOW_ROW_KEYS.day]: String(window.day),
|
|
1609
|
+
[WINDOW_ROW_KEYS.startMinute]: String(window.startMinute),
|
|
1610
|
+
[WINDOW_ROW_KEYS.endMinute]: String(window.endMinute)
|
|
1611
|
+
}));
|
|
1612
|
+
const description = describeSupport(`${schedule.granularityMinutes > 1 ? `This camera's schedule moves in ${String(schedule.granularityMinutes)}-minute steps.` : ""}${schedule.supportsOverlappingTriggers ? "" : " It stores one trigger per time range, so two triggers cannot cover the same minutes on one day."}`.trim(), schedule.support);
|
|
1613
|
+
const field = {
|
|
1614
|
+
type: "editable-array",
|
|
1615
|
+
key: RECORDING_ONBOARD_KEYS.windows,
|
|
1616
|
+
label: "Weekly schedule",
|
|
1617
|
+
itemFields,
|
|
1618
|
+
defaultItem: {
|
|
1619
|
+
[WINDOW_ROW_KEYS.trigger]: schedule.triggers[0] ?? "",
|
|
1620
|
+
[WINDOW_ROW_KEYS.day]: "0",
|
|
1621
|
+
[WINDOW_ROW_KEYS.startMinute]: "0",
|
|
1622
|
+
[WINDOW_ROW_KEYS.endMinute]: String(MINUTES_PER_DAY)
|
|
1623
|
+
},
|
|
1624
|
+
default: rows,
|
|
1625
|
+
disabled: !schedule.support.writable,
|
|
1626
|
+
addLabel: "Add window",
|
|
1627
|
+
emptyMessage: "The camera records on no schedule.",
|
|
1628
|
+
rowTitleTemplate: "{day} · {trigger}"
|
|
1629
|
+
};
|
|
1630
|
+
return description === void 0 ? field : {
|
|
1631
|
+
...field,
|
|
1632
|
+
description
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Build the `recording-onboard` `ConfigUISchema`.
|
|
1637
|
+
*
|
|
1638
|
+
* Never returns null. The storage read works on every camera in the
|
|
1639
|
+
* fleet, so the section always has something true to say — and a camera
|
|
1640
|
+
* CamStack has not reached yet says exactly that, rather than
|
|
1641
|
+
* disappearing (D315: not-yet-known must not look like not-installed).
|
|
1642
|
+
*/
|
|
1643
|
+
function buildRecordingOnboardConfigSchema(options, status) {
|
|
1644
|
+
return { sections: [{
|
|
1645
|
+
id: "recording-onboard-storage",
|
|
1646
|
+
tab: "recording",
|
|
1647
|
+
title: "Camera storage",
|
|
1648
|
+
description: "The card in the camera, as the camera itself describes it.",
|
|
1649
|
+
columns: 1,
|
|
1650
|
+
fields: [
|
|
1651
|
+
storageBanner(status),
|
|
1652
|
+
volumesField(status),
|
|
1653
|
+
tracksField(status)
|
|
1654
|
+
]
|
|
1655
|
+
}, {
|
|
1656
|
+
id: "recording-onboard-recording",
|
|
1657
|
+
tab: "recording",
|
|
1658
|
+
title: "Camera recording",
|
|
1659
|
+
description: "What the camera records to its own card. This writes the camera, not CamStack's recorder.",
|
|
1660
|
+
columns: 2,
|
|
1661
|
+
fields: [
|
|
1662
|
+
booleanField(RECORDING_ONBOARD_KEYS.enabled, "Record to card", "The camera-side master switch.", options.enabled, status?.enabled ?? null),
|
|
1663
|
+
booleanField(RECORDING_ONBOARD_KEYS.overwriteWhenFull, "Overwrite when full", "Keep recording by discarding the oldest footage.", options.overwriteWhenFull, status?.overwriteWhenFull ?? null),
|
|
1664
|
+
numberField(RECORDING_ONBOARD_KEYS.preRecordSec, "Pre-record", "s", "Seconds kept from before the trigger.", options.preRecordSec, options.preRecordSecRange, status?.preRecordSec ?? null, options.preRecordSecAllowed),
|
|
1665
|
+
numberField(RECORDING_ONBOARD_KEYS.postRecordSec, "Post-record", "s", "Seconds kept after the trigger clears.", options.postRecordSec, options.postRecordSecRange, status?.postRecordSec ?? null, options.postRecordSecAllowed),
|
|
1666
|
+
numberField(RECORDING_ONBOARD_KEYS.segmentMinutes, "File length", "min", "How long one recorded file runs before the camera starts another.", options.segmentMinutes, options.segmentMinutesRange, status?.segmentMinutes ?? null, options.segmentMinutesAllowed),
|
|
1667
|
+
windowsField(options, status)
|
|
1668
|
+
]
|
|
1669
|
+
}] };
|
|
1670
|
+
}
|
|
1671
|
+
function readBoolean$1(patch, key) {
|
|
1672
|
+
if (!(key in patch)) return void 0;
|
|
1673
|
+
const value = patch[key];
|
|
1674
|
+
if (typeof value === "boolean") return value;
|
|
1675
|
+
if (value === "true") return true;
|
|
1676
|
+
if (value === "false") return false;
|
|
1677
|
+
}
|
|
1678
|
+
function readNumber$1(patch, key) {
|
|
1679
|
+
if (!(key in patch)) return void 0;
|
|
1680
|
+
const value = Number(patch[key]);
|
|
1681
|
+
return Number.isFinite(value) ? value : void 0;
|
|
1682
|
+
}
|
|
1683
|
+
/** Is this string one of the cap's triggers? A guard, never a cast. */
|
|
1684
|
+
function isRecordTrigger(value) {
|
|
1685
|
+
return typeof value === "string" && value in TRIGGER_LABELS;
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* Read one row of the windows `editable-array`.
|
|
1689
|
+
*
|
|
1690
|
+
* Returns null for a row that is not a window. Validation of whether
|
|
1691
|
+
* THIS camera accepts the window is deliberately not done here: the
|
|
1692
|
+
* provider refuses it by name through `describeOnboardRefusal`, which is
|
|
1693
|
+
* the one refusal vocabulary, and duplicating a weaker copy of it in the
|
|
1694
|
+
* form parser would give the operator two different answers.
|
|
1695
|
+
*/
|
|
1696
|
+
function readWindowRow(row) {
|
|
1697
|
+
if (typeof row !== "object" || row === null) return null;
|
|
1698
|
+
const record = { ...row };
|
|
1699
|
+
const trigger = record[WINDOW_ROW_KEYS.trigger];
|
|
1700
|
+
if (!isRecordTrigger(trigger)) return null;
|
|
1701
|
+
const day = Number(record[WINDOW_ROW_KEYS.day]);
|
|
1702
|
+
const startMinute = Number(record[WINDOW_ROW_KEYS.startMinute]);
|
|
1703
|
+
const endMinute = Number(record[WINDOW_ROW_KEYS.endMinute]);
|
|
1704
|
+
if (!Number.isInteger(day) || day < 0 || day > 6) return null;
|
|
1705
|
+
if (!Number.isInteger(startMinute) || !Number.isInteger(endMinute)) return null;
|
|
1706
|
+
return {
|
|
1707
|
+
trigger,
|
|
1708
|
+
day,
|
|
1709
|
+
startMinute,
|
|
1710
|
+
endMinute
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Re-parse a flat `ConfigFormBuilder` patch into a
|
|
1715
|
+
* `RecordingOnboardPatch`-shaped object.
|
|
1716
|
+
*
|
|
1717
|
+
* Returns only the keys the patch actually carried. The windows key is
|
|
1718
|
+
* all-or-nothing on purpose: the field is a single editable array, so
|
|
1719
|
+
* its presence means the operator submitted the COMPLETE window set. A
|
|
1720
|
+
* row that cannot be read is DROPPED and the remaining set is still
|
|
1721
|
+
* submitted — the provider then refuses or applies a set the operator
|
|
1722
|
+
* can see in the form, rather than the form and the camera silently
|
|
1723
|
+
* disagreeing about a row neither shows.
|
|
1724
|
+
*/
|
|
1725
|
+
function parseRecordingOnboardFormPatch(patch) {
|
|
1726
|
+
const out = {};
|
|
1727
|
+
const enabled = readBoolean$1(patch, RECORDING_ONBOARD_KEYS.enabled);
|
|
1728
|
+
if (enabled !== void 0) out.enabled = enabled;
|
|
1729
|
+
const overwrite = readBoolean$1(patch, RECORDING_ONBOARD_KEYS.overwriteWhenFull);
|
|
1730
|
+
if (overwrite !== void 0) out.overwriteWhenFull = overwrite;
|
|
1731
|
+
const pre = readNumber$1(patch, RECORDING_ONBOARD_KEYS.preRecordSec);
|
|
1732
|
+
if (pre !== void 0) out.preRecordSec = pre;
|
|
1733
|
+
const post = readNumber$1(patch, RECORDING_ONBOARD_KEYS.postRecordSec);
|
|
1734
|
+
if (post !== void 0) out.postRecordSec = post;
|
|
1735
|
+
const segment = readNumber$1(patch, RECORDING_ONBOARD_KEYS.segmentMinutes);
|
|
1736
|
+
if (segment !== void 0) out.segmentMinutes = segment;
|
|
1737
|
+
if (RECORDING_ONBOARD_KEYS.windows in patch) {
|
|
1738
|
+
const raw = patch[RECORDING_ONBOARD_KEYS.windows];
|
|
1739
|
+
if (Array.isArray(raw)) {
|
|
1740
|
+
const windows = [];
|
|
1741
|
+
for (const row of raw) {
|
|
1742
|
+
const parsed = readWindowRow(row);
|
|
1743
|
+
if (parsed) windows.push(parsed);
|
|
1744
|
+
}
|
|
1745
|
+
out.windows = windows;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
return out;
|
|
1749
|
+
}
|
|
1750
|
+
//#endregion
|
|
1289
1751
|
//#region src/builtins/device-manager/device-config-contribution.ts
|
|
1290
1752
|
/**
|
|
1291
1753
|
* D14 device-config archetype — framework-side contribution derivation.
|
|
@@ -1313,6 +1775,7 @@ function parseImageSettingsFormPatch(patch) {
|
|
|
1313
1775
|
var STREAM_PARAMS_BUILDER_ID = "stream-params";
|
|
1314
1776
|
var DAY_NIGHT_BUILDER_ID = "day-night";
|
|
1315
1777
|
var IMAGE_SETTINGS_BUILDER_ID = "image-settings";
|
|
1778
|
+
var RECORDING_ONBOARD_BUILDER_ID = "recording-onboard";
|
|
1316
1779
|
var STREAM_PARAMS_REDUCER = {
|
|
1317
1780
|
kind: "profile",
|
|
1318
1781
|
buildSchema: (options, status) => buildStreamParamsConfigSchema(options, status ?? null),
|
|
@@ -1333,10 +1796,16 @@ var IMAGE_SETTINGS_REDUCER = {
|
|
|
1333
1796
|
buildSchema: (options, status) => buildImageSettingsConfigSchema(options, status ?? null),
|
|
1334
1797
|
parsePatch: (patch) => parseImageSettingsFormPatch(patch)
|
|
1335
1798
|
};
|
|
1799
|
+
var RECORDING_ONBOARD_REDUCER = {
|
|
1800
|
+
kind: "settings",
|
|
1801
|
+
buildSchema: (options, status) => buildRecordingOnboardConfigSchema(options, status ?? null),
|
|
1802
|
+
parsePatch: (patch) => parseRecordingOnboardFormPatch(patch)
|
|
1803
|
+
};
|
|
1336
1804
|
var BUILDER_REDUCERS = {
|
|
1337
1805
|
[STREAM_PARAMS_BUILDER_ID]: STREAM_PARAMS_REDUCER,
|
|
1338
1806
|
[DAY_NIGHT_BUILDER_ID]: DAY_NIGHT_REDUCER,
|
|
1339
|
-
[IMAGE_SETTINGS_BUILDER_ID]: IMAGE_SETTINGS_REDUCER
|
|
1807
|
+
[IMAGE_SETTINGS_BUILDER_ID]: IMAGE_SETTINGS_REDUCER,
|
|
1808
|
+
[RECORDING_ONBOARD_BUILDER_ID]: RECORDING_ONBOARD_REDUCER
|
|
1340
1809
|
};
|
|
1341
1810
|
function resolveReducer(builderId) {
|
|
1342
1811
|
const reducer = BUILDER_REDUCERS[builderId];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ConfigUISchema, RecordingOnboardOptions, RecordingOnboardStatus } from '@camstack/types';
|
|
2
|
+
export declare const RECORDING_ONBOARD_KEYS: {
|
|
3
|
+
readonly storageBanner: "recordingOnboard_storageBanner";
|
|
4
|
+
readonly volumes: "recordingOnboard_volumes";
|
|
5
|
+
readonly tracks: "recordingOnboard_tracks";
|
|
6
|
+
readonly enabled: "recordingOnboard_enabled";
|
|
7
|
+
readonly overwriteWhenFull: "recordingOnboard_overwriteWhenFull";
|
|
8
|
+
readonly preRecordSec: "recordingOnboard_preRecordSec";
|
|
9
|
+
readonly postRecordSec: "recordingOnboard_postRecordSec";
|
|
10
|
+
readonly segmentMinutes: "recordingOnboard_segmentMinutes";
|
|
11
|
+
readonly windows: "recordingOnboard_windows";
|
|
12
|
+
};
|
|
13
|
+
/** Row keys inside the windows `editable-array`. Local, not prefixed. */
|
|
14
|
+
export declare const WINDOW_ROW_KEYS: {
|
|
15
|
+
readonly trigger: "trigger";
|
|
16
|
+
readonly day: "day";
|
|
17
|
+
readonly startMinute: "startMinute";
|
|
18
|
+
readonly endMinute: "endMinute";
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Build the `recording-onboard` `ConfigUISchema`.
|
|
22
|
+
*
|
|
23
|
+
* Never returns null. The storage read works on every camera in the
|
|
24
|
+
* fleet, so the section always has something true to say — and a camera
|
|
25
|
+
* CamStack has not reached yet says exactly that, rather than
|
|
26
|
+
* disappearing (D315: not-yet-known must not look like not-installed).
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildRecordingOnboardConfigSchema(options: RecordingOnboardOptions, status: RecordingOnboardStatus | null): ConfigUISchema | null;
|
|
29
|
+
/**
|
|
30
|
+
* Re-parse a flat `ConfigFormBuilder` patch into a
|
|
31
|
+
* `RecordingOnboardPatch`-shaped object.
|
|
32
|
+
*
|
|
33
|
+
* Returns only the keys the patch actually carried. The windows key is
|
|
34
|
+
* all-or-nothing on purpose: the field is a single editable array, so
|
|
35
|
+
* its presence means the operator submitted the COMPLETE window set. A
|
|
36
|
+
* row that cannot be read is DROPPED and the remaining set is still
|
|
37
|
+
* submitted — the provider then refuses or applies a set the operator
|
|
38
|
+
* can see in the form, rather than the form and the camera silently
|
|
39
|
+
* disagreeing about a row neither shows.
|
|
40
|
+
*/
|
|
41
|
+
export declare function parseRecordingOnboardFormPatch(patch: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BYnHJU96.js");
|
|
7
7
|
//#region src/builtins/doorbell/trigger-engine.ts
|
|
8
8
|
/**
|
|
9
9
|
* Pure trigger logic for the virtual-doorbell builtin — edge detection over
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as SOURCE_DEVICE_TYPES, E as SOURCE_CAPS, H as bareAddonId, Rn as EventCategory, _n as DeviceType, dn as BaseAddon, ln as errMsg, rt as doorbellCapability, st as evaluateSensorEdge, vt as isSameAddonId, wn as createEvent, yt as isSourceCap } from "../../dist-DKFb2936.mjs";
|
|
2
2
|
//#region src/builtins/doorbell/trigger-engine.ts
|
|
3
3
|
/**
|
|
4
4
|
* Pure trigger logic for the virtual-doorbell builtin — edge detection over
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BYnHJU96.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
//#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
|
|
9
9
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Et as logDestinationCapability, dn as BaseAddon } from "../../dist-DKFb2936.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
//#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
|
|
4
4
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
3
|
+
const require_dist = require("../../dist-BYnHJU96.js");
|
|
4
4
|
//#region src/builtins/liveness-monitor/liveness-checks.ts
|
|
5
5
|
var NO_DEVICES = "liveness:no-devices";
|
|
6
6
|
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Rn as EventCategory, T as RecordingStatusSchema, dn as BaseAddon, ln as errMsg, wn as createEvent } from "../../dist-DKFb2936.mjs";
|
|
2
2
|
//#region src/builtins/liveness-monitor/liveness-checks.ts
|
|
3
3
|
var NO_DEVICES = "liveness:no-devices";
|
|
4
4
|
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BYnHJU96.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
9
9
|
let crypto$1 = require("crypto");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as __require, o as __toESM, t as __commonJSMin } from "../../chunk-CNf5ZN-e.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { A as ScopedTokenSchema, B as authProviderCapability, F as UserRecordSchema, an as userManagementCapability, dn as BaseAddon, r as ApiKeyRecordSchema } from "../../dist-DKFb2936.mjs";
|
|
3
3
|
import * as crypto$2 from "node:crypto";
|
|
4
4
|
import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
5
5
|
import nodeCrypto from "crypto";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
3
|
+
const require_dist = require("../../dist-BYnHJU96.js");
|
|
4
4
|
const require_tls = require("../../tls-D_wUeOTD.js");
|
|
5
5
|
let node_fs_promises = require("node:fs/promises");
|
|
6
6
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Rn as EventCategory, dn as BaseAddon, wt as localNetworkCapability } from "../../dist-DKFb2936.mjs";
|
|
2
2
|
import { S as readLanHttpState, _ as DEFAULT_LAN_HTTP_PORT, a as writeTlsMode, i as writeExtraSans, l as reissueTlsLeaf, n as readTlsAccessStatus, o as validateUploadedTls, r as readTlsMode, s as ensureTlsCert, t as readExtraSans, y as applyLanHttp } from "../../tls-IM-v7JV4.mjs";
|
|
3
3
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BYnHJU96.js");
|
|
7
7
|
//#region src/builtins/loki-logging/loki-payload.ts
|
|
8
8
|
/**
|
|
9
9
|
* Loki rejects a label name that is not a valid Prometheus label
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Et as logDestinationCapability, dn as BaseAddon } from "../../dist-DKFb2936.mjs";
|
|
2
2
|
//#region src/builtins/loki-logging/loki-payload.ts
|
|
3
3
|
/**
|
|
4
4
|
* Loki rejects a label name that is not a valid Prometheus label
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BYnHJU96.js");
|
|
7
7
|
let node_fs_promises = require("node:fs/promises");
|
|
8
8
|
let node_fs = require("node:fs");
|
|
9
9
|
node_fs = require_chunk.__toESM(node_fs);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Ht as resolveBucketMs, Rn as EventCategory, Vt as reducePoints, dn as BaseAddon, dt as foldSnapshotByFunction, jt as metricsProviderCapability, ln as errMsg, wn as createEvent } from "../../dist-DKFb2936.mjs";
|
|
2
2
|
import { access, readFile, readdir } from "node:fs/promises";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import { constants, readFileSync } from "node:fs";
|