@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.js CHANGED
@@ -7665,7 +7665,7 @@ var RecordingBandSchema = object({
7665
7665
  postBufferSec: number().min(0).optional()
7666
7666
  });
7667
7667
  ({
7668
- preBufferSec: 10,
7668
+ preBufferSec: 15,
7669
7669
  postBufferSec: 30
7670
7670
  }).postBufferSec * 1e3;
7671
7671
  /**
@@ -13813,128 +13813,103 @@ var COCO_TO_MACRO = {
13813
13813
  var AUDIO_MACRO_LABELS = [
13814
13814
  {
13815
13815
  id: "speech",
13816
- name: "Speech",
13817
- icon: "🗣️"
13816
+ name: "Speech"
13818
13817
  },
13819
13818
  {
13820
13819
  id: "scream",
13821
- name: "Scream / Shout",
13822
- icon: "😱"
13820
+ name: "Scream / Shout"
13823
13821
  },
13824
13822
  {
13825
13823
  id: "crying",
13826
- name: "Crying / Baby",
13827
- icon: "😢"
13824
+ name: "Crying / Baby"
13828
13825
  },
13829
13826
  {
13830
13827
  id: "laughter",
13831
- name: "Laughter",
13832
- icon: "😂"
13828
+ name: "Laughter"
13833
13829
  },
13834
13830
  {
13835
13831
  id: "music",
13836
- name: "Music",
13837
- icon: "🎵"
13832
+ name: "Music"
13838
13833
  },
13839
13834
  {
13840
13835
  id: "dog",
13841
- name: "Dog",
13842
- icon: "🐕"
13836
+ name: "Dog"
13843
13837
  },
13844
13838
  {
13845
13839
  id: "cat",
13846
- name: "Cat",
13847
- icon: "🐈"
13840
+ name: "Cat"
13848
13841
  },
13849
13842
  {
13850
13843
  id: "bird",
13851
- name: "Bird",
13852
- icon: "🐦"
13844
+ name: "Bird"
13853
13845
  },
13854
13846
  {
13855
13847
  id: "animal",
13856
- name: "Animal (other)",
13857
- icon: "🐾"
13848
+ name: "Animal (other)"
13858
13849
  },
13859
13850
  {
13860
13851
  id: "alarm",
13861
- name: "Alarm / Siren",
13862
- icon: "🚨"
13852
+ name: "Alarm / Siren"
13863
13853
  },
13864
13854
  {
13865
13855
  id: "doorbell",
13866
- name: "Doorbell / Knock",
13867
- icon: "🔔"
13856
+ name: "Doorbell / Knock"
13868
13857
  },
13869
13858
  {
13870
13859
  id: "glass_breaking",
13871
- name: "Glass Breaking",
13872
- icon: "💥"
13860
+ name: "Glass Breaking"
13873
13861
  },
13874
13862
  {
13875
13863
  id: "gunshot",
13876
- name: "Gunshot / Explosion",
13877
- icon: "💣"
13864
+ name: "Gunshot / Explosion"
13878
13865
  },
13879
13866
  {
13880
13867
  id: "vehicle",
13881
- name: "Vehicle",
13882
- icon: "🚗"
13868
+ name: "Vehicle"
13883
13869
  },
13884
13870
  {
13885
13871
  id: "siren",
13886
- name: "Emergency Siren",
13887
- icon: "🚑"
13872
+ name: "Emergency Siren"
13888
13873
  },
13889
13874
  {
13890
13875
  id: "fire",
13891
- name: "Fire / Smoke",
13892
- icon: "🔥"
13876
+ name: "Fire / Smoke"
13893
13877
  },
13894
13878
  {
13895
13879
  id: "water",
13896
- name: "Water",
13897
- icon: "💧"
13880
+ name: "Water"
13898
13881
  },
13899
13882
  {
13900
13883
  id: "wind",
13901
- name: "Wind / Weather",
13902
- icon: "🌬️"
13884
+ name: "Wind / Weather"
13903
13885
  },
13904
13886
  {
13905
13887
  id: "door",
13906
- name: "Door",
13907
- icon: "🚪"
13888
+ name: "Door"
13908
13889
  },
13909
13890
  {
13910
13891
  id: "footsteps",
13911
- name: "Footsteps",
13912
- icon: "👣"
13892
+ name: "Footsteps"
13913
13893
  },
13914
13894
  {
13915
13895
  id: "crowd",
13916
- name: "Crowd / Chatter",
13917
- icon: "👥"
13896
+ name: "Crowd / Chatter"
13918
13897
  },
13919
13898
  {
13920
13899
  id: "telephone",
13921
- name: "Telephone",
13922
- icon: "📞"
13900
+ name: "Telephone"
13923
13901
  },
13924
13902
  {
13925
13903
  id: "engine",
13926
- name: "Engine / Motor",
13927
- icon: "⚙️"
13904
+ name: "Engine / Motor"
13928
13905
  },
13929
13906
  {
13930
13907
  id: "tools",
13931
- name: "Tools / Construction",
13932
- icon: "🔨"
13908
+ name: "Tools / Construction"
13933
13909
  },
13934
13910
  {
13935
13911
  id: "silence",
13936
- name: "Silence",
13937
- icon: "🤫"
13912
+ name: "Silence"
13938
13913
  }
13939
13914
  ];
13940
13915
  var YAMNET_TO_MACRO = {
@@ -16436,7 +16411,7 @@ var TrackEnvelopeSchema = object({
16436
16411
  * `snapshots[]` references — megabytes across a page of tracks. `slim`
16437
16412
  * keeps every scalar the list surfaces actually render (ids, class(es),
16438
16413
  * label / audioLabels / importance enrichment, firstSeen/lastSeen, state,
16439
- * zonesVisited, bestEventId, envelope, hasFace, hasRider) and returns `positions` /
16414
+ * zonesVisited, bestEventId, envelope, hasFace, hasEmbeddedFace, hasRider) and returns `positions` /
16440
16415
  * `snapshots` as EMPTY arrays — detail views re-fetch the full row via
16441
16416
  * `getTrack`. Mirrors the event-store `projection` convention
16442
16417
  * (`getObjectEvents` et al.).
@@ -16731,6 +16706,35 @@ var TrackSchema = object({
16731
16706
  */
16732
16707
  hasFace: boolean().optional(),
16733
16708
  /**
16709
+ * This track has a face row IN THE GALLERY: a crop **and** an embedding — a
16710
+ * face an operator could ASSIGN to an identity.
16711
+ *
16712
+ * The STRICT twin of {@link hasFace}, and the pair only earns its keep
16713
+ * because the two disagree. `hasFace` is stamped at the TOP of the face
16714
+ * branch, before every gate, and means no more than "a face detector produced
16715
+ * a face detail". This one is stamped at the single moment the gallery row
16716
+ * LANDS — after `FaceRecognizer.onTrackEnd` successfully persists it, i.e.
16717
+ * past the embedding-magnitude verdict, the `minFacePx` detection gate, the
16718
+ * candidate gate, the imageless-track drop (no crop was ever captured) and
16719
+ * the crop-store drop. Everything between the detector and that insert can
16720
+ * legitimately refuse the face, so a flag written any earlier promises the
16721
+ * operator something to assign and delivers nothing.
16722
+ *
16723
+ * **Independent of recognition.** A face collected but never auto-matched is
16724
+ * still assignable — it is in fact the face an operator most wants to reach —
16725
+ * so this is NOT gated on `recognizedIdentityId`. Recognition lands in
16726
+ * `subLabel`; this says only that the raw material exists.
16727
+ *
16728
+ * **Set once, never cleared.** A track that produced a gallery row produced
16729
+ * one; deleting the row later is the gallery's business, not this flag's.
16730
+ *
16731
+ * **Absent ≠ false**, the same rule as {@link hasFace}: every row written
16732
+ * before the column omits it, and so does every server that predates the
16733
+ * field. A consumer must test `=== true` and render nothing otherwise —
16734
+ * never infer "no assignable face".
16735
+ */
16736
+ hasEmbeddedFace: boolean().optional(),
16737
+ /**
16734
16738
  * This subject CONTAINS a folded rider — a person the rider-pairing step
16735
16739
  * ([D34](../decisions/adr-0034.md)) removed from the frame BEFORE the tracker,
16736
16740
  * so the passage is tracked once and as a VEHICLE.
@@ -18120,6 +18124,32 @@ var DetailResultSchema = object({
18120
18124
  /** FRAME-space bbox (already mapped back from crop space). */
18121
18125
  bbox: NativeCropBboxSchema.optional(),
18122
18126
  embedding: string().optional(),
18127
+ /**
18128
+ * L2 magnitude of the RAW pre-normalization embedding. Additive and optional
18129
+ * so an older runner that does not send it degrades to "unmeasurable", which
18130
+ * the consuming gate treats as accept — never as reject.
18131
+ */
18132
+ embeddingMagnitude: number().optional(),
18133
+ /**
18134
+ * Model that produced `embedding` — the SAME contract `labelModelId` has for
18135
+ * `label`, and for the same reason: the producing node is the only side that
18136
+ * knows which model actually ran, and re-deriving it downstream is exactly
18137
+ * the inference this model exists to forbid.
18138
+ *
18139
+ * Additive and optional, so a runner that predates it degrades to
18140
+ * "unmeasurable" rather than to a wrong answer — the consumer falls back to
18141
+ * its previous assumption instead of refusing.
18142
+ *
18143
+ * Its absence was not cosmetic. `FaceRecognizer` needs the probe's model id
18144
+ * to use `face-matcher`'s same-model gate (`sample.modelId !== probe.modelId`);
18145
+ * with nothing on the wire it took the id from the GALLERY, so the gate
18146
+ * compared the gallery against itself and could never fire. That turned the
18147
+ * one guard protecting the face feature space into a tautology, and it is
18148
+ * why swapping `face-embedding`'s model would NOT have stranded the enrolled
18149
+ * gallery as the catalog comment predicts — it would have scored the new
18150
+ * model's vectors against the old model's samples in one cosine space.
18151
+ */
18152
+ embeddingModelId: string().optional(),
18123
18153
  label: string().optional(),
18124
18154
  /**
18125
18155
  * The tier `label` occupies, copied VERBATIM from the producing step's
@@ -18995,6 +19025,12 @@ var CameraStatusSchema = object({
18995
19025
  /** Unix timestamp (ms) when this snapshot was composed server-side. */
18996
19026
  fetchedAt: number()
18997
19027
  });
19028
+ var InferenceDeviceExclusionReasonSchema = _enum([
19029
+ "disabled",
19030
+ "unavailable",
19031
+ "cannot-host-camera-root",
19032
+ "accelerator-preferred"
19033
+ ]);
18998
19034
  var NodeInferenceDeviceSchema = object({
18999
19035
  /** Stable per-node device key, e.g. `openvino:npu`, `edgetpu:usb`, `cpu`. */
19000
19036
  key: string(),
@@ -19025,7 +19061,17 @@ var NodeInferenceDeviceSchema = object({
19025
19061
  * available per format; this is the stored selection that becomes the
19026
19062
  * default for EVERY camera landing on this accelerator.
19027
19063
  */
19028
- steps: record(string(), DeviceStepConfigSchema).optional()
19064
+ steps: record(string(), DeviceStepConfigSchema).optional(),
19065
+ /**
19066
+ * `null` when the device IS a camera-root candidate on this node; otherwise
19067
+ * the reason the dispatcher drops it. Computed by the SAME
19068
+ * `resolveInferenceDeviceEligibility` the dispatcher runs, so this view can
19069
+ * never disagree with the election — deriving it in the UI from
19070
+ * `enabled`/`available` would silently miss `cannot-host-camera-root` (needs
19071
+ * the node's model catalog) and `accelerator-preferred` (needs the node-wide
19072
+ * "an accelerator is serving" predicate).
19073
+ */
19074
+ exclusion: InferenceDeviceExclusionReasonSchema.nullable()
19029
19075
  });
19030
19076
  var NodeInferenceDevicesSchema = object({
19031
19077
  nodeId: string(),
@@ -19584,6 +19630,7 @@ var snapshotCapability = {
19584
19630
  isBattery: boolean(),
19585
19631
  reason: _enum([
19586
19632
  "disabled",
19633
+ "offline",
19587
19634
  "sleeping",
19588
19635
  "unreachable",
19589
19636
  "waking"
@@ -19673,9 +19720,17 @@ targets: array(object({
19673
19720
  sleeping: boolean(),
19674
19721
  /** Current device state rendered over the cached frame. State images
19675
19722
  * remain authoritative even when their photographic background is
19676
- * old; null means the link must carry a current camera frame. */
19723
+ * old; null means the link must carry a current camera frame.
19724
+ *
19725
+ * **This is the ONLY source a surface may derive an inactive tile
19726
+ * from.** A client that reads a device's `online`/`disabled` flags and
19727
+ * draws its own verdict will disagree with the pixels it is painting
19728
+ * — the frame comes from here, and so must the sentence over it
19729
+ * (D190). Pair with `ageMs` for the age of the photograph behind the
19730
+ * state. */
19677
19731
  stateReason: _enum([
19678
19732
  "disabled",
19733
+ "offline",
19679
19734
  "sleeping",
19680
19735
  "unreachable",
19681
19736
  "waking"
@@ -24014,7 +24069,12 @@ var ListResultSchema = object({
24014
24069
  probedAt: number()
24015
24070
  });
24016
24071
  var PreferredSchema = LocalInterfaceSchema.nullable();
24017
- var GetConnectionEndpointsResultSchema = object({ endpoints: array(object({
24072
+ /**
24073
+ * Candidate base URL for the SDK to race on connect. Order matters —
24074
+ * the SDK should attempt these top-to-bottom with a short per-candidate
24075
+ * timeout (e.g. 1500ms) and cache the winner for the session.
24076
+ */
24077
+ var ConnectionEndpointSchema = object({
24018
24078
  /** Operator-facing label (e.g. "LAN — en0", "Public tunnel"). */
24019
24079
  label: string(),
24020
24080
  /** Fully-formed base URL with scheme + host + port. */
@@ -24057,7 +24117,42 @@ var GetConnectionEndpointsResultSchema = object({ endpoints: array(object({
24057
24117
  * ordering between polls.
24058
24118
  */
24059
24119
  priority: number()
24060
- })).readonly() });
24120
+ });
24121
+ /**
24122
+ * Where the advertised local port came from. Ordered most → least
24123
+ * authoritative, and the whole point of returning it: a client must be able to
24124
+ * tell a FACT about the hub's socket from an echo of its own guess.
24125
+ */
24126
+ var LocalPortSourceEnum = _enum([
24127
+ "server-config",
24128
+ "server-env",
24129
+ "caller-hint",
24130
+ "default"
24131
+ ]);
24132
+ /** The port every LAN/loopback `baseUrl` in the same result was built with. */
24133
+ var AdvertisedLocalPortSchema = object({
24134
+ port: number().int().min(1).max(65535),
24135
+ source: LocalPortSourceEnum
24136
+ });
24137
+ var GetConnectionEndpointsResultSchema = object({
24138
+ endpoints: array(ConnectionEndpointSchema).readonly(),
24139
+ /**
24140
+ * The port the hub built the LAN/loopback URLs with, and where that number
24141
+ * came from.
24142
+ *
24143
+ * Returned rather than merely applied, because "the URL is right" and "the
24144
+ * client can KNOW the URL is right" are different properties. A client that
24145
+ * only sees a corrected URL cannot distinguish a hub that fixed the port from
24146
+ * a hub that echoed the port the client sent, so it cannot decide whether to
24147
+ * race the candidate or discard it. With `source` it can: anything but
24148
+ * `caller-hint` is the hub's own socket.
24149
+ *
24150
+ * Absent on hubs predating this field — a client that finds it missing is
24151
+ * talking to an echoing hub and must degrade exactly as it does for
24152
+ * `caller-hint`.
24153
+ */
24154
+ localPort: AdvertisedLocalPortSchema
24155
+ });
24061
24156
  /**
24062
24157
  * The chosen outbound endpoint for notification artifacts. `baseUrl: null` =
24063
24158
  * AUTO (resolved from the candidate ranking at send time); `resolved` reports
@@ -24078,8 +24173,13 @@ var AllowedAddressesSchema = object({
24078
24173
  */
24079
24174
  addresses: array(string()).readonly() });
24080
24175
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
24081
- /** Local hub HTTP port to use in base URLs. */
24082
- port: number().int().min(1).max(65535),
24176
+ /**
24177
+ * LEGACY HINT — do not send from new code. Kept optional so clients
24178
+ * written against the echoing contract keep working; the hub uses it
24179
+ * only when it cannot read its own port, and says so via
24180
+ * `localPort.source === 'caller-hint'`.
24181
+ */
24182
+ port: number().int().min(1).max(65535).optional(),
24083
24183
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
24084
24184
  * candidate. Default `true`. */
24085
24185
  includeLoopback: boolean().optional(),
@@ -37265,7 +37365,6 @@ var NC_AUDIO_DEFAULTS = {
37265
37365
  hitPercent: 60,
37266
37366
  samplingSeconds: 10
37267
37367
  };
37268
- new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
37269
37368
  new Set(["devices", "classes"]);
37270
37369
  NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
37271
37370
  /**
@@ -37499,6 +37598,39 @@ TimelapseRuleInputSchema.extend({
37499
37598
  createdAt: number(),
37500
37599
  updatedAt: number()
37501
37600
  });
37601
+ Object.freeze(Object.fromEntries([{
37602
+ stepId: "face-embedding",
37603
+ label: "Face recognition model",
37604
+ defaultModelId: "arcface-r100",
37605
+ indexName: "the enrolled face gallery",
37606
+ options: [
37607
+ {
37608
+ id: "arcface-r100",
37609
+ label: "ArcFace (ResNet34)"
37610
+ },
37611
+ {
37612
+ id: "auraface-r100",
37613
+ label: "AuraFace R100"
37614
+ },
37615
+ {
37616
+ id: "inception-resnet-v1",
37617
+ label: "Inception ResNet V1"
37618
+ }
37619
+ ]
37620
+ }, {
37621
+ stepId: "clip-embedding",
37622
+ label: "Semantic search model",
37623
+ defaultModelId: "mobileclip-s1",
37624
+ indexName: "the object semantic-search index",
37625
+ options: [{
37626
+ id: "mobileclip-s1",
37627
+ label: "MobileCLIP S1"
37628
+ }, {
37629
+ id: "mobileclip-s2",
37630
+ label: "MobileCLIP S2"
37631
+ }]
37632
+ }].map((s) => [s.stepId, s.defaultModelId])));
37633
+ string().min(1);
37502
37634
  object({
37503
37635
  /**
37504
37636
  * Fraction of the box's own size added on EACH side before cutting.
@@ -37519,10 +37651,21 @@ object({
37519
37651
  */
37520
37652
  square: boolean()
37521
37653
  });
37522
- ({
37654
+ /**
37655
+ * The convention in force when nobody has configured one — byte-for-byte the
37656
+ * behaviour of the pre-unification LIVE path (`DETAIL_CROP_PADDING_RATIO`).
37657
+ */
37658
+ var DEFAULT_DETAIL_CROP_CONVENTION = {
37523
37659
  paddingRatio: .15,
37524
37660
  square: false
37525
- }).paddingRatio;
37661
+ };
37662
+ Object.freeze({
37663
+ x: 0,
37664
+ y: 0,
37665
+ w: 1,
37666
+ h: 1
37667
+ });
37668
+ DEFAULT_DETAIL_CROP_CONVENTION.paddingRatio;
37526
37669
  /**
37527
37670
  * WHICH delivered frames the decode worker retains a native copy of.
37528
37671
  *
@@ -44134,6 +44277,38 @@ var TerminalSessionManager = class {
44134
44277
  * (`node-pty`) and should update via `camstack deploy` independently of the
44135
44278
  * framework, exactly like the node-av decoder addon.
44136
44279
  */
44280
+ /**
44281
+ * Default argv for the Glances profile — deliberately EMPTY.
44282
+ *
44283
+ * Measured on the hub (2026-08-21, glances 4.5.6, embedded Python, a real pty
44284
+ * at the terminal camera's own 120x40 grid, CPU sampled from
44285
+ * /proc/<pid>/{utime,stime} so the number is the process's own and not the
44286
+ * box's contention):
44287
+ *
44288
+ * argv CPU (of one core)
44289
+ * (none — this default) 0.8 %
44290
+ * -t 3 0.7 %
44291
+ * -t 5 0.6 %
44292
+ * --disable-plugin processlist 0.7 %
44293
+ * --disable-plugin containers 0.9 %
44294
+ * --disable-plugin sensors 0.8 %
44295
+ * --disable-process 0.8 %
44296
+ * -t 3 --disable-plugin processlist,containers,sensors 0.5 %
44297
+ * -t 5 --disable-plugin processlist,containers,sensors 0.4 %
44298
+ *
44299
+ * Startup costs 0.19 core-seconds; steady state is 1.0 % of one core at 36 MB
44300
+ * RSS. The whole tuning range is worth ~0.4 % of ONE core out of 20 — below the
44301
+ * noise floor of a box that idles at 2.6 cores — while every entry that buys
44302
+ * something removes a panel the operator opened the camera to read. `-t` is
44303
+ * additionally counter-productive here: the terminal camera renders at 2 FPS
44304
+ * and glances already repaints every 2 s, so slowing glances only makes the
44305
+ * camera show stale numbers.
44306
+ *
44307
+ * So the default ships empty and the knob stays in front of the operator.
44308
+ * Anyone who does want to trade panels for CPU has the numbers above.
44309
+ */
44310
+ var DEFAULT_GLANCES_ARGS = [];
44311
+ 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.";
44137
44312
  var DEFAULTS = {
44138
44313
  btmPath: "",
44139
44314
  btmEnabled: true,
@@ -44143,7 +44318,7 @@ var DEFAULTS = {
44143
44318
  topArgs: [],
44144
44319
  glancesEnabled: true,
44145
44320
  glancesPath: "",
44146
- glancesArgs: [],
44321
+ glancesArgs: DEFAULT_GLANCES_ARGS,
44147
44322
  allowShell: false,
44148
44323
  shellPath: "",
44149
44324
  maxSessions: 4,
@@ -44530,85 +44705,13 @@ var TerminalAddon = class extends BaseAddon {
44530
44705
  return this.instanceInfo(instance);
44531
44706
  }
44532
44707
  globalSettingsSchema() {
44533
- return this.schema({ sections: [{
44534
- id: "terminal",
44535
- title: "Terminal",
44536
- description: "Interactive terminal sessions in the Admin UI. Only pre-declared profiles can be opened; a free-form command is never accepted.",
44537
- columns: 2,
44538
- fields: [
44539
- this.field({
44540
- type: "boolean",
44541
- key: "btmEnabled",
44542
- label: "Enable BTM camera",
44543
- default: true,
44544
- perNode: true
44545
- }),
44546
- this.field({
44547
- type: "text",
44548
- key: "btmPath",
44549
- label: "BTM binary",
44550
- description: "Path to the `btm` (bottom) executable. Leave empty to resolve from PATH.",
44551
- placeholder: "btm",
44552
- default: "",
44553
- perNode: true
44554
- }),
44555
- this.field({
44556
- type: "tags",
44557
- key: "btmArgs",
44558
- label: "BTM arguments",
44559
- description: "Exact arguments passed to btm.",
44560
- default: [],
44561
- perNode: true
44562
- }),
44563
- this.field({
44564
- type: "boolean",
44565
- key: "topEnabled",
44566
- label: "Enable Top camera",
44567
- default: true,
44568
- perNode: true
44569
- }),
44570
- this.field({
44571
- type: "text",
44572
- key: "topPath",
44573
- label: "Top binary",
44574
- placeholder: "top",
44575
- default: "",
44576
- perNode: true
44577
- }),
44578
- this.field({
44579
- type: "tags",
44580
- key: "topArgs",
44581
- label: "Top arguments",
44582
- description: "Exact arguments passed to top.",
44583
- default: [],
44584
- perNode: true
44585
- }),
44586
- this.field({
44587
- type: "boolean",
44588
- key: "glancesEnabled",
44589
- label: "Enable Glances camera",
44590
- description: "Glances is installed automatically into CamStack embedded Python.",
44591
- default: true,
44592
- perNode: true
44593
- }),
44594
- this.field({
44595
- type: "text",
44596
- key: "glancesPath",
44597
- label: "Glances binary override",
44598
- description: "Optional executable override. Empty uses the automatically managed Python package.",
44599
- placeholder: "glances",
44600
- default: "",
44601
- perNode: true
44602
- }),
44603
- this.field({
44604
- type: "tags",
44605
- key: "glancesArgs",
44606
- label: "Glances arguments",
44607
- description: "Exact arguments passed to Glances.",
44608
- default: [],
44609
- perNode: true
44610
- }),
44611
- this.field({
44708
+ return this.schema({ sections: [
44709
+ {
44710
+ id: "terminal",
44711
+ title: "Terminal",
44712
+ 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.",
44713
+ columns: 2,
44714
+ fields: [this.field({
44612
44715
  type: "number",
44613
44716
  key: "maxSessions",
44614
44717
  label: "Max concurrent sessions",
@@ -44617,16 +44720,122 @@ var TerminalAddon = class extends BaseAddon {
44617
44720
  step: 1,
44618
44721
  default: 4,
44619
44722
  perNode: true
44620
- }),
44621
- this.field({
44723
+ })]
44724
+ },
44725
+ {
44726
+ id: "terminal-btm",
44727
+ title: "BTM camera",
44728
+ description: "bottom (btm) — CPU, memory, network and process monitor.",
44729
+ columns: 2,
44730
+ fields: [
44731
+ this.field({
44732
+ type: "boolean",
44733
+ key: "btmEnabled",
44734
+ label: "Enabled",
44735
+ description: "Offer the BTM profile on this node.",
44736
+ default: true,
44737
+ perNode: true
44738
+ }),
44739
+ this.field({
44740
+ type: "text",
44741
+ key: "btmPath",
44742
+ label: "Binary",
44743
+ description: "Path to the `btm` (bottom) executable. Leave empty to resolve from PATH.",
44744
+ placeholder: "btm",
44745
+ default: "",
44746
+ perNode: true
44747
+ }),
44748
+ this.field({
44749
+ type: "tags",
44750
+ key: "btmArgs",
44751
+ label: "Arguments",
44752
+ description: "Exact arguments passed to btm. One tag per argv entry.",
44753
+ default: [],
44754
+ perNode: true,
44755
+ span: 2
44756
+ })
44757
+ ]
44758
+ },
44759
+ {
44760
+ id: "terminal-top",
44761
+ title: "Top camera",
44762
+ description: "The operating system process and resource monitor.",
44763
+ columns: 2,
44764
+ fields: [
44765
+ this.field({
44766
+ type: "boolean",
44767
+ key: "topEnabled",
44768
+ label: "Enabled",
44769
+ description: "Offer the Top profile on this node.",
44770
+ default: true,
44771
+ perNode: true
44772
+ }),
44773
+ this.field({
44774
+ type: "text",
44775
+ key: "topPath",
44776
+ label: "Binary",
44777
+ placeholder: "top",
44778
+ default: "",
44779
+ perNode: true
44780
+ }),
44781
+ this.field({
44782
+ type: "tags",
44783
+ key: "topArgs",
44784
+ label: "Arguments",
44785
+ description: "Exact arguments passed to top. One tag per argv entry.",
44786
+ default: [],
44787
+ perNode: true,
44788
+ span: 2
44789
+ })
44790
+ ]
44791
+ },
44792
+ {
44793
+ id: "terminal-glances",
44794
+ title: "Glances camera",
44795
+ 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.",
44796
+ columns: 2,
44797
+ fields: [
44798
+ this.field({
44799
+ type: "boolean",
44800
+ key: "glancesEnabled",
44801
+ label: "Enabled",
44802
+ description: "Offer the Glances profile on this node.",
44803
+ default: true,
44804
+ perNode: true
44805
+ }),
44806
+ this.field({
44807
+ type: "text",
44808
+ key: "glancesPath",
44809
+ label: "Binary override",
44810
+ description: "Optional executable override. Empty uses the automatically managed Python package.",
44811
+ placeholder: "glances",
44812
+ default: "",
44813
+ perNode: true
44814
+ }),
44815
+ this.field({
44816
+ type: "tags",
44817
+ key: "glancesArgs",
44818
+ label: "Arguments",
44819
+ description: GLANCES_ARGS_DESCRIPTION,
44820
+ default: [...DEFAULT_GLANCES_ARGS],
44821
+ perNode: true,
44822
+ span: 2
44823
+ })
44824
+ ]
44825
+ },
44826
+ {
44827
+ id: "terminal-shell",
44828
+ title: "Interactive shell",
44829
+ 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.",
44830
+ columns: 2,
44831
+ fields: [this.field({
44622
44832
  type: "boolean",
44623
44833
  key: "allowShell",
44624
44834
  label: "Allow interactive shell",
44625
- description: "Adds a \"Shell\" profile that grants command execution as the server user. Off by default.",
44835
+ description: "Adds a \"Shell\" profile on this node.",
44626
44836
  default: false,
44627
44837
  perNode: true
44628
- }),
44629
- this.field({
44838
+ }), this.field({
44630
44839
  type: "text",
44631
44840
  key: "shellPath",
44632
44841
  label: "Shell binary",
@@ -44634,89 +44843,90 @@ var TerminalAddon = class extends BaseAddon {
44634
44843
  placeholder: "/bin/bash",
44635
44844
  default: "",
44636
44845
  perNode: true
44637
- })
44638
- ]
44639
- }, {
44640
- id: "terminal-profiles",
44641
- title: "Custom profiles",
44642
- 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.",
44643
- columns: 1,
44644
- fields: [this.field({
44645
- type: "editable-array",
44646
- key: "customProfiles",
44647
- label: "Profiles",
44648
- perNode: true,
44649
- default: [],
44650
- maxRows: 32,
44651
- addLabel: "Add profile",
44652
- emptyMessage: "No custom profiles configured.",
44653
- rowTitleTemplate: "{label} — {executable}",
44654
- defaultItem: {
44655
- enabled: true,
44656
- profileId: "",
44657
- label: "",
44658
- description: "",
44659
- executable: "",
44660
- args: [],
44661
- cwd: "",
44662
- environment: []
44663
- },
44664
- itemFields: [
44665
- {
44666
- type: "boolean",
44667
- key: "enabled",
44668
- label: "Enabled",
44669
- default: true
44670
- },
44671
- {
44672
- type: "text",
44673
- key: "profileId",
44674
- label: "Profile ID",
44675
- description: "Stable lowercase slug, for example gpu-monitor.",
44676
- required: true
44677
- },
44678
- {
44679
- type: "text",
44680
- key: "label",
44681
- label: "Display name",
44682
- required: true
44683
- },
44684
- {
44685
- type: "textarea",
44686
- key: "description",
44687
- label: "Description",
44688
- rows: 2
44689
- },
44690
- {
44691
- type: "text",
44692
- key: "executable",
44693
- label: "Executable",
44694
- description: "Absolute path or a binary resolved from PATH.",
44695
- required: true
44696
- },
44697
- {
44698
- type: "tags",
44699
- key: "args",
44700
- label: "Arguments",
44701
- description: "One exact process argument per tag.",
44702
- maxTags: 64
44703
- },
44704
- {
44705
- type: "text",
44706
- key: "cwd",
44707
- label: "Working directory",
44708
- description: "Optional working directory for this profile."
44846
+ })]
44847
+ },
44848
+ {
44849
+ id: "terminal-profiles",
44850
+ title: "Custom profiles",
44851
+ 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.",
44852
+ columns: 1,
44853
+ fields: [this.field({
44854
+ type: "editable-array",
44855
+ key: "customProfiles",
44856
+ label: "Profiles",
44857
+ perNode: true,
44858
+ default: [],
44859
+ maxRows: 32,
44860
+ addLabel: "Add profile",
44861
+ emptyMessage: "No custom profiles configured.",
44862
+ rowTitleTemplate: "{label} — {executable}",
44863
+ defaultItem: {
44864
+ enabled: true,
44865
+ profileId: "",
44866
+ label: "",
44867
+ description: "",
44868
+ executable: "",
44869
+ args: [],
44870
+ cwd: "",
44871
+ environment: []
44709
44872
  },
44710
- {
44711
- type: "tags",
44712
- key: "environment",
44713
- label: "Environment",
44714
- description: "Optional NAME=value entries merged into the process environment.",
44715
- maxTags: 64
44716
- }
44717
- ]
44718
- })]
44719
- }] });
44873
+ itemFields: [
44874
+ {
44875
+ type: "boolean",
44876
+ key: "enabled",
44877
+ label: "Enabled",
44878
+ default: true
44879
+ },
44880
+ {
44881
+ type: "text",
44882
+ key: "profileId",
44883
+ label: "Profile ID",
44884
+ description: "Stable lowercase slug, for example gpu-monitor.",
44885
+ required: true
44886
+ },
44887
+ {
44888
+ type: "text",
44889
+ key: "label",
44890
+ label: "Display name",
44891
+ required: true
44892
+ },
44893
+ {
44894
+ type: "textarea",
44895
+ key: "description",
44896
+ label: "Description",
44897
+ rows: 2
44898
+ },
44899
+ {
44900
+ type: "text",
44901
+ key: "executable",
44902
+ label: "Executable",
44903
+ description: "Absolute path or a binary resolved from PATH.",
44904
+ required: true
44905
+ },
44906
+ {
44907
+ type: "tags",
44908
+ key: "args",
44909
+ label: "Arguments",
44910
+ description: "One exact process argument per tag.",
44911
+ maxTags: 64
44912
+ },
44913
+ {
44914
+ type: "text",
44915
+ key: "cwd",
44916
+ label: "Working directory",
44917
+ description: "Optional working directory for this profile."
44918
+ },
44919
+ {
44920
+ type: "tags",
44921
+ key: "environment",
44922
+ label: "Environment",
44923
+ description: "Optional NAME=value entries merged into the process environment.",
44924
+ maxTags: 64
44925
+ }
44926
+ ]
44927
+ })]
44928
+ }
44929
+ ] });
44720
44930
  }
44721
44931
  };
44722
44932
  //#endregion