@camstack/addon-post-analysis 1.2.34 → 1.2.35
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/{dist-7ujnICn7.js → dist-Cmb3B0Ij.js} +203 -96
- package/dist/{dist-Bjhp9nkX.mjs → dist-nh2kuzSC.mjs} +203 -96
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/{node-B4EfMwxa.js → node-BNgGpVnp.js} +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Dh0UMdC9.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DsOwA-XR.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-PQKnzGEk.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-tUf8t2IV.mjs → hostInit-BPatwh25.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +550 -57
- package/dist/pipeline-analytics/index.mjs +549 -56
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DmydN-_W.mjs +0 -26
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-Cmb3B0Ij.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
let node_path = require("node:path");
|
|
8
8
|
node_path = require_dist.__toESM(node_path);
|
|
@@ -550,7 +550,132 @@ function mimeFromExtension(file) {
|
|
|
550
550
|
return "application/octet-stream";
|
|
551
551
|
}
|
|
552
552
|
//#endregion
|
|
553
|
+
//#region src/notification-center/alarm/alarm-mode-coverage.ts
|
|
554
|
+
/**
|
|
555
|
+
* Whether the panel just BECAME armed, in the sense a person means.
|
|
556
|
+
*
|
|
557
|
+
* Two transitions reach an `armed_*` state and only one of them is an arm:
|
|
558
|
+
*
|
|
559
|
+
* - `arming`/`disarmed` → `armed_away` is somebody arming the alarm. Announce.
|
|
560
|
+
* - `triggered` → `armed_away` is the siren's duration ending and the panel
|
|
561
|
+
* re-arming itself. Announcing it would send "Away armed" in the middle of
|
|
562
|
+
* a break-in, seconds after the alarm notification, saying nothing new.
|
|
563
|
+
*
|
|
564
|
+
* `armed_home` → `armed_away` IS an arm: the operator changed mode, and the
|
|
565
|
+
* set of devices covered just changed with it — which is the whole content of
|
|
566
|
+
* the message.
|
|
567
|
+
*
|
|
568
|
+
* A null `previous` is the FIRST publish and never announces. A hub restarting
|
|
569
|
+
* while armed re-publishes `armed_away`, and nobody armed anything — every
|
|
570
|
+
* deploy would otherwise send one. Handled here rather than by the caller
|
|
571
|
+
* happening to wire the hook late, because "it works because of the order two
|
|
572
|
+
* unrelated things run in" is how this stops working.
|
|
573
|
+
*/
|
|
574
|
+
function shouldAnnounceArm(previous, next) {
|
|
575
|
+
if (previous === null) return false;
|
|
576
|
+
if (!next.startsWith("armed_")) return false;
|
|
577
|
+
if (previous === next) return false;
|
|
578
|
+
if (previous === "triggered") return false;
|
|
579
|
+
return true;
|
|
580
|
+
}
|
|
581
|
+
/** The mode inside an `armed_<mode>` state, or null for any other state. */
|
|
582
|
+
function armModeOf(state, modes) {
|
|
583
|
+
for (const mode of modes) if (state === armedStateFor(mode)) return mode;
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
/** The `deviceState` word a rule must gate on for `mode` to cover it. */
|
|
587
|
+
function armedStateFor(mode) {
|
|
588
|
+
return `armed_${mode}`;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* One entry per mode the panel offers, including modes nothing is gated on.
|
|
592
|
+
*
|
|
593
|
+
* Empty modes are KEPT rather than filtered: "Night arms nothing" is the single
|
|
594
|
+
* most useful thing this can tell an operator, and a list that omits it looks
|
|
595
|
+
* identical to a list where night is covered.
|
|
596
|
+
*/
|
|
597
|
+
function alarmModeCoverage(rules, panelDeviceId, modes) {
|
|
598
|
+
return modes.map((mode) => coverageFor(rules, panelDeviceId, mode));
|
|
599
|
+
}
|
|
600
|
+
function coverageFor(rules, panelDeviceId, mode) {
|
|
601
|
+
const wanted = armedStateFor(mode);
|
|
602
|
+
const ids = /* @__PURE__ */ new Set();
|
|
603
|
+
let ruleCount = 0;
|
|
604
|
+
let allDevices = false;
|
|
605
|
+
for (const rule of rules) {
|
|
606
|
+
if (!rule.enabled) continue;
|
|
607
|
+
const gate = rule.conditions.deviceState;
|
|
608
|
+
if (gate === void 0 || gate.deviceId !== panelDeviceId) continue;
|
|
609
|
+
if (!gate.states.includes(wanted)) continue;
|
|
610
|
+
ruleCount += 1;
|
|
611
|
+
const scope = rule.conditions.devices;
|
|
612
|
+
if (scope === void 0 || scope.length === 0) {
|
|
613
|
+
allDevices = true;
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
for (const id of scope) ids.add(id);
|
|
617
|
+
}
|
|
618
|
+
return {
|
|
619
|
+
mode,
|
|
620
|
+
ruleCount,
|
|
621
|
+
allDevices,
|
|
622
|
+
deviceIds: [...ids].toSorted((a, b) => a - b)
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* "Away armed — 3 cameras: Front door, Garage, Gate."
|
|
627
|
+
*
|
|
628
|
+
* Names, not ids: this is read by a person standing at a door. An id that has
|
|
629
|
+
* no name falls back to `Device <id>` rather than being dropped — a silently
|
|
630
|
+
* shorter list would understate what is armed, which is the one error this
|
|
631
|
+
* message must not make.
|
|
632
|
+
*
|
|
633
|
+
* Returns null when the mode covers NOTHING and no rule is gated on it. A
|
|
634
|
+
* notification saying "Night armed" that is followed by nothing happening all
|
|
635
|
+
* night is worse than no notification: it is a false assurance. The caller
|
|
636
|
+
* still logs the arm.
|
|
637
|
+
*/
|
|
638
|
+
function buildArmAnnouncement(coverage, deviceNames) {
|
|
639
|
+
if (coverage.ruleCount === 0) return null;
|
|
640
|
+
const title = `${modeLabel(coverage.mode)} armed`;
|
|
641
|
+
if (coverage.allDevices) return {
|
|
642
|
+
title,
|
|
643
|
+
body: `Every device is armed (${plural$1(coverage.ruleCount, "rule")}).`
|
|
644
|
+
};
|
|
645
|
+
if (coverage.deviceIds.length === 0) return {
|
|
646
|
+
title,
|
|
647
|
+
body: `${plural$1(coverage.ruleCount, "rule")}, no device restriction.`
|
|
648
|
+
};
|
|
649
|
+
const names = coverage.deviceIds.map((id) => deviceNames.get(id) ?? `Device ${id}`);
|
|
650
|
+
return {
|
|
651
|
+
title,
|
|
652
|
+
body: `${plural$1(names.length, "device")} armed: ${names.join(", ")}.`
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
function modeLabel(mode) {
|
|
656
|
+
return mode.charAt(0).toUpperCase() + mode.slice(1).replace(/_/g, " ");
|
|
657
|
+
}
|
|
658
|
+
function plural$1(n, one) {
|
|
659
|
+
return n === 1 ? `1 ${one}` : `${n} ${one}s`;
|
|
660
|
+
}
|
|
661
|
+
//#endregion
|
|
553
662
|
//#region src/notification-center/alarm/alarm-state-machine.ts
|
|
663
|
+
/**
|
|
664
|
+
* Defaults an operator can change per panel. `home` has no exit delay on
|
|
665
|
+
* purpose — nobody is leaving — but the shared value is applied to every mode
|
|
666
|
+
* until per-mode delays are asked for.
|
|
667
|
+
*
|
|
668
|
+
* `triggeredDurationSec: 0` = "sound until somebody disarms it". That is what
|
|
669
|
+
* the panel did before the field existed, so an install that never opens the
|
|
670
|
+
* tab keeps behaving exactly as it did — a default that silently re-armed
|
|
671
|
+
* every existing alarm after N seconds would be a behaviour change nobody
|
|
672
|
+
* asked for.
|
|
673
|
+
*/
|
|
674
|
+
var DEFAULT_DELAYS = {
|
|
675
|
+
exitDelaySec: 30,
|
|
676
|
+
entryDelaySec: 20,
|
|
677
|
+
triggeredDurationSec: 0
|
|
678
|
+
};
|
|
554
679
|
var DISARMED = {
|
|
555
680
|
target: "disarmed",
|
|
556
681
|
fired: false,
|
|
@@ -567,6 +692,7 @@ function armedState(mode) {
|
|
|
567
692
|
* answer is still correct the moment anyone asks.
|
|
568
693
|
*/
|
|
569
694
|
function stateAt(m, now) {
|
|
695
|
+
if (m.clearAt !== void 0 && now >= m.clearAt) return m.target;
|
|
570
696
|
if (m.fired) return "triggered";
|
|
571
697
|
if (m.triggerAt !== void 0) return now >= m.triggerAt ? "triggered" : "pending";
|
|
572
698
|
if (m.armedAt !== void 0) return now >= m.armedAt ? m.target : "arming";
|
|
@@ -620,19 +746,24 @@ function disarm(now) {
|
|
|
620
746
|
* intruder walking past two sensors would otherwise postpone the alarm.
|
|
621
747
|
*/
|
|
622
748
|
function trigger(m, delays, now) {
|
|
749
|
+
const state = stateAt(m, now);
|
|
750
|
+
if (state === "triggered" || state === "pending") return m;
|
|
623
751
|
if (!isArmed(m, now)) return m;
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
...m,
|
|
627
|
-
fired: true,
|
|
628
|
-
changedAt: now
|
|
629
|
-
};
|
|
752
|
+
const base = settle(m, now);
|
|
753
|
+
const firesAt = now + Math.max(0, delays.entryDelaySec) * 1e3;
|
|
630
754
|
return {
|
|
631
|
-
...
|
|
632
|
-
triggerAt:
|
|
755
|
+
...base,
|
|
756
|
+
triggerAt: firesAt,
|
|
757
|
+
...autoClear(delays, firesAt),
|
|
758
|
+
fired: false,
|
|
633
759
|
changedAt: now
|
|
634
760
|
};
|
|
635
761
|
}
|
|
762
|
+
/** `clearAt` iff the operator asked for a bounded siren — see {@link AlarmDelays}. */
|
|
763
|
+
function autoClear(delays, firesAt) {
|
|
764
|
+
if (delays.triggeredDurationSec <= 0) return {};
|
|
765
|
+
return { clearAt: firesAt + delays.triggeredDurationSec * 1e3 };
|
|
766
|
+
}
|
|
636
767
|
/**
|
|
637
768
|
* Settle the machine so a stored value never depends on when it is read.
|
|
638
769
|
*
|
|
@@ -641,10 +772,16 @@ function trigger(m, delays, now) {
|
|
|
641
772
|
* than only in the reading of it.
|
|
642
773
|
*/
|
|
643
774
|
function settle(m, now) {
|
|
775
|
+
if (m.clearAt !== void 0 && now >= m.clearAt) return {
|
|
776
|
+
target: m.target,
|
|
777
|
+
fired: false,
|
|
778
|
+
changedAt: m.clearAt
|
|
779
|
+
};
|
|
644
780
|
if (m.triggerAt !== void 0 && now >= m.triggerAt) return {
|
|
645
781
|
target: m.target,
|
|
646
782
|
fired: true,
|
|
647
|
-
changedAt: m.triggerAt
|
|
783
|
+
changedAt: m.triggerAt,
|
|
784
|
+
...m.clearAt !== void 0 ? { clearAt: m.clearAt } : {}
|
|
648
785
|
};
|
|
649
786
|
if (m.armedAt !== void 0 && now >= m.armedAt) return {
|
|
650
787
|
target: m.target,
|
|
@@ -673,21 +810,12 @@ function settle(m, now) {
|
|
|
673
810
|
* and re-publish when a delay elapses.
|
|
674
811
|
*/
|
|
675
812
|
/** Every arm mode a camstack-owned panel offers. */
|
|
676
|
-
var
|
|
813
|
+
var NC_ALARM_MODES = [
|
|
677
814
|
"home",
|
|
678
815
|
"away",
|
|
679
816
|
"night"
|
|
680
817
|
];
|
|
681
818
|
/**
|
|
682
|
-
* Defaults an operator can change per panel. `home` has no exit delay on
|
|
683
|
-
* purpose — nobody is leaving — but the shared value is applied to every mode
|
|
684
|
-
* until per-mode delays are asked for.
|
|
685
|
-
*/
|
|
686
|
-
var DEFAULT_DELAYS = {
|
|
687
|
-
exitDelaySec: 30,
|
|
688
|
-
entryDelaySec: 20
|
|
689
|
-
};
|
|
690
|
-
/**
|
|
691
819
|
* How often the panel re-publishes while a delay is running.
|
|
692
820
|
*
|
|
693
821
|
* The state is computed from an INSTANT, so this tick only decides how quickly
|
|
@@ -703,30 +831,94 @@ var TICK_MS = 1e3;
|
|
|
703
831
|
var ncAlarmPanelSchema = require_dist.object({
|
|
704
832
|
exitDelaySec: require_dist.number().int().min(0).max(600).default(DEFAULT_DELAYS.exitDelaySec),
|
|
705
833
|
entryDelaySec: require_dist.number().int().min(0).max(600).default(DEFAULT_DELAYS.entryDelaySec),
|
|
834
|
+
triggeredDurationSec: require_dist.number().int().min(0).max(3600).default(DEFAULT_DELAYS.triggeredDurationSec),
|
|
835
|
+
/** Send a notification when a mode takes effect. Off until asked for. */
|
|
836
|
+
announceArm: require_dist.boolean().default(false),
|
|
837
|
+
/** Target ids that announcement goes to — see `NcAlarmSettingsSchema`. */
|
|
838
|
+
announceTargets: require_dist.array(require_dist.string()).default([]),
|
|
706
839
|
machine: require_dist.record(require_dist.string(), require_dist.unknown()).optional()
|
|
707
840
|
});
|
|
841
|
+
/** `armed_home` and friends — the only targets a machine may settle into. */
|
|
842
|
+
function isArmTarget(value) {
|
|
843
|
+
return value === "disarmed" || typeof value === "string" && value.startsWith("armed_");
|
|
844
|
+
}
|
|
845
|
+
function optionalInstant(value) {
|
|
846
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Rebuild the machine from the persisted blob, or null when there is nothing
|
|
850
|
+
* trustworthy in it.
|
|
851
|
+
*
|
|
852
|
+
* A type GUARD, not a cast: the blob has survived a schema change once already
|
|
853
|
+
* (`clearAt` did not exist), and the failure mode of a cast here is an alarm
|
|
854
|
+
* that reports a state it cannot reach. Anything unrecognised falls back to
|
|
855
|
+
* DISARMED, which is the state an operator will notice.
|
|
856
|
+
*
|
|
857
|
+
* Exported for the test that proves a pre-`clearAt` blob still restores.
|
|
858
|
+
*/
|
|
859
|
+
function machineFromPersisted(raw) {
|
|
860
|
+
if (raw === null || typeof raw !== "object") return null;
|
|
861
|
+
const rec = { ...raw };
|
|
862
|
+
if (!isArmTarget(rec["target"]) || typeof rec["changedAt"] !== "number") return null;
|
|
863
|
+
const armedAt = optionalInstant(rec["armedAt"]);
|
|
864
|
+
const triggerAt = optionalInstant(rec["triggerAt"]);
|
|
865
|
+
const clearAt = optionalInstant(rec["clearAt"]);
|
|
866
|
+
return {
|
|
867
|
+
target: rec["target"],
|
|
868
|
+
...armedAt !== void 0 ? { armedAt } : {},
|
|
869
|
+
...triggerAt !== void 0 ? { triggerAt } : {},
|
|
870
|
+
...clearAt !== void 0 ? { clearAt } : {},
|
|
871
|
+
fired: rec["fired"] === true,
|
|
872
|
+
changedAt: rec["changedAt"]
|
|
873
|
+
};
|
|
874
|
+
}
|
|
708
875
|
var NcAlarmPanelDevice = class extends require_dist.BaseDevice {
|
|
709
876
|
features = [];
|
|
710
877
|
machine = DISARMED;
|
|
711
878
|
delays = DEFAULT_DELAYS;
|
|
712
879
|
ticker = null;
|
|
880
|
+
onArmed = null;
|
|
881
|
+
/**
|
|
882
|
+
* The state the last publish reported. Null until the first one, so the
|
|
883
|
+
* BOOT publish never announces: a hub restarting while armed would otherwise
|
|
884
|
+
* announce "Away armed" on every deploy.
|
|
885
|
+
*/
|
|
886
|
+
lastPublished = null;
|
|
713
887
|
constructor(ctx) {
|
|
714
888
|
super(ctx, ncAlarmPanelSchema, { type: ctx.deviceMeta.type });
|
|
715
|
-
this.delays =
|
|
716
|
-
exitDelaySec: this.config.get("exitDelaySec"),
|
|
717
|
-
entryDelaySec: this.config.get("entryDelaySec")
|
|
718
|
-
};
|
|
889
|
+
this.delays = this.readDelays();
|
|
719
890
|
this.restore();
|
|
720
891
|
this.registerAlarmCap();
|
|
721
892
|
this.publish();
|
|
722
893
|
}
|
|
723
|
-
/**
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
894
|
+
/** The operator-visible configuration, as the cap serves it. */
|
|
895
|
+
settings() {
|
|
896
|
+
return {
|
|
897
|
+
...this.readDelays(),
|
|
898
|
+
announceArm: this.config.get("announceArm"),
|
|
899
|
+
announceTargets: [...this.config.get("announceTargets")]
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Apply an editor's patch and return what the panel settled on.
|
|
904
|
+
*
|
|
905
|
+
* The new delays take effect on the NEXT transition, never retroactively: a
|
|
906
|
+
* machine already counting down keeps the instants it was given. Re-deriving
|
|
907
|
+
* `armedAt` from a delay the operator changed mid-exit would move the arm
|
|
908
|
+
* instant under somebody who is walking out of the door.
|
|
909
|
+
*/
|
|
910
|
+
async applySettings(patch) {
|
|
911
|
+
if (patch.exitDelaySec !== void 0) await this.config.set("exitDelaySec", patch.exitDelaySec);
|
|
912
|
+
if (patch.entryDelaySec !== void 0) await this.config.set("entryDelaySec", patch.entryDelaySec);
|
|
913
|
+
if (patch.triggeredDurationSec !== void 0) await this.config.set("triggeredDurationSec", patch.triggeredDurationSec);
|
|
914
|
+
if (patch.announceArm !== void 0) await this.config.set("announceArm", patch.announceArm);
|
|
915
|
+
if (patch.announceTargets !== void 0) await this.config.set("announceTargets", [...patch.announceTargets]);
|
|
916
|
+
this.delays = this.readDelays();
|
|
917
|
+
return this.settings();
|
|
918
|
+
}
|
|
919
|
+
/** Wire the arm announcement. See {@link NcAlarmArmedHook}. */
|
|
920
|
+
setArmedHook(hook) {
|
|
921
|
+
this.onArmed = hook;
|
|
730
922
|
}
|
|
731
923
|
/** Current lifecycle word — what the `deviceState` gate compares against. */
|
|
732
924
|
currentState(now = Date.now()) {
|
|
@@ -749,19 +941,24 @@ var NcAlarmPanelDevice = class extends require_dist.BaseDevice {
|
|
|
749
941
|
this.publish();
|
|
750
942
|
},
|
|
751
943
|
trigger: async () => {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
944
|
+
const now = Date.now();
|
|
945
|
+
const next = trigger(this.machine, this.delays, now);
|
|
946
|
+
if (next === this.machine) {
|
|
947
|
+
this.ctx.logger.info("alarm trigger ignored — the panel is not armed", {
|
|
948
|
+
tags: { deviceId: this.id },
|
|
949
|
+
meta: { state: stateAt(this.machine, now) }
|
|
950
|
+
});
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
this.machine = next;
|
|
954
|
+
this.publish(now);
|
|
758
955
|
}
|
|
759
956
|
});
|
|
760
957
|
}
|
|
761
958
|
status(now = Date.now()) {
|
|
762
959
|
return {
|
|
763
960
|
state: stateAt(this.machine, now),
|
|
764
|
-
availableModes: [...
|
|
961
|
+
availableModes: [...NC_ALARM_MODES],
|
|
765
962
|
requiresCode: false,
|
|
766
963
|
lastChangedAt: this.machine.changedAt
|
|
767
964
|
};
|
|
@@ -775,11 +972,48 @@ var NcAlarmPanelDevice = class extends require_dist.BaseDevice {
|
|
|
775
972
|
*/
|
|
776
973
|
publish(now = Date.now()) {
|
|
777
974
|
this.machine = settle(this.machine, now);
|
|
975
|
+
const state = stateAt(this.machine, now);
|
|
778
976
|
this.runtimeState.setCapState("alarm-panel", this.status(now));
|
|
779
977
|
this.persist();
|
|
780
|
-
|
|
978
|
+
this.announceIfArmed(state);
|
|
979
|
+
if (this.machine.armedAt !== void 0 || this.machine.triggerAt !== void 0 || this.machine.clearAt !== void 0) this.startTicking();
|
|
781
980
|
else this.stopTicking();
|
|
782
981
|
}
|
|
982
|
+
/**
|
|
983
|
+
* Fire the arm hook on the transition INTO an armed mode.
|
|
984
|
+
*
|
|
985
|
+
* Never throws into the publish path: an announcement that could take the
|
|
986
|
+
* panel's own state update with it would make a notification failure look
|
|
987
|
+
* like an alarm failure.
|
|
988
|
+
*/
|
|
989
|
+
announceIfArmed(state) {
|
|
990
|
+
const previous = this.lastPublished;
|
|
991
|
+
this.lastPublished = state;
|
|
992
|
+
const hook = this.onArmed;
|
|
993
|
+
if (hook === null) return;
|
|
994
|
+
if (!shouldAnnounceArm(previous, state)) return;
|
|
995
|
+
const mode = armModeOf(state, NC_ALARM_MODES);
|
|
996
|
+
if (mode === null) return;
|
|
997
|
+
try {
|
|
998
|
+
hook(mode);
|
|
999
|
+
} catch (err) {
|
|
1000
|
+
this.ctx.logger.warn("alarm arm announcement failed", {
|
|
1001
|
+
tags: { deviceId: this.id },
|
|
1002
|
+
meta: {
|
|
1003
|
+
mode,
|
|
1004
|
+
error: String(err)
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
/** The three durations, read from the persisted config. */
|
|
1010
|
+
readDelays() {
|
|
1011
|
+
return {
|
|
1012
|
+
exitDelaySec: this.config.get("exitDelaySec"),
|
|
1013
|
+
entryDelaySec: this.config.get("entryDelaySec"),
|
|
1014
|
+
triggeredDurationSec: this.config.get("triggeredDurationSec")
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
783
1017
|
startTicking() {
|
|
784
1018
|
if (this.ticker !== null) return;
|
|
785
1019
|
this.ticker = setInterval(() => this.publish(), TICK_MS);
|
|
@@ -796,17 +1030,9 @@ var NcAlarmPanelDevice = class extends require_dist.BaseDevice {
|
|
|
796
1030
|
this.config.set("machine", { ...this.machine });
|
|
797
1031
|
}
|
|
798
1032
|
restore() {
|
|
799
|
-
const
|
|
800
|
-
if (
|
|
801
|
-
|
|
802
|
-
if (typeof m.target !== "string" || typeof m.changedAt !== "number") return;
|
|
803
|
-
this.machine = {
|
|
804
|
-
target: m.target,
|
|
805
|
-
...typeof m.armedAt === "number" ? { armedAt: m.armedAt } : {},
|
|
806
|
-
...typeof m.triggerAt === "number" ? { triggerAt: m.triggerAt } : {},
|
|
807
|
-
fired: m.fired === true,
|
|
808
|
-
changedAt: m.changedAt
|
|
809
|
-
};
|
|
1033
|
+
const restored = machineFromPersisted(this.config.get("machine"));
|
|
1034
|
+
if (restored === null) return;
|
|
1035
|
+
this.machine = restored;
|
|
810
1036
|
}
|
|
811
1037
|
};
|
|
812
1038
|
//#endregion
|
|
@@ -5074,14 +5300,21 @@ function readSensorEventType(value) {
|
|
|
5074
5300
|
const eventType = last["eventType"];
|
|
5075
5301
|
return typeof eventType === "string" && eventType.length > 0 ? eventType : void 0;
|
|
5076
5302
|
}
|
|
5077
|
-
/**
|
|
5078
|
-
*
|
|
5303
|
+
/**
|
|
5304
|
+
* Build the subject for a `device-event` evaluation (a persisted SensorEvent —
|
|
5305
|
+
* one row per linked camera; `deviceId` is the CAMERA).
|
|
5306
|
+
*
|
|
5307
|
+
* BOTH ids ride along. The camera is what the notification shows; the sensor is
|
|
5308
|
+
* what the operator named in the rule. Carrying only the camera is what made a
|
|
5309
|
+
* sensor-scoped rule unmatched — see {@link NcRuleSubject.sourceDeviceId}.
|
|
5310
|
+
*/
|
|
5079
5311
|
function subjectFromSensorEvent(ev) {
|
|
5080
5312
|
const eventType = readSensorEventType(ev.value);
|
|
5081
5313
|
return {
|
|
5082
5314
|
kind: "device-event",
|
|
5083
5315
|
recordId: ev.id,
|
|
5084
5316
|
deviceId: ev.deviceId,
|
|
5317
|
+
...ev.sourceDeviceId !== ev.deviceId ? { sourceDeviceId: ev.sourceDeviceId } : {},
|
|
5085
5318
|
timestamp: ev.timestamp,
|
|
5086
5319
|
classNames: [],
|
|
5087
5320
|
zones: [],
|
|
@@ -5313,7 +5546,7 @@ function evaluateRule(rule, subject, deviceState) {
|
|
|
5313
5546
|
if (current === void 0) return fail("deviceState");
|
|
5314
5547
|
if (!toLowerSet(c.deviceState.states).has(current.trim().toLowerCase())) return fail("deviceState");
|
|
5315
5548
|
}
|
|
5316
|
-
if (c.devices !== void 0 && c.devices.length > 0 && !c.devices
|
|
5549
|
+
if (c.devices !== void 0 && c.devices.length > 0 && !matchesDeviceScope(c.devices, subject)) return fail("devices");
|
|
5317
5550
|
if (c.source !== void 0 && c.source !== "any") {
|
|
5318
5551
|
if ((subject.source ?? "pipeline") !== c.source) return fail("source");
|
|
5319
5552
|
}
|
|
@@ -5500,6 +5733,28 @@ function keysPerClass(rule, subject) {
|
|
|
5500
5733
|
return rule.throttle.granularity === "per-class";
|
|
5501
5734
|
}
|
|
5502
5735
|
/** Stable cooldown key per the rule's throttle scope + class granularity. */
|
|
5736
|
+
/**
|
|
5737
|
+
* Does the rule's device scope cover this subject?
|
|
5738
|
+
*
|
|
5739
|
+
* EITHER id matches, and both are needed for the same rule to be authorable
|
|
5740
|
+
* the two ways an operator thinks about it:
|
|
5741
|
+
*
|
|
5742
|
+
* - "when anything happens on the front-door CAMERA" → the camera id, which
|
|
5743
|
+
* is what a sensor row is attributed to;
|
|
5744
|
+
* - "when the front-door CONTACT opens" → the sensor id, which is the device
|
|
5745
|
+
* the operator actually cares about and the one they pick in the Devices
|
|
5746
|
+
* tab. Before this, that rule matched nothing, ever, silently.
|
|
5747
|
+
*
|
|
5748
|
+
* Consequence worth knowing: a sensor linked to N cameras persists N rows, so
|
|
5749
|
+
* a SENSOR-scoped rule is evaluated N times for one door opening. The rule's
|
|
5750
|
+
* own throttle is what collapses that — `scope: 'rule'` gives one
|
|
5751
|
+
* notification, `scope: 'device'` gives one per camera, which is the right
|
|
5752
|
+
* choice when each carries its own picture.
|
|
5753
|
+
*/
|
|
5754
|
+
function matchesDeviceScope(scope, subject) {
|
|
5755
|
+
if (scope.includes(subject.deviceId)) return true;
|
|
5756
|
+
return subject.sourceDeviceId !== void 0 && scope.includes(subject.sourceDeviceId);
|
|
5757
|
+
}
|
|
5503
5758
|
function cooldownKey(rule, subject) {
|
|
5504
5759
|
const first = subject.classNames[0];
|
|
5505
5760
|
const classKey = keysPerClass(rule, subject) && first !== void 0 ? `:c:${first}` : "";
|
|
@@ -5774,6 +6029,82 @@ var NcDispatcher = class {
|
|
|
5774
6029
|
return false;
|
|
5775
6030
|
}
|
|
5776
6031
|
}
|
|
6032
|
+
/**
|
|
6033
|
+
* Send a Notification-Center-composed message to ONE target.
|
|
6034
|
+
*
|
|
6035
|
+
* Never throws and never retries — see {@link NcAnnouncementSendInput}. Every
|
|
6036
|
+
* way of not delivering produces a line: an announcement that silently did
|
|
6037
|
+
* not arrive is indistinguishable from the alarm not having armed, which is
|
|
6038
|
+
* the exact confusion this feature exists to remove.
|
|
6039
|
+
*/
|
|
6040
|
+
async deliverAnnouncement(input) {
|
|
6041
|
+
const tags = input.deviceId !== void 0 ? { deviceId: input.deviceId } : void 0;
|
|
6042
|
+
let target;
|
|
6043
|
+
try {
|
|
6044
|
+
target = await this.resolveTarget(input.targetId);
|
|
6045
|
+
} catch (err) {
|
|
6046
|
+
this.deps.logger.warn("announcement: target catalog unreachable", {
|
|
6047
|
+
...tags !== void 0 ? { tags } : {},
|
|
6048
|
+
meta: {
|
|
6049
|
+
reason: input.reason,
|
|
6050
|
+
targetId: input.targetId,
|
|
6051
|
+
error: String(err)
|
|
6052
|
+
}
|
|
6053
|
+
});
|
|
6054
|
+
return false;
|
|
6055
|
+
}
|
|
6056
|
+
if (target === null || !target.enabled) {
|
|
6057
|
+
this.deps.logger.warn("announcement: target gone or disabled", {
|
|
6058
|
+
...tags !== void 0 ? { tags } : {},
|
|
6059
|
+
meta: {
|
|
6060
|
+
reason: input.reason,
|
|
6061
|
+
targetId: input.targetId
|
|
6062
|
+
}
|
|
6063
|
+
});
|
|
6064
|
+
return false;
|
|
6065
|
+
}
|
|
6066
|
+
try {
|
|
6067
|
+
const result = await this.deps.send({
|
|
6068
|
+
addonId: target.addonId,
|
|
6069
|
+
targetId: target.id,
|
|
6070
|
+
notification: {
|
|
6071
|
+
title: input.title,
|
|
6072
|
+
body: input.body,
|
|
6073
|
+
priority: 2,
|
|
6074
|
+
...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {}
|
|
6075
|
+
}
|
|
6076
|
+
});
|
|
6077
|
+
if (!result.success) {
|
|
6078
|
+
this.deps.logger.warn("announcement send failed", {
|
|
6079
|
+
...tags !== void 0 ? { tags } : {},
|
|
6080
|
+
meta: {
|
|
6081
|
+
reason: input.reason,
|
|
6082
|
+
targetId: target.id,
|
|
6083
|
+
error: result.error
|
|
6084
|
+
}
|
|
6085
|
+
});
|
|
6086
|
+
return false;
|
|
6087
|
+
}
|
|
6088
|
+
this.deps.logger.info("announcement delivered", {
|
|
6089
|
+
...tags !== void 0 ? { tags } : {},
|
|
6090
|
+
meta: {
|
|
6091
|
+
reason: input.reason,
|
|
6092
|
+
target: target.name
|
|
6093
|
+
}
|
|
6094
|
+
});
|
|
6095
|
+
return true;
|
|
6096
|
+
} catch (err) {
|
|
6097
|
+
this.deps.logger.warn("announcement send threw", {
|
|
6098
|
+
...tags !== void 0 ? { tags } : {},
|
|
6099
|
+
meta: {
|
|
6100
|
+
reason: input.reason,
|
|
6101
|
+
targetId: target.id,
|
|
6102
|
+
error: String(err)
|
|
6103
|
+
}
|
|
6104
|
+
});
|
|
6105
|
+
return false;
|
|
6106
|
+
}
|
|
6107
|
+
}
|
|
5777
6108
|
/** Device display names for a set of ids, for the digest's lines. */
|
|
5778
6109
|
async resolveDeviceNames(deviceIds) {
|
|
5779
6110
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -8130,6 +8461,19 @@ var TimelapseStore = class {
|
|
|
8130
8461
|
* CRUD served elsewhere becomes effective within one rule-reload tick.
|
|
8131
8462
|
*/
|
|
8132
8463
|
/**
|
|
8464
|
+
* What `getAlarmConfig` answers on a node with no panel.
|
|
8465
|
+
*
|
|
8466
|
+
* The DEFAULTS, not zeroes: paired with `deviceId: null` this tells a client
|
|
8467
|
+
* "there is no panel here" while still describing the shape it would have.
|
|
8468
|
+
* Zeroes would render as "no entry delay", which is a claim about an alarm
|
|
8469
|
+
* that does not exist.
|
|
8470
|
+
*/
|
|
8471
|
+
var NC_ALARM_SETTINGS_FALLBACK = {
|
|
8472
|
+
...DEFAULT_DELAYS,
|
|
8473
|
+
announceArm: false,
|
|
8474
|
+
announceTargets: []
|
|
8475
|
+
};
|
|
8476
|
+
/**
|
|
8133
8477
|
* How often the "matched NO rule" report may fire per device. Long enough that
|
|
8134
8478
|
* a busy camera prints one line rather than one per event, short enough that a
|
|
8135
8479
|
* rule which has stopped matching is visible within minutes rather than by
|
|
@@ -8255,6 +8599,8 @@ var NotificationCenter = class NotificationCenter {
|
|
|
8255
8599
|
/** True when ≥1 enabled `device-event` rule declares an occupancy condition —
|
|
8256
8600
|
* the watcher is idle (zero per-frame cost) otherwise. */
|
|
8257
8601
|
occupancyEnabled = false;
|
|
8602
|
+
/** The panel this node owns, or null. See {@link NcAlarmPanelPort}. */
|
|
8603
|
+
alarmPanel = null;
|
|
8258
8604
|
/** In-memory cooldown map — seeded from persisted outbox rows on start. */
|
|
8259
8605
|
lastFiredAt = /* @__PURE__ */ new Map();
|
|
8260
8606
|
/** Per-device rate limit for the "matched NO rule" report — see `reportNoMatch`. */
|
|
@@ -8685,9 +9031,105 @@ var NotificationCenter = class NotificationCenter {
|
|
|
8685
9031
|
cancelSnooze: async ({ snoozeId, caller }) => {
|
|
8686
9032
|
await this.cancelSnooze(snoozeId, caller);
|
|
8687
9033
|
return { success: true };
|
|
9034
|
+
},
|
|
9035
|
+
getAlarmConfig: async () => this.alarmConfig(),
|
|
9036
|
+
setAlarmConfig: async ({ patch }) => {
|
|
9037
|
+
const panel = this.alarmPanel;
|
|
9038
|
+
if (panel === null) throw new Error("this node has no alarm panel");
|
|
9039
|
+
const settings = await panel.applySettings(patch);
|
|
9040
|
+
this.logger.info("alarm settings changed", {
|
|
9041
|
+
tags: { deviceId: panel.deviceId },
|
|
9042
|
+
meta: {
|
|
9043
|
+
exitDelaySec: settings.exitDelaySec,
|
|
9044
|
+
entryDelaySec: settings.entryDelaySec,
|
|
9045
|
+
triggeredDurationSec: settings.triggeredDurationSec,
|
|
9046
|
+
announceArm: settings.announceArm,
|
|
9047
|
+
announceTargets: settings.announceTargets.length
|
|
9048
|
+
}
|
|
9049
|
+
});
|
|
9050
|
+
return this.alarmConfig();
|
|
8688
9051
|
}
|
|
8689
9052
|
};
|
|
8690
9053
|
}
|
|
9054
|
+
/**
|
|
9055
|
+
* Adopt the panel this node owns. Called once by the addon after
|
|
9056
|
+
* `ensureAlarmPanel`; a node without one never calls it and the cap then
|
|
9057
|
+
* answers `deviceId: null`.
|
|
9058
|
+
*/
|
|
9059
|
+
setAlarmPanel(panel) {
|
|
9060
|
+
this.alarmPanel = panel;
|
|
9061
|
+
}
|
|
9062
|
+
/** Settings + the DERIVED coverage. Read together — see the cap doc. */
|
|
9063
|
+
alarmConfig() {
|
|
9064
|
+
const panel = this.alarmPanel;
|
|
9065
|
+
if (panel === null) return {
|
|
9066
|
+
deviceId: null,
|
|
9067
|
+
settings: NC_ALARM_SETTINGS_FALLBACK,
|
|
9068
|
+
coverage: []
|
|
9069
|
+
};
|
|
9070
|
+
return {
|
|
9071
|
+
deviceId: panel.deviceId,
|
|
9072
|
+
settings: panel.settings(),
|
|
9073
|
+
coverage: this.coverageFor(panel).map((c) => ({
|
|
9074
|
+
...c,
|
|
9075
|
+
deviceIds: [...c.deviceIds]
|
|
9076
|
+
}))
|
|
9077
|
+
};
|
|
9078
|
+
}
|
|
9079
|
+
coverageFor(panel) {
|
|
9080
|
+
return alarmModeCoverage(this.rules.list(), panel.deviceId, panel.availableModes());
|
|
9081
|
+
}
|
|
9082
|
+
/**
|
|
9083
|
+
* Say which devices a mode just armed.
|
|
9084
|
+
*
|
|
9085
|
+
* Called by the panel on the transition INTO an armed mode. Fire-and-forget
|
|
9086
|
+
* and never able to fail the panel: `announceArm` returns void, and every
|
|
9087
|
+
* branch that sends nothing logs why — a mode that arms nothing is the most
|
|
9088
|
+
* important thing this can report, and it is exactly the case where no
|
|
9089
|
+
* message goes out.
|
|
9090
|
+
*/
|
|
9091
|
+
announceArm(mode) {
|
|
9092
|
+
this.deliverArmAnnouncement(mode).catch((err) => {
|
|
9093
|
+
this.logger.warn("alarm arm announcement failed", { meta: {
|
|
9094
|
+
mode,
|
|
9095
|
+
error: String(err)
|
|
9096
|
+
} });
|
|
9097
|
+
});
|
|
9098
|
+
}
|
|
9099
|
+
async deliverArmAnnouncement(mode) {
|
|
9100
|
+
const panel = this.alarmPanel;
|
|
9101
|
+
if (panel === null) return;
|
|
9102
|
+
const settings = panel.settings();
|
|
9103
|
+
const tags = { deviceId: panel.deviceId };
|
|
9104
|
+
if (!settings.announceArm) return;
|
|
9105
|
+
if (settings.announceTargets.length === 0) {
|
|
9106
|
+
this.logger.info("alarm armed but no announcement target is configured", {
|
|
9107
|
+
tags,
|
|
9108
|
+
meta: { mode }
|
|
9109
|
+
});
|
|
9110
|
+
return;
|
|
9111
|
+
}
|
|
9112
|
+
const coverage = this.coverageFor(panel).find((c) => c.mode === mode);
|
|
9113
|
+
if (coverage === void 0) return;
|
|
9114
|
+
const message = buildArmAnnouncement(coverage, await this.dispatcher.resolveDeviceNames(coverage.deviceIds));
|
|
9115
|
+
if (message === null) {
|
|
9116
|
+
this.logger.warn("alarm armed into a mode no enabled rule is gated on", {
|
|
9117
|
+
tags,
|
|
9118
|
+
meta: {
|
|
9119
|
+
mode,
|
|
9120
|
+
state: `armed_${mode}`
|
|
9121
|
+
}
|
|
9122
|
+
});
|
|
9123
|
+
return;
|
|
9124
|
+
}
|
|
9125
|
+
for (const targetId of settings.announceTargets) await this.dispatcher.deliverAnnouncement({
|
|
9126
|
+
reason: "alarm-arm",
|
|
9127
|
+
targetId,
|
|
9128
|
+
title: message.title,
|
|
9129
|
+
body: message.body,
|
|
9130
|
+
deviceId: panel.deviceId
|
|
9131
|
+
});
|
|
9132
|
+
}
|
|
8691
9133
|
/** Append one evaluation to the serialized chain (see {@link evalChain}). */
|
|
8692
9134
|
scheduleEvaluation(subject, kind, logContext) {
|
|
8693
9135
|
this.evalChain = this.evalChain.then(async () => {
|
|
@@ -22666,6 +23108,17 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
22666
23108
|
* `notification-rules` provider (CRUD over the central store) is
|
|
22667
23109
|
* registered from every node. */
|
|
22668
23110
|
notificationCenter = null;
|
|
23111
|
+
/**
|
|
23112
|
+
* The live alarm panel on THIS node, or null.
|
|
23113
|
+
*
|
|
23114
|
+
* Held, where the earlier note said not to. The objection then was a field
|
|
23115
|
+
* nothing reads growing into a second source of truth for which device the
|
|
23116
|
+
* alarm is — and it stands: the panel is still addressed by stable id
|
|
23117
|
+
* everywhere else. This reference exists because two things now READ it, and
|
|
23118
|
+
* neither can go through the device id: the settings tab, which needs the
|
|
23119
|
+
* panel's own config, and the arm announcement, which the panel pushes.
|
|
23120
|
+
*/
|
|
23121
|
+
alarmPanelDevice = null;
|
|
22669
23122
|
/** THE single owner of capture pressure (S3, refactor spec 2026-07-22):
|
|
22670
23123
|
* every native-fetch call site routed so far (the per-frame batch media
|
|
22671
23124
|
* dispatch + `persistKeyFrames`; part B routes the rest) goes through ONE
|
|
@@ -22956,14 +23409,18 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
22956
23409
|
return found === void 0 ? null : { id: found.id };
|
|
22957
23410
|
},
|
|
22958
23411
|
createDevice: async ({ stableId, integrationId, name }) => {
|
|
22959
|
-
|
|
23412
|
+
const device = await devices.create(stableId, NC_ALARM_DEVICE_CLASS, {}, null, {
|
|
22960
23413
|
type: NC_ALARM_DEVICE_TYPE,
|
|
22961
23414
|
name,
|
|
22962
23415
|
integrationId
|
|
22963
|
-
})
|
|
23416
|
+
});
|
|
23417
|
+
this.holdAlarmPanel(device);
|
|
23418
|
+
return { id: device.id };
|
|
22964
23419
|
},
|
|
22965
23420
|
adoptDevice: async ({ stableId }) => {
|
|
22966
|
-
|
|
23421
|
+
const device = await devices.create(stableId, NC_ALARM_DEVICE_CLASS, {}, null, void 0);
|
|
23422
|
+
this.holdAlarmPanel(device);
|
|
23423
|
+
return { id: device.id };
|
|
22967
23424
|
}
|
|
22968
23425
|
});
|
|
22969
23426
|
if (result.created) this.ctx.logger.info("notification-center alarm panel ready", { tags: { deviceId: result.deviceId } });
|
|
@@ -22971,6 +23428,41 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
22971
23428
|
this.ctx.logger.warn("alarm panel could not be ensured — rules still notify", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
22972
23429
|
}
|
|
22973
23430
|
}
|
|
23431
|
+
/**
|
|
23432
|
+
* Keep the constructed panel, if that is what came back.
|
|
23433
|
+
*
|
|
23434
|
+
* An `instanceof` narrowing rather than a cast: `devices.create` promises an
|
|
23435
|
+
* `IDevice`, and the only honest way to know it is the alarm is to ask. A
|
|
23436
|
+
* cast here would compile against any future device class and fail at the
|
|
23437
|
+
* first `settings()` call, in production, with the tab already open.
|
|
23438
|
+
*/
|
|
23439
|
+
holdAlarmPanel(device) {
|
|
23440
|
+
if (!(device instanceof NcAlarmPanelDevice)) {
|
|
23441
|
+
this.ctx.logger.warn("alarm panel is not the expected device class — settings tab disabled");
|
|
23442
|
+
return;
|
|
23443
|
+
}
|
|
23444
|
+
this.alarmPanelDevice = device;
|
|
23445
|
+
}
|
|
23446
|
+
/**
|
|
23447
|
+
* Join the panel and the Notification Center, once both exist.
|
|
23448
|
+
*
|
|
23449
|
+
* They are built in that order (`ensureAlarmPanel` runs before the centre is
|
|
23450
|
+
* constructed) and neither can reach the other on its own, so the join is
|
|
23451
|
+
* here. A node with no panel simply never calls it, and the cap answers
|
|
23452
|
+
* `deviceId: null` — which is what an agent node should say.
|
|
23453
|
+
*/
|
|
23454
|
+
wireAlarmPanel(center) {
|
|
23455
|
+
const panel = this.alarmPanelDevice;
|
|
23456
|
+
if (panel === null) return;
|
|
23457
|
+
center.setAlarmPanel({
|
|
23458
|
+
deviceId: panel.id,
|
|
23459
|
+
settings: () => panel.settings(),
|
|
23460
|
+
applySettings: (patch) => panel.applySettings(patch),
|
|
23461
|
+
availableModes: () => NC_ALARM_MODES
|
|
23462
|
+
});
|
|
23463
|
+
panel.setArmedHook((mode) => center.announceArm(mode));
|
|
23464
|
+
this.ctx.logger.info("alarm panel wired to the notification center", { tags: { deviceId: panel.id } });
|
|
23465
|
+
}
|
|
22974
23466
|
async declareCollections(api) {
|
|
22975
23467
|
await TrackStore.declare(api.settingsStore);
|
|
22976
23468
|
await MediaStore.declare(api.settingsStore);
|
|
@@ -22995,7 +23487,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
22995
23487
|
let storage = this.ctx.kernel.storage;
|
|
22996
23488
|
const mediaRoot = process.env.CAMSTACK_MEDIA_ROOT?.trim();
|
|
22997
23489
|
if (mediaRoot) {
|
|
22998
|
-
const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-
|
|
23490
|
+
const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-BNgGpVnp.js"));
|
|
22999
23491
|
storage = new FilesystemStorageProvider(mediaRoot, { eventMedia: mediaRoot });
|
|
23000
23492
|
logger.info("pipeline-analytics: event media rooted at CAMSTACK_MEDIA_ROOT", { meta: { mediaRoot } });
|
|
23001
23493
|
}
|
|
@@ -23456,6 +23948,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
23456
23948
|
})).tracks;
|
|
23457
23949
|
}
|
|
23458
23950
|
});
|
|
23951
|
+
this.wireAlarmPanel(this.notificationCenter);
|
|
23459
23952
|
await this.serveNcActionPlane(this.notificationCenter);
|
|
23460
23953
|
await this.notificationCenter.start({ evaluation: this.isPostProcessingNode });
|
|
23461
23954
|
}
|