@camstack/addon-terminal 0.1.26 → 0.1.28

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.
Files changed (3) hide show
  1. package/dist/addon.js +438 -228
  2. package/dist/addon.mjs +438 -228
  3. package/package.json +1 -1
package/dist/addon.mjs CHANGED
@@ -7642,7 +7642,7 @@ var RecordingBandSchema = object({
7642
7642
  postBufferSec: number().min(0).optional()
7643
7643
  });
7644
7644
  ({
7645
- preBufferSec: 10,
7645
+ preBufferSec: 15,
7646
7646
  postBufferSec: 30
7647
7647
  }).postBufferSec * 1e3;
7648
7648
  /**
@@ -13790,128 +13790,103 @@ var COCO_TO_MACRO = {
13790
13790
  var AUDIO_MACRO_LABELS = [
13791
13791
  {
13792
13792
  id: "speech",
13793
- name: "Speech",
13794
- icon: "🗣️"
13793
+ name: "Speech"
13795
13794
  },
13796
13795
  {
13797
13796
  id: "scream",
13798
- name: "Scream / Shout",
13799
- icon: "😱"
13797
+ name: "Scream / Shout"
13800
13798
  },
13801
13799
  {
13802
13800
  id: "crying",
13803
- name: "Crying / Baby",
13804
- icon: "😢"
13801
+ name: "Crying / Baby"
13805
13802
  },
13806
13803
  {
13807
13804
  id: "laughter",
13808
- name: "Laughter",
13809
- icon: "😂"
13805
+ name: "Laughter"
13810
13806
  },
13811
13807
  {
13812
13808
  id: "music",
13813
- name: "Music",
13814
- icon: "🎵"
13809
+ name: "Music"
13815
13810
  },
13816
13811
  {
13817
13812
  id: "dog",
13818
- name: "Dog",
13819
- icon: "🐕"
13813
+ name: "Dog"
13820
13814
  },
13821
13815
  {
13822
13816
  id: "cat",
13823
- name: "Cat",
13824
- icon: "🐈"
13817
+ name: "Cat"
13825
13818
  },
13826
13819
  {
13827
13820
  id: "bird",
13828
- name: "Bird",
13829
- icon: "🐦"
13821
+ name: "Bird"
13830
13822
  },
13831
13823
  {
13832
13824
  id: "animal",
13833
- name: "Animal (other)",
13834
- icon: "🐾"
13825
+ name: "Animal (other)"
13835
13826
  },
13836
13827
  {
13837
13828
  id: "alarm",
13838
- name: "Alarm / Siren",
13839
- icon: "🚨"
13829
+ name: "Alarm / Siren"
13840
13830
  },
13841
13831
  {
13842
13832
  id: "doorbell",
13843
- name: "Doorbell / Knock",
13844
- icon: "🔔"
13833
+ name: "Doorbell / Knock"
13845
13834
  },
13846
13835
  {
13847
13836
  id: "glass_breaking",
13848
- name: "Glass Breaking",
13849
- icon: "💥"
13837
+ name: "Glass Breaking"
13850
13838
  },
13851
13839
  {
13852
13840
  id: "gunshot",
13853
- name: "Gunshot / Explosion",
13854
- icon: "💣"
13841
+ name: "Gunshot / Explosion"
13855
13842
  },
13856
13843
  {
13857
13844
  id: "vehicle",
13858
- name: "Vehicle",
13859
- icon: "🚗"
13845
+ name: "Vehicle"
13860
13846
  },
13861
13847
  {
13862
13848
  id: "siren",
13863
- name: "Emergency Siren",
13864
- icon: "🚑"
13849
+ name: "Emergency Siren"
13865
13850
  },
13866
13851
  {
13867
13852
  id: "fire",
13868
- name: "Fire / Smoke",
13869
- icon: "🔥"
13853
+ name: "Fire / Smoke"
13870
13854
  },
13871
13855
  {
13872
13856
  id: "water",
13873
- name: "Water",
13874
- icon: "💧"
13857
+ name: "Water"
13875
13858
  },
13876
13859
  {
13877
13860
  id: "wind",
13878
- name: "Wind / Weather",
13879
- icon: "🌬️"
13861
+ name: "Wind / Weather"
13880
13862
  },
13881
13863
  {
13882
13864
  id: "door",
13883
- name: "Door",
13884
- icon: "🚪"
13865
+ name: "Door"
13885
13866
  },
13886
13867
  {
13887
13868
  id: "footsteps",
13888
- name: "Footsteps",
13889
- icon: "👣"
13869
+ name: "Footsteps"
13890
13870
  },
13891
13871
  {
13892
13872
  id: "crowd",
13893
- name: "Crowd / Chatter",
13894
- icon: "👥"
13873
+ name: "Crowd / Chatter"
13895
13874
  },
13896
13875
  {
13897
13876
  id: "telephone",
13898
- name: "Telephone",
13899
- icon: "📞"
13877
+ name: "Telephone"
13900
13878
  },
13901
13879
  {
13902
13880
  id: "engine",
13903
- name: "Engine / Motor",
13904
- icon: "⚙️"
13881
+ name: "Engine / Motor"
13905
13882
  },
13906
13883
  {
13907
13884
  id: "tools",
13908
- name: "Tools / Construction",
13909
- icon: "🔨"
13885
+ name: "Tools / Construction"
13910
13886
  },
13911
13887
  {
13912
13888
  id: "silence",
13913
- name: "Silence",
13914
- icon: "🤫"
13889
+ name: "Silence"
13915
13890
  }
13916
13891
  ];
13917
13892
  var YAMNET_TO_MACRO = {
@@ -16413,7 +16388,7 @@ var TrackEnvelopeSchema = object({
16413
16388
  * `snapshots[]` references — megabytes across a page of tracks. `slim`
16414
16389
  * keeps every scalar the list surfaces actually render (ids, class(es),
16415
16390
  * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
16416
- * zonesVisited, bestEventId, envelope, hasFace, hasRider) and returns `positions` /
16391
+ * zonesVisited, bestEventId, envelope, hasFace, hasEmbeddedFace, hasRider) and returns `positions` /
16417
16392
  * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
16418
16393
  * `getTrack`. Mirrors the event-store `projection` convention
16419
16394
  * (`getObjectEvents` et al.).
@@ -16708,6 +16683,35 @@ var TrackSchema = object({
16708
16683
  */
16709
16684
  hasFace: boolean().optional(),
16710
16685
  /**
16686
+ * This track has a face row IN THE GALLERY: a crop **and** an embedding — a
16687
+ * face an operator could ASSIGN to an identity.
16688
+ *
16689
+ * The STRICT twin of {@link hasFace}, and the pair only earns its keep
16690
+ * because the two disagree. `hasFace` is stamped at the TOP of the face
16691
+ * branch, before every gate, and means no more than "a face detector produced
16692
+ * a face detail". This one is stamped at the single moment the gallery row
16693
+ * LANDS — after `FaceRecognizer.onTrackEnd` successfully persists it, i.e.
16694
+ * past the embedding-magnitude verdict, the `minFacePx` detection gate, the
16695
+ * candidate gate, the imageless-track drop (no crop was ever captured) and
16696
+ * the crop-store drop. Everything between the detector and that insert can
16697
+ * legitimately refuse the face, so a flag written any earlier promises the
16698
+ * operator something to assign and delivers nothing.
16699
+ *
16700
+ * **Independent of recognition.** A face collected but never auto-matched is
16701
+ * still assignable — it is in fact the face an operator most wants to reach —
16702
+ * so this is NOT gated on `recognizedIdentityId`. Recognition lands in
16703
+ * `subLabel`; this says only that the raw material exists.
16704
+ *
16705
+ * **Set once, never cleared.** A track that produced a gallery row produced
16706
+ * one; deleting the row later is the gallery's business, not this flag's.
16707
+ *
16708
+ * **Absent ≠ false**, the same rule as {@link hasFace}: every row written
16709
+ * before the column omits it, and so does every server that predates the
16710
+ * field. A consumer must test `=== true` and render nothing otherwise —
16711
+ * never infer "no assignable face".
16712
+ */
16713
+ hasEmbeddedFace: boolean().optional(),
16714
+ /**
16711
16715
  * This subject CONTAINS a folded rider — a person the rider-pairing step
16712
16716
  * ([D34](../decisions/adr-0034.md)) removed from the frame BEFORE the tracker,
16713
16717
  * so the passage is tracked once and as a VEHICLE.
@@ -18097,6 +18101,32 @@ var DetailResultSchema = object({
18097
18101
  /** FRAME-space bbox (already mapped back from crop space). */
18098
18102
  bbox: NativeCropBboxSchema.optional(),
18099
18103
  embedding: string().optional(),
18104
+ /**
18105
+ * L2 magnitude of the RAW pre-normalization embedding. Additive and optional
18106
+ * so an older runner that does not send it degrades to "unmeasurable", which
18107
+ * the consuming gate treats as accept — never as reject.
18108
+ */
18109
+ embeddingMagnitude: number().optional(),
18110
+ /**
18111
+ * Model that produced `embedding` — the SAME contract `labelModelId` has for
18112
+ * `label`, and for the same reason: the producing node is the only side that
18113
+ * knows which model actually ran, and re-deriving it downstream is exactly
18114
+ * the inference this model exists to forbid.
18115
+ *
18116
+ * Additive and optional, so a runner that predates it degrades to
18117
+ * "unmeasurable" rather than to a wrong answer — the consumer falls back to
18118
+ * its previous assumption instead of refusing.
18119
+ *
18120
+ * Its absence was not cosmetic. `FaceRecognizer` needs the probe's model id
18121
+ * to use `face-matcher`'s same-model gate (`sample.modelId !== probe.modelId`);
18122
+ * with nothing on the wire it took the id from the GALLERY, so the gate
18123
+ * compared the gallery against itself and could never fire. That turned the
18124
+ * one guard protecting the face feature space into a tautology, and it is
18125
+ * why swapping `face-embedding`'s model would NOT have stranded the enrolled
18126
+ * gallery as the catalog comment predicts — it would have scored the new
18127
+ * model's vectors against the old model's samples in one cosine space.
18128
+ */
18129
+ embeddingModelId: string().optional(),
18100
18130
  label: string().optional(),
18101
18131
  /**
18102
18132
  * The tier `label` occupies, copied VERBATIM from the producing step's
@@ -18972,6 +19002,12 @@ var CameraStatusSchema = object({
18972
19002
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
18973
19003
  fetchedAt: number()
18974
19004
  });
19005
+ var InferenceDeviceExclusionReasonSchema = _enum([
19006
+ "disabled",
19007
+ "unavailable",
19008
+ "cannot-host-camera-root",
19009
+ "accelerator-preferred"
19010
+ ]);
18975
19011
  var NodeInferenceDeviceSchema = object({
18976
19012
  /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
18977
19013
  key: string(),
@@ -19002,7 +19038,17 @@ var NodeInferenceDeviceSchema = object({
19002
19038
  * available per format; this is the stored selection that becomes the
19003
19039
  * default for EVERY camera landing on this accelerator.
19004
19040
  */
19005
- steps: record(string(), DeviceStepConfigSchema).optional()
19041
+ steps: record(string(), DeviceStepConfigSchema).optional(),
19042
+ /**
19043
+ * `null` when the device IS a camera-root candidate on this node; otherwise
19044
+ * the reason the dispatcher drops it. Computed by the SAME
19045
+ * `resolveInferenceDeviceEligibility` the dispatcher runs, so this view can
19046
+ * never disagree with the election — deriving it in the UI from
19047
+ * `enabled`/`available` would silently miss `cannot-host-camera-root` (needs
19048
+ * the node's model catalog) and `accelerator-preferred` (needs the node-wide
19049
+ * "an accelerator is serving" predicate).
19050
+ */
19051
+ exclusion: InferenceDeviceExclusionReasonSchema.nullable()
19006
19052
  });
19007
19053
  var NodeInferenceDevicesSchema = object({
19008
19054
  nodeId: string(),
@@ -19561,6 +19607,7 @@ var snapshotCapability = {
19561
19607
  isBattery: boolean(),
19562
19608
  reason: _enum([
19563
19609
  "disabled",
19610
+ "offline",
19564
19611
  "sleeping",
19565
19612
  "unreachable",
19566
19613
  "waking"
@@ -19650,9 +19697,17 @@ targets: array(object({
19650
19697
  sleeping: boolean(),
19651
19698
  /** Current device state rendered over the cached frame. State images
19652
19699
  * remain authoritative even when their photographic background is
19653
- * old; null means the link must carry a current camera frame. */
19700
+ * old; null means the link must carry a current camera frame.
19701
+ *
19702
+ * **This is the ONLY source a surface may derive an inactive tile
19703
+ * from.** A client that reads a device's `online`/`disabled` flags and
19704
+ * draws its own verdict will disagree with the pixels it is painting
19705
+ * — the frame comes from here, and so must the sentence over it
19706
+ * (D190). Pair with `ageMs` for the age of the photograph behind the
19707
+ * state. */
19654
19708
  stateReason: _enum([
19655
19709
  "disabled",
19710
+ "offline",
19656
19711
  "sleeping",
19657
19712
  "unreachable",
19658
19713
  "waking"
@@ -23991,7 +24046,12 @@ var ListResultSchema = object({
23991
24046
  probedAt: number()
23992
24047
  });
23993
24048
  var PreferredSchema = LocalInterfaceSchema.nullable();
23994
- var GetConnectionEndpointsResultSchema = object({ endpoints: array(object({
24049
+ /**
24050
+ * Candidate base URL for the SDK to race on connect. Order matters —
24051
+ * the SDK should attempt these top-to-bottom with a short per-candidate
24052
+ * timeout (e.g. 1500ms) and cache the winner for the session.
24053
+ */
24054
+ var ConnectionEndpointSchema = object({
23995
24055
  /** Operator-facing label (e.g. "LAN — en0", "Public tunnel"). */
23996
24056
  label: string(),
23997
24057
  /** Fully-formed base URL with scheme + host + port. */
@@ -24034,7 +24094,42 @@ var GetConnectionEndpointsResultSchema = object({ endpoints: array(object({
24034
24094
  * ordering between polls.
24035
24095
  */
24036
24096
  priority: number()
24037
- })).readonly() });
24097
+ });
24098
+ /**
24099
+ * Where the advertised local port came from. Ordered most → least
24100
+ * authoritative, and the whole point of returning it: a client must be able to
24101
+ * tell a FACT about the hub's socket from an echo of its own guess.
24102
+ */
24103
+ var LocalPortSourceEnum = _enum([
24104
+ "server-config",
24105
+ "server-env",
24106
+ "caller-hint",
24107
+ "default"
24108
+ ]);
24109
+ /** The port every LAN/loopback `baseUrl` in the same result was built with. */
24110
+ var AdvertisedLocalPortSchema = object({
24111
+ port: number().int().min(1).max(65535),
24112
+ source: LocalPortSourceEnum
24113
+ });
24114
+ var GetConnectionEndpointsResultSchema = object({
24115
+ endpoints: array(ConnectionEndpointSchema).readonly(),
24116
+ /**
24117
+ * The port the hub built the LAN/loopback URLs with, and where that number
24118
+ * came from.
24119
+ *
24120
+ * Returned rather than merely applied, because "the URL is right" and "the
24121
+ * client can KNOW the URL is right" are different properties. A client that
24122
+ * only sees a corrected URL cannot distinguish a hub that fixed the port from
24123
+ * a hub that echoed the port the client sent, so it cannot decide whether to
24124
+ * race the candidate or discard it. With `source` it can: anything but
24125
+ * `caller-hint` is the hub's own socket.
24126
+ *
24127
+ * Absent on hubs predating this field — a client that finds it missing is
24128
+ * talking to an echoing hub and must degrade exactly as it does for
24129
+ * `caller-hint`.
24130
+ */
24131
+ localPort: AdvertisedLocalPortSchema
24132
+ });
24038
24133
  /**
24039
24134
  * The chosen outbound endpoint for notification artifacts. `baseUrl: null` =
24040
24135
  * AUTO (resolved from the candidate ranking at send time); `resolved` reports
@@ -24055,8 +24150,13 @@ var AllowedAddressesSchema = object({
24055
24150
  */
24056
24151
  addresses: array(string()).readonly() });
24057
24152
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
24058
- /** Local hub HTTP port to use in base URLs. */
24059
- port: number().int().min(1).max(65535),
24153
+ /**
24154
+ * LEGACY HINT — do not send from new code. Kept optional so clients
24155
+ * written against the echoing contract keep working; the hub uses it
24156
+ * only when it cannot read its own port, and says so via
24157
+ * `localPort.source === 'caller-hint'`.
24158
+ */
24159
+ port: number().int().min(1).max(65535).optional(),
24060
24160
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
24061
24161
  * candidate. Default `true`. */
24062
24162
  includeLoopback: boolean().optional(),
@@ -37242,7 +37342,6 @@ var NC_AUDIO_DEFAULTS = {
37242
37342
  hitPercent: 60,
37243
37343
  samplingSeconds: 10
37244
37344
  };
37245
- new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
37246
37345
  new Set(["devices", "classes"]);
37247
37346
  NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
37248
37347
  /**
@@ -37476,6 +37575,39 @@ TimelapseRuleInputSchema.extend({
37476
37575
  createdAt: number(),
37477
37576
  updatedAt: number()
37478
37577
  });
37578
+ Object.freeze(Object.fromEntries([{
37579
+ stepId: "face-embedding",
37580
+ label: "Face recognition model",
37581
+ defaultModelId: "arcface-r100",
37582
+ indexName: "the enrolled face gallery",
37583
+ options: [
37584
+ {
37585
+ id: "arcface-r100",
37586
+ label: "ArcFace (ResNet34)"
37587
+ },
37588
+ {
37589
+ id: "auraface-r100",
37590
+ label: "AuraFace R100"
37591
+ },
37592
+ {
37593
+ id: "inception-resnet-v1",
37594
+ label: "Inception ResNet V1"
37595
+ }
37596
+ ]
37597
+ }, {
37598
+ stepId: "clip-embedding",
37599
+ label: "Semantic search model",
37600
+ defaultModelId: "mobileclip-s1",
37601
+ indexName: "the object semantic-search index",
37602
+ options: [{
37603
+ id: "mobileclip-s1",
37604
+ label: "MobileCLIP S1"
37605
+ }, {
37606
+ id: "mobileclip-s2",
37607
+ label: "MobileCLIP S2"
37608
+ }]
37609
+ }].map((s) => [s.stepId, s.defaultModelId])));
37610
+ string().min(1);
37479
37611
  object({
37480
37612
  /**
37481
37613
  * Fraction of the box's own size added on EACH side before cutting.
@@ -37496,10 +37628,21 @@ object({
37496
37628
  */
37497
37629
  square: boolean()
37498
37630
  });
37499
- ({
37631
+ /**
37632
+ * The convention in force when nobody has configured one — byte-for-byte the
37633
+ * behaviour of the pre-unification LIVE path (`DETAIL_CROP_PADDING_RATIO`).
37634
+ */
37635
+ var DEFAULT_DETAIL_CROP_CONVENTION = {
37500
37636
  paddingRatio: .15,
37501
37637
  square: false
37502
- }).paddingRatio;
37638
+ };
37639
+ Object.freeze({
37640
+ x: 0,
37641
+ y: 0,
37642
+ w: 1,
37643
+ h: 1
37644
+ });
37645
+ DEFAULT_DETAIL_CROP_CONVENTION.paddingRatio;
37503
37646
  /**
37504
37647
  * WHICH delivered frames the decode worker retains a native copy of.
37505
37648
  *
@@ -44111,6 +44254,38 @@ var TerminalSessionManager = class {
44111
44254
  * (`node-pty`) and should update via `camstack deploy` independently of the
44112
44255
  * framework, exactly like the node-av decoder addon.
44113
44256
  */
44257
+ /**
44258
+ * Default argv for the Glances profile — deliberately EMPTY.
44259
+ *
44260
+ * Measured on the hub (2026-08-21, glances 4.5.6, embedded Python, a real pty
44261
+ * at the terminal camera's own 120x40 grid, CPU sampled from
44262
+ * /proc/<pid>/{utime,stime} so the number is the process's own and not the
44263
+ * box's contention):
44264
+ *
44265
+ * argv CPU (of one core)
44266
+ * (none — this default) 0.8 %
44267
+ * -t 3 0.7 %
44268
+ * -t 5 0.6 %
44269
+ * --disable-plugin processlist 0.7 %
44270
+ * --disable-plugin containers 0.9 %
44271
+ * --disable-plugin sensors 0.8 %
44272
+ * --disable-process 0.8 %
44273
+ * -t 3 --disable-plugin processlist,containers,sensors 0.5 %
44274
+ * -t 5 --disable-plugin processlist,containers,sensors 0.4 %
44275
+ *
44276
+ * Startup costs 0.19 core-seconds; steady state is 1.0 % of one core at 36 MB
44277
+ * RSS. The whole tuning range is worth ~0.4 % of ONE core out of 20 — below the
44278
+ * noise floor of a box that idles at 2.6 cores — while every entry that buys
44279
+ * something removes a panel the operator opened the camera to read. `-t` is
44280
+ * additionally counter-productive here: the terminal camera renders at 2 FPS
44281
+ * and glances already repaints every 2 s, so slowing glances only makes the
44282
+ * camera show stale numbers.
44283
+ *
44284
+ * So the default ships empty and the knob stays in front of the operator.
44285
+ * Anyone who does want to trade panels for CPU has the numbers above.
44286
+ */
44287
+ var DEFAULT_GLANCES_ARGS = [];
44288
+ var GLANCES_ARGS_DESCRIPTION = "Exact arguments passed to Glances. One tag per argv entry. Empty is the measured-best default: at the camera grid Glances costs ~1% of one core, and the cheapest tuning found (`-t 5 --disable-plugin processlist,containers,sensors`) saves only ~0.4% of one core while removing the panels most people open it for. `-t` also makes the camera show stale numbers — it renders at 2 FPS and Glances already repaints every 2s.";
44114
44289
  var DEFAULTS = {
44115
44290
  btmPath: "",
44116
44291
  btmEnabled: true,
@@ -44120,7 +44295,7 @@ var DEFAULTS = {
44120
44295
  topArgs: [],
44121
44296
  glancesEnabled: true,
44122
44297
  glancesPath: "",
44123
- glancesArgs: [],
44298
+ glancesArgs: DEFAULT_GLANCES_ARGS,
44124
44299
  allowShell: false,
44125
44300
  shellPath: "",
44126
44301
  maxSessions: 4,
@@ -44507,85 +44682,13 @@ var TerminalAddon = class extends BaseAddon {
44507
44682
  return this.instanceInfo(instance);
44508
44683
  }
44509
44684
  globalSettingsSchema() {
44510
- return this.schema({ sections: [{
44511
- id: "terminal",
44512
- title: "Terminal",
44513
- description: "Interactive terminal sessions in the Admin UI. Only pre-declared profiles can be opened; a free-form command is never accepted.",
44514
- columns: 2,
44515
- fields: [
44516
- this.field({
44517
- type: "boolean",
44518
- key: "btmEnabled",
44519
- label: "Enable BTM camera",
44520
- default: true,
44521
- perNode: true
44522
- }),
44523
- this.field({
44524
- type: "text",
44525
- key: "btmPath",
44526
- label: "BTM binary",
44527
- description: "Path to the `btm` (bottom) executable. Leave empty to resolve from PATH.",
44528
- placeholder: "btm",
44529
- default: "",
44530
- perNode: true
44531
- }),
44532
- this.field({
44533
- type: "tags",
44534
- key: "btmArgs",
44535
- label: "BTM arguments",
44536
- description: "Exact arguments passed to btm.",
44537
- default: [],
44538
- perNode: true
44539
- }),
44540
- this.field({
44541
- type: "boolean",
44542
- key: "topEnabled",
44543
- label: "Enable Top camera",
44544
- default: true,
44545
- perNode: true
44546
- }),
44547
- this.field({
44548
- type: "text",
44549
- key: "topPath",
44550
- label: "Top binary",
44551
- placeholder: "top",
44552
- default: "",
44553
- perNode: true
44554
- }),
44555
- this.field({
44556
- type: "tags",
44557
- key: "topArgs",
44558
- label: "Top arguments",
44559
- description: "Exact arguments passed to top.",
44560
- default: [],
44561
- perNode: true
44562
- }),
44563
- this.field({
44564
- type: "boolean",
44565
- key: "glancesEnabled",
44566
- label: "Enable Glances camera",
44567
- description: "Glances is installed automatically into CamStack embedded Python.",
44568
- default: true,
44569
- perNode: true
44570
- }),
44571
- this.field({
44572
- type: "text",
44573
- key: "glancesPath",
44574
- label: "Glances binary override",
44575
- description: "Optional executable override. Empty uses the automatically managed Python package.",
44576
- placeholder: "glances",
44577
- default: "",
44578
- perNode: true
44579
- }),
44580
- this.field({
44581
- type: "tags",
44582
- key: "glancesArgs",
44583
- label: "Glances arguments",
44584
- description: "Exact arguments passed to Glances.",
44585
- default: [],
44586
- perNode: true
44587
- }),
44588
- this.field({
44685
+ return this.schema({ sections: [
44686
+ {
44687
+ id: "terminal",
44688
+ title: "Terminal",
44689
+ description: "Interactive terminal sessions in the Admin UI. Only pre-declared profiles can be opened; a free-form command is never accepted. Every setting below is per node — the values you see belong to the node selected above.",
44690
+ columns: 2,
44691
+ fields: [this.field({
44589
44692
  type: "number",
44590
44693
  key: "maxSessions",
44591
44694
  label: "Max concurrent sessions",
@@ -44594,16 +44697,122 @@ var TerminalAddon = class extends BaseAddon {
44594
44697
  step: 1,
44595
44698
  default: 4,
44596
44699
  perNode: true
44597
- }),
44598
- this.field({
44700
+ })]
44701
+ },
44702
+ {
44703
+ id: "terminal-btm",
44704
+ title: "BTM camera",
44705
+ description: "bottom (btm) — CPU, memory, network and process monitor.",
44706
+ columns: 2,
44707
+ fields: [
44708
+ this.field({
44709
+ type: "boolean",
44710
+ key: "btmEnabled",
44711
+ label: "Enabled",
44712
+ description: "Offer the BTM profile on this node.",
44713
+ default: true,
44714
+ perNode: true
44715
+ }),
44716
+ this.field({
44717
+ type: "text",
44718
+ key: "btmPath",
44719
+ label: "Binary",
44720
+ description: "Path to the `btm` (bottom) executable. Leave empty to resolve from PATH.",
44721
+ placeholder: "btm",
44722
+ default: "",
44723
+ perNode: true
44724
+ }),
44725
+ this.field({
44726
+ type: "tags",
44727
+ key: "btmArgs",
44728
+ label: "Arguments",
44729
+ description: "Exact arguments passed to btm. One tag per argv entry.",
44730
+ default: [],
44731
+ perNode: true,
44732
+ span: 2
44733
+ })
44734
+ ]
44735
+ },
44736
+ {
44737
+ id: "terminal-top",
44738
+ title: "Top camera",
44739
+ description: "The operating system process and resource monitor.",
44740
+ columns: 2,
44741
+ fields: [
44742
+ this.field({
44743
+ type: "boolean",
44744
+ key: "topEnabled",
44745
+ label: "Enabled",
44746
+ description: "Offer the Top profile on this node.",
44747
+ default: true,
44748
+ perNode: true
44749
+ }),
44750
+ this.field({
44751
+ type: "text",
44752
+ key: "topPath",
44753
+ label: "Binary",
44754
+ placeholder: "top",
44755
+ default: "",
44756
+ perNode: true
44757
+ }),
44758
+ this.field({
44759
+ type: "tags",
44760
+ key: "topArgs",
44761
+ label: "Arguments",
44762
+ description: "Exact arguments passed to top. One tag per argv entry.",
44763
+ default: [],
44764
+ perNode: true,
44765
+ span: 2
44766
+ })
44767
+ ]
44768
+ },
44769
+ {
44770
+ id: "terminal-glances",
44771
+ title: "Glances camera",
44772
+ description: "Cross-platform curses monitor, installed automatically into CamStack embedded Python. Glances is the most expensive built-in profile — it only runs while somebody is watching, but while it runs it costs real CPU. See the Arguments help below before widening it.",
44773
+ columns: 2,
44774
+ fields: [
44775
+ this.field({
44776
+ type: "boolean",
44777
+ key: "glancesEnabled",
44778
+ label: "Enabled",
44779
+ description: "Offer the Glances profile on this node.",
44780
+ default: true,
44781
+ perNode: true
44782
+ }),
44783
+ this.field({
44784
+ type: "text",
44785
+ key: "glancesPath",
44786
+ label: "Binary override",
44787
+ description: "Optional executable override. Empty uses the automatically managed Python package.",
44788
+ placeholder: "glances",
44789
+ default: "",
44790
+ perNode: true
44791
+ }),
44792
+ this.field({
44793
+ type: "tags",
44794
+ key: "glancesArgs",
44795
+ label: "Arguments",
44796
+ description: GLANCES_ARGS_DESCRIPTION,
44797
+ default: [...DEFAULT_GLANCES_ARGS],
44798
+ perNode: true,
44799
+ span: 2
44800
+ })
44801
+ ]
44802
+ },
44803
+ {
44804
+ id: "terminal-shell",
44805
+ title: "Interactive shell",
44806
+ description: "The one profile that is genuinely a command surface. Off by default; turning it on grants command execution as the server user to every admin.",
44807
+ columns: 2,
44808
+ fields: [this.field({
44599
44809
  type: "boolean",
44600
44810
  key: "allowShell",
44601
44811
  label: "Allow interactive shell",
44602
- description: "Adds a \"Shell\" profile that grants command execution as the server user. Off by default.",
44812
+ description: "Adds a \"Shell\" profile on this node.",
44603
44813
  default: false,
44604
44814
  perNode: true
44605
- }),
44606
- this.field({
44815
+ }), this.field({
44607
44816
  type: "text",
44608
44817
  key: "shellPath",
44609
44818
  label: "Shell binary",
@@ -44611,89 +44820,90 @@ var TerminalAddon = class extends BaseAddon {
44611
44820
  placeholder: "/bin/bash",
44612
44821
  default: "",
44613
44822
  perNode: true
44614
- })
44615
- ]
44616
- }, {
44617
- id: "terminal-profiles",
44618
- title: "Custom profiles",
44619
- description: "Enabled profiles are available templates on this node. Create a Terminal instance on the Terminal page to declare a camera; session requests only carry the profile ID and commands cannot be overridden by clients.",
44620
- columns: 1,
44621
- fields: [this.field({
44622
- type: "editable-array",
44623
- key: "customProfiles",
44624
- label: "Profiles",
44625
- perNode: true,
44626
- default: [],
44627
- maxRows: 32,
44628
- addLabel: "Add profile",
44629
- emptyMessage: "No custom profiles configured.",
44630
- rowTitleTemplate: "{label} — {executable}",
44631
- defaultItem: {
44632
- enabled: true,
44633
- profileId: "",
44634
- label: "",
44635
- description: "",
44636
- executable: "",
44637
- args: [],
44638
- cwd: "",
44639
- environment: []
44640
- },
44641
- itemFields: [
44642
- {
44643
- type: "boolean",
44644
- key: "enabled",
44645
- label: "Enabled",
44646
- default: true
44647
- },
44648
- {
44649
- type: "text",
44650
- key: "profileId",
44651
- label: "Profile ID",
44652
- description: "Stable lowercase slug, for example gpu-monitor.",
44653
- required: true
44654
- },
44655
- {
44656
- type: "text",
44657
- key: "label",
44658
- label: "Display name",
44659
- required: true
44660
- },
44661
- {
44662
- type: "textarea",
44663
- key: "description",
44664
- label: "Description",
44665
- rows: 2
44666
- },
44667
- {
44668
- type: "text",
44669
- key: "executable",
44670
- label: "Executable",
44671
- description: "Absolute path or a binary resolved from PATH.",
44672
- required: true
44673
- },
44674
- {
44675
- type: "tags",
44676
- key: "args",
44677
- label: "Arguments",
44678
- description: "One exact process argument per tag.",
44679
- maxTags: 64
44680
- },
44681
- {
44682
- type: "text",
44683
- key: "cwd",
44684
- label: "Working directory",
44685
- description: "Optional working directory for this profile."
44823
+ })]
44824
+ },
44825
+ {
44826
+ id: "terminal-profiles",
44827
+ title: "Custom profiles",
44828
+ description: "Enabled profiles are available templates on this node. Create a Terminal instance on the Terminal page to declare a camera; session requests only carry the profile ID and commands cannot be overridden by clients.",
44829
+ columns: 1,
44830
+ fields: [this.field({
44831
+ type: "editable-array",
44832
+ key: "customProfiles",
44833
+ label: "Profiles",
44834
+ perNode: true,
44835
+ default: [],
44836
+ maxRows: 32,
44837
+ addLabel: "Add profile",
44838
+ emptyMessage: "No custom profiles configured.",
44839
+ rowTitleTemplate: "{label} — {executable}",
44840
+ defaultItem: {
44841
+ enabled: true,
44842
+ profileId: "",
44843
+ label: "",
44844
+ description: "",
44845
+ executable: "",
44846
+ args: [],
44847
+ cwd: "",
44848
+ environment: []
44686
44849
  },
44687
- {
44688
- type: "tags",
44689
- key: "environment",
44690
- label: "Environment",
44691
- description: "Optional NAME=value entries merged into the process environment.",
44692
- maxTags: 64
44693
- }
44694
- ]
44695
- })]
44696
- }] });
44850
+ itemFields: [
44851
+ {
44852
+ type: "boolean",
44853
+ key: "enabled",
44854
+ label: "Enabled",
44855
+ default: true
44856
+ },
44857
+ {
44858
+ type: "text",
44859
+ key: "profileId",
44860
+ label: "Profile ID",
44861
+ description: "Stable lowercase slug, for example gpu-monitor.",
44862
+ required: true
44863
+ },
44864
+ {
44865
+ type: "text",
44866
+ key: "label",
44867
+ label: "Display name",
44868
+ required: true
44869
+ },
44870
+ {
44871
+ type: "textarea",
44872
+ key: "description",
44873
+ label: "Description",
44874
+ rows: 2
44875
+ },
44876
+ {
44877
+ type: "text",
44878
+ key: "executable",
44879
+ label: "Executable",
44880
+ description: "Absolute path or a binary resolved from PATH.",
44881
+ required: true
44882
+ },
44883
+ {
44884
+ type: "tags",
44885
+ key: "args",
44886
+ label: "Arguments",
44887
+ description: "One exact process argument per tag.",
44888
+ maxTags: 64
44889
+ },
44890
+ {
44891
+ type: "text",
44892
+ key: "cwd",
44893
+ label: "Working directory",
44894
+ description: "Optional working directory for this profile."
44895
+ },
44896
+ {
44897
+ type: "tags",
44898
+ key: "environment",
44899
+ label: "Environment",
44900
+ description: "Optional NAME=value entries merged into the process environment.",
44901
+ maxTags: 64
44902
+ }
44903
+ ]
44904
+ })]
44905
+ }
44906
+ ] });
44697
44907
  }
44698
44908
  };
44699
44909
  //#endregion