@camstack/system 1.2.436 → 1.2.438

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 (96) hide show
  1. package/dist/addon-runner.js +64 -12
  2. package/dist/addon-runner.mjs +65 -13
  3. package/dist/{agent-https-B8lKYVtU.mjs → agent-https-BPfHrI4c.mjs} +3 -3
  4. package/dist/auth/login-session-manager.d.ts +2 -0
  5. package/dist/auth/session-revocation-bus.d.ts +32 -0
  6. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  7. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  9. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  10. package/dist/builtins/alerts/alerts.addon.js +7 -1
  11. package/dist/builtins/alerts/alerts.addon.mjs +7 -1
  12. package/dist/builtins/autotrack/index.js +1 -1
  13. package/dist/builtins/autotrack/index.mjs +1 -1
  14. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
  15. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
  16. package/dist/builtins/calendars/calendars.addon.js +1 -1
  17. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  18. package/dist/builtins/camera-grid/index.js +9 -1
  19. package/dist/builtins/camera-grid/index.mjs +9 -1
  20. package/dist/builtins/composer/composer.addon.js +2 -2
  21. package/dist/builtins/composer/composer.addon.mjs +2 -2
  22. package/dist/builtins/console-logging/index.js +12 -5
  23. package/dist/builtins/console-logging/index.mjs +12 -5
  24. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  25. package/dist/builtins/core-blocks/core-blocks.addon.mjs +5 -5
  26. package/dist/builtins/device-manager/device-manager.addon.d.ts +5 -0
  27. package/dist/builtins/device-manager/device-manager.addon.js +702 -28
  28. package/dist/builtins/device-manager/device-manager.addon.mjs +702 -28
  29. package/dist/builtins/device-manager/device-meta-types.d.ts +4 -0
  30. package/dist/builtins/device-manager/device-provider-context.d.ts +9 -0
  31. package/dist/builtins/device-manager/device-removal-hooks.d.ts +103 -0
  32. package/dist/builtins/device-manager/device-row-store.d.ts +1 -0
  33. package/dist/builtins/device-manager/device-tombstone-store.d.ts +58 -0
  34. package/dist/builtins/device-manager/removal-cleanup.d.ts +50 -0
  35. package/dist/builtins/hub-forwarder/index.js +1 -1
  36. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  37. package/dist/builtins/keychain/index.js +37 -4
  38. package/dist/builtins/keychain/index.mjs +37 -4
  39. package/dist/builtins/keychain/keychain-service.d.ts +14 -0
  40. package/dist/builtins/keychain/keychain-store.d.ts +1 -1
  41. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  42. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  43. package/dist/builtins/local-auth/local-auth.addon.js +53 -12
  44. package/dist/builtins/local-auth/local-auth.addon.mjs +53 -13
  45. package/dist/builtins/local-network/local-network.addon.js +10 -1
  46. package/dist/builtins/local-network/local-network.addon.mjs +13 -4
  47. package/dist/builtins/loki-logging/index.js +28 -5
  48. package/dist/builtins/loki-logging/index.mjs +28 -5
  49. package/dist/builtins/native-metrics/native-metrics.addon.js +11 -1
  50. package/dist/builtins/native-metrics/native-metrics.addon.mjs +20 -10
  51. package/dist/builtins/platform-probe/index.js +1 -1
  52. package/dist/builtins/platform-probe/index.mjs +8 -8
  53. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  54. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  55. package/dist/builtins/snapshot/index.js +11 -1
  56. package/dist/builtins/snapshot/index.mjs +11 -1
  57. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +4 -1
  58. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +4 -1
  59. package/dist/builtins/sqlite-storage/index.js +1 -1
  60. package/dist/builtins/sqlite-storage/index.mjs +1 -1
  61. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +3 -3
  62. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +3 -3
  63. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  64. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  65. package/dist/builtins/system-config/system-config.addon.d.ts +9 -0
  66. package/dist/builtins/system-config/system-config.addon.js +135 -72
  67. package/dist/builtins/system-config/system-config.addon.mjs +135 -73
  68. package/dist/builtins/winston-logging/index.js +14 -5
  69. package/dist/builtins/winston-logging/index.mjs +14 -5
  70. package/dist/{composition-sources-BAOlCu4j.js → composition-sources-DI3Z83vG.js} +1 -1
  71. package/dist/{composition-sources-gN_BWUKp.mjs → composition-sources-DpHGCjdL.mjs} +1 -1
  72. package/dist/{constants-BFpowodq.mjs → constants-DQK705Sb.mjs} +1 -1
  73. package/dist/{constants-GENKqk9k.js → constants-wkcn5Ij3.js} +1 -1
  74. package/dist/{device-ca-CuWuRW9N.js → device-ca-BSvkXqyI.js} +1 -1
  75. package/dist/{device-ca-BNM5ai4t.mjs → device-ca-DvIZjXIl.mjs} +1 -1
  76. package/dist/{dist-DjD2M79P.mjs → dist-Dc8GKC5H.mjs} +301 -6
  77. package/dist/{dist-DVe0Nsp8.js → dist-GgG-7amw.js} +348 -5
  78. package/dist/file-host-worker.js +1 -1
  79. package/dist/file-host-worker.mjs +1 -1
  80. package/dist/{hub-mdns-ciao-DJRwtlA8.mjs → hub-mdns-ciao-DMlQTdPU.mjs} +1 -1
  81. package/dist/i18n/catalog-registry.d.ts +35 -0
  82. package/dist/i18n/index.d.ts +2 -0
  83. package/dist/index.d.ts +3 -0
  84. package/dist/index.js +666 -21
  85. package/dist/index.mjs +676 -38
  86. package/dist/kernel/gc-census.d.ts +18 -1
  87. package/dist/kernel/settings-view-types.d.ts +2 -0
  88. package/dist/kernel/transport/local-transport.d.ts +3 -0
  89. package/dist/kernel/transport/peer-answered-error.d.ts +5 -1
  90. package/dist/{restore-archive-D4h904rb.js → restore-archive-CTRz8v9o.js} +1 -1
  91. package/dist/{restore-archive-BxmP1ChO.mjs → restore-archive-DPg9duVY.mjs} +1 -1
  92. package/dist/{retired-settings-keys-CYP5IZJ5.js → retired-settings-keys-Dr_4lfKu.js} +1 -1
  93. package/dist/{retired-settings-keys-CQaknvx1.mjs → retired-settings-keys-Is0zO8gX.mjs} +1 -1
  94. package/dist/{wal-maintenance-CaDsrJM_.mjs → wal-maintenance-Bx3lcVA7.mjs} +0 -0
  95. package/dist/{wal-maintenance-Br60onAQ.js → wal-maintenance-mjB2i-VR.js} +0 -0
  96. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
1
  import { i as __require, o as __toESM, t as __commonJSMin } from "../../chunk-CNf5ZN-e.mjs";
2
- import { At as deviceCredentialsCapability, C as DEVICE_ENROLLMENT_TTL_MS, G as ScopedTokenSchema, Gt as formatDeviceRevoker, K as SsoBridgeClaimsSchema, Qn as userManagementCapability, Z as UserRecordSchema, a as ApiKeyRecordSchema, ct as buildDeviceSignatureMessage, nt as authProviderCapability, st as buildDeviceEnrollmentLink, w as DEVICE_NONCE_TTL_MS, xr as BaseAddon } from "../../dist-DjD2M79P.mjs";
2
+ import { E as DEVICE_NONCE_TTL_MS, J as ScopedTokenSchema, Or as BaseAddon, Pt as deviceCredentialsCapability, T as DEVICE_ENROLLMENT_TTL_MS, X as SsoBridgeClaimsSchema, Yt as formatDeviceRevoker, a as ApiKeyRecordSchema, ar as userManagementCapability, dt as buildDeviceEnrollmentLink, ft as buildDeviceSignatureMessage, ot as authProviderCapability, tt as UserRecordSchema, vi as userError } from "../../dist-Dc8GKC5H.mjs";
3
3
  import { t as require_safe_buffer } from "../../safe-buffer-B1OjL1I9.mjs";
4
4
  import { t as require_ms } from "../../ms-CgH2OFlD.mjs";
5
- import { _ as setProcessJwtSigningKey, a as importDevicePublicKey, c as rebuildDeviceCaCertificate, d as JWT_SIGNING_KEY_REF, g as processJwtSigningKey, h as loadJwtSigningKey, i as generateDeviceCa, l as verifyDeviceLeaf, m as getProcessJwtSigningKeyState, n as DEVICE_CA_KEY_REF, o as issueDeviceLeaf, r as DEVICE_CA_SETTINGS_SECTION, s as loadDeviceCa, t as DEVICE_CA_FIELD, u as verifyDeviceSignature } from "../../device-ca-BNM5ai4t.mjs";
5
+ import { _ as setProcessJwtSigningKey, a as importDevicePublicKey, c as rebuildDeviceCaCertificate, d as JWT_SIGNING_KEY_REF, g as processJwtSigningKey, h as loadJwtSigningKey, i as generateDeviceCa, l as verifyDeviceLeaf, m as getProcessJwtSigningKeyState, n as DEVICE_CA_KEY_REF, o as issueDeviceLeaf, r as DEVICE_CA_SETTINGS_SECTION, s as loadDeviceCa, t as DEVICE_CA_FIELD, u as verifyDeviceSignature } from "../../device-ca-DvIZjXIl.mjs";
6
6
  import * as crypto$1 from "node:crypto";
7
7
  import { X509Certificate, createHash, randomUUID, timingSafeEqual } from "node:crypto";
8
8
  import nodeCrypto from "crypto";
@@ -6448,6 +6448,33 @@ var ScopedTokenManager = class {
6448
6448
  }
6449
6449
  };
6450
6450
  //#endregion
6451
+ //#region src/auth/session-revocation-bus.ts
6452
+ var BUS_KEY = Symbol.for("camstack.login-sessions.revocation-bus");
6453
+ function listeners() {
6454
+ const existing = Reflect.get(globalThis, BUS_KEY);
6455
+ if (existing instanceof Set) return existing;
6456
+ const created = /* @__PURE__ */ new Set();
6457
+ Reflect.set(globalThis, BUS_KEY, created);
6458
+ return created;
6459
+ }
6460
+ /** Subscribe; returns the unsubscribe. */
6461
+ function onSessionsRevoked(listener) {
6462
+ const set = listeners();
6463
+ set.add(listener);
6464
+ return () => {
6465
+ set.delete(listener);
6466
+ };
6467
+ }
6468
+ /** Announce an ended batch. A listener that throws never reaches the revoke. */
6469
+ function announceSessionsRevoked(event, onListenerError) {
6470
+ if (event.sessionIds.length === 0 && !event.allOfUser) return;
6471
+ for (const listener of [...listeners()]) try {
6472
+ listener(event);
6473
+ } catch (error) {
6474
+ onListenerError?.(error);
6475
+ }
6476
+ }
6477
+ //#endregion
6451
6478
  //#region src/auth/login-session-manager.ts
6452
6479
  /**
6453
6480
  * Login sessions — the server-side record behind every session token.
@@ -6797,6 +6824,7 @@ var LoginSessionManager = class {
6797
6824
  revokedReason: reason
6798
6825
  };
6799
6826
  this.replace(session, updated);
6827
+ this.announce([updated.id], session.userId, false, null, reason);
6800
6828
  await this.persist(updated);
6801
6829
  return true;
6802
6830
  }
@@ -6820,6 +6848,7 @@ var LoginSessionManager = class {
6820
6848
  this.replace(session, updated);
6821
6849
  ended.push(updated);
6822
6850
  }
6851
+ this.announce(ended.map((s) => s.id), userId, true, options.exceptSessionId ?? null, options.reason);
6823
6852
  await this.persistCutoff(userId, at);
6824
6853
  for (const session of ended) await this.persist(session);
6825
6854
  return ended.length;
@@ -6838,6 +6867,7 @@ var LoginSessionManager = class {
6838
6867
  this.replace(session, updated);
6839
6868
  ended.push(updated);
6840
6869
  }
6870
+ this.announce(ended.map((s) => s.id), null, false, null, reason);
6841
6871
  for (const session of ended) await this.persist(session);
6842
6872
  return ended.length;
6843
6873
  }
@@ -6863,6 +6893,16 @@ var LoginSessionManager = class {
6863
6893
  }
6864
6894
  return dead.length;
6865
6895
  }
6896
+ /** Tell the hub which sessions just ended — right after the mirror, before the store write. */
6897
+ announce(sessionIds, userId, allOfUser, exceptSessionId, reason) {
6898
+ announceSessionsRevoked({
6899
+ sessionIds,
6900
+ userId,
6901
+ allOfUser,
6902
+ exceptSessionId,
6903
+ reason
6904
+ }, (error) => this.logger?.error("A session-revocation listener threw — the revoke stands", { meta: { error: error instanceof Error ? error.message : String(error) } }));
6905
+ }
6866
6906
  newRefreshToken() {
6867
6907
  return `${REFRESH_TOKEN_PREFIX}${crypto$1.randomBytes(32).toString("base64url")}`;
6868
6908
  }
@@ -7026,9 +7066,9 @@ var AdminTokenManager = class {
7026
7066
  /** Mint a token for `createdBy`. Refuses an expiry in the past or past 90 days. */
7027
7067
  async create(input) {
7028
7068
  const at = this.now();
7029
- if (!(input.expiresAt > at) || input.expiresAt > at + 7776e6) throw new Error("An admin token must expire within 90 days");
7069
+ if (!(input.expiresAt > at) || input.expiresAt > at + 7776e6) throw userError("system.admin-token-expiry-too-long", void 0, "An admin token must expire within 90 days");
7030
7070
  const name = input.name.trim().slice(0, 80);
7031
- if (name.length === 0) throw new Error("An admin token needs a name");
7071
+ if (name.length === 0) throw userError("system.admin-token-name-required", void 0, "An admin token needs a name");
7032
7072
  const token = `${ADMIN_TOKEN_PREFIX}${crypto$1.randomBytes(32).toString("base64url")}`;
7033
7073
  const stored = {
7034
7074
  id: crypto$1.randomUUID(),
@@ -7417,7 +7457,7 @@ var DeviceCredentialManager = class {
7417
7457
  /** A one-shot code for `userId` (admin path). Returned once; only its hash is kept. */
7418
7458
  startEnrollment(input) {
7419
7459
  this.prune();
7420
- if (this.pendingEnrollments.size >= MAX_PENDING) throw new Error("Too many pending device enrollments — try again shortly");
7460
+ if (this.pendingEnrollments.size >= MAX_PENDING) throw userError("system.device-enrollment-busy", void 0, "Too many pending device enrollments — try again shortly");
7421
7461
  const code = crypto$1.randomBytes(24).toString("base64url");
7422
7462
  const expiresAt = this.now() + DEVICE_ENROLLMENT_TTL_MS;
7423
7463
  this.pendingEnrollments.set(hash(code), {
@@ -7988,7 +8028,7 @@ var UserManager = class {
7988
8028
  return this.storageAccess.getStore();
7989
8029
  }
7990
8030
  async create(input) {
7991
- if (await this.findByUsername(input.username)) throw new Error(`User with username "${input.username}" already exists`);
8031
+ if (await this.findByUsername(input.username)) throw userError("system.user-exists", { username: String(input.username) }, `User with username "${input.username}" already exists`);
7992
8032
  const passwordHash = await this.auth.hashPassword(input.password);
7993
8033
  const now = Date.now();
7994
8034
  const record = {
@@ -8040,7 +8080,7 @@ var UserManager = class {
8040
8080
  }
8041
8081
  async update(id, data) {
8042
8082
  const existing = await this.findById(id);
8043
- if (!existing) throw new Error(`User with id "${id}" not found`);
8083
+ if (!existing) throw userError("system.user-not-found", { id: String(id) }, `User with id "${id}" not found`);
8044
8084
  await this.store.update.mutate({
8045
8085
  collection: USERS_COLLECTION$1,
8046
8086
  id,
@@ -8059,7 +8099,7 @@ var UserManager = class {
8059
8099
  }
8060
8100
  async resetPassword(id, newPassword) {
8061
8101
  const existing = await this.findById(id);
8062
- if (!existing) throw new Error(`User with id "${id}" not found`);
8102
+ if (!existing) throw userError("system.user-not-found", { id: String(id) }, `User with id "${id}" not found`);
8063
8103
  const passwordHash = await this.auth.hashPassword(newPassword);
8064
8104
  await this.store.update.mutate({
8065
8105
  collection: USERS_COLLECTION$1,
@@ -11726,7 +11766,7 @@ var LocalAuthAddon = class extends BaseAddon {
11726
11766
  },
11727
11767
  confirmTotp: async (input) => {
11728
11768
  if (!this.totpManager) throw new Error("TOTP management not available");
11729
- if (!await this.totpManager.confirm(input.userId, input.code)) throw new Error("TOTP confirmation failed — code did not match");
11769
+ if (!await this.totpManager.confirm(input.userId, input.code)) throw userError("auth.totp-confirm-failed", void 0, "TOTP confirmation failed — code did not match");
11730
11770
  return { success: true };
11731
11771
  },
11732
11772
  disableTotp: async (input) => {
@@ -11784,7 +11824,7 @@ var LocalAuthAddon = class extends BaseAddon {
11784
11824
  /** The admin cap (`device-credentials`): list, enroll-by-QR, revoke, rename, settings. */
11785
11825
  buildDeviceCredentialsProvider() {
11786
11826
  const manager = () => {
11787
- if (this.deviceCredentials === null) throw new Error("Device credentials not available");
11827
+ if (this.deviceCredentials === null) throw userError("device.credentials-unavailable", void 0, "Device credentials not available");
11788
11828
  return this.deviceCredentials;
11789
11829
  };
11790
11830
  const hubOrigins = async () => {
@@ -11836,7 +11876,7 @@ var LocalAuthAddon = class extends BaseAddon {
11836
11876
  },
11837
11877
  revokeDeviceCredential: async (input) => {
11838
11878
  const record = manager().get(input.id);
11839
- if (record === void 0) throw new Error(`Device credential not found: ${input.id}`);
11879
+ if (record === void 0) throw userError("device.credential-not-found", { id: input.id }, `Device credential not found: ${input.id}`);
11840
11880
  const revoked = await manager().revoke(input.id, formatDeviceRevoker({
11841
11881
  kind: "admin",
11842
11882
  userId: input.caller.userId
@@ -11907,7 +11947,7 @@ var LocalAuthAddon = class extends BaseAddon {
11907
11947
  return this.loginSessions;
11908
11948
  };
11909
11949
  const devices = () => {
11910
- if (this.deviceCredentials === null) throw new Error("Device credentials not available");
11950
+ if (this.deviceCredentials === null) throw userError("device.credentials-unavailable", void 0, "Device credentials not available");
11911
11951
  return this.deviceCredentials;
11912
11952
  };
11913
11953
  return {
@@ -12061,4 +12101,4 @@ var LocalAuthAddon = class extends BaseAddon {
12061
12101
  }
12062
12102
  };
12063
12103
  //#endregion
12064
- export { LocalAuthAddon, LocalAuthAddon as default, AuthManager as _, attachUserManagementInternal as a, isSettingsReadUnavailable as b, DeviceCredentialManager as c, ADMIN_TOKEN_MAX_LIFETIME_MS as d, ADMIN_TOKEN_PREFIX as f, ScopedTokenManager as g, REFRESH_TOKEN_PREFIX as h, USER_MANAGEMENT_INTERNAL as i, PREVIOUS_LEAF_GRACE_MS as l, LoginSessionManager as m, FIRST_BOOT_ADMIN_USERNAME as n, getUserManagementInternal as o, AdminTokenManager as p, UserManager as r, AuthAuditLog as s, FIRST_BOOT_ADMIN_PASSWORD as t, DeviceCaStore as u, parseTokenExpiry as v, ApiKeyManager as x, SettingsReadUnavailableError as y };
12104
+ export { LocalAuthAddon, LocalAuthAddon as default, ApiKeyManager as S, ScopedTokenManager as _, attachUserManagementInternal as a, SettingsReadUnavailableError as b, DeviceCredentialManager as c, ADMIN_TOKEN_MAX_LIFETIME_MS as d, ADMIN_TOKEN_PREFIX as f, onSessionsRevoked as g, REFRESH_TOKEN_PREFIX as h, USER_MANAGEMENT_INTERNAL as i, PREVIOUS_LEAF_GRACE_MS as l, LoginSessionManager as m, FIRST_BOOT_ADMIN_USERNAME as n, getUserManagementInternal as o, AdminTokenManager as p, UserManager as r, AuthAuditLog as s, FIRST_BOOT_ADMIN_PASSWORD as t, DeviceCaStore as u, AuthManager as v, isSettingsReadUnavailable as x, parseTokenExpiry as y };
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-DVe0Nsp8.js");
3
+ const require_dist = require("../../dist-GgG-7amw.js");
4
4
  const require_hub_mdns_ciao = require("../../hub-mdns-ciao-CBLvq2oj.js");
5
5
  let zod = require("zod");
6
6
  let node_net = require("node:net");
@@ -953,13 +953,16 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
953
953
  return this.schema({ sections: [{
954
954
  id: "local-access",
955
955
  title: "LAN HTTP",
956
+ titleKey: "server:globalSettings.s_local-access.title",
956
957
  description: "Native apps on this Wi-Fi use http:// and skip the private certificate. The tunnel and this admin UI stay on HTTPS. Turn off once a publicly-trusted certificate covers the LAN name.",
958
+ descriptionKey: "server:globalSettings.s_local-access.description",
957
959
  columns: 1,
958
960
  fields: [
959
961
  this.field({
960
962
  type: "boolean",
961
963
  key: "httpEnabled",
962
964
  label: "Enable HTTP on the LAN",
965
+ labelKey: "server:globalSettings.s_local-access.httpEnabled.label",
963
966
  style: "switch",
964
967
  default: true
965
968
  }),
@@ -967,7 +970,9 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
967
970
  type: "number",
968
971
  key: "httpPort",
969
972
  label: "HTTP port",
973
+ labelKey: "server:globalSettings.s_local-access.httpPort.label",
970
974
  description: "Dedicated listener. Must not equal the HTTPS port (4443 by default).",
975
+ descriptionKey: "server:globalSettings.s_local-access.httpPort.description",
971
976
  min: 1,
972
977
  max: 65535,
973
978
  step: 1,
@@ -981,7 +986,9 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
981
986
  type: "boolean",
982
987
  key: "mdnsAdvertise",
983
988
  label: "Announce this hub on the LAN (mDNS)",
989
+ labelKey: "server:globalSettings.s_local-access.mdnsAdvertise.label",
984
990
  description: "Lets `camstack login` find the hub without an address. Advertises only the HTTPS address, never a secret. Needs UDP 5353 multicast to reach the LAN (host networking or a dedicated container IP).",
991
+ descriptionKey: "server:globalSettings.s_local-access.mdnsAdvertise.description",
985
992
  style: "switch",
986
993
  default: true
987
994
  }),
@@ -989,7 +996,9 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
989
996
  type: "text",
990
997
  key: "localHostname",
991
998
  label: "Local hostname",
999
+ labelKey: "server:globalSettings.s_local-access.localHostname.label",
992
1000
  description: "Advertised to the viewer instead of the raw LAN IP, and added to the certificate SAN. Requires this name to resolve on the LAN (router DNS, mDNS, or a hosts file). Leave empty to keep using the IP.",
1001
+ descriptionKey: "server:globalSettings.s_local-access.localHostname.description",
993
1002
  maxLength: 253,
994
1003
  placeholder: "camstack.lan"
995
1004
  })
@@ -1,8 +1,8 @@
1
- import { An as resolveAppConnections, B as NetworkTunnelStartedDataSchema, Cn as pickAppLinkHub, Jt as isAppLinkBase, Ln as resolveNotificationTapAddress, M as HUB_MDNS_SERVICE_TYPE, N as HUB_MDNS_TXT_KEYS, Pn as resolveHubAddressRoles, Zt as isDefaultAppConnectionEndpoint, di as EventCategory, er as validateCustomAddress, l as CAMSTACK_APP_LINK_ORIGIN, pt as checkRoleAddress, sn as localNetworkCapability, xr as BaseAddon } from "../../dist-DjD2M79P.mjs";
2
- import { C as DEFAULT_LAN_HTTP_PORT, O as readLanHttpState, T as applyLanHttp, a as storeUploadedCertificate, b as ensureTlsCert, d as servedHttpsLeafFingerprint, g as readTlsMode, h as readTlsAccessStatus, i as revertToGeneratedCertificate, m as readExtraSans, n as applyCertificateToListener, r as regenerateCertificate, t as createCiaoPublisher, v as writeExtraSans } from "../../hub-mdns-ciao-DJRwtlA8.mjs";
1
+ import { $t as isAppLinkBase, F as HUB_MDNS_SERVICE_TYPE, Fn as resolveAppConnections, Hn as resolveNotificationTapAddress, I as HUB_MDNS_TXT_KEYS, On as pickAppLinkHub, Or as BaseAddon, U as NetworkTunnelStartedDataSchema, _t as checkRoleAddress, fn as localNetworkCapability, l as CAMSTACK_APP_LINK_ORIGIN, nn as isDefaultAppConnectionEndpoint, sr as validateCustomAddress, yi as EventCategory, zn as resolveHubAddressRoles } from "../../dist-Dc8GKC5H.mjs";
2
+ import { C as DEFAULT_LAN_HTTP_PORT, O as readLanHttpState, T as applyLanHttp, a as storeUploadedCertificate, b as ensureTlsCert, d as servedHttpsLeafFingerprint, g as readTlsMode, h as readTlsAccessStatus, i as revertToGeneratedCertificate, m as readExtraSans, n as applyCertificateToListener, r as regenerateCertificate, t as createCiaoPublisher, v as writeExtraSans } from "../../hub-mdns-ciao-DMlQTdPU.mjs";
3
3
  import { z } from "zod";
4
4
  import { isIP } from "node:net";
5
- import * as os from "node:os";
5
+ import * as os$1 from "node:os";
6
6
  import { readFileSync } from "node:fs";
7
7
  //#region src/builtins/local-network/address-book.ts
8
8
  /**
@@ -950,13 +950,16 @@ var LocalNetworkAddon = class extends BaseAddon {
950
950
  return this.schema({ sections: [{
951
951
  id: "local-access",
952
952
  title: "LAN HTTP",
953
+ titleKey: "server:globalSettings.s_local-access.title",
953
954
  description: "Native apps on this Wi-Fi use http:// and skip the private certificate. The tunnel and this admin UI stay on HTTPS. Turn off once a publicly-trusted certificate covers the LAN name.",
955
+ descriptionKey: "server:globalSettings.s_local-access.description",
954
956
  columns: 1,
955
957
  fields: [
956
958
  this.field({
957
959
  type: "boolean",
958
960
  key: "httpEnabled",
959
961
  label: "Enable HTTP on the LAN",
962
+ labelKey: "server:globalSettings.s_local-access.httpEnabled.label",
960
963
  style: "switch",
961
964
  default: true
962
965
  }),
@@ -964,7 +967,9 @@ var LocalNetworkAddon = class extends BaseAddon {
964
967
  type: "number",
965
968
  key: "httpPort",
966
969
  label: "HTTP port",
970
+ labelKey: "server:globalSettings.s_local-access.httpPort.label",
967
971
  description: "Dedicated listener. Must not equal the HTTPS port (4443 by default).",
972
+ descriptionKey: "server:globalSettings.s_local-access.httpPort.description",
968
973
  min: 1,
969
974
  max: 65535,
970
975
  step: 1,
@@ -978,7 +983,9 @@ var LocalNetworkAddon = class extends BaseAddon {
978
983
  type: "boolean",
979
984
  key: "mdnsAdvertise",
980
985
  label: "Announce this hub on the LAN (mDNS)",
986
+ labelKey: "server:globalSettings.s_local-access.mdnsAdvertise.label",
981
987
  description: "Lets `camstack login` find the hub without an address. Advertises only the HTTPS address, never a secret. Needs UDP 5353 multicast to reach the LAN (host networking or a dedicated container IP).",
988
+ descriptionKey: "server:globalSettings.s_local-access.mdnsAdvertise.description",
982
989
  style: "switch",
983
990
  default: true
984
991
  }),
@@ -986,7 +993,9 @@ var LocalNetworkAddon = class extends BaseAddon {
986
993
  type: "text",
987
994
  key: "localHostname",
988
995
  label: "Local hostname",
996
+ labelKey: "server:globalSettings.s_local-access.localHostname.label",
989
997
  description: "Advertised to the viewer instead of the raw LAN IP, and added to the certificate SAN. Requires this name to resolve on the LAN (router DNS, mDNS, or a hosts file). Leave empty to keep using the IP.",
998
+ descriptionKey: "server:globalSettings.s_local-access.localHostname.description",
990
999
  maxLength: 253,
991
1000
  placeholder: "camstack.lan"
992
1001
  })
@@ -1186,7 +1195,7 @@ var LocalNetworkAddon = class extends BaseAddon {
1186
1195
  }
1187
1196
  }
1188
1197
  enumerate() {
1189
- return enumerateOsInterfaces(os.networkInterfaces(), readLinuxIpv6Flags());
1198
+ return enumerateOsInterfaces(os$1.networkInterfaces(), readLinuxIpv6Flags());
1190
1199
  }
1191
1200
  /**
1192
1201
  * Replace rotated IPv6 and drop addresses that no longer exist, without
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DVe0Nsp8.js");
6
+ const require_dist = require("../../dist-GgG-7amw.js");
7
7
  //#region src/builtins/loki-logging/loki-payload.ts
8
8
  /**
9
9
  * Loki rejects a label name that is not a valid Prometheus label
@@ -458,14 +458,18 @@ var LokiLoggingAddon = class extends require_dist.BaseAddon {
458
458
  return this.schema({ sections: [{
459
459
  id: "loki-logging",
460
460
  title: "Loki",
461
+ titleKey: "server:globalSettings.s_loki-logging.title",
461
462
  description: "Ship this node’s logs to a Grafana Loki instance. Independent of the rotated-file and console destinations. Turn it on or off with the Enable/Disable control on this destination — the same one every other destination uses.",
463
+ descriptionKey: "server:globalSettings.s_loki-logging.description",
462
464
  columns: 2,
463
465
  fields: [
464
466
  this.field({
465
467
  type: "text",
466
468
  key: "url",
467
469
  label: "Loki URL",
470
+ labelKey: "server:globalSettings.s_loki-logging.url.label",
468
471
  description: "Base URL, e.g. http://loki:3100 — the /loki/api/v1/push path is added.",
472
+ descriptionKey: "server:globalSettings.s_loki-logging.url.description",
469
473
  placeholder: "http://loki:3100",
470
474
  default: ""
471
475
  }),
@@ -473,23 +477,28 @@ var LokiLoggingAddon = class extends require_dist.BaseAddon {
473
477
  type: "select",
474
478
  key: "level",
475
479
  label: "Minimum level",
480
+ labelKey: "server:globalSettings.s_loki-logging.level.label",
476
481
  default: "info",
477
482
  options: [
478
483
  {
479
484
  value: "debug",
480
- label: "Debug"
485
+ label: "Debug",
486
+ labelKey: "server:globalSettings.s_loki-logging.level.o_debug.label"
481
487
  },
482
488
  {
483
489
  value: "info",
484
- label: "Info"
490
+ label: "Info",
491
+ labelKey: "server:globalSettings.s_loki-logging.level.o_info.label"
485
492
  },
486
493
  {
487
494
  value: "warn",
488
- label: "Warn"
495
+ label: "Warn",
496
+ labelKey: "server:globalSettings.s_loki-logging.level.o_warn.label"
489
497
  },
490
498
  {
491
499
  value: "error",
492
- label: "Error"
500
+ label: "Error",
501
+ labelKey: "server:globalSettings.s_loki-logging.level.o_error.label"
493
502
  }
494
503
  ]
495
504
  }),
@@ -497,14 +506,18 @@ var LokiLoggingAddon = class extends require_dist.BaseAddon {
497
506
  type: "text",
498
507
  key: "job",
499
508
  label: "Job label",
509
+ labelKey: "server:globalSettings.s_loki-logging.job.label",
500
510
  description: "The Loki `job` label. Keep it fixed — labels are indexed.",
511
+ descriptionKey: "server:globalSettings.s_loki-logging.job.description",
501
512
  default: "camstack"
502
513
  }),
503
514
  this.field({
504
515
  type: "text",
505
516
  key: "extraLabels",
506
517
  label: "Extra labels",
518
+ labelKey: "server:globalSettings.s_loki-logging.extraLabels.label",
507
519
  description: "Static `key=value,key2=value2`. Low-cardinality only: per-entry values belong in the line body, and a high-cardinality label will bring Loki down.",
520
+ descriptionKey: "server:globalSettings.s_loki-logging.extraLabels.description",
508
521
  placeholder: "env=home,site=garage",
509
522
  default: ""
510
523
  }),
@@ -512,27 +525,34 @@ var LokiLoggingAddon = class extends require_dist.BaseAddon {
512
525
  type: "text",
513
526
  key: "tenantId",
514
527
  label: "Tenant ID",
528
+ labelKey: "server:globalSettings.s_loki-logging.tenantId.label",
515
529
  description: "Sent as X-Scope-OrgID. Required by Grafana Cloud and multi-tenant Loki.",
530
+ descriptionKey: "server:globalSettings.s_loki-logging.tenantId.description",
516
531
  default: ""
517
532
  }),
518
533
  this.field({
519
534
  type: "text",
520
535
  key: "username",
521
536
  label: "Username",
537
+ labelKey: "server:globalSettings.s_loki-logging.username.label",
522
538
  description: "HTTP basic auth. Leave empty for none.",
539
+ descriptionKey: "server:globalSettings.s_loki-logging.username.description",
523
540
  default: ""
524
541
  }),
525
542
  this.field({
526
543
  type: "password",
527
544
  key: "password",
528
545
  label: "Password",
546
+ labelKey: "server:globalSettings.s_loki-logging.password.label",
529
547
  default: ""
530
548
  }),
531
549
  this.field({
532
550
  type: "number",
533
551
  key: "batchIntervalMs",
534
552
  label: "Flush interval",
553
+ labelKey: "server:globalSettings.s_loki-logging.batchIntervalMs.label",
535
554
  description: "How often a partial batch is pushed.",
555
+ descriptionKey: "server:globalSettings.s_loki-logging.batchIntervalMs.description",
536
556
  min: 250,
537
557
  max: 6e4,
538
558
  step: 250,
@@ -543,7 +563,9 @@ var LokiLoggingAddon = class extends require_dist.BaseAddon {
543
563
  type: "number",
544
564
  key: "batchSize",
545
565
  label: "Batch size",
566
+ labelKey: "server:globalSettings.s_loki-logging.batchSize.label",
546
567
  description: "Push early once this many entries are queued.",
568
+ descriptionKey: "server:globalSettings.s_loki-logging.batchSize.description",
547
569
  min: 1,
548
570
  max: 5e3,
549
571
  step: 10,
@@ -553,6 +575,7 @@ var LokiLoggingAddon = class extends require_dist.BaseAddon {
553
575
  type: "number",
554
576
  key: "timeoutMs",
555
577
  label: "Request timeout",
578
+ labelKey: "server:globalSettings.s_loki-logging.timeoutMs.label",
556
579
  min: 1e3,
557
580
  max: 6e4,
558
581
  step: 500,
@@ -1,4 +1,4 @@
1
- import { ln as logDestinationCapability, xr as BaseAddon } from "../../dist-DjD2M79P.mjs";
1
+ import { Or as BaseAddon, mn as logDestinationCapability } from "../../dist-Dc8GKC5H.mjs";
2
2
  //#region src/builtins/loki-logging/loki-payload.ts
3
3
  /**
4
4
  * Loki rejects a label name that is not a valid Prometheus label
@@ -453,14 +453,18 @@ var LokiLoggingAddon = class extends BaseAddon {
453
453
  return this.schema({ sections: [{
454
454
  id: "loki-logging",
455
455
  title: "Loki",
456
+ titleKey: "server:globalSettings.s_loki-logging.title",
456
457
  description: "Ship this node’s logs to a Grafana Loki instance. Independent of the rotated-file and console destinations. Turn it on or off with the Enable/Disable control on this destination — the same one every other destination uses.",
458
+ descriptionKey: "server:globalSettings.s_loki-logging.description",
457
459
  columns: 2,
458
460
  fields: [
459
461
  this.field({
460
462
  type: "text",
461
463
  key: "url",
462
464
  label: "Loki URL",
465
+ labelKey: "server:globalSettings.s_loki-logging.url.label",
463
466
  description: "Base URL, e.g. http://loki:3100 — the /loki/api/v1/push path is added.",
467
+ descriptionKey: "server:globalSettings.s_loki-logging.url.description",
464
468
  placeholder: "http://loki:3100",
465
469
  default: ""
466
470
  }),
@@ -468,23 +472,28 @@ var LokiLoggingAddon = class extends BaseAddon {
468
472
  type: "select",
469
473
  key: "level",
470
474
  label: "Minimum level",
475
+ labelKey: "server:globalSettings.s_loki-logging.level.label",
471
476
  default: "info",
472
477
  options: [
473
478
  {
474
479
  value: "debug",
475
- label: "Debug"
480
+ label: "Debug",
481
+ labelKey: "server:globalSettings.s_loki-logging.level.o_debug.label"
476
482
  },
477
483
  {
478
484
  value: "info",
479
- label: "Info"
485
+ label: "Info",
486
+ labelKey: "server:globalSettings.s_loki-logging.level.o_info.label"
480
487
  },
481
488
  {
482
489
  value: "warn",
483
- label: "Warn"
490
+ label: "Warn",
491
+ labelKey: "server:globalSettings.s_loki-logging.level.o_warn.label"
484
492
  },
485
493
  {
486
494
  value: "error",
487
- label: "Error"
495
+ label: "Error",
496
+ labelKey: "server:globalSettings.s_loki-logging.level.o_error.label"
488
497
  }
489
498
  ]
490
499
  }),
@@ -492,14 +501,18 @@ var LokiLoggingAddon = class extends BaseAddon {
492
501
  type: "text",
493
502
  key: "job",
494
503
  label: "Job label",
504
+ labelKey: "server:globalSettings.s_loki-logging.job.label",
495
505
  description: "The Loki `job` label. Keep it fixed — labels are indexed.",
506
+ descriptionKey: "server:globalSettings.s_loki-logging.job.description",
496
507
  default: "camstack"
497
508
  }),
498
509
  this.field({
499
510
  type: "text",
500
511
  key: "extraLabels",
501
512
  label: "Extra labels",
513
+ labelKey: "server:globalSettings.s_loki-logging.extraLabels.label",
502
514
  description: "Static `key=value,key2=value2`. Low-cardinality only: per-entry values belong in the line body, and a high-cardinality label will bring Loki down.",
515
+ descriptionKey: "server:globalSettings.s_loki-logging.extraLabels.description",
503
516
  placeholder: "env=home,site=garage",
504
517
  default: ""
505
518
  }),
@@ -507,27 +520,34 @@ var LokiLoggingAddon = class extends BaseAddon {
507
520
  type: "text",
508
521
  key: "tenantId",
509
522
  label: "Tenant ID",
523
+ labelKey: "server:globalSettings.s_loki-logging.tenantId.label",
510
524
  description: "Sent as X-Scope-OrgID. Required by Grafana Cloud and multi-tenant Loki.",
525
+ descriptionKey: "server:globalSettings.s_loki-logging.tenantId.description",
511
526
  default: ""
512
527
  }),
513
528
  this.field({
514
529
  type: "text",
515
530
  key: "username",
516
531
  label: "Username",
532
+ labelKey: "server:globalSettings.s_loki-logging.username.label",
517
533
  description: "HTTP basic auth. Leave empty for none.",
534
+ descriptionKey: "server:globalSettings.s_loki-logging.username.description",
518
535
  default: ""
519
536
  }),
520
537
  this.field({
521
538
  type: "password",
522
539
  key: "password",
523
540
  label: "Password",
541
+ labelKey: "server:globalSettings.s_loki-logging.password.label",
524
542
  default: ""
525
543
  }),
526
544
  this.field({
527
545
  type: "number",
528
546
  key: "batchIntervalMs",
529
547
  label: "Flush interval",
548
+ labelKey: "server:globalSettings.s_loki-logging.batchIntervalMs.label",
530
549
  description: "How often a partial batch is pushed.",
550
+ descriptionKey: "server:globalSettings.s_loki-logging.batchIntervalMs.description",
531
551
  min: 250,
532
552
  max: 6e4,
533
553
  step: 250,
@@ -538,7 +558,9 @@ var LokiLoggingAddon = class extends BaseAddon {
538
558
  type: "number",
539
559
  key: "batchSize",
540
560
  label: "Batch size",
561
+ labelKey: "server:globalSettings.s_loki-logging.batchSize.label",
541
562
  description: "Push early once this many entries are queued.",
563
+ descriptionKey: "server:globalSettings.s_loki-logging.batchSize.description",
542
564
  min: 1,
543
565
  max: 5e3,
544
566
  step: 10,
@@ -548,6 +570,7 @@ var LokiLoggingAddon = class extends BaseAddon {
548
570
  type: "number",
549
571
  key: "timeoutMs",
550
572
  label: "Request timeout",
573
+ labelKey: "server:globalSettings.s_loki-logging.timeoutMs.label",
551
574
  min: 1e3,
552
575
  max: 6e4,
553
576
  step: 500,
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-DVe0Nsp8.js");
6
+ const require_dist = require("../../dist-GgG-7amw.js");
7
7
  let node_fs_promises = require("node:fs/promises");
8
8
  let node_os = require("node:os");
9
9
  node_os = require_chunk.__toESM(node_os);
@@ -3511,11 +3511,14 @@ var NativeMetricsAddon = class NativeMetricsAddon extends require_dist.BaseAddon
3511
3511
  return this.schema({ sections: [{
3512
3512
  id: "native-metrics-settings",
3513
3513
  title: "System Metrics",
3514
+ titleKey: "server:globalSettings.s_native-metrics-settings.title",
3514
3515
  fields: [this.field({
3515
3516
  type: "number",
3516
3517
  key: "samplingIntervalMs",
3517
3518
  label: "Sampling Interval",
3519
+ labelKey: "server:globalSettings.s_native-metrics-settings.samplingIntervalMs.label",
3518
3520
  description: "How often to collect system metrics",
3521
+ descriptionKey: "server:globalSettings.s_native-metrics-settings.samplingIntervalMs.description",
3519
3522
  min: 1e3,
3520
3523
  max: 6e4,
3521
3524
  step: 1e3,
@@ -3525,12 +3528,15 @@ var NativeMetricsAddon = class NativeMetricsAddon extends require_dist.BaseAddon
3525
3528
  }, {
3526
3529
  id: "native-metrics-load-series",
3527
3530
  title: "Load history",
3531
+ titleKey: "server:globalSettings.s_native-metrics-load-series.title",
3528
3532
  fields: [
3529
3533
  this.field({
3530
3534
  type: "number",
3531
3535
  key: "loadSeriesCadenceSec",
3532
3536
  label: "Sampling cadence",
3537
+ labelKey: "server:globalSettings.s_native-metrics-load-series.loadSeriesCadenceSec.label",
3533
3538
  description: "How often every node reports its process tree. FIXED — a sample is emitted on every interval whether or not anything changed, so a gap in the chart means one thing only: nobody reported. Outside 5-60 s the value is refused.",
3539
+ descriptionKey: "server:globalSettings.s_native-metrics-load-series.loadSeriesCadenceSec.description",
3534
3540
  min: 5,
3535
3541
  max: 60,
3536
3542
  step: 1,
@@ -3541,7 +3547,9 @@ var NativeMetricsAddon = class NativeMetricsAddon extends require_dist.BaseAddon
3541
3547
  type: "number",
3542
3548
  key: "loadSeriesRetentionHours",
3543
3549
  label: "Retention",
3550
+ labelKey: "server:globalSettings.s_native-metrics-load-series.loadSeriesRetentionHours.label",
3544
3551
  description: "How long the load history is kept on disk (the hub database, on the NVMe cache — never the recordings disk). This is the INTENTION; the row cap below is the guarantee.",
3552
+ descriptionKey: "server:globalSettings.s_native-metrics-load-series.loadSeriesRetentionHours.description",
3545
3553
  min: 1,
3546
3554
  max: 72,
3547
3555
  step: 1,
@@ -3552,6 +3560,7 @@ var NativeMetricsAddon = class NativeMetricsAddon extends require_dist.BaseAddon
3552
3560
  type: "info",
3553
3561
  key: "load-series-cost",
3554
3562
  label: "What this configuration costs",
3563
+ labelKey: "server:globalSettings.s_native-metrics-load-series.load-series-cost.label",
3555
3564
  content: describeLoadSeriesCost(projection),
3556
3565
  variant: projection.capBites ? "warning" : "info"
3557
3566
  },
@@ -3559,6 +3568,7 @@ var NativeMetricsAddon = class NativeMetricsAddon extends require_dist.BaseAddon
3559
3568
  type: "info",
3560
3569
  key: "load-series-cap",
3561
3570
  label: "Hard row cap",
3571
+ labelKey: "server:globalSettings.s_native-metrics-load-series.load-series-cap.label",
3562
3572
  content: `The table never exceeds ${this.loadConfig.maxRows.toLocaleString("en-US")} rows. When it would, the OLDEST samples are evicted and a warning is logged — so raising the retention can never turn into an incident, only into a shorter effective window.`,
3563
3573
  variant: "info"
3564
3574
  }