@camstack/addon-admin-ui 1.2.430 → 1.2.432

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 (119) hide show
  1. package/dist/assets/{AddonAssetIcon-BWUL7eDI.js → AddonAssetIcon-BUMe5IDK.js} +1 -1
  2. package/dist/assets/{Addons-2-Q9J_R-.js → Addons-DeuXr1KP.js} +1 -1
  3. package/dist/assets/{AdminPage-DMiMIHwu.js → AdminPage-BIr_CXzx.js} +1 -1
  4. package/dist/assets/{AdminSectionedPage-DKH0UpKh.js → AdminSectionedPage-BaIEUBzU.js} +1 -1
  5. package/dist/assets/{AiProfiles-DQ5YpTn5.js → AiProfiles-DXFjP3HN.js} +1 -1
  6. package/dist/assets/{BrokerForm-wEqc4cpx.js → BrokerForm-D73q6J9S.js} +1 -1
  7. package/dist/assets/{Build-DIaw3f01.js → Build-v9eZF0g2.js} +1 -1
  8. package/dist/assets/{Calendars-BpyELuD4.js → Calendars-DKauMIgJ.js} +1 -1
  9. package/dist/assets/{Cluster-CfJQLikz.js → Cluster-Bk29KZQL.js} +1 -1
  10. package/dist/assets/{ClusterStepModel-B_1igjPf.js → ClusterStepModel-C4t8EYyW.js} +1 -1
  11. package/dist/assets/{CompositionEditor-DYkQHem0.js → CompositionEditor-Bw5I7XNl.js} +1 -1
  12. package/dist/assets/{CompositionEditorPage-DDBZOcqy.js → CompositionEditorPage-DLat14IE.js} +1 -1
  13. package/dist/assets/{Data-7qDFkx-Q.js → Data-C54aVzn0.js} +1 -1
  14. package/dist/assets/{DeviceDetail-Dy2NTHbz.js → DeviceDetail-UyQq0okE.js} +1 -1
  15. package/dist/assets/{DeviceDoActionFields-DmKzugUt.js → DeviceDoActionFields-BZPhSBgg.js} +1 -1
  16. package/dist/assets/{Devices-COzPWkpB.js → Devices-C2gBbpLK.js} +1 -1
  17. package/dist/assets/{EmbedPlayerPage-CD4jAYaX.js → EmbedPlayerPage-BMNLT-PN.js} +1 -1
  18. package/dist/assets/Identity-J4MwstTs.js +1 -0
  19. package/dist/assets/{IntegrationDetail-CP91fqQF.js → IntegrationDetail-lkVpMjqt.js} +1 -1
  20. package/dist/assets/{IntegrationHub-DMx7fLHV.js → IntegrationHub-B9rzgT5H.js} +1 -1
  21. package/dist/assets/{Integrations-Blb_S3_f.js → Integrations-CmqbxDj8.js} +1 -1
  22. package/dist/assets/{Integrations-BzqzbXQ2.js → Integrations-DhQ3DIcI.js} +1 -1
  23. package/dist/assets/{Library-Cqg-Y18g.js → Library-BvJfjpJY.js} +1 -1
  24. package/dist/assets/{Locations-EQXaWxpY.js → Locations-CgI3IeYv.js} +1 -1
  25. package/dist/assets/{Logs-hQ_F2BI5.js → Logs-DYZ-Wx2b.js} +1 -1
  26. package/dist/assets/{MaskShapeCanvas-BkBmWJHu.js → MaskShapeCanvas-D5vpR-SP.js} +1 -1
  27. package/dist/assets/{MotionZonesSettings-Ctl81l__.js → MotionZonesSettings-BEGvKMSe.js} +1 -1
  28. package/dist/assets/{MyAccess-D8vfWKFy.js → MyAccess-CfsgcW14.js} +1 -1
  29. package/dist/assets/Network-CfV9qDQb.js +1 -0
  30. package/dist/assets/{NodeAddonsSettingsPanel-DMEeQbDW.js → NodeAddonsSettingsPanel-Bm5HTEFT.js} +1 -1
  31. package/dist/assets/{Notifications-BtrpoxXL.js → Notifications-QmfzpIs2.js} +1 -1
  32. package/dist/assets/{PrivacyMaskSettings-DEbHPS5X.js → PrivacyMaskSettings-Dd5t3M3f.js} +1 -1
  33. package/dist/assets/{Recordings-BrGa72HR.js → Recordings-Crol71OX.js} +1 -1
  34. package/dist/assets/{ScanNetworkModal-CTjVwV69.js → ScanNetworkModal-CPFtW5pZ.js} +1 -1
  35. package/dist/assets/{SceneMonitorEditor-DgNqz_fJ.js → SceneMonitorEditor-kP-ZQsfj.js} +1 -1
  36. package/dist/assets/{SessionsList-CzSpm8u5.js → SessionsList-CTV6uO5n.js} +1 -1
  37. package/dist/assets/{Showroom-DC0Rdsfa.js → Showroom-DWJgeOiz.js} +1 -1
  38. package/dist/assets/{System-ByMuYkyz.js → System-CRn_eq5a.js} +1 -1
  39. package/dist/assets/{Terminal-CLof2QSK.js → Terminal-Dh_sliYJ.js} +1 -1
  40. package/dist/assets/{Viewer-CLEBXqqR.js → Viewer-B4ctSRpW.js} +1 -1
  41. package/dist/assets/{_virtual_mf-localSharedImportMap___mfe_internal__admin_ui_host-CxVe8UPZ.js → _virtual_mf-localSharedImportMap___mfe_internal__admin_ui_host-DNje-LmA.js} +1 -1
  42. package/dist/assets/{addon-icons-context-B6iVhWXv.js → addon-icons-context-D5fUq03j.js} +1 -1
  43. package/dist/assets/{archive-WKa1b0o1.js → archive-D75KchjA.js} +1 -1
  44. package/dist/assets/{authed-media-B0bKjjhb.js → authed-media-D2AKdsuc.js} +1 -1
  45. package/dist/assets/{bell-off-DQeMLsEK.js → bell-off-C1XQJfLA.js} +1 -1
  46. package/dist/assets/{board-model-DIWD11Nh.js → board-model-DR9pUgyG.js} +1 -1
  47. package/dist/assets/{boxes-DMqqPR7T.js → boxes-L_1M15a4.js} +1 -1
  48. package/dist/assets/circle-slash-jUACStpz.js +1 -0
  49. package/dist/assets/client-DSjr7-vB.js +358 -0
  50. package/dist/assets/{cloud-upload-8Rtciaox.js → cloud-upload-CX-DdQts.js} +1 -1
  51. package/dist/assets/crop--XlYmzNa.js +1 -0
  52. package/dist/assets/{device-integration-icon-BRxzTJPF.js → device-integration-icon-Bcm7Z6jY.js} +1 -1
  53. package/dist/assets/device-picker-model-C6sGbx0i.js +1 -0
  54. package/dist/assets/{dist-BiqTDJU8.js → dist-CWmjwgsm.js} +1 -1
  55. package/dist/assets/dist-DvZ0rTuL.js +358 -0
  56. package/dist/assets/{eraser-AtBM6hp_.js → eraser-BGE9R8_a.js} +1 -1
  57. package/dist/assets/{hard-drive-download-COMXm0vT.js → hard-drive-download-Bg2SJyCf.js} +1 -1
  58. package/dist/assets/{history-CNH6Hwzl.js → history-G5nuVYvw.js} +1 -1
  59. package/dist/assets/{hostInit-Cn-rvYrx.js → hostInit-rwNOXVdE.js} +1 -1
  60. package/dist/assets/i18n-CxM-3m0e.js +1 -0
  61. package/dist/assets/icons-C3xAUFxL.js +1 -0
  62. package/dist/assets/{index-IBVizVRk.js → index-C4TCQi7u.js} +3 -3
  63. package/dist/assets/key-D4l0MPuJ.js +1 -0
  64. package/dist/assets/{key-round-DhUJVMTJ.js → key-round-By7HqlM-.js} +1 -1
  65. package/dist/assets/{layers-BNtkJA8b.js → layers-CrP3_-zE.js} +1 -1
  66. package/dist/assets/{linked-devices-model-DXpryeZj.js → linked-devices-model-DzomDthM.js} +1 -1
  67. package/dist/assets/{list-ordered-DE6semVl.js → list-ordered-Q101NLCk.js} +1 -1
  68. package/dist/assets/{location-owned-usage-4K4CCW-I.js → location-owned-usage-BHGw7ZrT.js} +1 -1
  69. package/dist/assets/{log-in-k_3OshGq.js → log-in-CVN3HrLY.js} +1 -1
  70. package/dist/assets/{memory-stick--pKqiId8.js → memory-stick-DNkEIVDl.js} +1 -1
  71. package/dist/assets/{message-square-plus-DD_p-LBd.js → message-square-plus-BM5D9v7J.js} +1 -1
  72. package/dist/assets/{mf-entry-bootstrap-0-b5dba7ed.js → mf-entry-bootstrap-0-22a1f3ed.js} +2 -2
  73. package/dist/assets/{mic-off-abkMqo-Z.js → mic-off-D6Lc5R7l.js} +1 -1
  74. package/dist/assets/{monitor-off-Btwz3hik.js → monitor-off-RKGlWndF.js} +1 -1
  75. package/dist/assets/{monitor-smartphone-CewWbhfo.js → monitor-smartphone-BaBns5JL.js} +1 -1
  76. package/dist/assets/mount-C6sGbx0i.js +1 -0
  77. package/dist/assets/{notification-schedule-field-DALe8QBw.js → notification-schedule-field-MLRs0icT.js} +1 -1
  78. package/dist/assets/{octagon-alert-B-H38qMt.js → octagon-alert-DYq78E92.js} +1 -1
  79. package/dist/assets/{player-overlays-CTjp5_0E.js → player-overlays-BtrsPwRp.js} +1 -1
  80. package/dist/assets/{recycle-om2m5njd.js → recycle-CAwzzZqf.js} +1 -1
  81. package/dist/assets/remoteEntry-CBD0Jyz7.js +1 -0
  82. package/dist/assets/{scan-eye-RPomUFSt.js → scan-eye-BpAY7eTH.js} +1 -1
  83. package/dist/assets/{scissors-oLO0ZxF2.js → scissors-Be9z9iTK.js} +1 -1
  84. package/dist/assets/{search-x-By7QfPjb.js → search-x-ExdMvYlD.js} +1 -1
  85. package/dist/assets/{send-CWuZ-VHG.js → send-NfLeCT7d.js} +1 -1
  86. package/dist/assets/{settings-2-DrkSvhIU.js → settings-2-AuxgmdTU.js} +1 -1
  87. package/dist/assets/{speaker-Bi1HndpO.js → speaker-DcnALnai.js} +1 -1
  88. package/dist/assets/src-D7fj-1-O.js +67 -0
  89. package/dist/assets/{timer-reset-CX_75T7d.js → timer-reset-b5RtzF8y.js} +1 -1
  90. package/dist/assets/{ungroup-CQ9vK9Rb.js → ungroup-B_FqAxhG.js} +1 -1
  91. package/dist/assets/{unlink-DRIRCsre.js → unlink-_FaVL_1F.js} +1 -1
  92. package/dist/assets/{usb-CSAqtgKZ.js → usb-S8PAfCjY.js} +1 -1
  93. package/dist/assets/{use-scenes-feed-B3oVDWHB.js → use-scenes-feed-CCiJNPg1.js} +1 -1
  94. package/dist/assets/{use-stationary-objects-D8Q3fSKJ.js → use-stationary-objects-U550m5tx.js} +1 -1
  95. package/dist/assets/useEventInvalidation-C6sGbx0i.js +1 -0
  96. package/dist/assets/{useNodeUrlSelection-CbhdLJkk.js → useNodeUrlSelection-Dr_4iDNb.js} +1 -1
  97. package/dist/assets/user-round-CJ5fRzDO.js +1 -0
  98. package/dist/assets/{user-x-fVEkryXF.js → user-x-CEMv46WL.js} +1 -1
  99. package/dist/assets/{video-C9XbLIGZ.js → video-fjSOa6Uz.js} +1 -1
  100. package/dist/assets/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__admin_ui_host__remoteEntry-_hash_-CWUvAVp2.js → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__admin_ui_host__remoteEntry-_hash_-DD6rNG_X.js} +2 -2
  101. package/dist/index.html +10 -10
  102. package/dist/server/addon.js +178 -27
  103. package/package.json +1 -1
  104. package/dist/assets/Identity-BnBFoIrz.js +0 -1
  105. package/dist/assets/Network-BYFc8leF.js +0 -1
  106. package/dist/assets/circle-slash-HW5fnitA.js +0 -1
  107. package/dist/assets/client-Ck3kx2Z0.js +0 -358
  108. package/dist/assets/crop-BCuJfQIp.js +0 -1
  109. package/dist/assets/device-picker-model-DUsOqTKW.js +0 -1
  110. package/dist/assets/dist-D4jmeWXJ.js +0 -358
  111. package/dist/assets/file-up-DvSwIdQI.js +0 -1
  112. package/dist/assets/i18n-DWijAvai.js +0 -1
  113. package/dist/assets/icons-BzZe_Qg7.js +0 -1
  114. package/dist/assets/key-B62K9GgX.js +0 -1
  115. package/dist/assets/mount-DUsOqTKW.js +0 -1
  116. package/dist/assets/remoteEntry-Dmuue8--.js +0 -1
  117. package/dist/assets/src-CoNs4bys.js +0 -67
  118. package/dist/assets/useEventInvalidation-DUsOqTKW.js +0 -1
  119. package/dist/assets/user-round-DhADiv-t.js +0 -1
@@ -5388,7 +5388,7 @@ var ZodIssueCode = {
5388
5388
  var ZodFirstPartyTypeKind;
5389
5389
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5390
5390
  //#endregion
5391
- //#region ../types/dist/sleep-DRqxtc0C.mjs
5391
+ //#region ../types/dist/sleep-bwsTj221.mjs
5392
5392
  /**
5393
5393
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5394
5394
  * window to float samples (D455).
@@ -6639,7 +6639,6 @@ function createHandle(raw, opts) {
6639
6639
  const writeAddon = (patch) => write("addon", opts.addonTree(), void 0, patch, () => readStoredAddonBlob(raw), (p) => raw.writeAddonStore(p));
6640
6640
  const readAddonStoreResult = raw.readAddonStoreResult;
6641
6641
  const readAddonStoreKeyResult = raw.readAddonStoreKeyResult;
6642
- const readDeviceStoreBatch = raw.readDeviceStoreBatch;
6643
6642
  const overrides = {
6644
6643
  readAddonStore: async () => resolveRead("addon", await raw.readAddonStore()),
6645
6644
  ...readAddonStoreResult !== void 0 ? { readAddonStoreResult: async () => {
@@ -6663,12 +6662,12 @@ function createHandle(raw, opts) {
6663
6662
  };
6664
6663
  } } : {},
6665
6664
  readDeviceStore: async (deviceId) => resolveRead(`device:${deviceId}`, await raw.readDeviceStore(deviceId)),
6666
- ...readDeviceStoreBatch !== void 0 ? { readDeviceStoreBatch: async (deviceIds) => {
6667
- const rows = await readDeviceStoreBatch.call(raw, deviceIds);
6665
+ readDeviceStoreBatch: async (deviceIds) => {
6666
+ const rows = await raw.readDeviceStoreBatch(deviceIds);
6668
6667
  const out = /* @__PURE__ */ new Map();
6669
6668
  for (const [deviceId, blob] of rows) out.set(deviceId, await resolveRead(`device:${deviceId}`, blob));
6670
6669
  return out;
6671
- } } : {},
6670
+ },
6672
6671
  writeAddonStore: async (patch) => {
6673
6672
  await writeAddon(patch);
6674
6673
  },
@@ -7495,7 +7494,7 @@ var BaseAddon = class {
7495
7494
  return this._addonSecretTree;
7496
7495
  }
7497
7496
  deviceSecretTree() {
7498
- this._deviceSecretTree ??= secretTreeFromSchema(this.deviceSettingsSchema());
7497
+ this._deviceSecretTree ??= mergeSecretStatePaths(secretTreeFromSchema(this.deviceSettingsSchema()), this.secretStatePaths);
7499
7498
  return this._deviceSecretTree;
7500
7499
  }
7501
7500
  /**
@@ -7571,42 +7570,135 @@ var BaseAddon = class {
7571
7570
  this[timerOf] = timer;
7572
7571
  }
7573
7572
  /**
7574
- * Wrap `ctx.settings.readAddonStore()` with a short retry budget so a
7575
- * transient settings-store outage (mid-restart of sqlite-settings, tsx-watch
7576
- * swap, or any race where the SqliteSettingsBackend is between shutdown and
7577
- * re-initialize) doesn't propagate up into `initialize()` and leave the
7573
+ * Wrap `ctx.settings.readAddonStore()` with a retry budget so a transient
7574
+ * settings-store outage doesn't propagate up into `initialize()` and leave the
7578
7575
  * addon permanently broken.
7579
7576
  *
7580
- * Retry only the two known infra fingerprints. Anything else propagates so
7581
- * real bugs surface immediately. After the budget expires we fall back to
7582
- * `{}` (defaults) — the addon's first successful patch will rehydrate.
7577
+ * Two kinds of outage, two budgets:
7578
+ * - the store's own restart fingerprints (`SqliteSettingsBackend not
7579
+ * initialized`, `provider not available`): a short budget, then the addon
7580
+ * falls back to `{}` (defaults) - its first successful patch rehydrates;
7581
+ * - a TRANSPORT failure (the hub is restarting while this runner boots:
7582
+ * `discovery deadline exceeded`, `parent could not route`, a refused or
7583
+ * timed-out connection). Since D870 a runner's settings read THROWS instead
7584
+ * of answering `{}`, so without a budget a hub restart ends in a respawn
7585
+ * loop. A long, bounded budget; only after it the error propagates - the
7586
+ * addon never runs on defaults for a store it could not ask.
7587
+ * Anything else propagates immediately so real bugs surface.
7583
7588
  */
7584
7589
  async readAddonStoreWithRetry() {
7585
7590
  const settings = this.rawSettings();
7586
7591
  if (!settings) return {};
7587
- const delaysMs = [
7588
- 150,
7589
- 350,
7590
- 600,
7591
- 900
7592
- ];
7593
7592
  let lastErr;
7594
- for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
7593
+ for (let attempt = 0;; attempt++) try {
7595
7594
  const stored = await settings.readAddonStore() ?? {};
7596
7595
  this.settingsStoreReady = true;
7597
7596
  return stored;
7598
7597
  } catch (err) {
7599
7598
  lastErr = err;
7600
- const msg = err instanceof Error ? err.message : String(err);
7601
- if (!(msg.includes("SqliteSettingsBackend not initialized") || msg.includes("provider not available"))) throw err;
7602
- if (attempt === delaysMs.length) break;
7603
- await new Promise((r) => setTimeout(r, delaysMs[attempt]));
7599
+ const kind = classifyStoreError(err);
7600
+ if (kind === "permanent") throw err;
7601
+ const delay = (kind === "transport" ? TRANSPORT_RETRY_DELAYS_MS : RESTART_RETRY_DELAYS_MS)[attempt];
7602
+ if (delay === void 0) {
7603
+ if (kind === "transport") throw err;
7604
+ break;
7605
+ }
7606
+ await new Promise((r) => setTimeout(r, delay));
7604
7607
  }
7605
7608
  this.settingsStoreReady = false;
7606
7609
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
7607
7610
  return {};
7608
7611
  }
7609
7612
  };
7613
+ /** The settings store's own restart fingerprints. */
7614
+ var RESTART_RETRY_DELAYS_MS = [
7615
+ 150,
7616
+ 350,
7617
+ 600,
7618
+ 900
7619
+ ];
7620
+ /** A hub restarting under a booting runner: ~100 s in total. */
7621
+ var TRANSPORT_RETRY_DELAYS_MS = [
7622
+ 150,
7623
+ 350,
7624
+ 600,
7625
+ 900,
7626
+ 2e3,
7627
+ 5e3,
7628
+ 1e4,
7629
+ 2e4,
7630
+ 3e4,
7631
+ 3e4
7632
+ ];
7633
+ var RESTART_FINGERPRINTS = ["SqliteSettingsBackend not initialized", "provider not available"];
7634
+ /** Node / socket error codes of a connection that did not happen or broke. */
7635
+ var TRANSPORT_CODES = new Set([
7636
+ "ECONNREFUSED",
7637
+ "ECONNRESET",
7638
+ "ETIMEDOUT",
7639
+ "EPIPE",
7640
+ "EHOSTUNREACH",
7641
+ "ENETUNREACH"
7642
+ ]);
7643
+ /** tRPC error codes that mean "the other side is not answering now", not "your call is wrong". */
7644
+ var UNAVAILABLE_TRPC_CODES = new Set([
7645
+ "TIMEOUT",
7646
+ "SERVICE_UNAVAILABLE",
7647
+ "BAD_GATEWAY",
7648
+ "GATEWAY_TIMEOUT"
7649
+ ]);
7650
+ /** The exact phrases OUR transport raises when it cannot reach the hub. */
7651
+ var TRANSPORT_PHRASES = [
7652
+ "discovery deadline exceeded",
7653
+ "parent could not route child unowned cap call",
7654
+ "UDS request timed out after",
7655
+ "channel closed"
7656
+ ];
7657
+ /** Moleculer error `type`s of a call that could not be served right now. */
7658
+ var MOLECULER_TRANSIENT_TYPES = new Set([
7659
+ "REQUEST_TIMEOUT",
7660
+ "SERVICE_NOT_FOUND",
7661
+ "SERVICE_NOT_AVAILABLE",
7662
+ "REQUEST_REJECTED",
7663
+ "BROKER_DISCONNECTED"
7664
+ ]);
7665
+ function stringProp(value, key) {
7666
+ if (typeof value !== "object" || value === null) return void 0;
7667
+ const found = Reflect.get(value, key);
7668
+ return typeof found === "string" ? found : void 0;
7669
+ }
7670
+ /** The code an error carries: its own, its cause's, or a tRPC `data.code`. */
7671
+ function errorCode(err) {
7672
+ const out = {};
7673
+ const cause = typeof err === "object" && err !== null ? Reflect.get(err, "cause") : void 0;
7674
+ const data = typeof err === "object" && err !== null ? Reflect.get(err, "data") : void 0;
7675
+ const node = stringProp(err, "code") ?? stringProp(cause, "code");
7676
+ if (node !== void 0) out.node = node;
7677
+ const trpc = stringProp(data, "code");
7678
+ if (trpc !== void 0) out.trpc = trpc;
7679
+ return out;
7680
+ }
7681
+ /**
7682
+ * Is a settings read failure worth retrying, and with which budget? Only
7683
+ * EXPLICIT signals count: a socket error code, a tRPC "unavailable" code, or
7684
+ * one of the exact phrases our own transport raises. A message that merely
7685
+ * contains "timed out" or "unreachable" is not evidence of anything, and
7686
+ * `settings store not wired` is a wiring bug that retrying cannot fix.
7687
+ */
7688
+ function classifyStoreError(err) {
7689
+ const message = err instanceof Error ? err.message : String(err);
7690
+ if (RESTART_FINGERPRINTS.some((f) => message.includes(f))) return "restart";
7691
+ const code = errorCode(err);
7692
+ if (code.node !== void 0 && TRANSPORT_CODES.has(code.node)) return "transport";
7693
+ if (code.trpc !== void 0 && UNAVAILABLE_TRPC_CODES.has(code.trpc)) return "transport";
7694
+ if (typeof err === "object" && err !== null) {
7695
+ if (Reflect.get(err, "retryable") === true) return "transport";
7696
+ const type = stringProp(err, "type");
7697
+ if (type !== void 0 && MOLECULER_TRANSIENT_TYPES.has(type)) return "transport";
7698
+ }
7699
+ if (TRANSPORT_PHRASES.some((f) => message.includes(f))) return "transport";
7700
+ return "permanent";
7701
+ }
7610
7702
  /**
7611
7703
  * Collect the keys of every field marked `perNode: true`, recursing into
7612
7704
  * layout containers (`group` fields and `sub-tabs` tabs) the same way
@@ -8179,7 +8271,7 @@ object({
8179
8271
  params: record(string(), union([string(), number()])).optional()
8180
8272
  });
8181
8273
  //#endregion
8182
- //#region ../types/dist/err-msg-DLerKiVO.mjs
8274
+ //#region ../types/dist/err-msg--2RrYiLO.mjs
8183
8275
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
8184
8276
  DeviceType["Camera"] = "camera";
8185
8277
  DeviceType["Hub"] = "hub";
@@ -8448,6 +8540,9 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
8448
8540
  /** Virtual HA toggle (input_boolean.*) — distinguishable from a
8449
8541
  * real Switch device for UI rendering / export adapters. */
8450
8542
  DeviceRole["BinaryHelper"] = "binary-helper";
8543
+ /** A smart plug: a `switch` whose source says it is an outlet (HA
8544
+ * `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
8545
+ DeviceRole["Outlet"] = "outlet";
8451
8546
  /** Generic motion / occupancy / moving event source. Distinct from
8452
8547
  * the camera accessory PirSensor role: that one is a camera child;
8453
8548
  * this is a standalone HA / 3rd-party motion sensor. */
@@ -8515,7 +8610,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
8515
8610
  return DeviceRole;
8516
8611
  }({});
8517
8612
  //#endregion
8518
- //#region ../types/dist/composition-EQ9Ok0Ee.mjs
8613
+ //#region ../types/dist/composition-DOwQB_Ua.mjs
8519
8614
  /**
8520
8615
  * The ffmpeg build every CamStack node downloads, per platform and arch.
8521
8616
  *
@@ -15837,6 +15932,28 @@ id: union([string(), number()]) }), {
15837
15932
  kind: "mutation",
15838
15933
  auth: "admin"
15839
15934
  });
15935
+ /** One table of the settings database. */
15936
+ var StorageTableSchema = object({
15937
+ /** The table's full name, e.g. `recorder:addon-settings` or `addon-device-settings`. */
15938
+ name: string(),
15939
+ /** The owner prefix of a scoped name (`recorder` for `recorder:addon-settings`), else null. */
15940
+ owner: string().nullable(),
15941
+ rows: number().int().nonnegative(),
15942
+ /** Bytes the table's pages occupy (`dbstat`); null when the engine cannot measure. */
15943
+ bytes: number().int().nonnegative().nullable(),
15944
+ /** Declared by an addon since this engine process started (a running addon claims it). */
15945
+ declaredThisBoot: boolean()
15946
+ });
15947
+ method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: array(StorageTableSchema) }), { auth: "admin" }), method(object({
15948
+ collection: string().min(1),
15949
+ expectRows: number().int().nonnegative()
15950
+ }), object({
15951
+ dropped: literal(true),
15952
+ rows: number().int().nonnegative()
15953
+ }), {
15954
+ kind: "mutation",
15955
+ auth: "admin"
15956
+ });
15840
15957
  /**
15841
15958
  * `keychain` — the hub's encrypted store of secret values (D787).
15842
15959
  *
@@ -16378,6 +16495,16 @@ method(object({}), KeychainStatusSchema, { auth: "admin" }), method(object({}),
16378
16495
  kind: "mutation",
16379
16496
  auth: "admin",
16380
16497
  caller: "required"
16498
+ }), method(object({
16499
+ ref: string().min(1),
16500
+ value: string().min(1)
16501
+ }), object({
16502
+ ref: string(),
16503
+ version: number().int()
16504
+ }), {
16505
+ kind: "mutation",
16506
+ auth: "admin",
16507
+ caller: "required"
16381
16508
  }), method(object({ refs: array(string().min(1)).max(1e3) }), object({
16382
16509
  deleted: array(string()).readonly(),
16383
16510
  kept: array(object({
@@ -52823,6 +52950,12 @@ object({
52823
52950
  /** Horizontal louver swing on/off. Null when the device has no
52824
52951
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
52825
52952
  swingHorizontal: boolean().nullable(),
52953
+ /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
52954
+ minTemp: number().nullable().optional(),
52955
+ /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
52956
+ maxTemp: number().nullable().optional(),
52957
+ /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
52958
+ tempStep: number().positive().nullable().optional(),
52826
52959
  /** Ms epoch when the slice was last updated (push or command). */
52827
52960
  lastFetchedAt: number()
52828
52961
  });
@@ -63730,6 +63863,12 @@ Object.freeze({
63730
63863
  addonId: null,
63731
63864
  access: "create"
63732
63865
  },
63866
+ "keychainAdmin.importSecret": {
63867
+ capName: "keychain-admin",
63868
+ capScope: "system",
63869
+ addonId: null,
63870
+ access: "create"
63871
+ },
63733
63872
  "keychainAdmin.listAudit": {
63734
63873
  capName: "keychain-admin",
63735
63874
  capScope: "system",
@@ -67516,6 +67655,18 @@ Object.freeze({
67516
67655
  addonId: null,
67517
67656
  access: "create"
67518
67657
  },
67658
+ "storageAdmin.dropCollection": {
67659
+ capName: "storage-admin",
67660
+ capScope: "system",
67661
+ addonId: null,
67662
+ access: "delete"
67663
+ },
67664
+ "storageAdmin.listTables": {
67665
+ capName: "storage-admin",
67666
+ capScope: "system",
67667
+ addonId: null,
67668
+ access: "view"
67669
+ },
67519
67670
  "storageEvictable.evict": {
67520
67671
  capName: "storage-evictable",
67521
67672
  capScope: "system",
@@ -72688,7 +72839,7 @@ var AdminUIAddon = class extends BaseAddon {
72688
72839
  capability: adminUiCapability,
72689
72840
  provider: {
72690
72841
  getStaticDir: async () => ({ staticDir: resolveAdminUiDistDir() }),
72691
- getVersion: async () => ({ version: "1.2.430" })
72842
+ getVersion: async () => ({ version: "1.2.432" })
72692
72843
  }
72693
72844
  }];
72694
72845
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-admin-ui",
3
- "version": "1.2.430",
3
+ "version": "1.2.432",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Admin UI — Vite frontend build and server-side addon",
@@ -1 +0,0 @@
1
- import{$ as e,B as t,G as n,J as r,L as i,S as a,a as o,b as s,o as c,q as l,t as u,x as d}from"./_virtual_mf___mfe_internal__admin_ui_host__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-CEHpkaJP.js";import{Hi as f,Hr as p,Ir as m,It as h,Nt as g,On as _,Pn as v,Qt as y,Tt as b,_i as x,ar as S,cn as C,cr as w,dt as T,fi as E,fn as D,ia as O,nr as k,on as A,ot as ee,pr as j,sn as M,un as N,yi as P,yn as F}from"./player-overlays-CTjp5_0E.js";import{n as I,r as L,t as te}from"./key-B62K9GgX.js";import{c as R,o as z,u as B}from"./video-C9XbLIGZ.js";import{t as V}from"./history-CNH6Hwzl.js";import{t as ne}from"./key-round-DhUJVMTJ.js";import{t as re}from"./monitor-smartphone-CewWbhfo.js";import{t as ie}from"./unlink-DRIRCsre.js";import{Si as ae,a as oe,bi as se,i as ce,o as le,s as ue,xi as de}from"./client-Ck3kx2Z0.js";import{$l as fe,Al as pe,An as me,Bl as he,Bn as H,Cn as ge,Dn as _e,Dr as ve,En as U,Er as ye,Fl as be,Ft as xe,G as Se,Hl as Ce,Ht as W,Il as we,Jl as Te,L as Ee,Ll as De,Ln as Oe,Ml as ke,N as Ae,Nl as je,Ol as Me,P as Ne,Pl as Pe,Ql as Fe,Qn as Ie,Rl as Le,Rn as Re,Tn as ze,Ul as Be,Ut as Ve,Vl as He,Wt as Ue,X as We,Xl as Ge,Yl as Ke,Z as qe,Zl as Je,Zn as G,ar as K,bn as Ye,en as Xe,ha as Ze,in as Qe,jl as $e,jn as et,jo as tt,kl as nt,kn as rt,nn as it,on as at,rn as ot,yu as q,z as st,zl as ct,zn as lt}from"./src-CoNs4bys.js";import{t as J}from"./i18n-DWijAvai.js";import{t as Y}from"./useTranslation-D3Is_4B6.js";import{C as ut,Q as dt,X as ft,Y as pt,Z as mt,c as ht,et as gt,f as _t,m as vt,s as yt,z as bt}from"./index-IBVizVRk.js";import{t as xt}from"./AdminSectionedPage-DKH0UpKh.js";import{t as St}from"./AdminPage-DMiMIHwu.js";import{A as Ct,C as wt,D as Tt,E as Et,F as Dt,M as Ot,N as X,O as kt,P as Z,S as At,T as jt,_ as Mt,a as Nt,b as Pt,c as Ft,d as It,f as Lt,h as Rt,i as zt,j as Bt,k as Vt,l as Ht,m as Ut,n as Wt,o as Gt,p as Kt,s as qt,t as Jt,u as Yt,v as Xt,w as Zt,x as Qt,y as $t}from"./SessionsList-CzSpm8u5.js";import{i as en,n as tn,r as nn,t as rn}from"./IntegrationHub-DMx7fLHV.js";import{n as an,t as on}from"./addon-display-names-BkwMrExv.js";e();function sn(){let{t:e}=Y(),t=G(),i=q.useUtils(),a=Dt(),{data:o,isLoading:s}=we(),c=n(()=>o??[],[o]),l=q.useQueries(e=>c.map(t=>e.userManagement.listExternalIdentities({userId:t.id}))),u=q.auth.listLoginMethods.useQuery(),[f,p]=r(null),m=Ce({onSuccess:()=>void i.userManagement.listExternalIdentities.invalidate(),onError:e=>p(`Remove failed: ${e.message}`)}),h=n(()=>new Map((u.data??[]).flatMap(e=>e.kind===`redirect`?[[e.id,e.label]]:[])),[u.data]),g=c.flatMap((e,t)=>l[t]?.data??[]),_=g.map(e=>({...jt(e,a),kind:h.get(e.provider)??e.provider})),v=async n=>{let r=g.find(e=>Zt(e)===n.id);r!==void 0&&await t({title:e(`access.externalIdentitiesSection.removeSignInLink`,`Remove sign-in link`),message:e(`access.externalIdentitiesSection.willNoLongerBeAble`,`"{{ownerName}}" will no longer be able to sign in with this {{kind}} account.`,{ownerName:n.ownerName,kind:n.kind}),confirmLabel:e(`common.remove`,`Remove`),variant:`danger`})&&(p(null),m.mutate({userId:r.userId,provider:r.provider,subject:r.subject}))};return d(X,{notice:Z(f),onDismissNotice:()=>{p(null)},id:`external-identities`,title:e(`access.externalIdentitiesSection.signInLinks`,`Sign-in links`),icon:B,description:e(`access.externalIdentitiesSection.externalAccountsSingleSignOn`,`External accounts (single sign-on) each user linked to their CamStack account. Only the owner can link one, from their profile; an admin can remove it.`),children:d(Vt,{rows:_,label:`sign-in links`,isLoading:s||l.some(e=>e.isLoading),loadError:l.find(e=>e.isError)?.error?.message??null,emptyText:`No user has linked a sign-in account.`,expiresNone:`—`,action:{label:e(`common.remove`,`Remove`),icon:ie,isAvailable:()=>!0,onRun:e=>void v(e),busyId:null,disabled:m.isPending}})})}e();function cn(){let{t:e}=Y(),t=G(),i=c(),a=Dt(),o=je(),[s,l]=r(null),u=Le({onSuccess:()=>void i.invalidateQueries({queryKey:[[`userManagement`,`listApiKeys`]]}),onError:e=>l(`Revoke failed: ${e.message}`)}),f=n(()=>(o.data??[]).map(e=>wt(e,a)),[o.data,a]),p=async n=>{await t({title:e(`access.legacyApiKeysSection.revoke`,`Revoke "{{name}}"?`,{name:n.name}),message:e(`access.legacyApiKeysSection.anyClientUsingThisKey`,`Any client using this key loses access immediately. This cannot be undone.`),confirmLabel:e(`access.legacyApiKeysSection.revoke2`,`Revoke`),variant:`danger`})&&(l(null),u.mutate({id:n.id}))};return d(X,{notice:Z(s),onDismissNotice:()=>{l(null)},id:`legacy-api-keys`,title:e(`access.legacyApiKeysSection.legacyApiKeys`,`Legacy API keys`),icon:V,description:e(`access.legacyApiKeysSection.keysFromBeforeScopedTokens`,`Keys from before scoped tokens. Nothing creates them any more; revoke them and issue a scoped token instead.`),children:d(Vt,{rows:f,label:e(`access.legacyApiKeysSection.legacyApiKeys2`,`legacy API keys`),isLoading:o.isLoading,loadError:o.isError?o.error?.message??`Request failed`:null,emptyText:`No legacy API keys.`,action:{label:e(`access.legacyApiKeysSection.revoke2`,`Revoke`),icon:h,isAvailable:()=>!0,onRun:e=>void p(e),busyId:u.isPending?u.variables?.id??null:null,disabled:u.isPending}})})}e();var ln={key:`access.nouns.linkedApp`};function un(e){if(e.type===`category`){let t=e.target??``;return t===`device`?J.t(`access.linkedAppsSection.allDevices`):J.t(`access.linkedAppsSection.allTarget`,{target:t})}if(e.type===`device`)return`device:[${(e.targets??[]).join(`,`)}]`;let t=e;return`${t.type}:${t.target??``}`}function dn(){let{t:e}=Y(),t=G(),i=c(),a=be(),[o,s]=r(null),l=()=>void i.invalidateQueries({queryKey:[[`userManagement`,`listOauthSessions`]]}),u=ct({onSuccess:l,onError:e=>s(`Revoke failed: ${e.message}`)}),f=Ct(`oauth-session`,ln,l),p=n(()=>(a.data??[]).map(e=>Et(e,e.scopes.map(un).join(`, `))),[a.data]),m=async n=>{await t({title:e(`access.linkedAppsSection.revokeTheLink`,`Revoke the "{{name}}" link?`,{name:n.name}),message:e(`access.linkedAppsSection.losesAccessForOnIts`,`{{name}} loses access for {{ownerName}} on its next call. Linking it again needs the account-link flow from the start.`,{name:n.name,ownerName:n.ownerName}),confirmLabel:e(`access.linkedAppsSection.revoke`,`Revoke`),variant:`danger`})&&(s(null),u.mutate({id:n.id}))},g=u.isPending?u.variables?.id??null:null;return d(X,{notice:Z(o,f.outcome),onDismissNotice:()=>{s(null),f.dismiss()},id:`linked-apps`,title:e(`access.linkedAppsSection.linkedApps`,`Linked apps`),icon:w,description:e(`access.linkedAppsSection.externalIntegrationsAlexaLinkedTo`,`External integrations (Alexa, …) linked to this hub through OAuth. Revoking one stops that integration on its next call. Already-revoked rows can be selected and removed from the list.`),children:d(Vt,{rows:p,label:e(`access.linkedAppsSection.linkedApps2`,`linked apps`),isLoading:a.isLoading,loadError:a.isError?a.error?.message??`Request failed`:null,emptyText:`No linked apps — an integration appears here once it completes the account-link flow.`,expiresNone:`—`,action:{label:e(`access.linkedAppsSection.revoke`,`Revoke`),icon:h,isAvailable:e=>e.status===`active`||e.status===`revoked`,onRun:e=>void m(e),busyId:g,disabled:u.isPending||f.busy},bulk:{revoke:f,noun:ln,isSelectable:e=>e.status===`active`||e.status===`revoked`,quickSelect:[{label:e(`access.linkedAppsSection.selectRevoked`,`Select revoked`),match:e=>e.status===`revoked`}]}})})}e();var fn={key:`access.nouns.session`};function pn(){let{t:e}=Y(),t=G(),i=q.useUtils(),a=Dt(),o=q.auth.listSessions.useQuery(),[s,c]=r(null),l=()=>void i.auth.listSessions.invalidate(),u=q.auth.revokeSessions.useMutation({onSuccess:l,onError:e=>c(`Sign out failed: ${e.message}`)}),f=Ct(`login-session`,fn,l),p=n(()=>(o.data??[]).map(e=>Tt(e,a,{client:e=>Xt(e).label,method:$t})),[o.data,a]),m=async n=>{await t({title:e(`access.loginSessionsSection.signOutThisSession`,`Sign out this session`),message:e(`access.loginSessionsSection.onWillHaveToSign`,`{{ownerName}} on {{name}} will have to sign in again.`,{ownerName:n.ownerName,name:n.name}),confirmLabel:e(`access.loginSessionsSection.signOut`,`Sign out`),variant:`danger`})&&(c(null),u.mutate({id:n.id}))};return d(X,{notice:Z(s,f.outcome),onDismissNotice:()=>{c(null),f.dismiss()},id:`login-sessions`,title:e(`access.loginSessionsSection.loginSessions`,`Login sessions`),icon:re,description:e(`access.loginSessionsSection.everyBrowserAndAppSigned`,`Every browser and app signed in to this hub, for every user. Signing one out ends it at once; it has to sign in again. Your own current session is marked and never included in a bulk sign-out.`),children:d(Vt,{rows:p,label:e(`common.sessionsLabel`),isLoading:o.isLoading,loadError:o.isError?o.error?.message??`Request failed`:null,emptyText:`No active sessions.`,action:{label:e(`access.loginSessionsSection.signOut`,`Sign out`),icon:z,isAvailable:e=>e.status===`active`,onRun:e=>void m(e),busyId:u.isPending&&u.variables!==void 0&&`id`in u.variables?u.variables.id:null,disabled:u.isPending||f.busy},bulk:{revoke:f,noun:fn,isSelectable:e=>e.status===`active`}})})}function mn({token:e,label:t,onClose:n}){let{t:r}=Y();return d(ze,{open:!0,onOpenChange:e=>!e&&n(),children:a(_e,{width:`md`,scroll:`body`,children:[d(me,{children:d(et,{children:r(`auth.tokenModals.token`,`Token: {{label}}`,{label:t})})}),a(U,{className:`space-y-3`,children:[d(`div`,{className:`rounded-md border border-warning/30 bg-warning/10 px-3 py-2 text-xs text-warning`,children:r(`auth.tokenModals.copyThisTokenNowOnce`,`Copy this token now. Once you close this dialog the secret cannot be displayed again.`)}),d(`div`,{className:`select-all break-all rounded-md border border-border bg-background p-2 font-mono text-[11px] text-foreground`,children:e})]}),a(rt,{children:[d(xe,{value:e,label:r(`common.copy`,`Copy`),srLabel:`token`}),d(H,{type:`button`,variant:`primary`,size:`sm`,onClick:n,children:r(`auth.tokenModals.done`,`Done`)})]})]})})}var hn={token:`newToken`,scoped:`newScoped`,admin:`newAdmin`};e();var gn=7,_n=90,vn=80;function yn(e){let t=Number(e);return Number.isInteger(t)&&t>=1&&t<=_n?t:null}function bn({open:e,users:n,onClose:i,onSubmitScoped:o,onSubmitAdmin:c,submitting:l,formError:u,awaitingProof:f}){let{t:p}=Y(),m=O(),{user:h}=bt(),g=h?.isAdmin===!0,_=g?null:h?.scopes??[],[v,y]=r(!1),[b,x]=r(n[0]?.id??``),[C,w]=r(``),[T,E]=r([{type:`capability`,target:``,access:[`view`,`create`]}]),[D,k]=r(``),[ee,j]=r(String(gn));t(()=>{e||(y(!1),x(n[0]?.id??``),w(``),E([{type:`capability`,target:``,access:[`view`,`create`]}]),k(``),j(String(gn)))},[e,n]);let N=C.trim(),P=Ne(T,m),F=yn(ee),I=!v&&b!==``&&N.length>0&&T.length>0&&P===null&&!l,L=v&&g&&N.length>0&&N.length<=vn&&F!==null&&!l&&!f,te=()=>{if(v){if(!L||F===null)return;c({name:N,expiresInDays:F});return}if(!I)return;let e=parseInt(D,10),t=Number.isFinite(e)&&e>0?Date.now()+e*864e5:void 0;o({userId:b,name:N,scopes:T,...t===void 0?{}:{expiresAt:t}})},R=v,z=v?M:A;return d(st,{open:e,onClose:i,title:v?p(`access.createTokenModal.createAdminToken`,`Create admin token`):p(`access.createTokenModal.createToken`,`Create token`),icon:v?M:ne,error:u,width:`md`,testId:`create-token-dialog`,footer:a(H,{type:`button`,variant:R?`danger`:`primary`,size:`sm`,onClick:te,disabled:v?!L:!I,children:[l||f?d(S,{className:`h-3.5 w-3.5 animate-spin`,"aria-hidden":!0}):d(z,{className:`h-3.5 w-3.5`,"aria-hidden":!0}),v?p(`access.createTokenModal.createAdminToken`,`Create admin token`):p(`access.createTokenModal.issueToken`,`Issue token`)]}),children:a(`div`,{className:`space-y-3`,children:[g?a(`label`,{className:`flex items-start gap-2 text-sm text-foreground`,children:[d(`input`,{type:`checkbox`,className:`mt-0.5`,checked:v,onChange:e=>y(e.target.checked),"data-testid":`token-as-admin`}),a(`span`,{children:[d(`span`,{className:`font-medium`,children:p(`access.createTokenModal.administrator`,`Administrator`)}),d(`span`,{className:`block text-[11px] text-foreground-subtle`,children:p(`access.createTokenModal.offScopedToASurface`,`Off = scoped to a surface. On = full admin for scripts (audited, max 90 days).`)})]})]}):null,v?d(`div`,{className:`space-y-1 rounded-md border border-danger/30 bg-danger/5 px-3 py-2 text-[11px] leading-relaxed text-foreground`,"data-testid":`admin-token-warning`,children:d(`p`,{children:d(pt,{i18nKey:`auth.adminTokenNoticeShort`,components:{strong:d(`strong`,{})}})})}):null,v?null:a(`div`,{className:`space-y-1`,children:[d(at,{htmlFor:`token-user`,children:p(`access.createTokenModal.onBehalfOfUser`,`On behalf of user`)}),d(Te,{id:`token-user`,value:b,onChange:e=>x(e.target.value),options:n.map(e=>({value:e.id,label:e.username}))})]}),d(Se,{label:p(`access.createTokenModal.name`,`Name`),required:!0,children:d(fe,{id:`token-name`,value:C,maxLength:vn,onChange:e=>w(e.target.value),placeholder:v?`nightly-backup`:`cloudflare-tunnel-route`,autoFocus:!0})}),v?a(`div`,{className:`space-y-1`,children:[d(at,{htmlFor:`token-admin-days`,children:p(`access.createTokenModal.expiresInDays`,`Expires in (days)`)}),d(fe,{id:`token-admin-days`,type:`number`,min:1,max:_n,value:ee,onChange:e=>j(e.target.value),state:F===null?`error`:`default`}),F===null?d(`p`,{className:`text-[11px] text-danger`,children:p(`auth.expiryInvalid`,{max:_n})}):d(`p`,{className:`text-[11px] text-foreground-subtle`,children:p(`auth.expiryHint`,{max:_n})})]}):a(s,{children:[a(`div`,{className:`space-y-1`,children:[d(at,{children:p(`access.createTokenModal.scopes`,`Scopes`)}),d(Ae,{value:T,onChange:E,clampToParent:_}),P&&d(`p`,{className:`text-[10px] text-danger`,children:P}),d(`p`,{className:`text-[10px] text-foreground-subtle`,children:p(`access.createTokenModal.eachScopePicksACap`,`Each scope picks a (cap or addon) target + one or more access flavours. The token can access ONLY what's listed.`)})]}),a(`div`,{className:`space-y-1`,children:[d(at,{htmlFor:`token-scoped-days`,children:p(`access.createTokenModal.expiresInDays`,`Expires in (days)`)}),d(fe,{id:`token-scoped-days`,type:`number`,min:1,value:D,onChange:e=>k(e.target.value),placeholder:`30`}),d(`p`,{className:`text-[10px] text-foreground-subtle`,children:p(`access.createTokenModal.leaveBlankForNonExpiring`,`Leave blank for non-expiring.`)})]})]})]})})}e();var xn={key:`access.nouns.token`};function Sn(e,t){return e.type===`device`?`device:[${qe(e.selector,t)}]`:`${e.type}:${e.target??``}`}function Cn({createRequested:e=!1,onCreateConsumed:i}){let{t:o}=Y(),s=O(),{user:l}=bt(),u=G(),f=c(),p=q.useUtils(),m=Dt(),{data:g,isLoading:_}=we(),v=n(()=>g??[],[g]),y=q.useQueries(e=>v.map(t=>e.userManagement.listScopedTokens({userId:t.id}))),b=q.auth.listAdminTokens.useQuery(void 0,{enabled:l?.isAdmin===!0}),{requestProof:x,dialog:S}=Mt(`Creating an admin token`),{state:C,openNew:w,close:T}=Ee({newKey:hn.token}),[E,D]=r(null),[k,A]=r(null),[ee,j]=r(!1),[M,N]=r(null),[P,F]=r(null);t(()=>{e&&(w(),i?.())},[e,w,i]);let I=()=>{f.invalidateQueries({queryKey:[[`userManagement`,`listScopedTokens`]]}),p.auth.listAdminTokens.invalidate(),p.auth.listAuthAudit.invalidate()},L=Me({onSuccess:({token:e,record:t})=>{I(),T(),A(null),N({token:e,label:t.name})},onError:e=>{D(`Create failed: ${e.message}`),A(`Create failed: ${e.message}`)}}),te=q.auth.createAdminToken.useMutation({onSuccess:({token:e,record:t})=>{I(),T(),A(null),j(!1),N({token:e,label:t.name})},onError:e=>{D(`Create failed: ${e.message}`),A(`Create failed: ${e.message}`),j(!1)}}),R=he({onSuccess:()=>{F(null),I()},onError:e=>{F(null),D(`Revoke failed: ${e.message}`)}}),z=q.auth.revokeAdminToken.useMutation({onSuccess:()=>{F(null),I()},onError:e=>{F(null),D(`Revoke failed: ${e.message}`)}}),B=Ot(xn,I),V=Date.now(),re=v.flatMap((e,t)=>(y[t]?.data??[]).map(t=>kt(t,e.id,m,t.scopes.map(e=>Sn(e,s)).join(`, `),V))),ie=l?.isAdmin===!0?(b.data??[]).map(e=>At(e,m,V)):[],ae=[...re,...ie],oe=y.find(e=>e.isError)?.error?.message??(b.isError?b.error?.message??`Request failed`:null),se=async e=>{let t=e.kind===Qt;await u({title:o(`access.tokensSection.revoke`,`Revoke "{{name}}"?`,{name:e.name}),message:t?o(`access.tokensSection.everyScriptUsingItLoses`,`Every script using it loses access immediately. This cannot be undone.`):o(`access.tokensSection.sScopedTokenStopsWorking`,`{{ownerName}}'s scoped token stops working on its next use. This cannot be undone.`,{ownerName:e.ownerName}),confirmLabel:o(`access.tokensSection.revoke2`,`Revoke`),variant:`danger`})&&(D(null),F(e.id),t?z.mutate({id:e.id}):R.mutate({id:e.id}))},ce=L.isPending||te.isPending;return a(`div`,{"data-testid":`tokens`,children:[S,d(X,{notice:Z(E,B.outcome),onDismissNotice:()=>{D(null),B.dismiss()},id:`tokens`,title:o(`access.tokensSection.tokens`,`Tokens`),icon:ne,description:o(`access.tokensSection.scopedTokensForANarrow`,`Scoped tokens for a narrow surface, and admin tokens for full-administrator scripts. Revoking one stops it on its next use.`),children:d(Vt,{rows:ae,label:o(`common.tokensLabel`),isLoading:_||y.some(e=>e.isLoading)||l?.isAdmin===!0&&b.isLoading,loadError:oe,emptyText:`No tokens.`,action:{label:o(`access.tokensSection.revoke2`,`Revoke`),icon:h,isAvailable:e=>e.status===`active`,onRun:e=>void se(e),busyId:P,disabled:B.busy||P!==null},bulk:{revoke:B,noun:xn,isSelectable:e=>e.status===`active`}})}),d(bn,{open:C.mode===`new`,users:v.map(e=>({id:e.id,username:e.username})),onClose:()=>{T(),A(null),j(!1)},submitting:ce,formError:k,awaitingProof:ee,onSubmitScoped:e=>L.mutate({userId:e.userId,name:e.name,scopes:e.scopes,expiresAt:e.expiresAt}),onSubmitAdmin:e=>{(async()=>{D(null),A(null),j(!0);let t=await x();j(!1),t!==null&&te.mutate({...e,reauthToken:t})})()}}),M&&d(mn,{token:M.token,label:M.label,onClose:()=>N(null)})]})}var wn={"share-link":`helpers.authAuditWording.bulkNoun.shareLink`,"admin-token":`helpers.authAuditWording.bulkNoun.adminToken`,"login-session":`helpers.authAuditWording.bulkNoun.loginSession`};function Q(e,t){let n=e[t];return typeof n==`string`&&n.length>0?n:null}function Tn(e,t){let n=Q(e,`tokenId`);return Q(e,`name`)??(n===null?void 0:t.get(n))??n??`?`}function En(e){return e.filter(e=>e!==null)}function Dn(e){let t=Q(e,`prefix`);return t===null?null:J.t(`helpers.authAuditWording.prefix`,{prefix:t})}function On(e,t){let n=e.detail??{};switch(e.action){case`admin-token-created`:{let e=n.expiresAt;return{title:J.t(`helpers.authAuditWording.tokenCreated`,{name:Tn(n,t)}),details:En([Dn(n),typeof e==`number`?J.t(`helpers.authAuditWording.expires`,{at:Pt(e)}):null])}}case`admin-token-used`:{let e=Q(n,`path`);return{title:J.t(`helpers.authAuditWording.tokenUsed`,{name:Tn(n,t)}),details:En([Dn(n),e===null?null:J.t(`helpers.authAuditWording.call`,{path:e})])}}case`admin-token-revoked`:return{title:J.t(`helpers.authAuditWording.tokenRevoked`,{name:Tn(n,t)}),details:En([Dn(n)])};case`credentials-revoked`:{let e=Q(n,`kind`)??`credential`,t=n.revoked,r=n.skipped,i=wn[e],a=i===void 0?`${e}s`:J.t(i);return{title:J.t(`helpers.authAuditWording.bulkRevoked`,{n:typeof t==`number`?String(t):`?`,noun:a}),details:En([typeof r==`number`&&r>0?J.t(`helpers.authAuditWording.skipped`,{count:r}):null,Q(n,`ids`)===null?null:J.t(`helpers.authAuditWording.ids`,{ids:Q(n,`ids`)??``})])}}case`second-factor-skipped`:{let e=Q(n,`method`),t=Q(n,`factors`);return{title:J.t(`helpers.authAuditWording.secondFactorSkipped`),details:En([e===null?null:J.t(`helpers.authAuditWording.signedInWith`,{method:$t(e)}),t===null?null:J.t(`helpers.authAuditWording.skippedFactors`,{factors:t.split(`,`).join(`, `)})])}}default:return{title:e.action,details:Object.entries(n).map(([e,t])=>`${e}: ${String(t)}`)}}}e();var kn=100;function An(){let{user:e}=bt();return e?.isAdmin?d(Mn,{}):null}function jn(){let{data:e}=we(),{data:t}=q.auth.listAdminTokens.useQuery();return n(()=>{let n=new Map;for(let t of e??[])n.set(t.id,t.username);let r=new Map;for(let e of t??[])r.set(e.id,e.name);return{userNameById:n,tokenNameById:r}},[e,t])}function Mn(){let{t:e}=Y(),t=q.auth.listAuthAudit.useQuery({limit:kn}),{userNameById:n,tokenNameById:r}=jn(),i=t.data??[];return a(`div`,{"data-testid":`auth-audit`,className:`rounded-lg border border-border bg-surface overflow-hidden`,children:[a(`div`,{className:`px-4 py-2.5 border-b border-border flex items-center gap-2`,children:[d(D,{className:`h-3.5 w-3.5 text-foreground-subtle`}),d(`div`,{className:`text-xs font-semibold text-foreground`,children:e(`auth.authAuditSection.authenticationAudit`,`Authentication audit`)}),d(`span`,{className:`ml-auto text-[10px] text-foreground-subtle`,children:e(`auth.authAuditSection.latest`,{count:kn})})]}),d(`div`,{className:`p-4`,children:t.isLoading?a(`div`,{className:`flex items-center gap-2 text-xs text-foreground-subtle`,children:[d(S,{className:`h-3.5 w-3.5 animate-spin`}),e(`auth.authAuditSection.loadingAuditTrail`,`Loading audit trail…`)]}):t.isError?a(`div`,{className:`text-[11px] text-danger`,children:[e(`auth.authAuditSection.couldNotLoadTheAudit`,`Could not load the audit trail:`),` `,t.error?.message??e(`auth.authAuditSection.requestFailed`,`Request failed`)]}):i.length===0?a(`div`,{className:`rounded border border-dashed border-border px-3 py-4 text-xs text-foreground-subtle text-center space-y-1`,children:[d(`div`,{children:e(`auth.authAuditSection.noAuthenticationEventsRecordedYet`,`No authentication events recorded yet.`)}),d(`div`,{className:`text-[11px] text-foreground-subtle/90`,children:e(`auth.authAuditSection.thisTrailRecordsAdminToken`,`This trail records admin-token create/use/revoke, trusted-network 2FA skips, and credential revokes (including single share-link revoke). Browser password logins are not listed here.`)})]}):d(`ul`,{className:`divide-y divide-border rounded-md border border-border max-h-[420px] overflow-y-auto`,children:i.map(e=>d(Nn,{record:e,userName:e.userId===void 0?null:n.get(e.userId)??e.userId,tokenNameById:r},e.id))})})]})}function Nn({record:e,userName:t,tokenNameById:n}){let r=On(e,n);return a(`li`,{"data-testid":`auth-audit-row`,className:`px-3 py-2 text-xs space-y-0.5`,children:[a(`div`,{className:`flex flex-wrap items-baseline gap-x-2`,children:[d(`span`,{className:`font-medium text-foreground`,children:r.title}),d(`span`,{className:`ml-auto text-[10px] text-foreground-subtle`,children:Pt(e.at)})]}),a(`div`,{className:`flex flex-wrap gap-x-3 text-[11px] text-foreground-subtle`,children:[t!==null&&d(`span`,{className:`text-foreground`,children:t}),e.ip!==void 0&&d(`span`,{className:`font-mono`,children:e.ip}),r.details.map(e=>d(`span`,{className:`font-mono`,children:e},e))]})]})}e();var $={loginSessions:`login-sessions`,linkedApps:`linked-apps`,tokens:`tokens`,scopedTokens:`scoped-tokens`,adminTokens:`admin-tokens`,shareLinks:`share-links`,legacyApiKeys:`legacy-api-keys`,externalIdentities:`external-identities`,audit:`auth-audit`},Pn={[$.scopedTokens]:$.tokens,[$.adminTokens]:$.tokens};function Fn(e){return[{id:$.loginSessions,label:J.t(`system.access.loginSessions`,`Login sessions`),icon:re,render:()=>d(pn,{})},{id:$.linkedApps,label:J.t(`system.access.linkedApps`,`Linked apps`),icon:w,render:()=>d(dn,{})},{id:$.tokens,label:J.t(`system.access.tokens`,`Tokens`),icon:ne,render:({createRequested:e,onCreateConsumed:t})=>d(Cn,{headerOwnsCreate:!0,createRequested:e,onCreateConsumed:t})},{id:$.shareLinks,label:J.t(`system.access.shareLinks`,`Share links`),icon:C,render:()=>d(Rt,{})},...e?[{id:$.legacyApiKeys,label:J.t(`system.access.legacyApiKeys`,`Legacy API keys`),icon:V,render:()=>d(cn,{})}]:[],{id:$.externalIdentities,label:J.t(`system.access.signInLinks`,`Sign-in links`),icon:B,render:()=>d(sn,{})},{id:$.audit,label:J.t(`system.access.audit`,`Audit`),icon:D,render:()=>d(An,{})}]}function In({createRequested:e=!1,onCreateConsumed:t}){let{t:r}=Y(),i=je(),o=(i.data??[]).length>0,s=n(()=>Fn(o),[o]),c=en({ids:n(()=>s.map(e=>e.id),[s]),answered:!i.isLoading}),l=dt(),[u]=gt(),f=u.get(`integration`),p=f===null?void 0:Pn[f];if(p!==void 0){let e=new URLSearchParams(u);return e.set(`integration`,p),d(mt,{to:`?${e.toString()}`,replace:!0})}let m=s.find(e=>e.id===c.selectedId)??s[0],h=m?.id??$.loginSessions;return i.isLoading?d(`div`,{className:`text-sm text-foreground-subtle`,"data-testid":`access-page`,children:r(`common.loading`,`Loading…`)}):a(`div`,{className:`flex flex-col gap-3`,"data-testid":`access-page`,children:[d(Xe,{appearance:`pills`,value:h,onValueChange:e=>{l(`?${vt(u,e,null,null).toString()}`)},children:d(it,{"aria-label":r(`system.access.credentialKinds`,`Credential kinds`),children:s.map(e=>a(ot,{value:e.id,"data-testid":`access-kind-${e.id}`,children:[d(e.icon,{className:`h-3.5 w-3.5 shrink-0`,"aria-hidden":!0}),e.label]},e.id))})}),m===void 0?null:d(`div`,{role:`region`,"aria-label":m.label,"data-testid":`access-kind-body`,children:m.render({createRequested:e&&m.id===$.tokens,onCreateConsumed:t??(()=>void 0)})},m.id)]})}function Ln(e,t){if(e===$.tokens)return a(H,{size:`sm`,type:`button`,"data-testid":`access-new-token`,onClick:t,children:[d(ne,{className:`h-3.5 w-3.5`,"aria-hidden":!0}),J.t(`system.access.newToken`,`New token`)]})}function Rn(){let[e,t]=r(!1);return{createRequested:e,requestCreate:()=>t(!0),clearCreate:()=>t(!1)}}e();var zn=[`oidcConnections`,`listConnections`],Bn=[`oidcConnections`,`connectionFormSchema`],Vn={displayName:`OpenID Connect`,icon:`shield-check`,issuerUrl:``,clientId:``,clientSecret:``,redirectUri:``,scopes:`openid profile email`,usernameClaim:`preferred_username`},Hn=/^[a-z0-9][a-z0-9-]{0,63}$/;function Un(e){return e instanceof Error?e.message:String(e)}function Wn(e,t){if(t)return typeof e==`string`&&e.length>0?e:void 0;if(de(e))return se;if(typeof e==`string`&&e.length>0)return e;ae(e)}function Gn(e){return typeof e!=`object`||!e?!1:Array.isArray(Reflect.get(e,`sections`))}function Kn({addonId:e}){let{t:f}=Y(),p=ee(),m=c(),g=G(),v=Ee({editKey:`oidcEdit`,newKey:`oidcNew`}),[y,b]=r(!1),[x,S]=r(null),[C,w]=r(``),[T,E]=r({...Vn}),D=l(null),O=o({queryKey:[...zn,e],queryFn:async()=>p.trpcClient.oidcConnections.listConnections.query({})}),k=o({queryKey:Bn,queryFn:async()=>p.trpcClient.oidcConnections.connectionFormSchema.query({})}),j=O.data??[],M=Gn(k.data)?k.data:void 0,N=v.state,P=N.mode===`edit`?j.find(e=>e.id===N.id)??null:null;t(()=>{if(v.state.mode===`edit`&&P!==null){let e=`edit:${P.id}`;if(D.current===e)return;D.current=e,S(null),w(P.id),E(P.formValues===void 0?{...Vn}:{...P.formValues});return}if(v.state.mode===`new`){if(D.current===`new`)return;D.current=`new`,S(null),w(``),E({...Vn})}},[P,v.state]);let F=i(async()=>{await m.invalidateQueries({queryKey:zn})},[m]),I=u({mutationFn:async e=>p.trpcClient.oidcConnections.deleteConnection.mutate({id:e}),onSuccess:async()=>F()}),L=i(async e=>{await g({title:f(`system.oidcSettingsConnections.deleteOidcConnection`,`Delete OIDC connection?`),message:f(`system.oidcSettingsConnections.removeTheLoginButtonAnd`,`Remove “{{displayName}}” ({{id}}). The login button and IdP credentials for this id are dropped.`,{displayName:e.displayName,id:e.id}),confirmLabel:f(`common.delete`,`Delete`),cancelLabel:f(`common.cancel`,`Cancel`),variant:`danger`})&&(await I.mutateAsync(e.id),v.state.mode===`edit`&&v.state.id===e.id&&(v.close(),D.current=null))},[g,I,v,f]),te=i(async()=>{let e=T.displayName,t=typeof e==`string`&&e.trim()!==``?e.trim():``;if(t===``){S(`Display name is required.`);return}let n=v.state.mode===`new`,r=n?C.trim():P?.id??``;if(n&&!Hn.test(r)){S(`Instance id must be a lowercase slug (e.g. authentik, google).`);return}if(r===``){S(`That connection is no longer available.`);return}let i=Wn(T.clientSecret,n),a=T.icon,o=T.issuerUrl,s=T.clientId,c=T.redirectUri,l=T.scopes,u=T.usernameClaim,d=u===`email`||u===`sub`?u:`preferred_username`;b(!0),S(null);try{n&&await p.trpcClient.oidcConnections.createConnection.mutate({id:r,displayName:t}),await p.trpcClient.oidcConnections.updateConnection.mutate({id:r,displayName:t,icon:typeof a==`string`?a:`shield-check`,issuerUrl:typeof o==`string`?o:``,clientId:typeof s==`string`?s:``,...i===void 0?{}:{clientSecret:i},redirectUri:typeof c==`string`?c:``,scopes:typeof l==`string`?l:`openid profile email`,usernameClaim:d}),await F(),v.close(),D.current=null}catch(e){S(Un(e))}finally{b(!1)}},[C,P?.id,v,T,F,p]),R=n(()=>[{key:`id`,header:f(`system.oidcSettingsConnections.id`,`Id`),sortValue:e=>e.id,render:e=>d(`span`,{className:`font-mono text-xs text-foreground`,children:e.id})},{key:`name`,header:f(`system.oidcSettingsConnections.name`,`Name`),sortValue:e=>e.displayName,render:e=>d(`span`,{className:`truncate font-medium text-foreground`,children:e.displayName})},{key:`issuer`,header:f(`system.oidcSettingsConnections.issuer`,`Issuer`),sortValue:e=>e.issuerUrl,render:e=>d(`span`,{className:`truncate text-xs text-foreground-muted`,children:e.issuerUrl===``?`—`:e.issuerUrl})},{key:`status`,header:f(`system.oidcSettingsConnections.status`,`Status`),notSortableReason:`Badge`,render:e=>d(K,{variant:e.configured?`success`:`warning`,children:e.configured?f(`system.oidcSettingsConnections.configured`,`Configured`):f(`system.oidcSettingsConnections.notConfigured`,`Not configured`)})},{key:`actions`,header:``,align:`right`,notSortableReason:`Row actions`,nowrap:!0,render:e=>a(`div`,{className:`flex justify-end gap-1`,children:[d(H,{type:`button`,size:`sm`,variant:`secondary`,onClick:t=>{t.stopPropagation(),v.openEdit(e.id)},children:f(`common.edit`,`Edit`)}),d(H,{type:`button`,size:`sm`,variant:`ghost`,"aria-label":f(`system.oidcSettingsConnections.delete`,`Delete {{displayName}}`,{displayName:e.displayName}),title:f(`common.delete`,`Delete`),onClick:t=>{t.stopPropagation(),L(e)},children:d(h,{className:`h-3.5 w-3.5 text-danger`})})]})}],[v,L,f]),z=v.state.mode===`new`,B=z||P!==null,V=z?`new`:P===null?`closed`:`edit:${P.id}:${P.configured?`cfg`:`empty`}`;return a(`div`,{className:`flex min-h-0 flex-col gap-3`,"data-testid":`oidc-settings-connections`,children:[a(`div`,{className:`flex flex-wrap items-center justify-between gap-2`,children:[a(`p`,{className:`text-sm text-foreground-muted`,children:[f(`system.oidcSettingsConnections.eachRowIsOneIdp`,`Each row is one IdP. Redirect URI:`),` `,d(`span`,{className:`font-mono text-xs`,children:`/addon/${e}/<id>/callback`})]}),a(H,{type:`button`,size:`sm`,"data-testid":`oidc-connections-add`,onClick:()=>v.openNew(),children:[d(_,{className:`h-3.5 w-3.5`}),f(`system.oidcSettingsConnections.addConnection`,`Add connection`)]})]}),O.isError?d(`p`,{className:`text-sm text-danger`,children:f(`system.oidcSettingsConnections.couldNotLoadConnections`,`Could not load connections.`)}):null,O.isPending?d(lt,{className:`h-32 w-full`}):d(Ve,{columns:R,rows:j,rowKey:e=>e.id,pageSize:25,emptyMessage:f(`system.oidcSettingsConnections.noOidcConnectionsYetAdd`,`No OIDC connections yet. Add one to register an IdP.`),onRowClick:e=>v.openEdit(e.id),defaultSort:{columnKey:`name`,dir:`asc`}}),d(st,{open:B,onClose:()=>{v.close(),S(null),D.current=null},title:z?f(`system.oidcSettingsConnections.addOidcConnection`,`Add OIDC connection`):f(`system.oidcSettingsConnections.edit`,`Edit {{value1}}`,{value1:P?.displayName??`connection`}),icon:A,width:`xl`,testId:`oidc-connection-editor`,error:x,footer:d(H,{type:`button`,size:`sm`,disabled:y||z&&C.trim()===``,onClick:()=>void te(),children:y?f(`system.oidcSettingsConnections.saving`,`Saving…`):f(`common.save`,`Save`)}),children:B?a(s,{children:[z?a(`div`,{children:[d(at,{htmlFor:`oidc-connection-id`,children:f(`system.oidcSettingsConnections.instanceId`,`Instance id`)}),d(fe,{id:`oidc-connection-id`,value:C,onChange:e=>w(e.target.value),placeholder:`authentik`,autoComplete:`off`}),d(`p`,{className:`mt-1 text-xs text-foreground-muted`,children:f(`system.oidcSettingsConnections.urlSlugAppearsInAddon`,`URL slug — appears in /addon/{{addonId}}/<id>/callback. Stable; renaming is not supported after save.`,{addonId:e})})]}):a(`p`,{className:`text-sm text-foreground-muted`,children:[f(`system.oidcSettingsConnections.instanceId2`,`Instance id:`),` `,d(`span`,{className:`font-mono`,children:P?.id})]}),M===void 0?d(`p`,{className:`text-sm text-foreground-muted`,children:f(`system.oidcSettingsConnections.loadingForm`,`Loading form…`)}):d(Ye,{schema:M,values:T,onChange:E,bare:!0},V)]}):null})]})}e();var qn=3e4,Jn=[{capName:`auth-provider`,stageBadge:null},{capName:`user-passkeys`,stageBadge:`Second factor`}],Yn={titleKey:`system.authentication.sectionTitle`,subtitleKey:`system.authentication.sectionSubtitle`,emptyTitleKey:`system.authentication.sectionEmptyTitle`,emptyDescriptionKey:`system.authentication.sectionEmptyDescription`,installLabelKey:`system.authentication.sectionInstall`};function Xn(e){return e===`local-auth`?{headline:`Username + password (local).`,icon:I,bullets:[`Users live in the hub’s SQLite database — no IdP roundtrip.`,`TOTP / 2FA is self-enrolled: each user enables it from their own profile page (My access → Two-factor authentication).`,`Scoped tokens, admin tokens and sessions are managed from Identity → Tokens & sessions.`],setupSteps:[`A fresh hub has one account, admin / changeme — the first sign-in asks for a new password. Add users from Users → New user.`,`Hand out either passwords or scoped tokens — both validate through this provider.`]}:e.startsWith(`auth-oidc`)?{headline:`OpenID Connect (Google, Microsoft, Okta, Keycloak, …).`,icon:R,bullets:[`Three-leg redirect flow with PKCE S256 + nonce.`,`id_token is signature-verified against the IdP’s JWKS — no implicit trust.`,`Explicit linking only: an IdP account signs in only the CamStack user who linked it from their profile (My access → Linked sign-in accounts). No auto-created users, no email matching.`,`The linked user’s role, scopes and two-factor authentication apply.`],setupSteps:[`Register a web app at your IdP and copy the client_id + client_secret.`,"Choose a stable instance id (slug). Set the IdP redirect URI to `https://<this-hub>/addon/auth-oidc/<id>/callback`.",`In Settings below, Add connection — enter the id, issuer, and credentials; Save.`,`Each user then links their IdP account from My access → Linked sign-in accounts.`]}:e.startsWith(`auth-saml`)?{headline:`SAML 2.0 — enterprise SSO (Azure AD, Okta, OneLogin).`,icon:R,bullets:[`Heavier handshake than OIDC — exchange metadata XML with your IdP.`,`Group → role mapping via SAML assertions.`]}:e.startsWith(`auth-webauthn`)||e.startsWith(`auth-passkey`)?{headline:`Passkeys / WebAuthn — passwordless second factor.`,icon:y,bullets:[`Enroll a YubiKey, Touch ID, or platform passkey per user.`,`Resistant to phishing — the browser binds the credential to your hub origin.`]}:e.startsWith(`auth-totp`)?{headline:`TOTP — second factor.`,icon:y,bullets:[`Time-based codes from an authenticator app (RFC 6238).`,`Compatible with Google Authenticator, Aegis, 1Password, Bitwarden, etc.`]}:{headline:`Authentication provider.`,icon:A,bullets:[`Generic authentication provider. No additional hints registered.`]}}function Zn(){let{t:e}=Y(),t=ve({capName:Jn[0].capName},{refetchInterval:qn}),r=ve({capName:Jn[1].capName},{refetchInterval:qn}),i=ye(void 0,{refetchInterval:qn}),a=n(()=>on(i.data??[]),[i.data]),o=n(()=>{let e=[],n=[t.data??[],r.data??[]];for(let[t,r]of Jn.entries())for(let i of n[t]??[])e.push({addonId:i.addonId,displayName:a.get(i.addonId)??i.addonId,isActive:i.isActive,capName:r.capName,stageBadge:r.stageBadge});return e},[t.data,r.data,a]),s=[{id:`authentication`,label:e(`system.authentication.authenticationProviders`,`Authentication providers`),install:{capability:Jn[0].capName,label:e(Yn.installLabelKey)},entries:o.map(t=>({id:t.addonId,name:t.displayName,fallbackIcon:A,...an(t.isActive),render:()=>d(tn,{addonId:t.addonId,displayName:t.displayName,capName:t.capName,isActive:t.isActive,badges:t.stageBadge===null?null:d(K,{variant:`info`,className:nn,children:t.stageBadge}),settingsContent:t.addonId.startsWith(`auth-oidc`)?d(Kn,{addonId:t.addonId}):void 0,panels:[{id:`about`,title:e(`system.authentication.about`,`About`),icon:Xn(t.addonId).icon,content:d(Qn,{addonId:t.addonId})}]})}))}];return d(rn,{embedded:!0,icon:A,title:e(Yn.titleKey),subtitle:e(Yn.subtitleKey),groups:s,isLoading:t.isLoading||r.isLoading,emptyTitle:e(Yn.emptyTitleKey),emptyDescription:e(Yn.emptyDescriptionKey)})}function Qn({addonId:e}){let{t}=Y(),n=Xn(e),r=n.icon;return a(`div`,{className:`space-y-3 rounded-lg border border-border bg-surface p-2 sm:p-4`,children:[a(`div`,{className:`flex items-start gap-2.5`,children:[d(r,{className:`h-4 w-4 mt-0.5 text-primary shrink-0`}),d(`div`,{className:`text-xs text-foreground font-medium`,children:n.headline})]}),d(`ul`,{className:`text-xs text-foreground-subtle space-y-1.5 pl-6 list-disc`,children:n.bullets.map((e,t)=>d(`li`,{children:e},t))}),n.setupSteps&&n.setupSteps.length>0&&a(`div`,{className:`rounded-md border border-border bg-surface-hover/40 px-3 py-2.5 space-y-1.5`,children:[a(`div`,{className:`flex items-center gap-1.5 text-[10px] font-semibold text-foreground-subtle uppercase tracking-wide`,children:[d(L,{className:`h-3 w-3`}),t(`system.authentication.setupSteps`,`Setup steps`)]}),d(`ol`,{className:`text-xs text-foreground space-y-0.5 pl-4 list-decimal`,children:n.setupSteps.map((e,t)=>d(`li`,{children:e},t))})]}),e===`local-auth`&&a(`div`,{className:`rounded-md border border-info/30 bg-info/5 px-3 py-2 flex items-start gap-2 text-[11px] text-foreground`,children:[d(j,{className:`h-3.5 w-3.5 mt-0.5 text-info shrink-0`}),d(`span`,{children:d(pt,{i18nKey:`system.authentication.totpNote`,components:{access:d(ft,{to:`/system/my-access`,className:`underline text-primary`}),users:d(ft,{to:`/system/identity?tab=users`,className:`underline text-primary`})}})})]})]})}function $n({isPending:e,error:t,what:n,onRetry:r,children:i}){let{t:o}=Y();return e?a(`div`,{className:`flex items-center gap-2 py-3 text-xs text-foreground-subtle`,role:`status`,"data-testid":`query-pending`,children:[d(S,{className:`h-3.5 w-3.5 animate-spin`,"aria-hidden":!0}),o(`certificates.queryBody.reading`,{what:n})]}):t===null?i:d(Oe,{title:o(`certificates.queryBody.couldNotRead`,`Could not read {{what}}`,{what:n}),message:t.message.length>0?t.message:o(`certificates.queryBody.requestFailed`,`Request failed`),onRetry:r})}function er(){let{t:e}=Y(),t=q.deviceCredentials.getDeviceCaStatus.useQuery({}),n=t.data;return d(X,{id:`device-ca`,title:e(`certificates.deviceCaCard.deviceCertificateAuthority`,`Device certificate authority`),icon:A,description:e(`certificates.deviceCaCard.signsTheCertificateEachPaired`,`Signs the certificate each paired device holds. It is created the first time a device is paired.`),children:d($n,{isPending:t.isPending,error:t.error,what:`the device CA`,onRetry:()=>void t.refetch(),children:n!==void 0&&a(`div`,{className:`space-y-1`,"data-testid":`device-ca-status`,children:[a(W,{label:e(`certificates.deviceCaCard.state`,`State`),children:[n.state===`ready`&&d(K,{variant:`success`,children:e(`certificates.deviceCaCard.ready`,`Ready`)}),n.state===`absent`&&d(K,{variant:`default`,children:e(`certificates.deviceCaCard.notCreatedYet`,`Not created yet`)}),n.state===`unavailable`&&d(K,{variant:`danger`,children:e(`certificates.deviceCaCard.unavailable`,`Unavailable`)})]}),n.state===`ready`&&a(s,{children:[d(W,{label:e(`certificates.deviceCaCard.subject`,`Subject`),valueClassName:`font-mono`,children:n.subject}),d(W,{label:e(`certificates.deviceCaCard.fingerprintSha256`,`Fingerprint (SHA-256)`),valueClassName:`font-mono`,children:n.fingerprintSha256}),d(W,{label:e(`certificates.deviceCaCard.validUntil`,`Valid until`),children:n.validTo})]}),n.state===`unavailable`&&d(W,{label:e(`certificates.deviceCaCard.reason`,`Reason`),children:d(`span`,{className:`text-danger`,children:n.reason})})]})})})}e();function tr(){let{t:e}=Y(),t=q.useUtils(),n=q.deviceCredentials.getDeviceCertificateSettings.useQuery({}),[i,o]=r(null),s=q.deviceCredentials.setDeviceCertificateSettings.useMutation({onSuccess:()=>{o(null),t.deviceCredentials.getDeviceCertificateSettings.invalidate()},onError:e=>o(`Could not save: ${e.message}`)});return d($n,{isPending:n.isPending,error:n.error,what:`the enrollment setting`,onRetry:()=>void n.refetch(),children:n.data!==void 0&&a(W,{label:e(`certificates.deviceSettingsRow.usersCanEnrollTheirOwn`,`Users can enroll their own devices`),actions:d(Qe,{checked:n.data.selfEnrollment===`allowed`,disabled:s.isPending,"aria-label":e(`certificates.deviceSettingsRow.usersCanEnrollTheirOwn`,`Users can enroll their own devices`),onCheckedChange:e=>s.mutate({selfEnrollment:e?`allowed`:`disabled`})}),children:[d(`span`,{className:`text-[11px] text-foreground-subtle`,children:n.data.selfEnrollment===`allowed`?e(`certificates.deviceSettingsRow.allowedAUserSignedIn`,`Allowed - a user signed in on a phone can keep it signed in. Pairing by QR works either way.`):e(`certificates.deviceSettingsRow.disabledOnlyAnAdminCan`,`Disabled - only an admin can pair a device, with a QR code.`)}),i!==null&&d(`span`,{className:`block text-[11px] text-danger`,children:i})]})})}e();function nr(e){let[n,i]=r(()=>Date.now());return t(()=>{let t=setInterval(()=>i(Date.now()),e);return()=>clearInterval(t)},[e]),n}e();var rr=80,ir=208;function ar({open:e,users:t,onClose:n,onPaired:i}){let{t:o}=Y(),s=q.deviceCredentials.startEnrollment.useMutation({onSuccess:i}),[c,l]=r(``),[u,f]=r(``),p=c===``?t[0]?.id??``:c,m=s.data,h=()=>{s.reset(),f(``),n()};return d(st,{open:e,onClose:h,title:o(`certificates.pairDeviceDialog.pairADevice`,`Pair a device`),icon:y,width:`md`,testId:`pair-device-dialog`,error:s.isError?s.error.message:null,footer:m===void 0?a(H,{type:`button`,onClick:()=>{let e=u.trim();p===``||e===``||s.mutate({userId:p,deviceName:e})},disabled:s.isPending||p===``||u.trim()===``,children:[s.isPending&&d(S,{className:`h-3.5 w-3.5 animate-spin`,"aria-hidden":!0}),o(`certificates.pairDeviceDialog.createPairingCode`,`Create pairing code`)]}):d(H,{type:`button`,variant:`outline`,onClick:h,children:o(`certificates.pairDeviceDialog.done`,`Done`)}),children:m===void 0?a(`div`,{className:`space-y-3`,children:[d(`p`,{className:`text-[11px] text-foreground-subtle`,children:o(`certificates.pairDeviceDialog.createsAOneTimeCode`,`Creates a one-time code for a phone you hold. Scan it with the CamStack app on that phone; the phone then stays signed in as the chosen user until you revoke it.`)}),d(Se,{label:o(`certificates.pairDeviceDialog.user`,`User`),required:!0,children:d(Te,{value:p,onChange:e=>l(e.target.value),options:t.map(e=>({value:e.id,label:e.username}))})}),d(Se,{label:o(`certificates.pairDeviceDialog.deviceName`,`Device name`),required:!0,description:o(`certificates.pairDeviceDialog.howThisPhoneIsListed`,`How this phone is listed, e.g. Anna's iPhone.`),children:d(fe,{value:u,maxLength:rr,onChange:e=>f(e.target.value),placeholder:o(`certificates.pairDeviceDialog.annaSIphone`,`Anna's iPhone`),autoFocus:!0})})]}):d(or,{enrollUrl:m.enrollUrl,expiresAt:m.expiresAt,hubKnown:m.hubOrigin!==null,onRestart:()=>s.reset()})})}function or({enrollUrl:e,expiresAt:t,hubKnown:n,onRestart:r}){let{t:i}=Y(),o=Gt(t,nr(1e3));return d(`div`,{className:`space-y-3`,"data-testid":`pair-issued`,children:a(`div`,{className:`flex flex-col items-center gap-3 sm:flex-row sm:items-start`,children:[d(`div`,{className:o.expired?`opacity-30`:void 0,children:d(ge,{value:e,size:ir,alt:i(`certificates.pairDeviceDialog.enrollmentQrCode`,`Enrollment QR code`)})}),a(`div`,{className:`min-w-0 flex-1 space-y-2`,children:[d(`p`,{className:`text-[11px] text-foreground-subtle`,children:i(`certificates.pairDeviceDialog.openTheCamstackAppOn`,`Open the CamStack app on the phone and scan this code. It works once.`)}),d(`div`,{className:`text-sm font-semibold tabular-nums text-foreground`,role:`timer`,"data-testid":`pair-countdown`,children:o.expired?i(`certificates.pairDeviceDialog.expiredCreateANewCode`,`Expired - create a new code`):i(`certificates.pairDeviceDialog.expiresIn`,`Expires in {{label}}`,{label:o.label})}),a(`div`,{className:`flex items-center gap-1 rounded-md border border-border bg-background px-2 py-1`,children:[d(`code`,{className:`min-w-0 flex-1 select-all break-all font-mono text-[11px] text-foreground`,children:e}),d(xe,{value:e,srLabel:`Enrollment link`})]}),!n&&a(`div`,{className:`flex items-start gap-2 rounded-md border border-warning/30 bg-warning/5 px-2.5 py-1.5 text-[11px] text-foreground`,children:[d(g,{className:`mt-0.5 h-3.5 w-3.5 shrink-0 text-warning`,"aria-hidden":!0}),d(`span`,{children:i(`certificates.pairDeviceDialog.theHubCouldNotName`,`The hub could not name an address for this link, so the app must already know it.`)})]}),o.expired&&d(H,{type:`button`,variant:`outline`,size:`sm`,onClick:r,children:i(`certificates.pairDeviceDialog.newCode`,`New code`)})]})]})})}e();var sr={key:`access.nouns.device`};function cr({pairOpen:e,onPairClose:t}){let{t:i}=Y(),o=G(),s=Ie(),c=q.useUtils(),l=q.deviceCredentials.listDeviceCredentials.useQuery({includeRevoked:!0}),u=we(),[p,m]=r(null),[g,_]=r(null),b=()=>void c.deviceCredentials.listDeviceCredentials.invalidate(),x=e=>t=>{_(null),m(`${e} failed: ${t.message}`)},S=()=>{_(null),b()},C=q.deviceCredentials.revokeDeviceCredential.useMutation({onSuccess:S,onError:x(`Revoke`)}),w=q.deviceCredentials.deleteDeviceCredential.useMutation({onSuccess:e=>{e.result!==`deleted`&&m(e.result===`live`?`Only a revoked device can be deleted: revoke it first.`:`That device no longer exists.`),S()},onError:x(`Delete`)}),T=q.deviceCredentials.renameDeviceCredential.useMutation({onSuccess:S,onError:x(`Rename`)}),E=Bt(sr,e=>C.mutateAsync({id:e}),b),D=E.busy||g!==null,O=n(()=>(l.data??[]).map(Wt),[l.data]),k=async e=>{await o({title:i(`certificates.devicesCard.revoke`,`Revoke "{{name}}"?`,{name:e.name}),message:i(`certificates.devicesCard.sDeviceStopsSigningIn`,`{{ownerName}}'s device stops signing in at once and its open sessions end. To use it again it has to be paired anew. This cannot be undone.`,{ownerName:e.ownerName}),confirmLabel:i(`certificates.devicesCard.revoke2`,`Revoke`),variant:`danger`})&&(m(null),_(e.id),C.mutate({id:e.id}))},A=async e=>{await o({title:i(`certificates.devicesCard.delete`,`Delete "{{name}}"?`,{name:e.name}),message:i(`certificates.devicesCard.isRemovedFromTheList`,`{{name}} is removed from the list. The audit trail keeps the revocation.`,{name:e.name}),confirmLabel:i(`common.delete`,`Delete`),variant:`danger`})&&(m(null),_(e.id),w.mutate({id:e.id}))},ee=async e=>{let t=(await s({title:i(`certificates.devicesCard.renameDevice`,`Rename device`),label:i(`certificates.devicesCard.deviceName`,`Device name`),defaultValue:e.name,confirmLabel:i(`certificates.devicesCard.rename`,`Rename`)}))?.trim()??``;t===``||t===e.name||(m(null),_(e.id),T.mutate({id:e.id,deviceName:t}))},j={label:i(`certificates.devicesCard.revoke2`,`Revoke`),icon:f,isAvailable:Nt,onRun:e=>void k(e),busyId:C.isPending?g:null,disabled:D},M=[{label:i(`certificates.devicesCard.rename`,`Rename`),icon:v,tone:`neutral`,isAvailable:()=>!0,onRun:e=>void ee(e),busyId:T.isPending?g:null,disabled:D},{label:i(`common.delete`,`Delete`),icon:h,isAvailable:zt,onRun:e=>void A(e),busyId:w.isPending?g:null,disabled:D}];return a(X,{id:`devices`,title:i(`certificates.devicesCard.devices`,`Devices`),icon:y,description:i(`certificates.devicesCard.phonesThatStaySignedIn`,`Phones that stay signed in with a certificate instead of a login. Revoking one signs it out at once; it has to be paired again.`),notice:Z(p,E.outcome),onDismissNotice:()=>{m(null),E.dismiss()},children:[d(tr,{}),d(Vt,{rows:O,label:i(`certificates.devicesCard.label`),isLoading:l.isPending,loadError:l.error===null?null:l.error.message||`Request failed`,emptyText:`No devices paired yet.`,expiresNone:`—`,action:j,extraActions:M,bulk:{revoke:E,noun:sr,isSelectable:Nt}}),d(ar,{open:e,users:u.data??[],onClose:t,onPaired:b})]})}var lr={ok:`text-foreground-subtle`,soon:`text-warning`,expired:`text-danger`,unknown:`text-foreground-subtle`};function ur(e){let t=e.find(e=>e.isHub);return t?.https.kind===`served`?t.https.status.caFingerprintSha256:null}function dr({https:e,mesh:t,now:n}){let{t:r}=Y(),i=tt().data?.caCertPem??``,o=()=>{if(i===``)return;let e=URL.createObjectURL(new Blob([i],{type:`application/x-pem-file`})),t=document.createElement(`a`);t.href=e,t.download=`camstack-ca.crt`,t.click(),URL.revokeObjectURL(e)},s=t?.ca,c=s?.state===`ready`?Date.parse(s.validTo):null,l=t?.listener;return a(`div`,{className:`space-y-1`,"data-testid":`ca-strip`,children:[d(W,{label:r(`certificates.caStrip.localHttpsCaSha256`,`Local HTTPS CA (SHA-256)`),valueClassName:`font-mono`,children:a(`span`,{className:`flex flex-wrap items-center gap-2`,children:[ur(e)??`—`,i!==``&&a(H,{type:`button`,variant:`ghost`,size:`sm`,onClick:o,children:[d(p,{className:`h-3 w-3`,"aria-hidden":!0}),r(`certificates.caStrip.downloadCa`,`Download CA`)]})]})}),d(W,{label:r(`certificates.caStrip.clusterCaSha256`,`Cluster CA (SHA-256)`),valueClassName:`font-mono`,children:s===void 0?`—`:s.state===`ready`?s.fingerprintSha256:d(`span`,{className:`text-danger`,children:r(`certificates.caStrip.unavailable`,{reason:s.reason})})}),s?.state===`ready`&&a(W,{label:r(`certificates.caStrip.clusterCaValidUntil`,`Cluster CA valid until`),children:[s.validTo,c!==null&&!Number.isNaN(c)&&a(`span`,{className:`ml-2 text-[10px] ${lr[Yt(c,n)]}`,children:[`(`,Ft(c,n),`)`]})]}),l!==void 0&&a(W,{label:r(`certificates.caStrip.nodeListener`,`Node listener`),children:[l.state===`open`&&d(K,{variant:`success`,children:r(`certificates.caStrip.open`,`Open`)}),l.state===`not-started`&&d(K,{variant:`default`,children:r(`certificates.caStrip.notStarted`,`Not started`)}),l.state===`closed`&&a(`span`,{className:`text-danger`,children:[r(`certificates.caStrip.closedSince`,{when:It(l.since)}),` `,l.reason]})]}),t!==void 0&&!t.registryAvailable&&a(`div`,{className:`flex items-start gap-2 rounded-md border border-warning/30 bg-warning/5 px-2.5 py-1.5 text-[11px] text-foreground`,children:[d(g,{className:`mt-0.5 h-3.5 w-3.5 shrink-0 text-warning`,"aria-hidden":!0}),d(`span`,{children:r(`certificates.caStrip.theCertificateRegistryIsNot`,`The certificate registry is not up, so the node list below is incomplete.`)})]})]})}function fr(e,t){let n=new Set;for(let t of e)n.add(t.nodeId);for(let e of t?.nodes??[])n.add(e.nodeId);return[...n].map(n=>{let r=e.find(e=>e.nodeId===n)??null,i=t?.nodes.find(e=>e.nodeId===n)??null;return{nodeId:n,isHub:r?.isHub??n===`hub`,online:r?.online??i?.online??!1,https:r,mesh:i}}).toSorted((e,t)=>Number(t.isHub)-Number(e.isHub)||e.nodeId.localeCompare(t.nodeId))}function pr(e){return e.isHub?J.t(`helpers.nodesModel.hubReissued`):e.mesh===null?J.t(`helpers.nodesModel.noCertRecorded`):e.online?null:J.t(`helpers.certificateModel.nodeOffline`)}function mr(e){return e.isHub||e.mesh===null?pr(e):null}var hr={ok:`text-foreground-subtle`,soon:`text-warning`,expired:`text-danger`,unknown:`text-foreground-subtle`},gr={enroll:`Issued at enrollment`,renewal:`Issued by a renewal`,boot:`Issued at boot`,pairing:`Issued at pairing`,observed:`Observed on its connection`},_r={uploaded:`Uploaded`,generated:`Generated`,disabled:`Disabled`};function vr(e){let t=e.https?.https;return t?.kind===`served`?qt(t.status.validTo):null}function yr({ms:e,now:t}){return e===null?d(`span`,{className:`text-foreground-subtle`,children:`—`}):a(`div`,{children:[d(`div`,{children:It(e)}),d(`div`,{className:`text-[10px] ${hr[Yt(e,t)]}`,children:Ft(e,t)})]})}function br({row:e}){let{t}=Y(),n=e.https?.https;return n===void 0?d(`span`,{className:`text-foreground-subtle`,children:`—`}):n.kind===`served`?a(`div`,{title:n.status.leafFingerprintSha256??``,children:[d(K,{variant:`success`,className:`text-[10px]`,children:t(`certificates.nodesColumns.served`,`Served`)}),d(`span`,{className:`ml-1.5 text-[10px] text-foreground-subtle`,children:_r[n.status.mode]}),d(`div`,{className:`mt-1 font-mono text-[10px] text-foreground-subtle`,children:Kt(n.status.leafFingerprintSha256)}),n.status.sans.length>0&&d(`div`,{className:`mt-0.5 max-w-[14rem] truncate font-mono text-[10px] text-foreground-subtle`,title:n.status.sans.join(`, `),children:n.status.sans.join(`, `)})]}):a(`div`,{className:`max-w-[16rem]`,children:[d(K,{variant:n.kind===`unreadable`?`danger`:`warning`,className:`text-[10px]`,children:n.kind===`unreadable`?t(`certificates.nodesColumns.unreadable`,`Unreadable`):t(`certificates.nodesColumns.notServed`,`Not served`)}),d(`div`,{className:`mt-1 whitespace-normal text-[10px] text-foreground-subtle`,children:n.reason})]})}function xr({row:e}){let{t}=Y(),n=e.mesh?.current??null;if(n===null)return d(`span`,{className:`text-foreground-subtle`,children:t(`certificates.nodesColumns.noneRecorded`,`None recorded`)});let r=e.mesh?.history.length??0;return a(`div`,{children:[d(`div`,{className:`font-mono text-[11px]`,title:n.serial,children:Ut(n.serial)}),d(`div`,{className:`font-mono text-[10px] text-foreground-subtle`,title:n.fingerprintSha256,children:Kt(n.fingerprintSha256)}),a(`div`,{className:`text-[10px] text-foreground-subtle`,children:[gr[n.issuedVia],r>0?` · ${t(`certificates.nodesColumns.earlier`,{count:r})}`:``]})]})}function Sr({row:e}){let{t}=Y(),n=e.mesh?.presentingCurrent??null;return n===null?d(`span`,{className:`text-foreground-subtle`,children:e.online?`—`:t(`certificates.nodesColumns.offline`,`Offline`)}):n?d(K,{variant:`success`,className:`text-[10px]`,children:t(`common.yes`,`Yes`)}):d(K,{variant:`warning`,className:`text-[10px]`,children:t(`certificates.nodesColumns.noPresentsAnother`,`No - presents another`)})}function Cr(e){let t=e.icon;return a(H,{type:`button`,variant:`ghost`,size:`sm`,className:e.danger?`text-danger hover:bg-danger/10`:void 0,disabled:e.reason!==null||e.busy,title:e.reason??void 0,"aria-label":e.ariaLabel,onClick:e.onClick,children:[e.spinning?d(S,{className:`h-3 w-3 animate-spin`,"aria-hidden":!0}):d(t,{className:`h-3 w-3`,"aria-hidden":!0}),e.label]})}function wr({reasons:e}){let{t}=Y();return a(Ge,{className:`inline-block`,children:[d(Fe,{children:d(`span`,{className:`cursor-help text-[11px] text-foreground-subtle`,"data-testid":`hub-managed-note`,children:t(`certificates.nodesColumns.managedByTheHub`,`Managed by the hub`)})}),d(Je,{children:d(`ul`,{className:`max-w-xs space-y-1 whitespace-normal text-left text-[11px]`,children:e.map(e=>d(`li`,{children:e},e))})})]})}function Tr({row:e,o:t}){let{t:n}=Y(),r=e.https===null?`No HTTPS inventory for this node.`:Lt(e.https).reason,i=pr(e),o=mr(e),s=n=>t.running?.nodeId===e.nodeId&&t.running.action===n,c=t=>!e.isHub||t===null,l=[r,i,o].filter((e,t,n)=>e!==null&&n.indexOf(e)===t);return a(`div`,{className:`flex w-44 flex-col items-end gap-1`,children:[a(`div`,{className:`flex flex-col items-end gap-0.5`,children:[c(r)&&d(Cr,{label:n(`certificates.nodesColumns.renewHttps`,`Renew HTTPS`),ariaLabel:n(`certificates.nodesColumns.renewTheCertificateOn`,`Renew the certificate on {{nodeId}}`,{nodeId:e.nodeId}),icon:F,reason:r,busy:t.busy,spinning:s(`renew-https`),danger:!1,onClick:()=>t.onRenewHttps(e)}),c(i)&&d(Cr,{label:s(`renew-identity`)?n(`certificates.nodesColumns.renewing`,`Renewing…`):n(`certificates.nodesColumns.renewIdentity`,`Renew identity`),ariaLabel:n(`certificates.nodesColumns.renewTheClusterCertificateOf`,`Renew the cluster certificate of {{nodeId}}`,{nodeId:e.nodeId}),icon:F,reason:i,busy:t.busy,spinning:s(`renew-identity`),danger:!1,onClick:()=>t.onRenewIdentity(e)}),c(o)&&d(Cr,{label:n(`certificates.nodesColumns.revokeIdentity`,`Revoke identity`),ariaLabel:n(`certificates.nodesColumns.revokeTheClusterCertificateOf`,`Revoke the cluster certificate of {{nodeId}}`,{nodeId:e.nodeId}),icon:f,reason:o,busy:t.busy,spinning:s(`revoke-identity`),danger:!0,onClick:()=>t.onRevokeIdentity(e)})]}),e.isHub&&l.length>0&&d(wr,{reasons:l}),!e.isHub&&r!==null&&e.https!==null&&d(`div`,{className:`w-full whitespace-normal text-right text-[10px] text-foreground-subtle`,"data-testid":`renew-reason-${e.nodeId}`,children:r}),!e.isHub&&i!==null&&d(`div`,{className:`w-full whitespace-normal text-right text-[10px] text-foreground-subtle`,"data-testid":`mesh-reason-${e.nodeId}`,children:i})]})}function Er(e){return[{key:`node`,header:J.t(`certificates.nodesColumns.node`,`Node`),sortValue:e=>e.isHub?``:e.nodeId.toLowerCase(),render:e=>a(`div`,{className:`flex items-center gap-1.5`,children:[d(`span`,{className:`font-medium text-foreground`,children:e.nodeId}),e.nodeId===`hub`&&d(K,{variant:`info`,className:`text-[10px]`,children:J.t(`common.hubWord`)}),!e.online&&d(K,{variant:`default`,className:`text-[10px]`,children:J.t(`common.offline`)})]})},{key:`https-state`,header:`HTTPS`,notSortableReason:`A state with a reason has no single order.`,render:e=>d(br,{row:e})},{key:`https-expires`,header:J.t(`certificates.nodesColumns.httpsExpires`,`HTTPS expires`),sortValue:e=>vr(e),render:t=>d(yr,{ms:vr(t),now:e.now})},{key:`identity`,header:J.t(`certificates.nodesColumns.identity`,`Identity`),sortValue:e=>e.mesh?.current?.serial??null,render:e=>d(xr,{row:e})},{key:`identity-expires`,header:J.t(`certificates.nodesColumns.identityExpires`,`Identity expires`),sortValue:e=>e.mesh?.current?.notAfter??null,render:t=>d(yr,{ms:t.mesh?.current?.notAfter??null,now:e.now})},{key:`presenting`,header:J.t(`certificates.nodesColumns.inUse`,`In use`),notSortableReason:`Offline nodes have no answer to order by.`,render:e=>d(Sr,{row:e})},{key:`actions`,header:J.t(`certificates.nodesColumns.actions`,`Actions`),align:`right`,notSortableReason:`Actions have no order.`,render:t=>d(Tr,{row:t,o:e})}]}e();function Dr(){let{t:e}=Y(),t=G(),i=q.useUtils(),o=nr(6e4),s=q.certificates.listHttpsCertificates.useQuery(),c=q.certificates.getMeshCertificates.useQuery(),[l,u]=r(null),[f,p]=r(null),[m,h]=r(null),g=()=>{h(null),i.certificates.listHttpsCertificates.invalidate(),i.certificates.getMeshCertificates.invalidate()},_=e=>(t,n)=>{h(null),u(`${n.nodeId}: ${e} - ${t.message}`)},v=q.certificates.renewHttpsOnNode.useMutation({onSuccess:(t,n)=>{p({tone:`success`,message:t.restartRequired?e(`certificates.nodesCard.newCertificateIssuedRestartThe`,`{{nodeId}}: new certificate issued. Restart the node to serve it.`,{nodeId:n.nodeId}):e(`certificates.nodesCard.newCertificateIssuedAndServed`,`{{nodeId}}: new certificate issued and served.`,{nodeId:n.nodeId})}),g()},onError:_(`renewal failed`)}),y=q.certificates.renewMeshOnNode.useMutation({onSuccess:e=>{p(Ht(e)),g()},onError:_(`renewal could not start`)}),b=q.certificates.revokeMeshOnNode.useMutation({onSuccess:e=>{p({tone:`success`,message:`${e.nodeId}: ${e.next}`}),g()},onError:_(`revoke failed`)}),x=v.isPending||y.isPending||b.isPending,S=(e,t)=>{u(null),p(null),h({nodeId:e,action:t})},C=async n=>{await t({title:e(`certificates.nodesCard.renewHttpsOn`,`Renew HTTPS on "{{nodeId}}"?`,{nodeId:n.nodeId}),message:n.isHub?e(`certificates.nodesCard.aNewCertificateIsIssued`,`A new certificate is issued from the local CA. Clients that trust the CA keep working.`):e(`certificates.nodesCard.theNodeMakesANew`,`The node makes a new key, the hub signs it and the node switches over without a restart. It keeps serving what it had if anything fails.`),confirmLabel:e(`certificates.nodesCard.renew`,`Renew`)})&&(S(n.nodeId,`renew-https`),v.mutate({nodeId:n.nodeId}))},w=e=>{S(e.nodeId,`renew-identity`),y.mutate({nodeId:e.nodeId})},T=async n=>{await t({title:e(`certificates.nodesCard.revokeTheIdentityOf`,`Revoke the identity of "{{nodeId}}"?`,{nodeId:n.nodeId}),message:e(`certificates.nodesCard.isDisconnectedAtOnceAnd`,`{{nodeId}} is disconnected at once and can no longer connect. To bring it back it must be paired again from System → Cluster → Agents. It stays in the node list until you forget it on the Cluster page. This cannot be undone.`,{nodeId:n.nodeId}),confirmLabel:e(`certificates.nodesCard.revokeAndDisconnect`,`Revoke and disconnect`),variant:`danger`})&&(S(n.nodeId,`revoke-identity`),b.mutate({nodeId:n.nodeId}))},E=Er({now:o,busy:x,running:m,onRenewHttps:e=>void C(e),onRenewIdentity:w,onRevokeIdentity:e=>void T(e)}),D=n(()=>fr(s.data??[],c.data),[s.data,c.data]),O=c.data?.registryAvailable??!0;return d(X,{id:`nodes`,title:e(`certificates.nodesCard.nodes`,`Nodes`),icon:N,description:e(`certificates.nodesCard.eachNodeHoldsTwoCertificates`,`Each node holds two certificates: one it serves over HTTPS, one that proves its identity to the hub. Renewing replaces a certificate without a gap; revoking an identity cuts the node off until it re-joins.`),notice:Z(l,f),onDismissNotice:()=>{u(null),p(null)},children:d($n,{isPending:s.isPending||c.isPending,error:s.error??c.error,what:`node certificates`,onRetry:()=>{s.refetch(),c.refetch()},children:a(`div`,{className:`space-y-3`,children:[d(dr,{https:s.data??[],mesh:c.data,now:o}),d(Ve,{columns:E,rows:D,rowKey:e=>e.nodeId,minWidthPx:960,maxBodyHeight:Ue,defaultSort:{columnKey:`node`,dir:`asc`},emptyMessage:O?e(`certificates.nodesCard.noNodesReported`,`No nodes reported.`):e(`certificates.nodesCard.theCertificateRegistryIsNot`,`The certificate registry is not available yet, so nodes cannot be listed.`)})]})})})}function Or({pairOpen:e,onPairClose:t}){return a(`div`,{className:`space-y-4`,"data-testid":`certificates-page`,children:[d(cr,{pairOpen:e,onPairClose:t}),d(er,{}),d(Dr,{})]})}function kr({userId:e,username:t,onClose:n}){let{t:r}=Y(),i=G(),o=q.useUtils(),s=Pe({userId:e}),c=q.auth.listLoginMethods.useQuery(),l=Ce({onSuccess:()=>void o.userManagement.listExternalIdentities.invalidate()}),u=new Map((c.data??[]).flatMap(e=>e.kind===`redirect`?[[e.id,e.label]]:[])),f=s.data??[],p=async(n,a)=>{await i({title:r(`users.externalIdentitiesModal.removeSignInLink`,`Remove sign-in link`),message:r(`users.externalIdentitiesModal.willNoLongerBeAble`,`"{{username}}" will no longer be able to sign in with this {{label}} account.`,{username:t,label:a}),confirmLabel:r(`common.remove`,`Remove`),variant:`danger`})&&l.mutate({userId:e,provider:n.provider,subject:n.subject})};return d(ze,{open:!0,onOpenChange:e=>!e&&n(),children:a(_e,{width:`md`,placement:`sheet-on-mobile`,scroll:`body`,"data-testid":`external-identities-modal`,children:[d(me,{children:a(et,{className:`flex items-center gap-2`,children:[d(w,{className:`h-4 w-4 text-primary`}),a(`span`,{children:[r(`users.externalIdentitiesModal.linkedSignInAccounts`,`Linked sign-in accounts`),a(`span`,{className:`mt-0.5 block text-[11px] font-normal text-foreground-subtle`,children:[r(`users.externalIdentitiesModal.user`,`User:`),` `,d(`span`,{className:`font-mono`,children:t})]})]})]})}),d(U,{className:`space-y-3`,children:s.isLoading?a(`div`,{className:`flex items-center gap-2 text-xs text-foreground-subtle`,children:[d(S,{className:`h-3.5 w-3.5 animate-spin`}),r(`common.loading`,`Loading…`)]}):f.length===0?d(`p`,{className:`text-xs text-foreground-subtle`,children:r(`users.externalIdentitiesModal.noLinkedAccountsOnlyThe`,`No linked accounts. Only the user can link one, from their own profile page.`)}):d(`ul`,{className:`divide-y divide-border rounded-md border border-border`,children:f.map(e=>{let t=u.get(e.provider),n=t??e.provider;return a(`li`,{className:`flex items-center gap-3 px-3 py-2 text-xs`,children:[a(`div`,{className:`flex-1 min-w-0`,children:[a(`div`,{className:`font-medium text-foreground truncate`,children:[n,t===void 0&&d(`span`,{className:`ml-2 rounded bg-foreground-subtle/15 px-1.5 py-0.5 text-[10px] font-normal text-foreground-subtle`,children:r(`users.externalIdentitiesModal.providerUnavailable`,`provider unavailable`)})]}),a(`div`,{className:`text-[11px] text-foreground-subtle truncate`,children:[e.label??e.subject,` ·`,` `,new Date(e.linkedAt).toLocaleDateString()]})]}),a(`button`,{onClick:()=>void p(e,n),disabled:l.isPending,className:`inline-flex items-center gap-1 rounded px-2 py-1 text-[10px] text-danger hover:bg-danger/10 disabled:opacity-50`,children:[d(ie,{className:`h-3 w-3`}),r(`common.remove`,`Remove`)]})]},`${e.provider}|${e.subject}`)})})}),d(rt,{children:d(H,{onClick:n,variant:`secondary`,children:r(`common.close`,`Close`)})})]})})}function Ar({userId:e,username:t,onClose:n}){let{t:r}=Y(),i=c(),o=G(),l=ke({userId:e}),u=l.data,f=()=>i.invalidateQueries({queryKey:[[`userManagement`,`getTotpStatus`]]}),p=$e({onSuccess:()=>{f(),n()}}),m=l.isLoading?`loading`:u?.enabled?`enrolled`:`not_enrolled`;return d(ze,{open:!0,onOpenChange:e=>!e&&n(),children:a(_e,{width:`md`,placement:`sheet-on-mobile`,scroll:`body`,children:[d(me,{children:a(et,{className:`flex items-center gap-2`,children:[d(y,{className:`h-4 w-4 text-primary`}),a(`span`,{children:[r(`users.totpModal.twoFactorAuthentication`,`Two-factor authentication`),a(`span`,{className:`mt-0.5 block text-[11px] font-normal text-foreground-subtle`,children:[r(`users.totpModal.user`,`User:`),` `,d(`span`,{className:`font-mono`,children:t})]})]})]})}),a(U,{className:`space-y-4`,children:[m===`loading`&&a(`div`,{className:`flex items-center gap-2 text-xs text-foreground-subtle`,children:[d(S,{className:`h-3.5 w-3.5 animate-spin`}),r(`users.totpModal.checkingStatus`,`Checking status…`)]}),m===`not_enrolled`&&a(`div`,{className:`rounded-md border border-border bg-surface-hover/30 px-3 py-2.5 flex items-start gap-2.5 text-xs text-foreground-subtle`,children:[d(j,{className:`h-4 w-4 mt-0.5 shrink-0`}),a(`div`,{children:[d(`div`,{className:`font-medium text-foreground`,children:r(`users.totpModal.2faNotEnrolled`,`2FA not enrolled`)}),d(`p`,{className:`mt-0.5`,children:r(`users.totpModal.onlyTheUserThemselvesCan`,`Only the user themselves can enable 2FA, from their own profile page.`)})]})]}),m===`enrolled`&&u&&a(s,{children:[a(`div`,{className:`rounded-md border border-emerald-500/30 bg-emerald-500/5 px-3 py-2.5 flex items-start gap-2.5`,children:[d(E,{className:`h-4 w-4 mt-0.5 text-emerald-500 shrink-0`}),a(`div`,{className:`text-xs text-foreground space-y-0.5`,children:[d(`div`,{className:`font-medium`,children:r(`users.totpModal.2faIsActive`,`2FA is active`)}),d(`div`,{className:`text-foreground-subtle`,children:r(`common.enrolledOn`,{when:u.confirmedAt?new Date(u.confirmedAt).toLocaleString():`—`})})]})]}),d(`div`,{className:`text-[11px] text-foreground-subtle`,children:r(`users.totpModal.removingHereClearsTheServer`,`Removing here clears the server-side secret. Use this only when the user has lost access — they'll have to re-enroll from their profile page afterwards.`)}),a(`div`,{className:`flex justify-end gap-2`,children:[d(H,{onClick:n,variant:`secondary`,children:r(`common.close`,`Close`)}),a(`button`,{onClick:async()=>{await o({title:r(`users.totpModal.remove2fa`,`Remove 2FA`),message:r(`users.totpModal.removeTwoFactorAuthenticationFor`,`Remove two-factor authentication for "{{username}}"? They'll be able to log in with just their password until they re-enroll from their own profile page.`,{username:t}),confirmLabel:r(`common.remove`,`Remove`),variant:`danger`})&&p.mutate({userId:e})},disabled:p.isPending,className:`rounded bg-danger px-3 py-1.5 text-xs font-medium text-white hover:bg-danger/90 disabled:opacity-50 inline-flex items-center gap-1`,children:[p.isPending?d(S,{className:`h-3 w-3 animate-spin`}):d(T,{className:`h-3 w-3`}),r(`users.totpModal.remove2fa`,`Remove 2FA`)]})]})]})]})]})})}e();function jr({userId:e,username:t,onClose:n}){let{t:i}=Y(),{user:o,logout:s}=bt(),c=o?.id===e,l=G(),u=q.useUtils(),f=q.auth.listSessions.useQuery({userId:e}),[p,m]=r(null),h=q.auth.revokeSessions.useMutation({onSuccess:(e,t)=>{if(c&&`userId`in t){s();return}u.auth.listSessions.invalidate()},onError:e=>m(`Sign out failed: ${e.message}`)}),g=async e=>{await l({title:i(`users.userSessionsModal.signOutThisSession`,`Sign out this session`),message:i(`users.userSessionsModal.onWillHaveToSign`,`"{{username}}" on {{label}} will have to sign in again.`,{username:t,label:Xt(e.client).label}),confirmLabel:i(`users.userSessionsModal.signOut`,`Sign out`),variant:`danger`})&&(m(null),h.mutate({id:e.id}))},_=async()=>{await l({title:i(`users.userSessionsModal.signOutEverywhere`,`Sign out everywhere`),message:c?i(`users.userSessionsModal.everySessionOfYourOwn`,`Every session of your own account ends, this browser included: you will be signed out here too.`):i(`users.userSessionsModal.everySessionOfEndsAt`,`Every session of "{{username}}" ends at once; each device has to sign in again.`,{username:t}),confirmLabel:i(`users.userSessionsModal.signOutEverywhere`,`Sign out everywhere`),variant:`danger`})&&(m(null),h.mutate({userId:e}))},v=h.isPending&&h.variables!==void 0&&`id`in h.variables?h.variables.id:null;return d(ze,{open:!0,onOpenChange:e=>!e&&n(),children:a(_e,{width:`lg`,placement:`sheet-on-mobile`,scroll:`body`,"data-testid":`user-sessions-modal`,children:[d(me,{children:a(et,{className:`flex items-center gap-2`,children:[d(re,{className:`h-4 w-4 text-primary`}),a(`span`,{children:[i(`users.userSessionsModal.activeSessions`,`Active sessions`),a(`span`,{className:`mt-0.5 block text-[11px] font-normal text-foreground-subtle`,children:[i(`users.userSessionsModal.user`,`User:`),` `,d(`span`,{className:`font-mono`,children:t})]})]})]})}),a(U,{className:`space-y-3`,children:[p!==null&&d(`div`,{className:`text-[11px] text-danger`,children:p}),d(Jt,{sessions:f.data??[],isLoading:f.isLoading,loadError:f.isError?f.error?.message??`Request failed`:null,emptyText:`No active sessions.`,onRevoke:e=>void g(e),revokingId:v})]}),a(rt,{className:`justify-between`,children:[a(`button`,{onClick:()=>void _(),disabled:h.isPending||(f.data??[]).length===0,className:`inline-flex items-center gap-1.5 rounded border border-danger/40 px-2.5 py-1 text-[11px] font-medium text-danger hover:bg-danger/10 disabled:opacity-50`,children:[h.isPending&&v===null?d(S,{className:`h-3 w-3 animate-spin`}):d(z,{className:`h-3 w-3`}),i(`users.userSessionsModal.signOutEverywhere`,`Sign out everywhere`)]}),d(H,{onClick:n,variant:`secondary`,children:i(`common.close`,`Close`)})]})]})})}function Mr({icon:e,label:t,count:n,all:r,total:i}){let{t:o}=Y();return a(`div`,{className:`flex items-center gap-2 text-[11px]`,children:[d(e,{className:`h-3 w-3 shrink-0 text-foreground-subtle`}),d(`span`,{className:`min-w-0 flex-1 text-foreground-subtle`,children:t}),d(`span`,{className:n===0?`text-foreground-subtle`:`font-medium text-foreground`,children:n===0?o(`common.none`):r?o(`users.scopePreview.all`,`all {{total}}`,{total:i}):o(`users.scopePreview.of`,`{{count}} of {{total}}`,{count:n,total:i})})]})}function Nr({scopes:e,isAdmin:t,invalidReason:n}){let{t:r}=Y(),i=ee(),c=o({queryKey:[`auth`,`previewScope`,e,t],enabled:n===null,queryFn:()=>i.trpcClient.auth.previewScope.query({scopes:[...e],isAdmin:t})}),{data:l}=Ze({projection:`slim`}),u=l?.length??0;if(n!==null)return d(`div`,{className:`rounded-lg border border-border bg-surface p-3`,children:a(`p`,{className:`flex items-center gap-1.5 text-[11px] text-warning`,children:[d(M,{className:`h-3 w-3 shrink-0`}),n]})});if(c.isLoading)return a(`div`,{className:`flex items-center gap-2 rounded-lg border border-border bg-surface p-3 text-[11px] text-foreground-subtle`,children:[d(S,{className:`h-3 w-3 animate-spin`}),r(`users.scopePreview.resolving`,`Resolving…`)]});if(c.isError||!c.data)return d(`div`,{className:`rounded-lg border border-border bg-surface p-3`,children:d(`p`,{className:`text-[11px] text-danger`,children:r(`users.scopePreview.couldNotResolveThisGrant`,`Could not resolve this grant set. Saving is still allowed — the server enforces the same rules either way.`)})});let f=c.data;return d(`div`,{className:`space-y-2 rounded-lg border border-border bg-surface p-3`,children:f.isAdmin?a(`p`,{className:`text-[11px] text-foreground`,children:[d(`span`,{className:`font-medium`,children:r(`users.scopePreview.administrator`,`Administrator`)}),` `,r(`users.scopePreview.unrestricted`)]}):a(s,{children:[d(Mr,{icon:m,label:r(`users.scopePreview.devicesTheyCanSee`,`Devices they can see`),count:f.device.view.deviceCount,all:f.device.view.all,total:u}),d(Mr,{icon:v,label:r(`users.scopePreview.devicesTheyCanOperate`,`Devices they can operate`),count:f.device.create.deviceCount,all:f.device.create.all,total:u}),d(Mr,{icon:h,label:r(`users.scopePreview.devicesTheyCanDelete`,`Devices they can delete`),count:f.device.delete.deviceCount,all:f.device.delete.all,total:u}),a(`div`,{className:`border-t border-border pt-2 text-[10px] text-foreground-subtle`,children:[r(`users.scopePreview.systemAccess`,`System access:`),` `,f.system.length===0?r(`common.none`):f.system.join(`, `),f.capabilities.length>0&&a(s,{children:[` `,r(`users.scopePreview.extraCapabilities`,`· Extra capabilities:`),` `,f.capabilities.map(e=>e.name).join(`, `)]}),f.addons.length>0&&a(s,{children:[` `,r(`users.scopePreview.addons`,`· Addons:`),` `,f.addons.map(e=>e.name).join(`, `)]})]}),f.device.view.deviceCount===0&&d(`p`,{className:`text-[10px] text-warning`,children:r(`users.scopePreview.thisUserWillNotBe`,`This user will not be able to see any device.`)})]})})}e();function Pr(e){let t=le(e);return t===null?{mode:e.length===0?`role`:`advanced`,roleId:null,selector:{kind:`all`},includeLinked:!0,advancedScopes:e}:{mode:`role`,roleId:t.roleId,selector:t.selector,includeLinked:t.includeLinked,advancedScopes:e}}function Fr(e){return e.mode===`advanced`?e.advancedScopes:e.roleId===null?[]:oe({roleId:e.roleId,selector:e.selector,includeLinked:e.includeLinked})}function Ir({user:e,onClose:t,onSubmit:i,submitting:o,callerScopes:c}){let{t:l}=Y(),u=O(),f=n(()=>Pr(e.scopes??[]),[e.scopes]),[p,m]=r(f),[h,g]=r(f.mode===`advanced`),_=n(()=>Fr(p),[p]),v=!e.isAdmin&&p.mode===`role`&&p.roleId===null,y=n(()=>e.isAdmin?null:p.mode===`role`&&p.roleId===null?l(`users.userAccessModal.pickAccess`):Ne(_,u),[e.isAdmin,p.mode,p.roleId,_]),b=e=>m({...p,...e});return d(ze,{open:!0,onOpenChange:e=>!e&&t(),children:a(_e,{width:`lg`,placement:`sheet-on-mobile`,scroll:`body`,className:`md:max-w-2xl`,children:[a(me,{children:[d(et,{children:l(`users.userAccessModal.title`,{name:e.username})}),d(`p`,{className:`text-[10px] text-foreground-subtle`,children:e.isAdmin?l(`users.userAccessModal.thisUserIsAnAdministrator`,`This user is an administrator — they can do everything, and the settings below are ignored.`):l(`users.userAccessModal.chooseWhatTheyCanDo`,`Choose what they can do, then which devices it applies to.`)})]}),a(U,{className:`space-y-4`,children:[v&&a(`div`,{className:`flex items-start gap-2 rounded-lg border border-warning/40 bg-warning/10 p-3`,children:[d(M,{className:`mt-0.5 h-3.5 w-3.5 shrink-0 text-warning`}),a(`p`,{className:`text-[11px] text-foreground`,children:[d(`span`,{className:`font-medium`,children:l(`users.userAccessModal.noAccess`,`No access.`)}),` `,d(`span`,{className:`text-foreground-subtle`,children:l(`users.userAccessModal.cannotSeeAnything`,{name:e.username})})]})]}),a(`section`,{className:`space-y-2`,children:[d(`h3`,{className:`text-[11px] font-semibold uppercase tracking-wide text-foreground-subtle`,children:l(`users.userAccessModal.1WhatCanTheyDo`,`1 · What can they do`)}),d(`div`,{className:`space-y-1.5`,children:ce.map(e=>{let t=p.mode===`role`&&p.roleId===e.id;return a(`label`,{className:`flex cursor-pointer items-start gap-2 rounded-lg border p-2.5 transition-colors ${t?`border-primary bg-primary/5`:`border-border hover:bg-foreground-subtle/5`}`,children:[d(`input`,{type:`radio`,name:`access-role`,checked:t,onChange:()=>b({mode:`role`,roleId:e.id}),className:`mt-0.5 border-border focus:ring-primary`}),a(`span`,{className:`min-w-0`,children:[d(`span`,{className:`block text-[12px] font-medium text-foreground`,children:e.label}),d(`span`,{className:`mt-0.5 block text-[10px] text-foreground-subtle`,children:e.description})]})]},e.id)})}),a(`button`,{type:`button`,onClick:()=>{if(!h){m({...p,mode:`advanced`,advancedScopes:_}),g(!0);return}g(!1),m(Pr(p.advancedScopes))},className:`inline-flex items-center gap-1 rounded px-1.5 py-1 text-[10px] text-foreground-subtle transition-colors hover:bg-foreground-subtle/10 hover:text-foreground`,children:[d(h?P:x,{className:`h-3 w-3`}),l(`users.userAccessModal.advancedEditRawGrants`,`Advanced — edit raw grants`)]}),h&&a(`div`,{className:`rounded-lg border border-border p-2`,children:[d(`p`,{className:`mb-2 text-[10px] text-foreground-subtle`,children:l(`users.userAccessModal.oneRowPerGrantAnything`,`One row per grant. Anything expressible here overrides the three roles above; close this section to go back to them.`)}),d(Ae,{value:p.advancedScopes,onChange:e=>b({mode:`advanced`,advancedScopes:e}),clampToParent:c,emptyHint:d(s,{children:d(pt,{i18nKey:`users.userAccessModal.noGrants`,values:{name:e.username},components:{b:d(`span`,{className:`font-medium text-foreground`})}})})})]})]}),p.mode===`role`&&a(`section`,{className:`space-y-2`,children:[d(`h3`,{className:`text-[11px] font-semibold uppercase tracking-wide text-foreground-subtle`,children:l(`users.userAccessModal.2OnWhichDevices`,`2 · On which devices`)}),d(We,{value:p.selector,onChange:e=>b({selector:e}),includeLinked:p.includeLinked,onIncludeLinkedChange:e=>b({includeLinked:e}),clampToDeviceIds:null})]}),a(`section`,{className:`space-y-2`,children:[d(`h3`,{className:`text-[11px] font-semibold uppercase tracking-wide text-foreground-subtle`,children:p.mode===`role`?l(`users.userAccessModal.3Result`,`3 · Result`):l(`users.userAccessModal.2Result`,`2 · Result`)}),d(Nr,{scopes:_,isAdmin:e.isAdmin,invalidReason:y})]})]}),a(rt,{children:[d(H,{variant:`outline`,size:`sm`,onClick:t,children:l(`common.cancel`,`Cancel`)}),d(H,{size:`sm`,onClick:()=>i([..._]),disabled:o||y!==null,children:o?d(S,{className:`h-3 w-3 animate-spin`}):l(`users.userAccessModal.saveAccess`,`Save access`)})]})]})})}e();var Lr=8;function Rr(){return{username:``,password:``,isAdmin:!1}}function zr(e){return{id:e.id,username:e.username,password:``,isAdmin:e.isAdmin}}function Br(e){return e instanceof Error?e.message:J.t(`admin.system.users.genericError`)}function Vr(){let{t:e}=Y(),n=c(),{user:i}=bt(),{state:o,openNew:l,openEdit:u,close:f}=Ee(),[p,m]=r(null),[g,x]=r(null),[C,T]=r(!1),{data:E,isLoading:D,isError:O,error:k,refetch:ee}=we(),j=G(),M=Ie(),N=()=>n.invalidateQueries({queryKey:[[`userManagement`,`listUsers`]]}),P=nt(),F=pe({onSuccess:N}),I=Be({onSuccess:N}),L=De({onSuccess:N}),R=He({onSuccess:()=>{N(),B(null)}}),[z,B]=r(null),[V,ne]=r(null),[ie,ae]=r(null),[oe,se]=r(null),ce=E??[],le=o.mode===`edit`?ce.find(e=>e.id===o.id)??null:null,ue=()=>{m(null),x(null),T(!1),P.reset(),I.reset(),f()};t(()=>{if(o.mode===`closed`){m(null),x(null),T(!1);return}if(o.mode===`new`){m(e=>e!==null&&e.id===void 0?e:Rr());return}if(le===null){D||f();return}m(e=>e?.id===le.id?e:zr(le))},[o,le,D,f]);let de=o.mode===`new`&&p!==null&&p.id===void 0||o.mode===`edit`&&p!==null&&le!==null;async function me(t){await j({title:e(`admin.system.users.deleteUser`,`Delete user`),message:e(`admin.system.users.permanentlyDeleteUserThisCannot`,`Permanently delete user "{{username}}"? This cannot be undone — any active sessions will be revoked on next request.`,{username:t.username}),confirmLabel:e(`common.delete`,`Delete`),variant:`danger`})&&(F.mutate({id:t.id}),o.mode===`edit`&&o.id===t.id&&ue())}async function he(t){let n=await M({title:e(`admin.system.users.resetPassword`,`Reset password`),message:e(`admin.system.users.setANewPasswordFor`,`Set a new password for "{{username}}".`,{username:t.username}),label:e(`admin.system.users.newPassword`,`New password`),inputType:`password`,placeholder:e(`admin.system.users.atLeast8Characters`,`At least 8 characters`),confirmLabel:e(`admin.system.users.continue`,`Continue`),validate:e=>e.length<Lr?J.t(`admin.system.users.passwordTooShort`,{min:Lr}):null});n===null||n.length===0||await j({title:e(`admin.system.users.resetPassword`,`Reset password`),message:e(`admin.system.users.resetThePasswordForThey`,`Reset the password for "{{username}}"? They will need to use the new password on their next login.`,{username:t.username}),confirmLabel:e(`common.reset`,`Reset`)})&&L.mutate({id:t.id,newPassword:n})}function ge(e,t){t!==e.isAdmin&&I.mutate({id:e.id,isAdmin:t})}async function _e(){if(p!==null){x(null);try{if(p.id===void 0){let e=p.username.trim();if(e===``||p.password.length<Lr)return;await P.mutateAsync({username:e,password:p.password,isAdmin:p.isAdmin})}else await I.mutateAsync({id:p.id,isAdmin:p.isAdmin});N(),ue()}catch(e){x(Br(e))}}}function ve(e){return e?new Date(typeof e==`number`?e:String(e)).toLocaleDateString(`en-GB`,{day:`2-digit`,month:`short`,year:`numeric`}):`—`}let U=P.isPending||I.isPending,ye=p!==null&&p.id===void 0&&p.username.trim()!==``&&p.password.length>=Lr,be=p!==null&&p.id!==void 0,xe=U||(p?.id===void 0?!ye:!be),Ce=e=>F.isPending&&F.variables?.id===e.id||I.isPending&&I.variables?.id===e.id||L.isPending&&L.variables?.id===e.id,W=[{key:`username`,header:e(`admin.system.users.username`,`Username`),sortValue:e=>e.username,render:e=>d(`span`,{className:`font-medium text-foreground`,"data-testid":`user-open-${e.id}`,children:e.username})},{key:`isAdmin`,header:e(`admin.system.users.admin`,`Admin`),nowrap:!0,sortValue:e=>+!!e.isAdmin,render:t=>d(`span`,{onClick:e=>e.stopPropagation(),children:a(`label`,{className:`inline-flex items-center gap-1.5 text-xs cursor-pointer`,children:[d(`input`,{type:`checkbox`,checked:t.isAdmin,onChange:e=>ge(t,e.target.checked),disabled:Ce(t),className:`rounded border-border focus:ring-primary disabled:opacity-50`}),d(`span`,{className:t.isAdmin?`text-primary font-medium`:`text-foreground-subtle`,children:t.isAdmin?e(`admin.system.users.admin`,`Admin`):e(`admin.system.users.regular`,`Regular`)})]})})},{key:`scopes`,header:e(`admin.system.users.access`,`Access`),render:t=>t.isAdmin?d(`span`,{className:`text-[10px] italic text-foreground-subtle`,children:e(`admin.system.users.everythingAdmin`,`everything (admin)`)}):d(`span`,{onClick:e=>e.stopPropagation(),children:a(H,{size:`sm`,variant:`ghost`,type:`button`,onClick:()=>B(t),title:e(`admin.system.users.configureAccess`,`Configure access`),children:[d(A,{className:`h-3 w-3 shrink-0 text-foreground-subtle`}),d(Hr,{scopes:t.scopes??[]})]})})},{key:`createdAt`,header:e(`admin.system.users.created`,`Created`),nowrap:!0,sortValue:e=>e.createdAt,render:e=>d(`span`,{className:`text-foreground-subtle`,children:ve(e.createdAt)})},{key:`actions`,header:e(`admin.system.users.actions`,`Actions`),align:`right`,nowrap:!0,notSortableReason:`Row actions`,render:t=>{let n=Ce(t),r=t.id!==i?.id&&t.totpEnabled;return a(`div`,{className:`flex items-center justify-end gap-1`,onClick:e=>e.stopPropagation(),children:[a(H,{size:`sm`,variant:`ghost`,type:`button`,onClick:()=>u(t.id),disabled:n,title:e(`admin.system.users.editUser`,`Edit user`),children:[d(v,{className:`h-3 w-3`}),e(`common.edit`,`Edit`)]}),r&&a(H,{size:`sm`,variant:`ghost`,type:`button`,onClick:()=>ne(t),disabled:n,title:e(`admin.system.users.removeTwoFactorAuthentication`,`Remove two-factor authentication`),children:[d(y,{className:`h-3 w-3`}),e(`admin.system.users.remove2fa`,`Remove 2FA`)]}),a(H,{size:`sm`,variant:`ghost`,type:`button`,onClick:()=>ae(t),disabled:n,title:e(`admin.system.users.linkedSignInAccounts`,`Linked sign-in accounts`),children:[d(w,{className:`h-3 w-3`}),e(`admin.system.users.signInLinks`,`Sign-in links`)]}),a(H,{size:`sm`,variant:`ghost`,type:`button`,onClick:()=>se(t),disabled:n,title:e(`admin.system.users.activeSessions`,`Active sessions`),children:[d(re,{className:`h-3 w-3`}),e(`admin.system.users.sessions`,`Sessions`)]}),a(H,{size:`sm`,variant:`ghost`,type:`button`,onClick:()=>void he(t),disabled:n,title:e(`admin.system.users.resetPassword`,`Reset password`),children:[d(te,{className:`h-3 w-3`}),e(`admin.system.users.resetPassword`,`Reset password`)]}),a(H,{size:`sm`,variant:`ghost`,type:`button`,onClick:()=>void me(t),disabled:n,title:e(`admin.system.users.deleteUser`,`Delete user`),className:`text-danger hover:bg-danger/10`,children:[n?d(S,{className:`h-3 w-3 animate-spin`}):d(h,{className:`h-3 w-3`}),e(`common.delete`,`Delete`)]})]})}}];return a(St,{embedded:!0,icon:b,title:e(`admin.system.users.users`,`Users`),subtitle:e(`admin.system.users.hubAccountsAdminFlagAccess`,`Hub accounts, admin flag, access scopes, sessions and sign-in links.`),actions:a(H,{size:`sm`,type:`button`,onClick:()=>{x(null),l()},children:[d(_,{className:`h-3.5 w-3.5`}),e(`admin.system.users.createUser`,`Create User`)]}),children:[O&&d(Oe,{title:e(`admin.system.users.couldNotLoadUsers`,`Could not load users`),message:Br(k),onRetry:()=>void ee()}),D&&a(`div`,{className:`flex items-center gap-2 text-xs text-foreground-subtle`,children:[d(S,{className:`h-3.5 w-3.5 animate-spin`}),e(`common.loading`,`Loading…`)]}),!D&&!O&&ce.length===0&&d(Re,{icon:b,title:e(`admin.system.users.noUsersYet`,`No users yet`),description:e(`admin.system.users.createAUserToGrant`,`Create a user to grant hub access.`)}),!D&&!O&&ce.length>0&&d(Ve,{columns:W,rows:ce,rowKey:e=>e.id,minWidthPx:560,maxBodyHeight:`max-h-[60vh]`,onRowClick:e=>u(e.id)}),d(st,{open:de,onClose:ue,title:p?.id===void 0?e(`admin.system.users.createUser`,`Create User`):e(`admin.system.users.edit`,`Edit {{value1}}`,{value1:p.username||`user`}),icon:b,error:g,testId:`user-form-dialog`,footer:d(H,{size:`sm`,type:`button`,disabled:xe,onClick:()=>void _e(),children:U?a(s,{children:[d(S,{className:`h-3.5 w-3.5 animate-spin`}),e(`admin.system.users.saving`,`Saving…`)]}):p?.id===void 0?e(`admin.system.users.create`,`Create`):e(`common.save`,`Save`)}),children:p!==null&&a(`div`,{className:`space-y-3`,children:[d(Se,{label:e(`admin.system.users.username`,`Username`),required:p.id===void 0,children:d(fe,{value:p.username,onChange:e=>m({...p,username:e.target.value}),placeholder:`john`,autoFocus:p.id===void 0,disabled:p.id!==void 0,"data-testid":`user-form-username`})}),p.id===void 0&&d(Se,{label:e(`admin.system.users.password`,`Password`),required:!0,description:e(`admin.system.users.atLeastCharacters`,`At least {{value1}} characters.`,{value1:String(Lr)}),children:d(Ke,{value:p.password,onValueChange:e=>m({...p,password:e}),shown:C,onToggleShown:()=>T(e=>!e),placeholder:`••••••••`,"data-testid":`user-form-password`})}),d(Se,{label:e(`admin.system.users.admin`,`Admin`),description:e(`admin.system.users.unrestrictedAccessToEveryCapability`,`Unrestricted access to every capability and device.`),children:a(`label`,{className:`inline-flex items-center gap-2 text-xs text-foreground cursor-pointer`,children:[d(`input`,{type:`checkbox`,checked:p.isAdmin,onChange:e=>m({...p,isAdmin:e.target.checked}),className:`rounded border-border focus:ring-primary`,"data-testid":`user-form-admin`}),d(`span`,{children:e(`admin.system.users.admin`,`Admin`)})]})}),p.id!==void 0&&d(`p`,{className:`text-[11px] text-foreground-subtle`,children:e(`admin.system.users.toChangeThePasswordUse`,`To change the password, use Reset password on the user's row. Access scopes are configured from the Access column.`)})]})}),V&&d(Ar,{userId:V.id,username:V.username,onClose:()=>ne(null)}),ie&&d(kr,{userId:ie.id,username:ie.username,onClose:()=>ae(null)}),oe&&d(jr,{userId:oe.id,username:oe.username,onClose:()=>se(null)}),z&&d(Ur,{user:z,onClose:()=>B(null),onSubmit:e=>R.mutate({userId:z.id,scopes:e}),submitting:R.isPending})]})}function Hr({scopes:e}){let{t}=Y(),n=O();if(e.length===0)return d(`span`,{className:`font-medium text-warning`,children:t(`admin.system.users.noAccess`,`No access`)});let r=le(e);return r===null?d(`span`,{className:`text-foreground-subtle`,children:t(`users.customGrants`,{count:e.length})}):a(`span`,{className:`text-foreground-subtle`,children:[d(`span`,{className:`font-medium text-foreground`,children:ue(r.roleId).label}),` ·`,` `,qe(r.selector,n)]})}function Ur(e){let{user:t}=bt(),n=t?.isAdmin===!0;return d(Ir,{...e,callerScopes:n?null:t?.scopes??[]})}e();var Wr={"api-keys":$.tokens,"linked-sessions":$.linkedApps},Gr=`certificates`;function Kr(e){return a(H,{size:`sm`,type:`button`,"data-testid":`certificates-pair-device`,onClick:e,children:[d(y,{className:`h-3.5 w-3.5`,"aria-hidden":!0}),J.t(`systemPage.identity.pairADevice`,`Pair a device`)]})}function qr(){let{t:e}=Y(),[t]=gt(),n=Rn(),[i,a]=r(!1),o=Wr[t.get(`tab`)??``];if(o!==void 0)return d(mt,{to:_t(`access`,{integration:o}),replace:!0});let s=ut(`/system/identity`,{t:e,isAdmin:!0})??{icon:k,title:e(`system.identity`,`Identity & Access`)},c=t.get(`tab`)??`users`,l=t.get(`integration`),u=c===ht.access.tab?Ln(l,n.requestCreate):c===`certificates`?Kr(()=>a(!0)):void 0;return d(xt,{icon:s.icon,title:s.title,defaultSection:`users`,sectionsLabel:`${s.title} sections`,actions:u,sections:[{id:`users`,label:e(`systemPage.identity.users`,`Users`),content:d(Vr,{})},{id:ht.access.tab,label:e(`systemPage.identity.tokensSessions`,`Tokens & sessions`),content:d(In,{createRequested:n.createRequested,onCreateConsumed:n.clearCreate}),ownedParams:yt.concat([hn.token,hn.scoped,hn.admin])},{id:Gr,label:e(`systemPage.identity.certificates`,`Certificates`),content:d(Or,{pairOpen:i,onPairClose:()=>a(!1)})},{id:ht.authentication.tab,label:e(`systemPage.identity.authentication`,`Authentication`),content:d(Zn,{}),ownedParams:yt}]})}export{qr as IdentityPage};