@camstack/system 1.2.286 → 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-C2rpGptn.js → dist-BYnHJU96.js} +2027 -3
- package/dist/{dist-B6uD33b6.mjs → dist-DKFb2936.mjs} +2010 -4
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{retired-settings-keys-CZvs6OHE.js → retired-settings-keys-C6rFqHR7.js} +1 -1
- package/dist/{retired-settings-keys-BB798vLn.mjs → retired-settings-keys-tuNxNNpR.mjs} +1 -1
- package/package.json +1 -1
|
@@ -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
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { I as addonPagesCapability, Rn as EventCategory, dn as BaseAddon, ln as errMsg } from "../../dist-DKFb2936.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -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
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { R as addonWidgetsCapability, Rn as EventCategory, dn as BaseAddon, ln as errMsg } from "../../dist-DKFb2936.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import * as path$1 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/alerts/prune-policy.ts
|
|
8
8
|
/**
|
|
9
9
|
* Ids older than `cutoffMs`, oldest first.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Rn as EventCategory, dn as BaseAddon, ln as errMsg, n as AlertSchema, wn as createEvent, z as alertsCapability } from "../../dist-DKFb2936.mjs";
|
|
2
2
|
//#region src/builtins/alerts/prune-policy.ts
|
|
3
3
|
/**
|
|
4
4
|
* Ids older than `cutoffMs`, oldest first.
|
|
@@ -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/autotrack/autotrack-config.ts
|
|
8
8
|
var DEFAULT_AUTOTRACK_LOOP_CONFIG = {
|
|
9
9
|
deadZoneFraction: .5,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { An as isEvent, Bt as ptzAutotrackCapability, H as bareAddonId, Rn as EventCategory, dn as BaseAddon, g as FIRST_LEVEL_MACRO_CLASSES, ln as errMsg } from "../../dist-DKFb2936.mjs";
|
|
2
2
|
//#region src/builtins/autotrack/autotrack-config.ts
|
|
3
3
|
var DEFAULT_AUTOTRACK_LOOP_CONFIG = {
|
|
4
4
|
deadZoneFraction: .5,
|
|
@@ -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
|
let buffer = require("buffer");
|
|
9
9
|
let node_fs_promises = require("node:fs/promises");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { At as mayWriteToLocation, Rn as EventCategory, V as backupCapability, dn as BaseAddon, tn as storageOccupancyCapability } from "../../dist-DKFb2936.mjs";
|
|
2
2
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
3
3
|
import { Buffer as Buffer$1 } from "buffer";
|
|
4
4
|
import * as fsp from "node:fs/promises";
|
|
@@ -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
|
let zod = require("zod");
|
|
8
8
|
let _camstack_types_node = require("@camstack/types/node");
|
|
9
9
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { G as cameraGridLayoutCapability, Nt as motionCapability, On as hydrateSchema, Qt as snapshotCapability, Rn as EventCategory, S as MOTION_CLOSE_AFTER_MS, Z as declarationOwnerNodeId, _n as DeviceType, dn as BaseAddon, hn as DeviceFeature, i as BaseDevice, ln as errMsg, m as DeclaredDevices, p as DERIVED_CAMERA_SILENCED_CAP_NAMES, rn as streamCatalogCapability, un as buildFfmpegArgs, wn as createEvent } from "../../dist-DKFb2936.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { FfmpegProcess } from "@camstack/types/node";
|
|
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
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
//#region src/builtins/console-logging/console-destination.ts
|
|
9
9
|
var LEVEL_RANK = {
|
|
@@ -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/console-logging/console-destination.ts
|
|
4
4
|
var LEVEL_RANK = {
|
|
@@ -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
|
let node_crypto = require("node:crypto");
|
|
5
5
|
let node_fs_promises = require("node:fs/promises");
|
|
6
6
|
node_fs_promises = require_chunk.__toESM(node_fs_promises);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { J as coreBlockIdFromAddonId, Y as coreBlocksCapability, c as CORE_BLOCKS_ADDON_ID, dn as BaseAddon, l as CORE_BLOCK_ADDON_PREFIX, ln as errMsg, m as DeclaredDevices, q as coreBlockAddonId, u as CoreBlockSchema } from "../../dist-DKFb2936.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -23,6 +23,7 @@ export type ProfileSetter = (profile: StreamProfile, patch: StreamProfilePatch)
|
|
|
23
23
|
export declare const STREAM_PARAMS_BUILDER_ID: "stream-params";
|
|
24
24
|
export declare const DAY_NIGHT_BUILDER_ID: "day-night";
|
|
25
25
|
export declare const IMAGE_SETTINGS_BUILDER_ID: "image-settings";
|
|
26
|
+
export declare const RECORDING_ONBOARD_BUILDER_ID: "recording-onboard";
|
|
26
27
|
/**
|
|
27
28
|
* Build the device-detail form section for a `derived-form` device-config
|
|
28
29
|
* cap. Returns null when the camera exposes no configurable property.
|
|
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
7
|
-
const require_retired_settings_keys = require("../../retired-settings-keys-
|
|
6
|
+
const require_dist = require("../../dist-BYnHJU96.js");
|
|
7
|
+
const require_retired_settings_keys = require("../../retired-settings-keys-C6rFqHR7.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let _camstack_types_node = require("@camstack/types/node");
|
|
10
10
|
//#region src/builtins/device-manager/adoption-job-engine.ts
|
|
@@ -1291,6 +1291,468 @@ function parseImageSettingsFormPatch(patch) {
|
|
|
1291
1291
|
return out;
|
|
1292
1292
|
}
|
|
1293
1293
|
//#endregion
|
|
1294
|
+
//#region src/builtins/device-manager/recording-onboard-config-schema.ts
|
|
1295
|
+
var KEY_PREFIX = "recordingOnboard_";
|
|
1296
|
+
var RECORDING_ONBOARD_KEYS = {
|
|
1297
|
+
storageBanner: `${KEY_PREFIX}storageBanner`,
|
|
1298
|
+
volumes: `${KEY_PREFIX}volumes`,
|
|
1299
|
+
tracks: `${KEY_PREFIX}tracks`,
|
|
1300
|
+
enabled: `${KEY_PREFIX}enabled`,
|
|
1301
|
+
overwriteWhenFull: `${KEY_PREFIX}overwriteWhenFull`,
|
|
1302
|
+
preRecordSec: `${KEY_PREFIX}preRecordSec`,
|
|
1303
|
+
postRecordSec: `${KEY_PREFIX}postRecordSec`,
|
|
1304
|
+
segmentMinutes: `${KEY_PREFIX}segmentMinutes`,
|
|
1305
|
+
windows: `${KEY_PREFIX}windows`
|
|
1306
|
+
};
|
|
1307
|
+
/** Row keys inside the windows `editable-array`. Local, not prefixed. */
|
|
1308
|
+
var WINDOW_ROW_KEYS = {
|
|
1309
|
+
trigger: "trigger",
|
|
1310
|
+
day: "day",
|
|
1311
|
+
startMinute: "startMinute",
|
|
1312
|
+
endMinute: "endMinute"
|
|
1313
|
+
};
|
|
1314
|
+
var TRIGGER_LABELS = {
|
|
1315
|
+
continuous: "Continuous",
|
|
1316
|
+
motion: "Motion",
|
|
1317
|
+
person: "Person",
|
|
1318
|
+
vehicle: "Vehicle",
|
|
1319
|
+
animal: "Animal",
|
|
1320
|
+
lineCrossing: "Line crossing",
|
|
1321
|
+
intrusion: "Intrusion",
|
|
1322
|
+
loitering: "Loitering",
|
|
1323
|
+
alarmInput: "Alarm input"
|
|
1324
|
+
};
|
|
1325
|
+
/**
|
|
1326
|
+
* Append a support reason to a description, so a disabled control always
|
|
1327
|
+
* says WHY. Returns undefined when there is nothing to say, because an
|
|
1328
|
+
* empty description renders as a stray blank line.
|
|
1329
|
+
*/
|
|
1330
|
+
function describeSupport(base, support) {
|
|
1331
|
+
if (support.writable) return base.length > 0 ? base : void 0;
|
|
1332
|
+
const suffix = `Read-only on this camera: ${support.reason ?? "this camera does not expose it"}`;
|
|
1333
|
+
return base.length > 0 ? `${base} ${suffix}` : suffix;
|
|
1334
|
+
}
|
|
1335
|
+
function storageBanner(status) {
|
|
1336
|
+
if (!status) return {
|
|
1337
|
+
type: "info",
|
|
1338
|
+
key: RECORDING_ONBOARD_KEYS.storageBanner,
|
|
1339
|
+
label: "Onboard storage",
|
|
1340
|
+
variant: "info",
|
|
1341
|
+
content: "Not read from the camera yet."
|
|
1342
|
+
};
|
|
1343
|
+
if (status.recordingToNowhere) return {
|
|
1344
|
+
type: "info",
|
|
1345
|
+
key: RECORDING_ONBOARD_KEYS.storageBanner,
|
|
1346
|
+
label: "Onboard storage",
|
|
1347
|
+
variant: "danger",
|
|
1348
|
+
content: "This camera is scheduled to record and has no usable storage. Everything it records is discarded."
|
|
1349
|
+
};
|
|
1350
|
+
if (status.storage.kind === "absent") return {
|
|
1351
|
+
type: "info",
|
|
1352
|
+
key: RECORDING_ONBOARD_KEYS.storageBanner,
|
|
1353
|
+
label: "Onboard storage",
|
|
1354
|
+
variant: "warning",
|
|
1355
|
+
content: `No usable storage. ${status.storage.reason}`
|
|
1356
|
+
};
|
|
1357
|
+
if (status.storage.kind === "unknown") return {
|
|
1358
|
+
type: "info",
|
|
1359
|
+
key: RECORDING_ONBOARD_KEYS.storageBanner,
|
|
1360
|
+
label: "Onboard storage",
|
|
1361
|
+
variant: "warning",
|
|
1362
|
+
content: `CamStack could not read this camera's storage, so its state is unknown — not empty. ${status.storage.reason}`
|
|
1363
|
+
};
|
|
1364
|
+
return {
|
|
1365
|
+
type: "info",
|
|
1366
|
+
key: RECORDING_ONBOARD_KEYS.storageBanner,
|
|
1367
|
+
label: "Onboard storage",
|
|
1368
|
+
variant: "info",
|
|
1369
|
+
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."
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
function volumesField(status) {
|
|
1373
|
+
const rows = status && status.storage.kind === "present" ? status.storage.volumes.map((volume) => ({
|
|
1374
|
+
id: volume.id,
|
|
1375
|
+
label: volume.label ?? null,
|
|
1376
|
+
status: volume.status === "ok" ? "ok" : "warn",
|
|
1377
|
+
capacity: volume.capacityMb === null ? null : `${String(volume.capacityMb)} MB`,
|
|
1378
|
+
free: volume.freeMb === null ? null : `${String(volume.freeMb)} MB`
|
|
1379
|
+
})) : [];
|
|
1380
|
+
return {
|
|
1381
|
+
type: "object-array",
|
|
1382
|
+
key: RECORDING_ONBOARD_KEYS.volumes,
|
|
1383
|
+
label: "Cards",
|
|
1384
|
+
columns: [
|
|
1385
|
+
{
|
|
1386
|
+
key: "id",
|
|
1387
|
+
label: "Id",
|
|
1388
|
+
kind: "monospace",
|
|
1389
|
+
width: "70px"
|
|
1390
|
+
},
|
|
1391
|
+
{
|
|
1392
|
+
key: "label",
|
|
1393
|
+
label: "Name",
|
|
1394
|
+
kind: "monospace"
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
key: "status",
|
|
1398
|
+
label: "Status",
|
|
1399
|
+
kind: "status"
|
|
1400
|
+
},
|
|
1401
|
+
{
|
|
1402
|
+
key: "capacity",
|
|
1403
|
+
label: "Capacity"
|
|
1404
|
+
},
|
|
1405
|
+
{
|
|
1406
|
+
key: "free",
|
|
1407
|
+
label: "Free"
|
|
1408
|
+
}
|
|
1409
|
+
],
|
|
1410
|
+
value: rows,
|
|
1411
|
+
emptyMessage: "The camera reports no storage."
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
function tracksField(status) {
|
|
1415
|
+
const rows = (status?.tracks ?? []).map((track) => ({
|
|
1416
|
+
id: track.id,
|
|
1417
|
+
primary: track.id === status?.primaryTrackId ? "yes" : "",
|
|
1418
|
+
enabled: track.enabled ? "ok" : "idle",
|
|
1419
|
+
codec: track.codec,
|
|
1420
|
+
resolution: track.resolution
|
|
1421
|
+
}));
|
|
1422
|
+
return {
|
|
1423
|
+
type: "object-array",
|
|
1424
|
+
key: RECORDING_ONBOARD_KEYS.tracks,
|
|
1425
|
+
label: "Recording tracks",
|
|
1426
|
+
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.",
|
|
1427
|
+
columns: [
|
|
1428
|
+
{
|
|
1429
|
+
key: "id",
|
|
1430
|
+
label: "Track",
|
|
1431
|
+
kind: "monospace",
|
|
1432
|
+
width: "80px"
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
key: "primary",
|
|
1436
|
+
label: "Primary",
|
|
1437
|
+
width: "80px"
|
|
1438
|
+
},
|
|
1439
|
+
{
|
|
1440
|
+
key: "enabled",
|
|
1441
|
+
label: "Enabled",
|
|
1442
|
+
kind: "status",
|
|
1443
|
+
width: "90px"
|
|
1444
|
+
},
|
|
1445
|
+
{
|
|
1446
|
+
key: "codec",
|
|
1447
|
+
label: "Codec"
|
|
1448
|
+
},
|
|
1449
|
+
{
|
|
1450
|
+
key: "resolution",
|
|
1451
|
+
label: "Resolution"
|
|
1452
|
+
}
|
|
1453
|
+
],
|
|
1454
|
+
value: rows,
|
|
1455
|
+
emptyMessage: "The camera reports no recording track."
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
function booleanField(key, label, description, support, current) {
|
|
1459
|
+
const field = {
|
|
1460
|
+
type: "boolean",
|
|
1461
|
+
key,
|
|
1462
|
+
label,
|
|
1463
|
+
default: current ?? false,
|
|
1464
|
+
disabled: !support.writable
|
|
1465
|
+
};
|
|
1466
|
+
const text = describeSupport(description, support);
|
|
1467
|
+
return text === void 0 ? field : {
|
|
1468
|
+
...field,
|
|
1469
|
+
description: text
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Label one member of an allowed SET.
|
|
1474
|
+
*
|
|
1475
|
+
* Zero is "Off" in every field that carries a set here — no pre-roll and
|
|
1476
|
+
* no post-roll are both the feature switched off, and "0 s" is a
|
|
1477
|
+
* duration an operator would read as a very short one. A sentinel is
|
|
1478
|
+
* named by its meaning rather than printed: Hikvision's no-limit member
|
|
1479
|
+
* is `2147483647`, which as a number on a form is nonsense.
|
|
1480
|
+
*
|
|
1481
|
+
* Whole minutes are shown as minutes because that is how the vendor's
|
|
1482
|
+
* own UI offers them — 120, 300 and 600 read back to the operator as
|
|
1483
|
+
* "2 min", "5 min", "10 min", which is the list they picked from.
|
|
1484
|
+
*/
|
|
1485
|
+
function describeChoice(value, unit, allowed) {
|
|
1486
|
+
const sentinel = allowed.sentinel;
|
|
1487
|
+
if (sentinel !== void 0 && sentinel.value === value) return sentinel.meaning === "no-limit" ? "No limit" : "Off";
|
|
1488
|
+
if (value === 0) return "Off";
|
|
1489
|
+
if (unit === "s" && value >= 60 && value % 60 === 0) return `${String(value / 60)} min`;
|
|
1490
|
+
return `${String(value)} ${unit}`;
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* A numeric camera setting.
|
|
1494
|
+
*
|
|
1495
|
+
* An allowed SET is preferred over a range whenever the camera stated
|
|
1496
|
+
* one, and it renders a SELECT rather than a number input. A spinner
|
|
1497
|
+
* over a set is a control that offers values the camera answers 200 to
|
|
1498
|
+
* and then discards — measured on both vendors, and the reason
|
|
1499
|
+
* `*Allowed` exists at all. The cap says the set wins; before this, the
|
|
1500
|
+
* builder read only the range and the set was declared and rendered by
|
|
1501
|
+
* nobody.
|
|
1502
|
+
*/
|
|
1503
|
+
function numberField(key, label, unit, description, support, range, current, allowed) {
|
|
1504
|
+
const text = describeSupport(description, support);
|
|
1505
|
+
if (allowed !== void 0) {
|
|
1506
|
+
const options = allowed.values.map((value) => ({
|
|
1507
|
+
value: String(value),
|
|
1508
|
+
label: describeChoice(value, unit, allowed)
|
|
1509
|
+
}));
|
|
1510
|
+
const fallback = allowed.values[0];
|
|
1511
|
+
const selected = current !== null && allowed.values.includes(current) ? current : current ?? fallback;
|
|
1512
|
+
const field = {
|
|
1513
|
+
type: "select",
|
|
1514
|
+
key,
|
|
1515
|
+
label,
|
|
1516
|
+
options: current !== null && !allowed.values.includes(current) ? [...options, {
|
|
1517
|
+
value: String(current),
|
|
1518
|
+
label: describeChoice(current, unit, allowed)
|
|
1519
|
+
}] : options,
|
|
1520
|
+
default: String(selected),
|
|
1521
|
+
disabled: !support.writable
|
|
1522
|
+
};
|
|
1523
|
+
return text === void 0 ? field : {
|
|
1524
|
+
...field,
|
|
1525
|
+
description: text
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1528
|
+
const field = {
|
|
1529
|
+
type: "number",
|
|
1530
|
+
key,
|
|
1531
|
+
label,
|
|
1532
|
+
unit,
|
|
1533
|
+
default: current ?? range?.min ?? 0,
|
|
1534
|
+
disabled: !support.writable,
|
|
1535
|
+
...range ? {
|
|
1536
|
+
min: range.min,
|
|
1537
|
+
max: range.max,
|
|
1538
|
+
step: range.step
|
|
1539
|
+
} : {}
|
|
1540
|
+
};
|
|
1541
|
+
return text === void 0 ? field : {
|
|
1542
|
+
...field,
|
|
1543
|
+
description: text
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
/**
|
|
1547
|
+
* The time-of-day offer for a window edge.
|
|
1548
|
+
*
|
|
1549
|
+
* Stepped at the camera's own granularity when that is coarse (Reolink's
|
|
1550
|
+
* 60), and at 15 minutes when it is fine (Hikvision's 1) — a 1-minute
|
|
1551
|
+
* offer would be 1 441 options. 15 is a multiple of 1, so every value
|
|
1552
|
+
* offered is one the camera accepts; the converse is not true, and a
|
|
1553
|
+
* window the camera already holds that falls between the offered values
|
|
1554
|
+
* still round-trips because it is carried in the row's value, not
|
|
1555
|
+
* re-derived from the offer.
|
|
1556
|
+
*/
|
|
1557
|
+
function timeOptions(granularityMinutes) {
|
|
1558
|
+
const step = granularityMinutes >= 30 ? granularityMinutes : 15;
|
|
1559
|
+
const out = [];
|
|
1560
|
+
for (let minute = 0; minute < require_dist.MINUTES_PER_DAY; minute += step) out.push({
|
|
1561
|
+
value: String(minute),
|
|
1562
|
+
label: require_dist.formatMinutes(minute)
|
|
1563
|
+
});
|
|
1564
|
+
out.push({
|
|
1565
|
+
value: String(require_dist.MINUTES_PER_DAY),
|
|
1566
|
+
label: "24:00"
|
|
1567
|
+
});
|
|
1568
|
+
return out;
|
|
1569
|
+
}
|
|
1570
|
+
function windowsField(options, status) {
|
|
1571
|
+
const schedule = options.schedule;
|
|
1572
|
+
const triggerOptions = schedule.triggers.map((trigger) => ({
|
|
1573
|
+
value: trigger,
|
|
1574
|
+
label: TRIGGER_LABELS[trigger]
|
|
1575
|
+
}));
|
|
1576
|
+
const dayOptions = require_dist.DAY_NAMES.map((name, index) => ({
|
|
1577
|
+
value: String(index),
|
|
1578
|
+
label: name
|
|
1579
|
+
}));
|
|
1580
|
+
const times = timeOptions(schedule.granularityMinutes);
|
|
1581
|
+
const itemFields = [
|
|
1582
|
+
{
|
|
1583
|
+
type: "select",
|
|
1584
|
+
key: WINDOW_ROW_KEYS.trigger,
|
|
1585
|
+
label: "Record on",
|
|
1586
|
+
options: triggerOptions,
|
|
1587
|
+
default: schedule.triggers[0] ?? ""
|
|
1588
|
+
},
|
|
1589
|
+
{
|
|
1590
|
+
type: "select",
|
|
1591
|
+
key: WINDOW_ROW_KEYS.day,
|
|
1592
|
+
label: "Day",
|
|
1593
|
+
options: dayOptions,
|
|
1594
|
+
default: "0"
|
|
1595
|
+
},
|
|
1596
|
+
{
|
|
1597
|
+
type: "select",
|
|
1598
|
+
key: WINDOW_ROW_KEYS.startMinute,
|
|
1599
|
+
label: "From",
|
|
1600
|
+
options: times,
|
|
1601
|
+
default: "0"
|
|
1602
|
+
},
|
|
1603
|
+
{
|
|
1604
|
+
type: "select",
|
|
1605
|
+
key: WINDOW_ROW_KEYS.endMinute,
|
|
1606
|
+
label: "To",
|
|
1607
|
+
options: times,
|
|
1608
|
+
default: String(require_dist.MINUTES_PER_DAY)
|
|
1609
|
+
}
|
|
1610
|
+
];
|
|
1611
|
+
const rows = (status?.windows ?? []).map((window) => ({
|
|
1612
|
+
[WINDOW_ROW_KEYS.trigger]: window.trigger,
|
|
1613
|
+
[WINDOW_ROW_KEYS.day]: String(window.day),
|
|
1614
|
+
[WINDOW_ROW_KEYS.startMinute]: String(window.startMinute),
|
|
1615
|
+
[WINDOW_ROW_KEYS.endMinute]: String(window.endMinute)
|
|
1616
|
+
}));
|
|
1617
|
+
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);
|
|
1618
|
+
const field = {
|
|
1619
|
+
type: "editable-array",
|
|
1620
|
+
key: RECORDING_ONBOARD_KEYS.windows,
|
|
1621
|
+
label: "Weekly schedule",
|
|
1622
|
+
itemFields,
|
|
1623
|
+
defaultItem: {
|
|
1624
|
+
[WINDOW_ROW_KEYS.trigger]: schedule.triggers[0] ?? "",
|
|
1625
|
+
[WINDOW_ROW_KEYS.day]: "0",
|
|
1626
|
+
[WINDOW_ROW_KEYS.startMinute]: "0",
|
|
1627
|
+
[WINDOW_ROW_KEYS.endMinute]: String(require_dist.MINUTES_PER_DAY)
|
|
1628
|
+
},
|
|
1629
|
+
default: rows,
|
|
1630
|
+
disabled: !schedule.support.writable,
|
|
1631
|
+
addLabel: "Add window",
|
|
1632
|
+
emptyMessage: "The camera records on no schedule.",
|
|
1633
|
+
rowTitleTemplate: "{day} · {trigger}"
|
|
1634
|
+
};
|
|
1635
|
+
return description === void 0 ? field : {
|
|
1636
|
+
...field,
|
|
1637
|
+
description
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Build the `recording-onboard` `ConfigUISchema`.
|
|
1642
|
+
*
|
|
1643
|
+
* Never returns null. The storage read works on every camera in the
|
|
1644
|
+
* fleet, so the section always has something true to say — and a camera
|
|
1645
|
+
* CamStack has not reached yet says exactly that, rather than
|
|
1646
|
+
* disappearing (D315: not-yet-known must not look like not-installed).
|
|
1647
|
+
*/
|
|
1648
|
+
function buildRecordingOnboardConfigSchema(options, status) {
|
|
1649
|
+
return { sections: [{
|
|
1650
|
+
id: "recording-onboard-storage",
|
|
1651
|
+
tab: "recording",
|
|
1652
|
+
title: "Camera storage",
|
|
1653
|
+
description: "The card in the camera, as the camera itself describes it.",
|
|
1654
|
+
columns: 1,
|
|
1655
|
+
fields: [
|
|
1656
|
+
storageBanner(status),
|
|
1657
|
+
volumesField(status),
|
|
1658
|
+
tracksField(status)
|
|
1659
|
+
]
|
|
1660
|
+
}, {
|
|
1661
|
+
id: "recording-onboard-recording",
|
|
1662
|
+
tab: "recording",
|
|
1663
|
+
title: "Camera recording",
|
|
1664
|
+
description: "What the camera records to its own card. This writes the camera, not CamStack's recorder.",
|
|
1665
|
+
columns: 2,
|
|
1666
|
+
fields: [
|
|
1667
|
+
booleanField(RECORDING_ONBOARD_KEYS.enabled, "Record to card", "The camera-side master switch.", options.enabled, status?.enabled ?? null),
|
|
1668
|
+
booleanField(RECORDING_ONBOARD_KEYS.overwriteWhenFull, "Overwrite when full", "Keep recording by discarding the oldest footage.", options.overwriteWhenFull, status?.overwriteWhenFull ?? null),
|
|
1669
|
+
numberField(RECORDING_ONBOARD_KEYS.preRecordSec, "Pre-record", "s", "Seconds kept from before the trigger.", options.preRecordSec, options.preRecordSecRange, status?.preRecordSec ?? null, options.preRecordSecAllowed),
|
|
1670
|
+
numberField(RECORDING_ONBOARD_KEYS.postRecordSec, "Post-record", "s", "Seconds kept after the trigger clears.", options.postRecordSec, options.postRecordSecRange, status?.postRecordSec ?? null, options.postRecordSecAllowed),
|
|
1671
|
+
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),
|
|
1672
|
+
windowsField(options, status)
|
|
1673
|
+
]
|
|
1674
|
+
}] };
|
|
1675
|
+
}
|
|
1676
|
+
function readBoolean$1(patch, key) {
|
|
1677
|
+
if (!(key in patch)) return void 0;
|
|
1678
|
+
const value = patch[key];
|
|
1679
|
+
if (typeof value === "boolean") return value;
|
|
1680
|
+
if (value === "true") return true;
|
|
1681
|
+
if (value === "false") return false;
|
|
1682
|
+
}
|
|
1683
|
+
function readNumber$1(patch, key) {
|
|
1684
|
+
if (!(key in patch)) return void 0;
|
|
1685
|
+
const value = Number(patch[key]);
|
|
1686
|
+
return Number.isFinite(value) ? value : void 0;
|
|
1687
|
+
}
|
|
1688
|
+
/** Is this string one of the cap's triggers? A guard, never a cast. */
|
|
1689
|
+
function isRecordTrigger(value) {
|
|
1690
|
+
return typeof value === "string" && value in TRIGGER_LABELS;
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Read one row of the windows `editable-array`.
|
|
1694
|
+
*
|
|
1695
|
+
* Returns null for a row that is not a window. Validation of whether
|
|
1696
|
+
* THIS camera accepts the window is deliberately not done here: the
|
|
1697
|
+
* provider refuses it by name through `describeOnboardRefusal`, which is
|
|
1698
|
+
* the one refusal vocabulary, and duplicating a weaker copy of it in the
|
|
1699
|
+
* form parser would give the operator two different answers.
|
|
1700
|
+
*/
|
|
1701
|
+
function readWindowRow(row) {
|
|
1702
|
+
if (typeof row !== "object" || row === null) return null;
|
|
1703
|
+
const record = { ...row };
|
|
1704
|
+
const trigger = record[WINDOW_ROW_KEYS.trigger];
|
|
1705
|
+
if (!isRecordTrigger(trigger)) return null;
|
|
1706
|
+
const day = Number(record[WINDOW_ROW_KEYS.day]);
|
|
1707
|
+
const startMinute = Number(record[WINDOW_ROW_KEYS.startMinute]);
|
|
1708
|
+
const endMinute = Number(record[WINDOW_ROW_KEYS.endMinute]);
|
|
1709
|
+
if (!Number.isInteger(day) || day < 0 || day > 6) return null;
|
|
1710
|
+
if (!Number.isInteger(startMinute) || !Number.isInteger(endMinute)) return null;
|
|
1711
|
+
return {
|
|
1712
|
+
trigger,
|
|
1713
|
+
day,
|
|
1714
|
+
startMinute,
|
|
1715
|
+
endMinute
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
/**
|
|
1719
|
+
* Re-parse a flat `ConfigFormBuilder` patch into a
|
|
1720
|
+
* `RecordingOnboardPatch`-shaped object.
|
|
1721
|
+
*
|
|
1722
|
+
* Returns only the keys the patch actually carried. The windows key is
|
|
1723
|
+
* all-or-nothing on purpose: the field is a single editable array, so
|
|
1724
|
+
* its presence means the operator submitted the COMPLETE window set. A
|
|
1725
|
+
* row that cannot be read is DROPPED and the remaining set is still
|
|
1726
|
+
* submitted — the provider then refuses or applies a set the operator
|
|
1727
|
+
* can see in the form, rather than the form and the camera silently
|
|
1728
|
+
* disagreeing about a row neither shows.
|
|
1729
|
+
*/
|
|
1730
|
+
function parseRecordingOnboardFormPatch(patch) {
|
|
1731
|
+
const out = {};
|
|
1732
|
+
const enabled = readBoolean$1(patch, RECORDING_ONBOARD_KEYS.enabled);
|
|
1733
|
+
if (enabled !== void 0) out.enabled = enabled;
|
|
1734
|
+
const overwrite = readBoolean$1(patch, RECORDING_ONBOARD_KEYS.overwriteWhenFull);
|
|
1735
|
+
if (overwrite !== void 0) out.overwriteWhenFull = overwrite;
|
|
1736
|
+
const pre = readNumber$1(patch, RECORDING_ONBOARD_KEYS.preRecordSec);
|
|
1737
|
+
if (pre !== void 0) out.preRecordSec = pre;
|
|
1738
|
+
const post = readNumber$1(patch, RECORDING_ONBOARD_KEYS.postRecordSec);
|
|
1739
|
+
if (post !== void 0) out.postRecordSec = post;
|
|
1740
|
+
const segment = readNumber$1(patch, RECORDING_ONBOARD_KEYS.segmentMinutes);
|
|
1741
|
+
if (segment !== void 0) out.segmentMinutes = segment;
|
|
1742
|
+
if (RECORDING_ONBOARD_KEYS.windows in patch) {
|
|
1743
|
+
const raw = patch[RECORDING_ONBOARD_KEYS.windows];
|
|
1744
|
+
if (Array.isArray(raw)) {
|
|
1745
|
+
const windows = [];
|
|
1746
|
+
for (const row of raw) {
|
|
1747
|
+
const parsed = readWindowRow(row);
|
|
1748
|
+
if (parsed) windows.push(parsed);
|
|
1749
|
+
}
|
|
1750
|
+
out.windows = windows;
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
return out;
|
|
1754
|
+
}
|
|
1755
|
+
//#endregion
|
|
1294
1756
|
//#region src/builtins/device-manager/device-config-contribution.ts
|
|
1295
1757
|
/**
|
|
1296
1758
|
* D14 device-config archetype — framework-side contribution derivation.
|
|
@@ -1318,6 +1780,7 @@ function parseImageSettingsFormPatch(patch) {
|
|
|
1318
1780
|
var STREAM_PARAMS_BUILDER_ID = "stream-params";
|
|
1319
1781
|
var DAY_NIGHT_BUILDER_ID = "day-night";
|
|
1320
1782
|
var IMAGE_SETTINGS_BUILDER_ID = "image-settings";
|
|
1783
|
+
var RECORDING_ONBOARD_BUILDER_ID = "recording-onboard";
|
|
1321
1784
|
var STREAM_PARAMS_REDUCER = {
|
|
1322
1785
|
kind: "profile",
|
|
1323
1786
|
buildSchema: (options, status) => require_dist.buildStreamParamsConfigSchema(options, status ?? null),
|
|
@@ -1338,10 +1801,16 @@ var IMAGE_SETTINGS_REDUCER = {
|
|
|
1338
1801
|
buildSchema: (options, status) => buildImageSettingsConfigSchema(options, status ?? null),
|
|
1339
1802
|
parsePatch: (patch) => parseImageSettingsFormPatch(patch)
|
|
1340
1803
|
};
|
|
1804
|
+
var RECORDING_ONBOARD_REDUCER = {
|
|
1805
|
+
kind: "settings",
|
|
1806
|
+
buildSchema: (options, status) => buildRecordingOnboardConfigSchema(options, status ?? null),
|
|
1807
|
+
parsePatch: (patch) => parseRecordingOnboardFormPatch(patch)
|
|
1808
|
+
};
|
|
1341
1809
|
var BUILDER_REDUCERS = {
|
|
1342
1810
|
[STREAM_PARAMS_BUILDER_ID]: STREAM_PARAMS_REDUCER,
|
|
1343
1811
|
[DAY_NIGHT_BUILDER_ID]: DAY_NIGHT_REDUCER,
|
|
1344
|
-
[IMAGE_SETTINGS_BUILDER_ID]: IMAGE_SETTINGS_REDUCER
|
|
1812
|
+
[IMAGE_SETTINGS_BUILDER_ID]: IMAGE_SETTINGS_REDUCER,
|
|
1813
|
+
[RECORDING_ONBOARD_BUILDER_ID]: RECORDING_ONBOARD_REDUCER
|
|
1345
1814
|
};
|
|
1346
1815
|
function resolveReducer(builderId) {
|
|
1347
1816
|
const reducer = BUILDER_REDUCERS[builderId];
|