@camstack/addon-pipeline-orchestrator 1.2.128 → 1.2.129
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/_stub.js +2 -2
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-D6mD53Q1.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-ChSa_Eh-.mjs} +3 -3
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BlzMZjfz.mjs +26 -0
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DHxtMqJ3.mjs +26 -0
- package/dist/{hostInit-CAeXD4PM.mjs → hostInit-DomAMTeV.mjs} +3 -3
- package/dist/index.js +403 -34
- package/dist/index.mjs +403 -34
- package/dist/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-ChOONdP8.mjs +0 -26
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CKZAmz0G.mjs +0 -26
package/dist/index.js
CHANGED
|
@@ -8894,6 +8894,20 @@ var RelocateJobSchema = object({
|
|
|
8894
8894
|
* in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
|
|
8895
8895
|
*/
|
|
8896
8896
|
rowsReconciled: number().int().nonnegative().optional(),
|
|
8897
|
+
/**
|
|
8898
|
+
* Rows this run FORGOT because the file they name is not on disk.
|
|
8899
|
+
*
|
|
8900
|
+
* The mover derived the path from the row's own fields and `stat`ed it; an
|
|
8901
|
+
* ENOENT there is a per-path confirmation that the segment is gone (D296),
|
|
8902
|
+
* and the durable row is dropped through the same channel eviction uses. It
|
|
8903
|
+
* is reported for the same reason `rowsReconciled` is: this is a durable
|
|
8904
|
+
* mutation nobody asked for, and a migration that quietly erases hour rows is
|
|
8905
|
+
* the same failure as one that quietly skips them (D295).
|
|
8906
|
+
*
|
|
8907
|
+
* The production drain of 2026-08-30 would have reported 11 074 here — the
|
|
8908
|
+
* ledger claimed 5.65 GB of footage that no longer existed.
|
|
8909
|
+
*/
|
|
8910
|
+
rowsForgotten: number().int().nonnegative().optional(),
|
|
8897
8911
|
startedAt: number(),
|
|
8898
8912
|
finishedAt: number().nullable(),
|
|
8899
8913
|
error: string().nullable()
|
|
@@ -9257,6 +9271,91 @@ var RelocateResidueSchema = object({
|
|
|
9257
9271
|
segments: number().int().nonnegative(),
|
|
9258
9272
|
bytes: number().int().nonnegative()
|
|
9259
9273
|
}).nullable();
|
|
9274
|
+
/**
|
|
9275
|
+
* Ask one location whether its durable hour rows describe the disk — the walk
|
|
9276
|
+
* (D319).
|
|
9277
|
+
*
|
|
9278
|
+
* `apply` DEFAULTS TO FALSE and that default is the product: the operator's
|
|
9279
|
+
* missing tool is the question, and the dry run is how they sanity-check the
|
|
9280
|
+
* destructive run before authorising it.
|
|
9281
|
+
*/
|
|
9282
|
+
var LedgerWalkInputSchema = object({
|
|
9283
|
+
locationId: string().min(1),
|
|
9284
|
+
/** Forget the confirmed-absent rows, rather than only counting them. */
|
|
9285
|
+
apply: boolean().optional(),
|
|
9286
|
+
/** Narrow to one camera. */
|
|
9287
|
+
deviceId: number().int().positive().optional(),
|
|
9288
|
+
/** Narrow to these recording profiles; empty/absent = every profile. */
|
|
9289
|
+
profiles: array(string().min(1)).optional()
|
|
9290
|
+
});
|
|
9291
|
+
/** Why a whole walk did nothing. Every one leaves the ledger untouched. */
|
|
9292
|
+
var LedgerWalkRefusalSchema = _enum([
|
|
9293
|
+
"location-unknown",
|
|
9294
|
+
"source-writable",
|
|
9295
|
+
"no-ledger",
|
|
9296
|
+
"archive-unreadable",
|
|
9297
|
+
"anchor-absent",
|
|
9298
|
+
"anchor-unreadable",
|
|
9299
|
+
"anchor-moved"
|
|
9300
|
+
]);
|
|
9301
|
+
_enum([
|
|
9302
|
+
"live-tail",
|
|
9303
|
+
"listing-error",
|
|
9304
|
+
"path-mismatch",
|
|
9305
|
+
"durable-refused"
|
|
9306
|
+
]);
|
|
9307
|
+
/** Every skip reason, always present, always a number — so a reason that never
|
|
9308
|
+
* fired reports as zero rather than absent and the report shape is constant
|
|
9309
|
+
* between passes. Spelled out rather than `z.record` for exactly that. */
|
|
9310
|
+
var LedgerWalkSkipCountsSchema = object({
|
|
9311
|
+
"live-tail": number().int().nonnegative(),
|
|
9312
|
+
"listing-error": number().int().nonnegative(),
|
|
9313
|
+
"path-mismatch": number().int().nonnegative(),
|
|
9314
|
+
"durable-refused": number().int().nonnegative()
|
|
9315
|
+
});
|
|
9316
|
+
/** One camera's share of a walk, so a report names cameras and not rows. */
|
|
9317
|
+
var LedgerWalkDeviceReportSchema = object({
|
|
9318
|
+
deviceId: number().int(),
|
|
9319
|
+
hoursWalked: number().int().nonnegative(),
|
|
9320
|
+
hoursMissing: number().int().nonnegative(),
|
|
9321
|
+
ghostSegments: number().int().nonnegative(),
|
|
9322
|
+
ghostBytes: number().int().nonnegative(),
|
|
9323
|
+
forgottenSegments: number().int().nonnegative(),
|
|
9324
|
+
orphanFiles: number().int().nonnegative()
|
|
9325
|
+
});
|
|
9326
|
+
/**
|
|
9327
|
+
* What one walk claimed, listed, found and (only when armed) forgot.
|
|
9328
|
+
*
|
|
9329
|
+
* `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
|
|
9330
|
+
* walk that saw a fraction of the location is visible in its own report rather
|
|
9331
|
+
* than in the absence of one.
|
|
9332
|
+
*/
|
|
9333
|
+
var LedgerWalkReportSchema = object({
|
|
9334
|
+
locationId: string(),
|
|
9335
|
+
applied: boolean(),
|
|
9336
|
+
refused: LedgerWalkRefusalSchema.nullable(),
|
|
9337
|
+
archiveSegments: number().int().nonnegative().nullable(),
|
|
9338
|
+
archiveBytes: number().int().nonnegative().nullable(),
|
|
9339
|
+
hoursClaimed: number().int().nonnegative(),
|
|
9340
|
+
hoursWalked: number().int().nonnegative(),
|
|
9341
|
+
hoursMissing: number().int().nonnegative(),
|
|
9342
|
+
/** `readdir` calls issued — the cost, stated in the unit that is paid. */
|
|
9343
|
+
listings: number().int().nonnegative(),
|
|
9344
|
+
segmentsClaimed: number().int().nonnegative(),
|
|
9345
|
+
ghostSegments: number().int().nonnegative(),
|
|
9346
|
+
ghostBytes: number().int().nonnegative(),
|
|
9347
|
+
ghostHoursWhole: number().int().nonnegative(),
|
|
9348
|
+
forgottenSegments: number().int().nonnegative(),
|
|
9349
|
+
forgottenBytes: number().int().nonnegative(),
|
|
9350
|
+
/** Files under a claimed hour that no durable row names. Never deleted. */
|
|
9351
|
+
orphanFiles: number().int().nonnegative(),
|
|
9352
|
+
orphanSample: array(string()).readonly(),
|
|
9353
|
+
hoursSkipped: number().int().nonnegative(),
|
|
9354
|
+
skippedByReason: LedgerWalkSkipCountsSchema,
|
|
9355
|
+
/** The walk stopped at its per-pass hour bound with claims unwalked. */
|
|
9356
|
+
bounded: boolean(),
|
|
9357
|
+
byDevice: array(LedgerWalkDeviceReportSchema).readonly()
|
|
9358
|
+
});
|
|
9260
9359
|
/** How many rows a media pass would still act on against a given target — the
|
|
9261
9360
|
* media lane's denominator AND its residue, from ONE derivation so the two can
|
|
9262
9361
|
* never disagree. `null` = the count could not be taken. */
|
|
@@ -19833,13 +19932,15 @@ var ListGroupsPageSchema = object({
|
|
|
19833
19932
|
groups: array(AnalyticsGroupRecordSchema).readonly(),
|
|
19834
19933
|
nextCursor: string().nullable()
|
|
19835
19934
|
});
|
|
19935
|
+
var KEY_EVENTS_DEFAULT_LIMIT = 50;
|
|
19936
|
+
var KEY_EVENTS_MAX_LIMIT = 200;
|
|
19836
19937
|
var KeyEventQueryInput = object({
|
|
19837
19938
|
deviceId: number(),
|
|
19838
19939
|
/** Window lower bound (track firstSeen ≥ since). */
|
|
19839
19940
|
since: number(),
|
|
19840
19941
|
/** Window upper bound (track firstSeen ≤ until). */
|
|
19841
19942
|
until: number(),
|
|
19842
|
-
limit: number().int().min(1).max(
|
|
19943
|
+
limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
|
|
19843
19944
|
/** Drop tracks scoring below this importance. */
|
|
19844
19945
|
minImportance: number().min(0).max(1).optional(),
|
|
19845
19946
|
/** Restrict to a single class (e.g. 'person'). */
|
|
@@ -19861,6 +19962,32 @@ var KeyEventSchema = object({
|
|
|
19861
19962
|
...TrackFlagFields,
|
|
19862
19963
|
...TrackRetrainFields
|
|
19863
19964
|
});
|
|
19965
|
+
/** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
|
|
19966
|
+
var KeyEventBatchQueryInput = object({
|
|
19967
|
+
deviceIds: array(number()).min(1).max(200),
|
|
19968
|
+
since: number(),
|
|
19969
|
+
until: number(),
|
|
19970
|
+
/** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
|
|
19971
|
+
* across the set, which would let a busy camera starve a quiet one of its
|
|
19972
|
+
* rows and change what the merged feed contains. */
|
|
19973
|
+
limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
|
|
19974
|
+
minImportance: number().min(0).max(1).optional(),
|
|
19975
|
+
classFilter: string().optional()
|
|
19976
|
+
});
|
|
19977
|
+
/**
|
|
19978
|
+
* One camera's key events in a batch answer.
|
|
19979
|
+
*
|
|
19980
|
+
* The row exists for every requested id. `getKeyEvents` degrades to `[]` on
|
|
19981
|
+
* error rather than throwing, so a camera whose store read failed and one with
|
|
19982
|
+
* no events in the window were ALREADY indistinguishable per camera — the
|
|
19983
|
+
* batch does not make that worse, and the row keeps the deviceId the single
|
|
19984
|
+
* method's output never carried (the caller used to stamp it from the fan-out
|
|
19985
|
+
* key, which only worked because there was one query per camera).
|
|
19986
|
+
*/
|
|
19987
|
+
var KeyEventsForDeviceSchema = object({
|
|
19988
|
+
deviceId: number(),
|
|
19989
|
+
events: array(KeyEventSchema).readonly()
|
|
19990
|
+
});
|
|
19864
19991
|
object({
|
|
19865
19992
|
trackId: string(),
|
|
19866
19993
|
className: string(),
|
|
@@ -20130,7 +20257,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
20130
20257
|
until: number().optional(),
|
|
20131
20258
|
kinds: array(string()).optional(),
|
|
20132
20259
|
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
|
|
20133
|
-
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
|
|
20260
|
+
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
|
|
20134
20261
|
deviceId: number(),
|
|
20135
20262
|
since: number(),
|
|
20136
20263
|
until: number(),
|
|
@@ -28263,6 +28390,9 @@ method(object({
|
|
|
28263
28390
|
}), method(RelocateResidueInputSchema, RelocateResidueSchema, {
|
|
28264
28391
|
kind: "query",
|
|
28265
28392
|
auth: "admin"
|
|
28393
|
+
}), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
|
|
28394
|
+
kind: "mutation",
|
|
28395
|
+
auth: "admin"
|
|
28266
28396
|
}), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
|
|
28267
28397
|
kind: "mutation",
|
|
28268
28398
|
auth: "admin"
|
|
@@ -28654,7 +28784,26 @@ var SceneMonitorStatusSchema = object({
|
|
|
28654
28784
|
monitors: array(SceneMonitorSchema),
|
|
28655
28785
|
lastFetchedAt: number()
|
|
28656
28786
|
});
|
|
28657
|
-
|
|
28787
|
+
/**
|
|
28788
|
+
* One camera's row in a `listScenesBatch` answer.
|
|
28789
|
+
*
|
|
28790
|
+
* `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
|
|
28791
|
+
* is a `defaultActive` wrapper, so every camera is asked; a camera whose
|
|
28792
|
+
* provider is absent (pipeline-analytics not deployed, the post-processing node
|
|
28793
|
+
* down) cannot answer, and that is not the same fact as a camera with no scenes
|
|
28794
|
+
* configured. Fanned out per camera the difference was visible — one query
|
|
28795
|
+
* errored while the others resolved — and a batch that returned only the rows
|
|
28796
|
+
* it managed would have destroyed it, silently, by making an unreachable camera
|
|
28797
|
+
* indistinguishable from one that answered `monitors: []`.
|
|
28798
|
+
*
|
|
28799
|
+
* So: EVERY requested deviceId gets a row. `status: null` means "this camera
|
|
28800
|
+
* could not be read"; `status.monitors: []` means "read, and it has none".
|
|
28801
|
+
*/
|
|
28802
|
+
var SceneMonitorStatusForDeviceSchema = object({
|
|
28803
|
+
deviceId: number(),
|
|
28804
|
+
status: SceneMonitorStatusSchema.nullable()
|
|
28805
|
+
});
|
|
28806
|
+
DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()), method(object({
|
|
28658
28807
|
deviceId: number(),
|
|
28659
28808
|
label: string(),
|
|
28660
28809
|
roi: MaskRectShapeSchema,
|
|
@@ -29978,6 +30127,27 @@ var CameraOccupancySnapshotSchema = object({
|
|
|
29978
30127
|
stationaryObjects: array(StationaryObjectSchema).readonly().optional()
|
|
29979
30128
|
});
|
|
29980
30129
|
/**
|
|
30130
|
+
* One camera's row in a `getCurrentSnapshotBatch` answer.
|
|
30131
|
+
*
|
|
30132
|
+
* THREE outcomes, and the single-camera method could only express two of them
|
|
30133
|
+
* because `snapshot: null` was already spoken for:
|
|
30134
|
+
*
|
|
30135
|
+
* - `read: 'read'`, `snapshot` present — the live occupancy reading.
|
|
30136
|
+
* - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
|
|
30137
|
+
* yet: no frame since boot, and no parked-object registry to hydrate from.
|
|
30138
|
+
* - `read: 'unreadable'` — the owner could not answer for this
|
|
30139
|
+
* camera. `snapshot` is null, and it does NOT mean "nothing parked here".
|
|
30140
|
+
*
|
|
30141
|
+
* Collapsing the last two is the failure this field exists to prevent: a
|
|
30142
|
+
* hydration that threw would otherwise render as an empty Stationary section,
|
|
30143
|
+
* which is a definite claim about a camera nobody could read.
|
|
30144
|
+
*/
|
|
30145
|
+
var CameraOccupancySnapshotForDeviceSchema = object({
|
|
30146
|
+
deviceId: number(),
|
|
30147
|
+
read: _enum(["read", "unreadable"]),
|
|
30148
|
+
snapshot: CameraOccupancySnapshotSchema.nullable()
|
|
30149
|
+
});
|
|
30150
|
+
/**
|
|
29981
30151
|
* Time-series resolution. The history methods return one bucket per
|
|
29982
30152
|
* step over the requested range. Smaller resolutions cost more
|
|
29983
30153
|
* memory + bandwidth; bound to discrete steps so caller cannot ask
|
|
@@ -30001,7 +30171,7 @@ var HistoryPointSchema = object({
|
|
|
30001
30171
|
/** Object count averaged over the bucket (rounded to nearest integer). */
|
|
30002
30172
|
count: number().int().nonnegative()
|
|
30003
30173
|
});
|
|
30004
|
-
DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({
|
|
30174
|
+
DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()), method(object({
|
|
30005
30175
|
deviceId: number(),
|
|
30006
30176
|
zoneId: string(),
|
|
30007
30177
|
className: string().optional()
|
|
@@ -33390,6 +33560,12 @@ Object.freeze({
|
|
|
33390
33560
|
addonId: null,
|
|
33391
33561
|
access: "view"
|
|
33392
33562
|
},
|
|
33563
|
+
"pipelineAnalytics.getKeyEventsBatch": {
|
|
33564
|
+
capName: "pipeline-analytics",
|
|
33565
|
+
capScope: "device",
|
|
33566
|
+
addonId: null,
|
|
33567
|
+
access: "view"
|
|
33568
|
+
},
|
|
33393
33569
|
"pipelineAnalytics.getMotionEvents": {
|
|
33394
33570
|
capName: "pipeline-analytics",
|
|
33395
33571
|
capScope: "device",
|
|
@@ -34566,6 +34742,12 @@ Object.freeze({
|
|
|
34566
34742
|
addonId: null,
|
|
34567
34743
|
access: "view"
|
|
34568
34744
|
},
|
|
34745
|
+
"recording.reconcileLedgerAgainstDisk": {
|
|
34746
|
+
capName: "recording",
|
|
34747
|
+
capScope: "system",
|
|
34748
|
+
addonId: null,
|
|
34749
|
+
access: "create"
|
|
34750
|
+
},
|
|
34569
34751
|
"recording.refreshStorageLocationsForMigration": {
|
|
34570
34752
|
capName: "recording",
|
|
34571
34753
|
capScope: "system",
|
|
@@ -34692,6 +34874,12 @@ Object.freeze({
|
|
|
34692
34874
|
addonId: null,
|
|
34693
34875
|
access: "view"
|
|
34694
34876
|
},
|
|
34877
|
+
"sceneMonitor.listScenesBatch": {
|
|
34878
|
+
capName: "scene-monitor",
|
|
34879
|
+
capScope: "device",
|
|
34880
|
+
addonId: null,
|
|
34881
|
+
access: "view"
|
|
34882
|
+
},
|
|
34695
34883
|
"sceneMonitor.recheckNow": {
|
|
34696
34884
|
capName: "scene-monitor",
|
|
34697
34885
|
capScope: "device",
|
|
@@ -36048,6 +36236,12 @@ Object.freeze({
|
|
|
36048
36236
|
addonId: null,
|
|
36049
36237
|
access: "view"
|
|
36050
36238
|
},
|
|
36239
|
+
"zoneAnalytics.getCurrentSnapshotBatch": {
|
|
36240
|
+
capName: "zone-analytics",
|
|
36241
|
+
capScope: "device",
|
|
36242
|
+
addonId: null,
|
|
36243
|
+
access: "view"
|
|
36244
|
+
},
|
|
36051
36245
|
"zoneAnalytics.getUnzonedHistory": {
|
|
36052
36246
|
capName: "zone-analytics",
|
|
36053
36247
|
capScope: "device",
|
|
@@ -37052,6 +37246,11 @@ Object.freeze({
|
|
|
37052
37246
|
form: "single",
|
|
37053
37247
|
optional: false
|
|
37054
37248
|
}],
|
|
37249
|
+
"pipelineAnalytics.getKeyEventsBatch": [{
|
|
37250
|
+
name: "deviceIds",
|
|
37251
|
+
form: "array",
|
|
37252
|
+
optional: false
|
|
37253
|
+
}],
|
|
37055
37254
|
"pipelineAnalytics.getMotionEvents": [{
|
|
37056
37255
|
name: "deviceId",
|
|
37057
37256
|
form: "single",
|
|
@@ -37492,6 +37691,11 @@ Object.freeze({
|
|
|
37492
37691
|
form: "single",
|
|
37493
37692
|
optional: false
|
|
37494
37693
|
}],
|
|
37694
|
+
"recording.reconcileLedgerAgainstDisk": [{
|
|
37695
|
+
name: "deviceId",
|
|
37696
|
+
form: "single",
|
|
37697
|
+
optional: true
|
|
37698
|
+
}],
|
|
37495
37699
|
"recording.relocateFootage": [{
|
|
37496
37700
|
name: "deviceId",
|
|
37497
37701
|
form: "single",
|
|
@@ -37557,6 +37761,11 @@ Object.freeze({
|
|
|
37557
37761
|
form: "single",
|
|
37558
37762
|
optional: false
|
|
37559
37763
|
}],
|
|
37764
|
+
"sceneMonitor.listScenesBatch": [{
|
|
37765
|
+
name: "deviceIds",
|
|
37766
|
+
form: "array",
|
|
37767
|
+
optional: false
|
|
37768
|
+
}],
|
|
37560
37769
|
"sceneMonitor.recheckNow": [{
|
|
37561
37770
|
name: "deviceId",
|
|
37562
37771
|
form: "single",
|
|
@@ -37818,6 +38027,11 @@ Object.freeze({
|
|
|
37818
38027
|
form: "single",
|
|
37819
38028
|
optional: false
|
|
37820
38029
|
}],
|
|
38030
|
+
"zoneAnalytics.getCurrentSnapshotBatch": [{
|
|
38031
|
+
name: "deviceIds",
|
|
38032
|
+
form: "array",
|
|
38033
|
+
optional: false
|
|
38034
|
+
}],
|
|
37821
38035
|
"zoneAnalytics.getUnzonedHistory": [{
|
|
37822
38036
|
name: "deviceId",
|
|
37823
38037
|
form: "single",
|
|
@@ -40927,6 +41141,124 @@ function composeCameraStatus(input) {
|
|
|
40927
41141
|
};
|
|
40928
41142
|
}
|
|
40929
41143
|
//#endregion
|
|
41144
|
+
//#region src/camera-status/fleet-read-pass.ts
|
|
41145
|
+
/**
|
|
41146
|
+
* Keep a rejection from becoming an UNHANDLED one.
|
|
41147
|
+
*
|
|
41148
|
+
* A shared promise is created by the first camera to ask and consumed by the
|
|
41149
|
+
* others later — `listBindableCapsForDeviceType` is reached in stage 2 of
|
|
41150
|
+
* `readSwitchAuthorities`, after an await, so two cameras genuinely attach
|
|
41151
|
+
* their handlers in different ticks. Without this the first rejection would be
|
|
41152
|
+
* unhandled for the window in between.
|
|
41153
|
+
*
|
|
41154
|
+
* The handler is a no-op ON PURPOSE: it never becomes the answer. Every real
|
|
41155
|
+
* consumer attaches its own `.catch` and reports the failure per camera.
|
|
41156
|
+
*/
|
|
41157
|
+
function keepAlive(p) {
|
|
41158
|
+
p.catch(() => void 0);
|
|
41159
|
+
return p;
|
|
41160
|
+
}
|
|
41161
|
+
var FleetReadPass = class {
|
|
41162
|
+
deps;
|
|
41163
|
+
roster = null;
|
|
41164
|
+
bindings = null;
|
|
41165
|
+
mutes = null;
|
|
41166
|
+
bindableByType = /* @__PURE__ */ new Map();
|
|
41167
|
+
slotsByNode = /* @__PURE__ */ new Map();
|
|
41168
|
+
rtspByNode = /* @__PURE__ */ new Map();
|
|
41169
|
+
provisioningByNode = /* @__PURE__ */ new Map();
|
|
41170
|
+
selectedEngineByNode = /* @__PURE__ */ new Map();
|
|
41171
|
+
constructor(deps) {
|
|
41172
|
+
this.deps = deps;
|
|
41173
|
+
}
|
|
41174
|
+
/** The cameras this pass covers — the set both batch reads are issued for. */
|
|
41175
|
+
get deviceIds() {
|
|
41176
|
+
return this.deps.deviceIds;
|
|
41177
|
+
}
|
|
41178
|
+
/**
|
|
41179
|
+
* The device rows for the pass's set.
|
|
41180
|
+
*
|
|
41181
|
+
* **The shape follows the SET, not the caller.** A pass of one asks
|
|
41182
|
+
* `getDevice` — the exact question, and the one the write path
|
|
41183
|
+
* (`CameraSwitchService.setCameraSwitch`) has always asked. A pass of many
|
|
41184
|
+
* asks `listAll({deviceIds})`, which exists precisely because "these N" was
|
|
41185
|
+
* otherwise either N round trips or the whole fleet. Answering a set of one
|
|
41186
|
+
* with the fleet-shaped method would be no cheaper and would move the write
|
|
41187
|
+
* path onto a read it does not need.
|
|
41188
|
+
*
|
|
41189
|
+
* `projection: 'slim'` because the only fields read off it are `type` and
|
|
41190
|
+
* `disabled`: the full projection reads each device's settings row, which is
|
|
41191
|
+
* the per-device round trip this whole change exists to remove.
|
|
41192
|
+
*/
|
|
41193
|
+
deviceRoster(api) {
|
|
41194
|
+
const single = this.deps.deviceIds.length === 1 ? this.deps.deviceIds[0] : void 0;
|
|
41195
|
+
const source = single === void 0 ? "deviceManager.listAll" : "deviceManager.getDevice";
|
|
41196
|
+
if (this.roster === null) this.roster = keepAlive(single === void 0 ? api.deviceManager.listAll.query({
|
|
41197
|
+
deviceIds: [...this.deps.deviceIds],
|
|
41198
|
+
projection: "slim"
|
|
41199
|
+
}) : api.deviceManager.getDevice.query({ deviceId: single }).then((row) => row === null ? [] : [row]));
|
|
41200
|
+
return {
|
|
41201
|
+
source,
|
|
41202
|
+
result: this.roster
|
|
41203
|
+
};
|
|
41204
|
+
}
|
|
41205
|
+
/** The binding rows for the pass's set — `getBindings` for one, the batch for many. */
|
|
41206
|
+
bindingRows(api) {
|
|
41207
|
+
const single = this.deps.deviceIds.length === 1 ? this.deps.deviceIds[0] : void 0;
|
|
41208
|
+
const source = single === void 0 ? "deviceManager.getBindingsBatch" : "deviceManager.getBindings";
|
|
41209
|
+
if (this.bindings === null) this.bindings = keepAlive(single === void 0 ? api.deviceManager.getBindingsBatch.query({ deviceIds: [...this.deps.deviceIds] }) : api.deviceManager.getBindings.query({ deviceId: single }).then((row) => [row]));
|
|
41210
|
+
return {
|
|
41211
|
+
source,
|
|
41212
|
+
result: this.bindings
|
|
41213
|
+
};
|
|
41214
|
+
}
|
|
41215
|
+
/** The fleet's muted-camera list. One answer for every camera in the pass. */
|
|
41216
|
+
mutedDevices(api) {
|
|
41217
|
+
if (this.mutes === null) this.mutes = keepAlive(api.notificationRules.listDeviceMutes.query({}));
|
|
41218
|
+
return this.mutes;
|
|
41219
|
+
}
|
|
41220
|
+
/** The bindable caps for one device TYPE. Every camera shares one answer. */
|
|
41221
|
+
bindableCapsFor(api, deviceType) {
|
|
41222
|
+
const existing = this.bindableByType.get(deviceType);
|
|
41223
|
+
if (existing !== void 0) return existing;
|
|
41224
|
+
const created = keepAlive(api.deviceManager.listBindableCapsForDeviceType.query({ deviceType }));
|
|
41225
|
+
this.bindableByType.set(deviceType, created);
|
|
41226
|
+
return created;
|
|
41227
|
+
}
|
|
41228
|
+
/** One source node's whole profile-slot table. */
|
|
41229
|
+
profileSlots(api, sourceNodeId) {
|
|
41230
|
+
const existing = this.slotsByNode.get(sourceNodeId);
|
|
41231
|
+
if (existing !== void 0) return existing;
|
|
41232
|
+
const created = keepAlive(api.streamBroker.listAllProfileSlots.query(void 0, nodePin(sourceNodeId)));
|
|
41233
|
+
this.slotsByNode.set(sourceNodeId, created);
|
|
41234
|
+
return created;
|
|
41235
|
+
}
|
|
41236
|
+
/** One source node's whole RTSP-restream table. */
|
|
41237
|
+
rtspEntries(api, sourceNodeId) {
|
|
41238
|
+
const existing = this.rtspByNode.get(sourceNodeId);
|
|
41239
|
+
if (existing !== void 0) return existing;
|
|
41240
|
+
const created = keepAlive(api.streamBroker.getAllRtspEntries.query({}, nodePin(sourceNodeId)));
|
|
41241
|
+
this.rtspByNode.set(sourceNodeId, created);
|
|
41242
|
+
return created;
|
|
41243
|
+
}
|
|
41244
|
+
/** One detection node's runtime-provisioning snapshot. */
|
|
41245
|
+
engineProvisioning(api, detectionNodeId) {
|
|
41246
|
+
const existing = this.provisioningByNode.get(detectionNodeId);
|
|
41247
|
+
if (existing !== void 0) return existing;
|
|
41248
|
+
const created = keepAlive(api.pipelineExecutor.getEngineProvisioning.query({ nodeId: detectionNodeId }));
|
|
41249
|
+
this.provisioningByNode.set(detectionNodeId, created);
|
|
41250
|
+
return created;
|
|
41251
|
+
}
|
|
41252
|
+
/** The executor's bootstrap engine, as the detection stage reads it. */
|
|
41253
|
+
selectedEngine(api, detectionNodeId) {
|
|
41254
|
+
const existing = this.selectedEngineByNode.get(detectionNodeId);
|
|
41255
|
+
if (existing !== void 0) return existing;
|
|
41256
|
+
const created = keepAlive(api.pipelineExecutor.getSelectedEngine.query({ nodeId: detectionNodeId }));
|
|
41257
|
+
this.selectedEngineByNode.set(detectionNodeId, created);
|
|
41258
|
+
return created;
|
|
41259
|
+
}
|
|
41260
|
+
};
|
|
41261
|
+
//#endregion
|
|
40930
41262
|
//#region src/camera-status-service.ts
|
|
40931
41263
|
/** WebRTC consumer kinds counted toward `BrokerResult.webrtcSessions`. */
|
|
40932
41264
|
var WEBRTC_KINDS = new Set([
|
|
@@ -41115,7 +41447,7 @@ var CameraStatusService = class {
|
|
|
41115
41447
|
* broker's actual decode-session node into `liveDecoder` (T6) — the first
|
|
41116
41448
|
* slot that reports one wins.
|
|
41117
41449
|
*/
|
|
41118
|
-
buildBrokerStage(api, allSlotsFetch, deviceId, sourceNodeId, liveDecoder, sink) {
|
|
41450
|
+
buildBrokerStage(api, allSlotsFetch, deviceId, sourceNodeId, pass, liveDecoder, sink) {
|
|
41119
41451
|
if (!api || !allSlotsFetch) return Promise.resolve(null);
|
|
41120
41452
|
return this.boundedStage(allSlotsFetch.then(async (slots) => {
|
|
41121
41453
|
const deviceSlots = slots.filter((s) => s.deviceId === deviceId);
|
|
@@ -41125,12 +41457,13 @@ var CameraStatusService = class {
|
|
|
41125
41457
|
rtspRestream: false
|
|
41126
41458
|
};
|
|
41127
41459
|
const [statsAndClients, rtspEntry] = await Promise.all([Promise.all(deviceSlots.map(async (slot) => {
|
|
41460
|
+
const [stats, clients] = await Promise.all([api.streamBroker.getBrokerStats.query({ brokerId: slot.brokerId }, nodePin(sourceNodeId)).catch(() => null), api.streamBroker.listClients.query({ brokerId: slot.brokerId }, nodePin(sourceNodeId)).catch(() => null)]);
|
|
41128
41461
|
return {
|
|
41129
41462
|
slot,
|
|
41130
|
-
stats
|
|
41131
|
-
clients
|
|
41463
|
+
stats,
|
|
41464
|
+
clients
|
|
41132
41465
|
};
|
|
41133
|
-
})),
|
|
41466
|
+
})), pass.rtspEntries(api, sourceNodeId).catch(() => null)]);
|
|
41134
41467
|
const profileDetails = statsAndClients.map(({ slot, stats, clients }) => ({
|
|
41135
41468
|
profile: slot.profile,
|
|
41136
41469
|
status: slot.status,
|
|
@@ -41191,13 +41524,16 @@ var CameraStatusService = class {
|
|
|
41191
41524
|
};
|
|
41192
41525
|
}
|
|
41193
41526
|
/** Detection stage (pipeline-executor + runner metrics). */
|
|
41194
|
-
buildDetectionStage(api, detectionNodeId, deviceId, sink) {
|
|
41527
|
+
buildDetectionStage(api, detectionNodeId, deviceId, pass, sink) {
|
|
41195
41528
|
if (!api || !detectionNodeId) return Promise.resolve(null);
|
|
41196
|
-
return this.boundedStage(Promise.all([
|
|
41197
|
-
|
|
41529
|
+
return this.boundedStage(Promise.all([
|
|
41530
|
+
pass.engineProvisioning(api, detectionNodeId).catch(() => null),
|
|
41531
|
+
pass.selectedEngine(api, detectionNodeId).catch(() => null),
|
|
41532
|
+
api.pipelineRunner.getCameraMetrics.query({
|
|
41198
41533
|
deviceId,
|
|
41199
41534
|
nodeId: detectionNodeId
|
|
41200
|
-
}).catch(() => null)
|
|
41535
|
+
}).catch(() => null)
|
|
41536
|
+
]).then(async ([provisioning, engine, metrics]) => {
|
|
41201
41537
|
const phase = (() => {
|
|
41202
41538
|
const p = metrics?.phase;
|
|
41203
41539
|
if (p === "active") return "active";
|
|
@@ -41309,9 +41645,9 @@ var CameraStatusService = class {
|
|
|
41309
41645
|
* badge on a working camera); what matters is that the emptiness travels with
|
|
41310
41646
|
* the reason it is empty.
|
|
41311
41647
|
*/
|
|
41312
|
-
buildSwitchStage(deviceId, sink) {
|
|
41648
|
+
buildSwitchStage(deviceId, pass, sink) {
|
|
41313
41649
|
const startedAt = Date.now();
|
|
41314
|
-
return this.boundedStage(this.deps.switchAuthoritiesFor(deviceId), STAGE_TIMEOUT_MS, "switches", deviceId, sink).then((reads) => {
|
|
41650
|
+
return this.boundedStage(this.deps.switchAuthoritiesFor(deviceId, pass), STAGE_TIMEOUT_MS, "switches", deviceId, sink).then((reads) => {
|
|
41315
41651
|
if (reads === null) return [];
|
|
41316
41652
|
const { switchedOff, unreadable } = composeSwitchedOff(reads);
|
|
41317
41653
|
if (unreadable.length > 0) this.recordDegraded(sink, deviceId, "switches", "partial", Date.now() - startedAt, { unreadableAuthorities: unreadable });
|
|
@@ -41329,18 +41665,28 @@ var CameraStatusService = class {
|
|
|
41329
41665
|
* `null` of a camera that legitimately has no such stage.
|
|
41330
41666
|
*/
|
|
41331
41667
|
async getCameraStatus(deviceId) {
|
|
41668
|
+
return this.composeOne(deviceId, new FleetReadPass({ deviceIds: [deviceId] }));
|
|
41669
|
+
}
|
|
41670
|
+
/**
|
|
41671
|
+
* One camera's status, composed WITHIN a pass.
|
|
41672
|
+
*
|
|
41673
|
+
* Everything the camera alone can answer is fetched here; everything the pass
|
|
41674
|
+
* already knows (a node's slot table, the fleet's mute list, a device type's
|
|
41675
|
+
* bindable caps) is asked of `pass`, which issues it once for the whole call.
|
|
41676
|
+
*/
|
|
41677
|
+
async composeOne(deviceId, pass) {
|
|
41332
41678
|
const api = this.deps.api();
|
|
41333
41679
|
const degradations = { entries: [] };
|
|
41334
41680
|
const { detectionNodeId, sourceNodeId, pinned, detectionReason, audioNodeId, audioPinned } = this.buildAssignmentContext(deviceId);
|
|
41335
41681
|
const liveDecoder = { nodeId: null };
|
|
41336
|
-
const allSlotsFetch = api ?
|
|
41682
|
+
const allSlotsFetch = api ? pass.profileSlots(api, sourceNodeId) : null;
|
|
41337
41683
|
const sourceFetch = this.buildSourceStage(api, allSlotsFetch, deviceId, degradations);
|
|
41338
|
-
const brokerFetch = this.buildBrokerStage(api, allSlotsFetch, deviceId, sourceNodeId, liveDecoder, degradations);
|
|
41684
|
+
const brokerFetch = this.buildBrokerStage(api, allSlotsFetch, deviceId, sourceNodeId, pass, liveDecoder, degradations);
|
|
41339
41685
|
const decoderFetch = this.buildDecoderStage(detectionNodeId);
|
|
41340
41686
|
const motionResult = this.buildMotionStage(deviceId);
|
|
41341
|
-
const detectionFetch = this.buildDetectionStage(api, detectionNodeId, deviceId, degradations);
|
|
41687
|
+
const detectionFetch = this.buildDetectionStage(api, detectionNodeId, deviceId, pass, degradations);
|
|
41342
41688
|
const recordingFetch = this.buildRecordingStage(api, deviceId, degradations);
|
|
41343
|
-
const switchesFetch = this.buildSwitchStage(deviceId, degradations);
|
|
41689
|
+
const switchesFetch = this.buildSwitchStage(deviceId, pass, degradations);
|
|
41344
41690
|
const [sourceResult, brokerResult, decoderResult, detectionResult, recordingResult, switchedOff] = await Promise.all([
|
|
41345
41691
|
sourceFetch,
|
|
41346
41692
|
brokerFetch,
|
|
@@ -41383,15 +41729,30 @@ var CameraStatusService = class {
|
|
|
41383
41729
|
* `deviceIds` defaults to all cameras currently tracked by the
|
|
41384
41730
|
* orchestrator's assignment map when omitted.
|
|
41385
41731
|
*
|
|
41386
|
-
*
|
|
41387
|
-
*
|
|
41388
|
-
*
|
|
41389
|
-
*
|
|
41390
|
-
*
|
|
41732
|
+
* The whole call runs inside ONE {@link FleetReadPass}, and that is what
|
|
41733
|
+
* makes a five-second poll affordable. Composing a camera reaches seven
|
|
41734
|
+
* stages, but six of the questions those stages ask are about a NODE, a
|
|
41735
|
+
* device TYPE or the whole FLEET — the slot table, the restream table, the
|
|
41736
|
+
* mute list, the engine pair, the bindable caps — and asking them per camera
|
|
41737
|
+
* meant N identical round trips for N identical answers. Two more (the device
|
|
41738
|
+
* row, the binding table) are per-camera questions the device manager already
|
|
41739
|
+
* answers for a set. The pass asks each exactly once and shares the promise;
|
|
41740
|
+
* it holds nothing between calls, so the next poll asks again from scratch.
|
|
41741
|
+
*
|
|
41742
|
+
* What is left is genuinely per camera and stays per camera: the broker slot
|
|
41743
|
+
* stats, the runner metrics, the recorder, the broker mute and the camera's
|
|
41744
|
+
* own privacy provider. Collapsing any of those would be a narrower answer,
|
|
41745
|
+
* not a cheaper one.
|
|
41746
|
+
*
|
|
41747
|
+
* `Promise.all` over per-device composition (no concurrency cap). The fan-out
|
|
41748
|
+
* is now ~8 round trips per camera rather than ~16, and the six fleet reads
|
|
41749
|
+
* no longer multiply — which is the part that made the count grow with the
|
|
41750
|
+
* fleet twice over.
|
|
41391
41751
|
*/
|
|
41392
41752
|
async getCameraStatuses(deviceIds) {
|
|
41393
41753
|
const ids = deviceIds !== void 0 && deviceIds.length > 0 ? deviceIds : this.deps.listAssignedDeviceIds();
|
|
41394
|
-
|
|
41754
|
+
const pass = new FleetReadPass({ deviceIds: ids });
|
|
41755
|
+
return Promise.all(ids.map((deviceId) => this.composeOne(deviceId, pass)));
|
|
41395
41756
|
}
|
|
41396
41757
|
};
|
|
41397
41758
|
//#endregion
|
|
@@ -41640,10 +42001,15 @@ async function probeCamera(api, deviceId, deps) {
|
|
|
41640
42001
|
* @param deviceId The camera.
|
|
41641
42002
|
* @param deps Logger + source-owner resolver.
|
|
41642
42003
|
*/
|
|
41643
|
-
async function readSwitchAuthorities(api, deviceId, deps) {
|
|
42004
|
+
async function readSwitchAuthorities(api, deviceId, deps, sharedPass) {
|
|
41644
42005
|
if (!api) return unknownReads(deviceId);
|
|
41645
|
-
const
|
|
41646
|
-
|
|
42006
|
+
const pass = sharedPass ?? new FleetReadPass({ deviceIds: [deviceId] });
|
|
42007
|
+
const roster = pass.deviceRoster(api);
|
|
42008
|
+
const devicePromise = bounded(deps, deviceId, roster.source, roster.result.then((rows) => {
|
|
42009
|
+
const row = rows.find((d) => d.id === deviceId);
|
|
42010
|
+
return isDeviceShape(row) ? row : null;
|
|
42011
|
+
}).catch((err) => {
|
|
42012
|
+
warnUnreachable(deps, deviceId, roster.source, err);
|
|
41647
42013
|
return null;
|
|
41648
42014
|
}), null);
|
|
41649
42015
|
const unknownBindings = {
|
|
@@ -41651,11 +42017,14 @@ async function readSwitchAuthorities(api, deviceId, deps) {
|
|
|
41651
42017
|
providerAddonIdByCap: /* @__PURE__ */ new Map(),
|
|
41652
42018
|
allCapNames: null
|
|
41653
42019
|
};
|
|
41654
|
-
const
|
|
42020
|
+
const bindingRead = pass.bindingRows(api);
|
|
42021
|
+
const bindingsPromise = bounded(deps, deviceId, bindingRead.source, bindingRead.result.then((rows) => {
|
|
42022
|
+
const row = rows.find((r) => r.deviceId === deviceId);
|
|
42023
|
+
if (row === void 0) return unknownBindings;
|
|
41655
42024
|
const active = [];
|
|
41656
42025
|
const all = [];
|
|
41657
42026
|
const providers = /* @__PURE__ */ new Map();
|
|
41658
|
-
for (const e of
|
|
42027
|
+
for (const e of row.entries) {
|
|
41659
42028
|
all.push(e.capName);
|
|
41660
42029
|
if (e.kind !== "wrapped") continue;
|
|
41661
42030
|
active.push(e.capName);
|
|
@@ -41667,14 +42036,14 @@ async function readSwitchAuthorities(api, deviceId, deps) {
|
|
|
41667
42036
|
allCapNames: all
|
|
41668
42037
|
};
|
|
41669
42038
|
}).catch((err) => {
|
|
41670
|
-
warnUnreachable(deps, deviceId,
|
|
42039
|
+
warnUnreachable(deps, deviceId, bindingRead.source, err);
|
|
41671
42040
|
return unknownBindings;
|
|
41672
42041
|
}), unknownBindings);
|
|
41673
42042
|
const recordingPromise = bounded(deps, deviceId, "recording.getDeviceConfig", api.recording.getDeviceConfig.query({ deviceId }).then((c) => isRecordingConfig(c) ? c : null).catch((err) => {
|
|
41674
42043
|
warnUnreachable(deps, deviceId, "recording.getDeviceConfig", err);
|
|
41675
42044
|
return null;
|
|
41676
42045
|
}), null);
|
|
41677
|
-
const mutesPromise = bounded(deps, deviceId, "notificationRules.listDeviceMutes",
|
|
42046
|
+
const mutesPromise = bounded(deps, deviceId, "notificationRules.listDeviceMutes", pass.mutedDevices(api).then((r) => r.mutedDeviceIds).catch((err) => {
|
|
41678
42047
|
warnUnreachable(deps, deviceId, "notificationRules.listDeviceMutes", err);
|
|
41679
42048
|
return null;
|
|
41680
42049
|
}), null);
|
|
@@ -41689,7 +42058,7 @@ async function readSwitchAuthorities(api, deviceId, deps) {
|
|
|
41689
42058
|
mutesPromise,
|
|
41690
42059
|
brokerAudioPromise
|
|
41691
42060
|
]);
|
|
41692
|
-
const [bindable, privacy] = await Promise.all([device === null ? Promise.resolve(null) : bounded(deps, deviceId, "deviceManager.listBindableCapsForDeviceType",
|
|
42061
|
+
const [bindable, privacy] = await Promise.all([device === null ? Promise.resolve(null) : bounded(deps, deviceId, "deviceManager.listBindableCapsForDeviceType", pass.bindableCapsFor(api, device.type).catch((err) => {
|
|
41693
42062
|
warnUnreachable(deps, deviceId, "listBindableCapsForDeviceType", err);
|
|
41694
42063
|
return null;
|
|
41695
42064
|
}), null), readPrivacyPlanes(api, deviceId, deps, bindings.allCapNames)]);
|
|
@@ -50396,11 +50765,11 @@ async function buildOrchestratorControllers(deps) {
|
|
|
50396
50765
|
assignSource: (deviceId) => topology.assignSource(deviceId),
|
|
50397
50766
|
listAssignedDeviceIds: () => [...new Set([...ledger.listAssignedDeviceIds(), ...detectionWiring.activeDeviceIds()])],
|
|
50398
50767
|
isSessionCamera: (deviceId) => deps.isSessionCamera(deviceId),
|
|
50399
|
-
switchAuthoritiesFor: async (deviceId) => (await readSwitchAuthorities(deps.ctx().api ?? null, deviceId, {
|
|
50768
|
+
switchAuthoritiesFor: async (deviceId, pass) => (await readSwitchAuthorities(deps.ctx().api ?? null, deviceId, {
|
|
50400
50769
|
logger: deps.ctx().logger,
|
|
50401
50770
|
assignSource: (id) => topology.assignSource(id),
|
|
50402
50771
|
warnSampler: switchWarnSampler
|
|
50403
|
-
})).derivation
|
|
50772
|
+
}, pass)).derivation
|
|
50404
50773
|
});
|
|
50405
50774
|
const reconcile = new ReconcileController({
|
|
50406
50775
|
api: () => deps.ctx().api ?? null,
|