@better-forge/ui 0.0.0-stage → 0.1.0

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 (234) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +13 -3
  3. package/dist/api/client.d.ts +42 -0
  4. package/dist/api/client.d.ts.map +1 -0
  5. package/dist/api/client.js +130 -0
  6. package/dist/api/client.js.map +1 -0
  7. package/dist/components/ConfirmIdentityModal.d.ts +18 -0
  8. package/dist/components/ConfirmIdentityModal.d.ts.map +1 -0
  9. package/dist/components/ConfirmIdentityModal.js +30 -0
  10. package/dist/components/ConfirmIdentityModal.js.map +1 -0
  11. package/dist/components/ConfirmModal.d.ts +18 -0
  12. package/dist/components/ConfirmModal.d.ts.map +1 -0
  13. package/dist/components/ConfirmModal.js +11 -0
  14. package/dist/components/ConfirmModal.js.map +1 -0
  15. package/dist/components/CopyButton.d.ts +11 -0
  16. package/dist/components/CopyButton.d.ts.map +1 -0
  17. package/dist/components/CopyButton.js +44 -0
  18. package/dist/components/CopyButton.js.map +1 -0
  19. package/dist/components/CredentialSection.d.ts +38 -0
  20. package/dist/components/CredentialSection.d.ts.map +1 -0
  21. package/dist/components/CredentialSection.js +69 -0
  22. package/dist/components/CredentialSection.js.map +1 -0
  23. package/dist/components/ErrorBoundary.d.ts +18 -0
  24. package/dist/components/ErrorBoundary.d.ts.map +1 -0
  25. package/dist/components/ErrorBoundary.js +25 -0
  26. package/dist/components/ErrorBoundary.js.map +1 -0
  27. package/dist/components/ModalShell.d.ts +58 -0
  28. package/dist/components/ModalShell.d.ts.map +1 -0
  29. package/dist/components/ModalShell.js +92 -0
  30. package/dist/components/ModalShell.js.map +1 -0
  31. package/dist/components/ResetTwoFactorModal.d.ts +14 -0
  32. package/dist/components/ResetTwoFactorModal.d.ts.map +1 -0
  33. package/dist/components/ResetTwoFactorModal.js +16 -0
  34. package/dist/components/ResetTwoFactorModal.js.map +1 -0
  35. package/dist/components/SidePanel.d.ts +35 -0
  36. package/dist/components/SidePanel.d.ts.map +1 -0
  37. package/dist/components/SidePanel.js +19 -0
  38. package/dist/components/SidePanel.js.map +1 -0
  39. package/dist/components/SingleSignOnModal.d.ts +4 -0
  40. package/dist/components/SingleSignOnModal.d.ts.map +1 -0
  41. package/dist/components/SingleSignOnModal.js +64 -0
  42. package/dist/components/SingleSignOnModal.js.map +1 -0
  43. package/dist/components/SsoConfirmation.d.ts +22 -0
  44. package/dist/components/SsoConfirmation.d.ts.map +1 -0
  45. package/dist/components/SsoConfirmation.js +87 -0
  46. package/dist/components/SsoConfirmation.js.map +1 -0
  47. package/dist/components/VaultTypePicker.d.ts +13 -0
  48. package/dist/components/VaultTypePicker.d.ts.map +1 -0
  49. package/dist/components/VaultTypePicker.js +62 -0
  50. package/dist/components/VaultTypePicker.js.map +1 -0
  51. package/dist/components/WhatsNew.d.ts +18 -0
  52. package/dist/components/WhatsNew.d.ts.map +1 -0
  53. package/dist/components/WhatsNew.js +50 -0
  54. package/dist/components/WhatsNew.js.map +1 -0
  55. package/dist/components/formParts.d.ts +40 -0
  56. package/dist/components/formParts.d.ts.map +1 -0
  57. package/dist/components/formParts.js +31 -0
  58. package/dist/components/formParts.js.map +1 -0
  59. package/dist/components/ui/Alert.d.ts +15 -0
  60. package/dist/components/ui/Alert.d.ts.map +1 -0
  61. package/dist/components/ui/Alert.js +14 -0
  62. package/dist/components/ui/Alert.js.map +1 -0
  63. package/dist/components/ui/DataTable.d.ts +52 -0
  64. package/dist/components/ui/DataTable.d.ts.map +1 -0
  65. package/dist/components/ui/DataTable.js +112 -0
  66. package/dist/components/ui/DataTable.js.map +1 -0
  67. package/dist/components/ui/Field.d.ts +27 -0
  68. package/dist/components/ui/Field.d.ts.map +1 -0
  69. package/dist/components/ui/Field.js +25 -0
  70. package/dist/components/ui/Field.js.map +1 -0
  71. package/dist/components/ui/PageHeader.d.ts +18 -0
  72. package/dist/components/ui/PageHeader.d.ts.map +1 -0
  73. package/dist/components/ui/PageHeader.js +10 -0
  74. package/dist/components/ui/PageHeader.js.map +1 -0
  75. package/dist/components/ui/Pagination.d.ts +21 -0
  76. package/dist/components/ui/Pagination.d.ts.map +1 -0
  77. package/dist/components/ui/Pagination.js +10 -0
  78. package/dist/components/ui/Pagination.js.map +1 -0
  79. package/dist/components/ui/QuickStartPanel.d.ts +21 -0
  80. package/dist/components/ui/QuickStartPanel.d.ts.map +1 -0
  81. package/dist/components/ui/QuickStartPanel.js +11 -0
  82. package/dist/components/ui/QuickStartPanel.js.map +1 -0
  83. package/dist/components/ui/StateViews.d.ts +50 -0
  84. package/dist/components/ui/StateViews.d.ts.map +1 -0
  85. package/dist/components/ui/StateViews.js +33 -0
  86. package/dist/components/ui/StateViews.js.map +1 -0
  87. package/dist/components/ui/Switch.d.ts +16 -0
  88. package/dist/components/ui/Switch.d.ts.map +1 -0
  89. package/dist/components/ui/Switch.js +15 -0
  90. package/dist/components/ui/Switch.js.map +1 -0
  91. package/dist/components/ui/Toast.d.ts +20 -0
  92. package/dist/components/ui/Toast.d.ts.map +1 -0
  93. package/dist/components/ui/Toast.js +46 -0
  94. package/dist/components/ui/Toast.js.map +1 -0
  95. package/dist/components/ui/index.d.ts +10 -0
  96. package/dist/components/ui/index.d.ts.map +1 -0
  97. package/dist/components/ui/index.js +10 -0
  98. package/dist/components/ui/index.js.map +1 -0
  99. package/dist/config.d.ts +46 -0
  100. package/dist/config.d.ts.map +1 -0
  101. package/dist/config.js +12 -0
  102. package/dist/config.js.map +1 -0
  103. package/dist/hooks/useDarkMode.d.ts +2 -0
  104. package/dist/hooks/useDarkMode.d.ts.map +1 -0
  105. package/dist/hooks/useDarkMode.js +37 -0
  106. package/dist/hooks/useDarkMode.js.map +1 -0
  107. package/dist/hooks/useDebouncedValue.d.ts +6 -0
  108. package/dist/hooks/useDebouncedValue.d.ts.map +1 -0
  109. package/dist/hooks/useDebouncedValue.js +19 -0
  110. package/dist/hooks/useDebouncedValue.js.map +1 -0
  111. package/dist/index.d.ts +36 -0
  112. package/dist/index.d.ts.map +1 -0
  113. package/dist/index.js +36 -0
  114. package/dist/index.js.map +1 -0
  115. package/dist/lib/changelog.d.ts +32 -0
  116. package/dist/lib/changelog.d.ts.map +1 -0
  117. package/dist/lib/changelog.js +71 -0
  118. package/dist/lib/changelog.js.map +1 -0
  119. package/dist/lib/dateFormat.d.ts +24 -0
  120. package/dist/lib/dateFormat.d.ts.map +1 -0
  121. package/dist/lib/dateFormat.js +67 -0
  122. package/dist/lib/dateFormat.js.map +1 -0
  123. package/dist/navigation.d.ts +5 -0
  124. package/dist/navigation.d.ts.map +1 -0
  125. package/dist/navigation.js +7 -0
  126. package/dist/navigation.js.map +1 -0
  127. package/dist/pages/ApiTokens.d.ts +9 -0
  128. package/dist/pages/ApiTokens.d.ts.map +1 -0
  129. package/dist/pages/ApiTokens.js +116 -0
  130. package/dist/pages/ApiTokens.js.map +1 -0
  131. package/dist/pages/AuditLog.d.ts +14 -0
  132. package/dist/pages/AuditLog.d.ts.map +1 -0
  133. package/dist/pages/AuditLog.js +104 -0
  134. package/dist/pages/AuditLog.js.map +1 -0
  135. package/dist/pages/Backups.d.ts +2 -0
  136. package/dist/pages/Backups.d.ts.map +1 -0
  137. package/dist/pages/Backups.js +118 -0
  138. package/dist/pages/Backups.js.map +1 -0
  139. package/dist/pages/ChangePassword.d.ts +2 -0
  140. package/dist/pages/ChangePassword.d.ts.map +1 -0
  141. package/dist/pages/ChangePassword.js +40 -0
  142. package/dist/pages/ChangePassword.js.map +1 -0
  143. package/dist/pages/Login.d.ts +12 -0
  144. package/dist/pages/Login.d.ts.map +1 -0
  145. package/dist/pages/Login.js +69 -0
  146. package/dist/pages/Login.js.map +1 -0
  147. package/dist/pages/Settings.d.ts +2 -0
  148. package/dist/pages/Settings.d.ts.map +1 -0
  149. package/dist/pages/Settings.js +149 -0
  150. package/dist/pages/Settings.js.map +1 -0
  151. package/dist/pages/Setup.d.ts +2 -0
  152. package/dist/pages/Setup.d.ts.map +1 -0
  153. package/dist/pages/Setup.js +113 -0
  154. package/dist/pages/Setup.js.map +1 -0
  155. package/dist/pages/SsoConfirm.d.ts +3 -0
  156. package/dist/pages/SsoConfirm.d.ts.map +1 -0
  157. package/dist/pages/SsoConfirm.js +20 -0
  158. package/dist/pages/SsoConfirm.js.map +1 -0
  159. package/dist/pages/SsoTestResult.d.ts +2 -0
  160. package/dist/pages/SsoTestResult.d.ts.map +1 -0
  161. package/dist/pages/SsoTestResult.js +49 -0
  162. package/dist/pages/SsoTestResult.js.map +1 -0
  163. package/dist/pages/Users.d.ts +22 -0
  164. package/dist/pages/Users.d.ts.map +1 -0
  165. package/dist/pages/Users.js +124 -0
  166. package/dist/pages/Users.js.map +1 -0
  167. package/dist/pages/Vault.d.ts +2 -0
  168. package/dist/pages/Vault.d.ts.map +1 -0
  169. package/dist/pages/Vault.js +372 -0
  170. package/dist/pages/Vault.js.map +1 -0
  171. package/dist/shell/AdminShell.d.ts +46 -0
  172. package/dist/shell/AdminShell.d.ts.map +1 -0
  173. package/dist/shell/AdminShell.js +76 -0
  174. package/dist/shell/AdminShell.js.map +1 -0
  175. package/dist/shell/guards.d.ts +28 -0
  176. package/dist/shell/guards.d.ts.map +1 -0
  177. package/dist/shell/guards.js +71 -0
  178. package/dist/shell/guards.js.map +1 -0
  179. package/icons.json +170 -0
  180. package/package.json +71 -6
  181. package/src/api/client.ts +139 -0
  182. package/src/components/ConfirmIdentityModal.tsx +82 -0
  183. package/src/components/ConfirmModal.tsx +83 -0
  184. package/src/components/CopyButton.tsx +71 -0
  185. package/src/components/CredentialSection.tsx +270 -0
  186. package/src/components/ErrorBoundary.tsx +47 -0
  187. package/src/components/ModalShell.tsx +160 -0
  188. package/src/components/ResetTwoFactorModal.tsx +74 -0
  189. package/src/components/SidePanel.tsx +96 -0
  190. package/src/components/SingleSignOnModal.tsx +161 -0
  191. package/src/components/SsoConfirmation.tsx +96 -0
  192. package/src/components/VaultTypePicker.tsx +131 -0
  193. package/src/components/WhatsNew.tsx +84 -0
  194. package/src/components/formParts.tsx +59 -0
  195. package/src/components/ui/Alert.tsx +45 -0
  196. package/src/components/ui/DataTable.tsx +225 -0
  197. package/src/components/ui/Field.tsx +63 -0
  198. package/src/components/ui/PageHeader.tsx +36 -0
  199. package/src/components/ui/Pagination.tsx +37 -0
  200. package/src/components/ui/QuickStartPanel.tsx +57 -0
  201. package/src/components/ui/StateViews.tsx +135 -0
  202. package/src/components/ui/Switch.tsx +65 -0
  203. package/src/components/ui/Toast.tsx +104 -0
  204. package/src/components/ui/index.ts +9 -0
  205. package/src/config.ts +46 -0
  206. package/src/hooks/useDarkMode.ts +38 -0
  207. package/src/hooks/useDebouncedValue.ts +22 -0
  208. package/src/index.ts +42 -0
  209. package/src/lib/changelog.ts +93 -0
  210. package/src/lib/dateFormat.ts +90 -0
  211. package/src/navigation.ts +6 -0
  212. package/src/pages/ApiTokens.tsx +361 -0
  213. package/src/pages/AuditLog.tsx +388 -0
  214. package/src/pages/Backups.tsx +170 -0
  215. package/src/pages/ChangePassword.tsx +84 -0
  216. package/src/pages/Login.tsx +275 -0
  217. package/src/pages/Settings.tsx +264 -0
  218. package/src/pages/Setup.tsx +438 -0
  219. package/src/pages/SsoConfirm.tsx +26 -0
  220. package/src/pages/SsoTestResult.tsx +104 -0
  221. package/src/pages/Users.tsx +426 -0
  222. package/src/pages/Vault.tsx +1036 -0
  223. package/src/shell/AdminShell.tsx +256 -0
  224. package/src/shell/guards.tsx +88 -0
  225. package/styles/base.css +521 -0
  226. package/styles/fonts/inter-latin-1.woff2 +0 -0
  227. package/styles/fonts/inter-latin-ext.woff2 +0 -0
  228. package/styles/fonts/jetbrains-mono-latin-3.woff2 +0 -0
  229. package/styles/fonts/jetbrains-mono-latin-ext-2.woff2 +0 -0
  230. package/styles/fonts/manrope-latin-5.woff2 +0 -0
  231. package/styles/fonts/manrope-latin-ext-4.woff2 +0 -0
  232. package/styles/fonts/material-symbols-outlined-fill.woff2 +0 -0
  233. package/styles/icons.css +4 -0
  234. package/tailwind-preset.js +79 -0
@@ -0,0 +1,58 @@
1
+ import { type ReactNode } from 'react';
2
+ export declare const MODAL_Z_INDEX = 9999;
3
+ export declare const MODAL_BACKDROP = "rgba(0,0,0,0.55)";
4
+ interface ModalShellProps {
5
+ children: ReactNode;
6
+ /** `top` for tall forms that scroll with the page; `center` for short dialogs. */
7
+ align?: 'center' | 'top' | undefined;
8
+ /** Called when Escape is pressed while this is the topmost modal. Omit to ignore Escape. */
9
+ onClose?: (() => void) | undefined;
10
+ /**
11
+ * Accessible name. When given (or `labelledBy`), the shell itself carries role="dialog" aria-modal —
12
+ * use it when the child panel does not declare its own dialog role.
13
+ */
14
+ label?: string | undefined;
15
+ labelledBy?: string | undefined;
16
+ }
17
+ /**
18
+ * The single backdrop every admin modal renders through, so stacking, dimming and dismissal stay consistent:
19
+ * portalled to <body>, z-index 9999, a 55% black scrollable overlay, and a backdrop click never dismisses —
20
+ * a stray click must not throw away a half-filled form. Dialogs close from their own Cancel / × controls and,
21
+ * when `onClose` is passed, from Escape. Focus moves into the modal on open (unless a child already took it
22
+ * with autoFocus), Tab cycles inside it, and focus returns to the opener on close.
23
+ */
24
+ export declare function ModalShell({ children, align, onClose, label, labelledBy }: ModalShellProps): import("react").ReactPortal;
25
+ interface ModalHeaderProps {
26
+ /** Material Symbols name shown before the title, so every dialog header reads the same way. */
27
+ icon: string;
28
+ title: ReactNode;
29
+ /** Id for the heading, when the dialog is labelled by it via `labelledBy`. */
30
+ titleId?: string | undefined;
31
+ onClose: () => void;
32
+ }
33
+ /** The shared dialog header: icon, title and a × close button above a divider. */
34
+ export declare function ModalHeader({ icon, title, titleId, onClose }: ModalHeaderProps): import("react").JSX.Element;
35
+ interface ModalProps {
36
+ title: string;
37
+ /** Material Symbols name for the header icon. */
38
+ icon: string;
39
+ onClose: () => void;
40
+ children: ReactNode;
41
+ /** Skip the padded body so the content can bring its own, with a `ModalFooter` as the last thing in the dialog. */
42
+ flush?: boolean;
43
+ }
44
+ /** A titled panel with a close button inside the shared shell, for simple forms. Escape closes it. */
45
+ export declare function Modal({ title, icon, onClose, children, flush }: ModalProps): import("react").JSX.Element;
46
+ interface ModalFooterProps {
47
+ /** Secondary tools on the left (YAML, Test, …); kept apart from the buttons that close or save. */
48
+ start?: ReactNode;
49
+ /** Cancel and the main action, on the right. */
50
+ children: ReactNode;
51
+ }
52
+ /**
53
+ * The action row of a form dialog, stuck to the bottom of the screen while the form scrolls, so the main
54
+ * action never hides below the fold. Put it last inside the `<form>`, after the padded body.
55
+ */
56
+ export declare function ModalFooter({ start, children }: ModalFooterProps): import("react").JSX.Element;
57
+ export {};
58
+ //# sourceMappingURL=ModalShell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModalShell.d.ts","sourceRoot":"","sources":["../../src/components/ModalShell.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAGnE,eAAO,MAAM,aAAa,OAAO,CAAA;AACjC,eAAO,MAAM,cAAc,qBAAqB,CAAA;AAOhD,UAAU,eAAe;IACvB,QAAQ,EAAE,SAAS,CAAA;IACnB,kFAAkF;IAClF,KAAK,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,SAAS,CAAA;IACpC,4FAA4F;IAC5F,OAAO,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAA;IAClC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAChC;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,KAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,eAAe,+BA0DrG;AAED,UAAU,gBAAgB;IACxB,+FAA+F;IAC/F,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,SAAS,CAAA;IAChB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,gBAAgB,+BAY9E;AAED,UAAU,UAAU;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,QAAQ,EAAE,SAAS,CAAA;IACnB,mHAAmH;IACnH,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,sGAAsG;AACtG,wBAAgB,KAAK,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,UAAU,+BAS1E;AAED,UAAU,gBAAgB;IACxB,mGAAmG;IACnG,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,gDAAgD;IAChD,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+BAUhE"}
@@ -0,0 +1,92 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { createPortal } from 'react-dom';
4
+ export const MODAL_Z_INDEX = 9999;
5
+ export const MODAL_BACKDROP = 'rgba(0,0,0,0.55)';
6
+ const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
7
+ /** Open shells, innermost last: only the top one reacts to Escape and traps Tab. */
8
+ const openShells = [];
9
+ /**
10
+ * The single backdrop every admin modal renders through, so stacking, dimming and dismissal stay consistent:
11
+ * portalled to <body>, z-index 9999, a 55% black scrollable overlay, and a backdrop click never dismisses —
12
+ * a stray click must not throw away a half-filled form. Dialogs close from their own Cancel / × controls and,
13
+ * when `onClose` is passed, from Escape. Focus moves into the modal on open (unless a child already took it
14
+ * with autoFocus), Tab cycles inside it, and focus returns to the opener on close.
15
+ */
16
+ export function ModalShell({ children, align = 'center', onClose, label, labelledBy }) {
17
+ const panelRef = useRef(null);
18
+ const onCloseRef = useRef(onClose);
19
+ useEffect(() => { onCloseRef.current = onClose; });
20
+ // Read during the first render, before an autoFocus child steals focus, so closing returns it to the opener.
21
+ const [opener] = useState(() => document.activeElement instanceof HTMLElement ? document.activeElement : null);
22
+ useEffect(() => {
23
+ const panel = panelRef.current;
24
+ if (!panel)
25
+ return;
26
+ openShells.push(panel);
27
+ if (!panel.contains(document.activeElement))
28
+ panel.focus({ preventScroll: true });
29
+ const onKeyDown = (event) => {
30
+ if (openShells[openShells.length - 1] !== panel || event.defaultPrevented || event.isComposing)
31
+ return;
32
+ if (event.key === 'Escape') {
33
+ if (onCloseRef.current) {
34
+ event.preventDefault();
35
+ onCloseRef.current();
36
+ }
37
+ return;
38
+ }
39
+ if (event.key !== 'Tab')
40
+ return;
41
+ const focusable = Array.from(panel.querySelectorAll(FOCUSABLE));
42
+ if (focusable.length === 0) {
43
+ event.preventDefault();
44
+ return;
45
+ }
46
+ const first = focusable[0];
47
+ const last = focusable[focusable.length - 1];
48
+ const active = document.activeElement;
49
+ if (event.shiftKey && (active === first || active === panel)) {
50
+ event.preventDefault();
51
+ last.focus();
52
+ }
53
+ else if (!event.shiftKey && active === last) {
54
+ event.preventDefault();
55
+ first.focus();
56
+ }
57
+ else if (!panel.contains(active)) {
58
+ event.preventDefault();
59
+ first.focus();
60
+ }
61
+ };
62
+ document.addEventListener('keydown', onKeyDown);
63
+ return () => {
64
+ document.removeEventListener('keydown', onKeyDown);
65
+ const index = openShells.lastIndexOf(panel);
66
+ if (index >= 0)
67
+ openShells.splice(index, 1);
68
+ if (opener && opener.isConnected)
69
+ opener.focus({ preventScroll: true });
70
+ };
71
+ }, [opener]);
72
+ const dialogProps = label || labelledBy
73
+ ? { role: 'dialog', 'aria-modal': true, 'aria-label': label, 'aria-labelledby': labelledBy }
74
+ : {};
75
+ return createPortal(_jsx("div", { "data-testid": "modal-backdrop", style: { position: 'fixed', inset: 0, zIndex: MODAL_Z_INDEX, background: MODAL_BACKDROP, overflowY: 'auto' }, children: _jsx("div", { ref: panelRef, tabIndex: -1, ...dialogProps, style: { display: 'flex', minHeight: '100%', alignItems: align === 'top' ? 'flex-start' : 'center', justifyContent: 'center', padding: '16px', outline: 'none' }, children: children }) }), document.body);
76
+ }
77
+ /** The shared dialog header: icon, title and a × close button above a divider. */
78
+ export function ModalHeader({ icon, title, titleId, onClose }) {
79
+ return (_jsxs("div", { className: "flex items-center justify-between gap-3 px-6 py-5", style: { borderBottom: '1px solid var(--m3-outline-variant)' }, children: [_jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [_jsx("span", { className: "material-symbols-outlined shrink-0", "aria-hidden": "true", style: { fontSize: '22px', color: 'var(--m3-primary)' }, children: icon }), _jsx("h3", { id: titleId, className: "font-headline font-bold text-lg break-words min-w-0", style: { color: 'var(--m3-on-surface)' }, children: title })] }), _jsx("button", { type: "button", onClick: onClose, "aria-label": "Close", className: "btn-icon shrink-0", children: _jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: "close" }) })] }));
80
+ }
81
+ /** A titled panel with a close button inside the shared shell, for simple forms. Escape closes it. */
82
+ export function Modal({ title, icon, onClose, children, flush }) {
83
+ return (_jsx(ModalShell, { onClose: onClose, children: _jsxs("div", { role: "dialog", "aria-modal": "true", "aria-label": title, className: "w-full max-w-md rounded-2xl", style: { background: 'var(--m3-surface-container-low)', border: '1px solid var(--m3-outline-variant)' }, children: [_jsx(ModalHeader, { icon: icon, title: title, onClose: onClose }), flush ? children : _jsx("div", { className: "p-6", children: children })] }) }));
84
+ }
85
+ /**
86
+ * The action row of a form dialog, stuck to the bottom of the screen while the form scrolls, so the main
87
+ * action never hides below the fold. Put it last inside the `<form>`, after the padded body.
88
+ */
89
+ export function ModalFooter({ start, children }) {
90
+ return (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 px-4 sm:px-6 py-4", style: { position: 'sticky', bottom: 0, zIndex: 1, background: 'var(--m3-surface-container-low)', borderTop: '1px solid var(--m3-outline-variant)', borderRadius: '0 0 15px 15px' }, children: [start ? _jsx("div", { className: "flex flex-wrap gap-3", children: start }) : _jsx("span", {}), _jsx("div", { className: "flex flex-wrap gap-3 ml-auto", children: children })] }));
91
+ }
92
+ //# sourceMappingURL=ModalShell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModalShell.js","sourceRoot":"","sources":["../../src/components/ModalShell.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { type SsoConfirmationState } from './SsoConfirmation';
2
+ interface ResetTwoFactorModalProps {
3
+ email: string;
4
+ pending: boolean;
5
+ error?: string;
6
+ /** Progress of the SSO confirmation popup, which `onConfirm` opens when the API asks for it. */
7
+ ssoConfirmation?: Pick<SsoConfirmationState, 'waiting' | 'cancel'>;
8
+ /** `currentPassword` is undefined in an SSO session, which is confirmed at the identity provider instead. */
9
+ onConfirm: (currentPassword: string | undefined) => void;
10
+ onCancel: () => void;
11
+ }
12
+ export declare function ResetTwoFactorModal({ email, pending, error, ssoConfirmation, onConfirm, onCancel }: ResetTwoFactorModalProps): import("react").JSX.Element;
13
+ export {};
14
+ //# sourceMappingURL=ResetTwoFactorModal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResetTwoFactorModal.d.ts","sourceRoot":"","sources":["../../src/components/ResetTwoFactorModal.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAqC,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAKhG,UAAU,wBAAwB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gGAAgG;IAChG,eAAe,CAAC,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,GAAG,QAAQ,CAAC,CAAA;IAClE,6GAA6G;IAC7G,SAAS,EAAE,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAA;IACxD,QAAQ,EAAE,MAAM,IAAI,CAAA;CACrB;AAED,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,eAAmC,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,wBAAwB,+BAuDhJ"}
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useId, useState } from 'react';
3
+ import { ModalFooter, ModalHeader, ModalShell } from './ModalShell';
4
+ import { SsoConfirmationNote, isSsoSession } from './SsoConfirmation';
5
+ import { Alert } from './ui';
6
+ const IDLE_CONFIRMATION = { waiting: false, cancel: () => { } };
7
+ export function ResetTwoFactorModal({ email, pending, error, ssoConfirmation = IDLE_CONFIRMATION, onConfirm, onCancel }) {
8
+ const sso = isSsoSession();
9
+ const [currentPassword, setCurrentPassword] = useState('');
10
+ const [typedEmail, setTypedEmail] = useState('');
11
+ const titleId = useId();
12
+ const canConfirm = !pending && (sso || currentPassword.length > 0) && typedEmail === email;
13
+ const confirm = () => onConfirm(sso ? undefined : currentPassword);
14
+ return (_jsx(ModalShell, { onClose: pending ? undefined : onCancel, children: _jsxs("div", { role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, "aria-busy": pending || undefined, className: "rounded-2xl w-full max-w-md", style: { background: 'var(--m3-surface-container-low)', border: '1px solid var(--m3-outline-variant)' }, children: [_jsx(ModalHeader, { icon: "shield_lock", titleId: titleId, title: "Reset two-factor authentication", onClose: pending ? () => { } : onCancel }), _jsxs("div", { className: "p-6 space-y-4", children: [_jsx("div", { children: _jsxs("p", { className: "text-sm", style: { color: 'var(--m3-on-surface-variant)' }, children: ["This removes 2FA from ", _jsx("strong", { children: email }), " and signs out all of their active sessions. Their password will not change."] }) }), error && _jsx(Alert, { tone: "error", children: error }), sso ? _jsx(SsoConfirmationNote, { confirmation: ssoConfirmation }) : _jsxs("label", { className: "block space-y-2 text-sm", style: { color: 'var(--m3-on-surface-variant)' }, children: [_jsx("span", { children: "Your current password" }), _jsx("input", { type: "password", autoComplete: "current-password", autoFocus: true, value: currentPassword, onChange: (event) => setCurrentPassword(event.target.value), className: "input-sig w-full" })] }), _jsxs("label", { className: "block space-y-2 text-sm", style: { color: 'var(--m3-on-surface-variant)' }, children: [_jsxs("span", { children: ["Type ", _jsx("strong", { className: "font-mono", children: email }), " to confirm"] }), _jsx("input", { value: typedEmail, onChange: (event) => setTypedEmail(event.target.value), autoFocus: sso, autoComplete: "off", spellCheck: false, className: "input-sig w-full" })] })] }), _jsxs(ModalFooter, { children: [_jsx("button", { type: "button", onClick: onCancel, disabled: pending, className: "btn btn-secondary", children: "Cancel" }), _jsx("button", { type: "button", onClick: confirm, disabled: !canConfirm, className: "btn btn-danger-outline", children: pending ? 'Resetting…' : 'Reset 2FA' })] })] }) }));
15
+ }
16
+ //# sourceMappingURL=ResetTwoFactorModal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResetTwoFactorModal.js","sourceRoot":"","sources":["../../src/components/ResetTwoFactorModal.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface SidePanelMeta {
3
+ icon: string;
4
+ label: string;
5
+ }
6
+ /**
7
+ * `row`: the panel always sits to the right of the form (legacy layout).
8
+ * `responsive`: below the lg breakpoint the panel spans the full width under the form with a top border;
9
+ * from lg up it becomes the right-hand column. The parent must switch to `flex-col lg:flex-row` to match.
10
+ */
11
+ export type SidePanelLayout = 'row' | 'responsive';
12
+ /** The expandable column to the right of a modal form: a title bar and the open section. */
13
+ export declare function SidePanelFrame({ meta, children, layout, className }: {
14
+ meta: SidePanelMeta;
15
+ children: ReactNode;
16
+ layout?: SidePanelLayout | undefined;
17
+ className?: string | undefined;
18
+ }): import("react").JSX.Element;
19
+ export interface SideTab<K extends string> {
20
+ key: K;
21
+ badge: string | null;
22
+ }
23
+ /**
24
+ * The icon strip that opens and closes side-panel sections: vertical at the form's right edge, or with
25
+ * `layout="responsive"` a horizontal row below lg and the vertical strip from lg up.
26
+ */
27
+ export declare function SideTabStrip<K extends string>({ tabs, meta, active, onToggle, layout, className }: {
28
+ tabs: SideTab<K>[];
29
+ meta: Record<K, SidePanelMeta>;
30
+ active: K | null;
31
+ onToggle: (key: K | null) => void;
32
+ layout?: SidePanelLayout | undefined;
33
+ className?: string | undefined;
34
+ }): import("react").JSX.Element;
35
+ //# sourceMappingURL=SidePanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SidePanel.d.ts","sourceRoot":"","sources":["../../src/components/SidePanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,MAAM,WAAW,aAAa;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AAE9D;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,YAAY,CAAA;AAElD,4FAA4F;AAC5F,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAc,EAAE,SAAS,EAAE,EAAE;IAC5E,IAAI,EAAE,aAAa,CAAA;IACnB,QAAQ,EAAE,SAAS,CAAA;IACnB,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;IACpC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC/B,+BAsBA;AAED,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM;IAAI,GAAG,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE;AAE3E;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAc,EAAE,SAAS,EAAE,EAAE;IAC1G,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,CAAA;IAClB,IAAI,EAAE,MAAM,CAAC,CAAC,EAAE,aAAa,CAAC,CAAA;IAC9B,MAAM,EAAE,CAAC,GAAG,IAAI,CAAA;IAChB,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;IACjC,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;IACpC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC/B,+BAwCA"}
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import clsx from 'clsx';
3
+ /** The expandable column to the right of a modal form: a title bar and the open section. */
4
+ export function SidePanelFrame({ meta, children, layout = 'row', className }) {
5
+ return (_jsxs("div", { "data-testid": "side-panel-frame", className: clsx('flex flex-col flex-1 min-w-0 border-outline-variant', layout === 'row' ? 'border-l lg:min-w-[360px]' : 'w-full border-t lg:w-auto lg:border-t-0 lg:border-l lg:min-w-[360px]', className), children: [_jsxs("div", { className: "flex items-center gap-2 px-5 py-4", style: { borderBottom: '1px solid var(--m3-outline-variant)' }, children: [_jsx("span", { className: "material-symbols-outlined", style: { fontSize: '16px', color: 'var(--m3-primary)' }, children: meta.icon }), _jsx("span", { className: "font-mono text-xs uppercase tracking-wider font-medium", style: { color: 'var(--m3-on-surface)', flex: 1 }, children: meta.label })] }), _jsx("div", { className: "p-5 space-y-4 overflow-y-auto", style: { flex: 1 }, children: children })] }));
6
+ }
7
+ /**
8
+ * The icon strip that opens and closes side-panel sections: vertical at the form's right edge, or with
9
+ * `layout="responsive"` a horizontal row below lg and the vertical strip from lg up.
10
+ */
11
+ export function SideTabStrip({ tabs, meta, active, onToggle, layout = 'row', className }) {
12
+ const responsive = layout === 'responsive';
13
+ return (_jsx("div", { role: "toolbar", "aria-orientation": responsive ? undefined : 'vertical', className: clsx('flex items-stretch border-outline-variant', responsive ? 'flex-row w-full border-t overflow-hidden rounded-b-2xl lg:w-11 lg:flex-none lg:flex-col lg:border-t-0 lg:border-l lg:rounded-b-none lg:rounded-r-2xl' : 'flex-col flex-none w-11 border-l', className), children: tabs.map((tab) => {
14
+ const isActive = active === tab.key;
15
+ const current = meta[tab.key];
16
+ return (_jsxs("button", { type: "button", "aria-pressed": isActive, "aria-label": current.label, title: current.label, onClick: () => onToggle(isActive ? null : tab.key), className: clsx('relative flex flex-col items-center justify-center transition-all border-outline-variant focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-outline', responsive ? 'flex-1 py-3 border-r last:border-r-0 lg:flex-none lg:py-4 lg:border-r-0 lg:border-b' : 'py-4 border-b', isActive ? 'side-tab-active' : 'hover:bg-surface-container'), style: { color: isActive ? 'var(--m3-primary)' : 'var(--m3-secondary)', background: isActive ? 'var(--m3-primary-fixed)' : undefined }, children: [_jsx("span", { className: "material-symbols-outlined", style: { fontSize: '18px' }, children: current.icon }), tab.badge && (_jsx("span", { className: `absolute top-1.5 right-1 flex items-center justify-center rounded-full font-bold leading-none ${isActive ? 'side-tab-badge-active' : ''}`, style: { background: isActive ? 'var(--m3-primary)' : 'var(--m3-on-surface-variant)', color: isActive ? 'var(--m3-on-primary)' : 'var(--m3-surface)', minWidth: '14px', height: '14px', fontSize: '9px', padding: '0 2px' }, children: tab.badge }))] }, tab.key));
17
+ }) }));
18
+ }
19
+ //# sourceMappingURL=SidePanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SidePanel.js","sourceRoot":"","sources":["../../src/components/SidePanel.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare function SingleSignOnModal({ onClose }: {
2
+ onClose: () => void;
3
+ }): import("react").JSX.Element;
4
+ //# sourceMappingURL=SingleSignOnModal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SingleSignOnModal.d.ts","sourceRoot":"","sources":["../../src/components/SingleSignOnModal.tsx"],"names":[],"mappings":"AAwBA,wBAAgB,iBAAiB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,+BAwIrE"}
@@ -0,0 +1,64 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useState } from 'react';
3
+ import { api } from '../api/client';
4
+ import { uiConfig } from '../config';
5
+ import { Alert, ErrorState, Field, LoadingState, Switch, useToast } from './ui';
6
+ import { ModalFooter, ModalHeader, ModalShell } from './ModalShell';
7
+ import { SsoConfirmationNote, isSsoSession, useSsoConfirmation } from './SsoConfirmation';
8
+ const errorText = (e) => e instanceof Error ? e.message : String(e);
9
+ const CARD = { background: 'var(--m3-surface-container-lowest)', border: '1px solid var(--m3-outline-variant)', color: 'var(--m3-on-surface)' };
10
+ export function SingleSignOnModal({ onClose }) {
11
+ const [data, setData] = useState(null);
12
+ const [form, setForm] = useState(null);
13
+ const [clientSecret, setClientSecret] = useState('');
14
+ const [currentPassword, setCurrentPassword] = useState('');
15
+ const sso = isSsoSession();
16
+ const confirmation = useSsoConfirmation();
17
+ const [loadError, setLoadError] = useState('');
18
+ const [busy, setBusy] = useState(null);
19
+ const [message, setMessage] = useState(null);
20
+ const toast = useToast();
21
+ const load = useCallback(() => api.get('/admin/oidc')
22
+ .then((res) => { setData(res); setForm(res.settings); setLoadError(''); })
23
+ .catch((e) => setLoadError(errorText(e))), []);
24
+ useEffect(() => { void load(); }, [load]);
25
+ if (!data || !form) {
26
+ return _jsx(ModalShell, { align: "top", onClose: onClose, label: "Single sign-on", children: _jsxs("div", { className: "rounded-2xl p-6 w-full max-w-2xl", style: CARD, children: [loadError ? _jsx(ErrorState, { message: loadError, onRetry: () => void load() }) : _jsx(LoadingState, { label: "Loading sign-in settings\u2026" }), loadError && _jsx("button", { type: "button", className: "btn btn-secondary mt-4", onClick: onClose, children: "Close" })] }) });
27
+ }
28
+ const locked = data.envManaged;
29
+ const update = (changes) => setForm({ ...form, ...changes });
30
+ const redirectUri = form.redirectUri || data.defaultRedirectUri;
31
+ async function test() {
32
+ setBusy('test');
33
+ setMessage(null);
34
+ try {
35
+ const result = await api.post('/admin/oidc/test', { issuer: form.issuer, clientId: form.clientId, redirectUri: form.redirectUri });
36
+ setMessage({ tone: 'success', text: `Connected to ${result.issuer}.` });
37
+ }
38
+ catch (e) {
39
+ setMessage({ tone: 'error', text: errorText(e) });
40
+ }
41
+ finally {
42
+ setBusy(null);
43
+ }
44
+ }
45
+ async function save() {
46
+ setBusy('save');
47
+ setMessage(null);
48
+ try {
49
+ const saved = await confirmation.run(() => api.put('/admin/oidc', {
50
+ ...form, clientSecret, ...(sso ? {} : { currentPassword }),
51
+ }));
52
+ toast.success(saved.enabled ? 'Single sign-on settings saved. They apply immediately.' : 'Single sign-on settings saved.');
53
+ onClose();
54
+ }
55
+ catch (err) {
56
+ setMessage({ tone: 'error', text: errorText(err) });
57
+ setBusy(null);
58
+ }
59
+ }
60
+ return _jsx(ModalShell, { align: "top", onClose: busy ? undefined : onClose, label: "Single sign-on", children: _jsxs("div", { className: "w-full max-w-2xl rounded-2xl my-8", style: { background: 'var(--m3-surface-container-low)', border: '1px solid var(--m3-outline-variant)', color: 'var(--m3-on-surface)' }, children: [_jsx(ModalHeader, { icon: "passkey", title: "Single sign-on", onClose: onClose }), _jsxs("form", { onSubmit: (e) => { e.preventDefault(); void save(); }, children: [_jsxs("div", { className: "p-6 space-y-5", children: [locked && _jsx(Alert, { tone: "warning", children: "Single sign-on is configured through OIDC_* environment variables on the server. Remove them to manage it here." }), message && _jsx(Alert, { tone: message.tone, onDismiss: () => setMessage(null), children: message.text }), _jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { children: [_jsx("h2", { className: "font-headline text-base font-semibold", children: "OpenID Connect" }), _jsxs("p", { className: "text-sm mt-1", style: { color: 'var(--m3-secondary)' }, children: ["Users must already exist; they are matched by the verified email from the identity provider.", uiConfig().texts.ssoSignInHint ? ` ${uiConfig().texts.ssoSignInHint}` : ""] })] }), _jsx(Switch, { checked: form.enabled, disabled: locked, onChange: (enabled) => update({ enabled }), "aria-label": "Enable single sign-on" })] }), _jsxs("fieldset", { disabled: locked || busy !== null, className: "space-y-4", children: [_jsx(Field, { label: "Issuer URL", hint: "For example https://login.microsoftonline.com/<tenant>/v2.0 or https://keycloak.example.com/realms/main", children: _jsx("input", { className: "input-m3", value: form.issuer, onChange: (e) => update({ issuer: e.target.value }), placeholder: "https://idp.example.com" }) }), _jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsx(Field, { label: "Client ID", children: _jsx("input", { className: "input-m3", value: form.clientId, onChange: (e) => update({ clientId: e.target.value }), autoComplete: "off" }) }), _jsx(Field, { label: "Client secret", hint: form.hasClientSecret ? 'A secret is stored. Leave empty to keep it. Re-enter it when you change the issuer URL or client ID.' : 'Optional for public clients.', children: _jsx("input", { className: "input-m3", type: "password", autoComplete: "new-password", value: clientSecret, onChange: (e) => setClientSecret(e.target.value), placeholder: form.hasClientSecret ? '••••••••' : '' }) })] }), _jsx(Field, { label: "Redirect URI", hint: redirectUri ? _jsxs(_Fragment, { children: ["Register ", _jsx("code", { children: redirectUri }), " as the callback URL in your identity provider."] }) : 'Set PUBLIC_URL on the server or enter the callback URL here.', children: _jsx("input", { className: "input-m3", value: form.redirectUri, onChange: (e) => update({ redirectUri: e.target.value }), placeholder: data.defaultRedirectUri }) }), _jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsx(Field, { label: "Scopes", children: _jsx("input", { className: "input-m3", value: form.scopes, onChange: (e) => update({ scopes: e.target.value }), placeholder: "openid email profile" }) }), _jsx(Field, { label: "Button label", children: _jsx("input", { className: "input-m3", value: form.buttonLabel, onChange: (e) => update({ buttonLabel: e.target.value }), placeholder: "Sign in with SSO" }) })] }), _jsxs("label", { className: "flex items-start gap-2 text-sm", children: [_jsx("input", { type: "checkbox", className: "mt-1", checked: form.allowUnverifiedEmail, onChange: (e) => update({ allowUnverifiedEmail: e.target.checked }) }), _jsxs("span", { children: ["Accept identity providers that omit ", _jsx("code", { children: "email_verified" }), ". Enable only if the email claim is trustworthy. Not needed for Microsoft Entra ID: add the ", _jsx("code", { children: "xms_edov" }), " optional claim instead."] })] }), _jsxs("label", { className: "flex items-start gap-2 text-sm", children: [_jsx("input", { type: "checkbox", className: "mt-1", checked: form.disablePasswordLogin, onChange: (e) => update({ disablePasswordLogin: e.target.checked }) }), _jsxs("span", { children: ["Disable password sign-in. The identity provider must be reachable to save this. Set ", _jsx("code", { children: "OIDC_FORCE_PASSWORD_LOGIN=true" }), " on the server to recover if it goes down."] })] })] }), !locked && _jsx(_Fragment, { children: sso
61
+ ? _jsx(SsoConfirmationNote, { confirmation: confirmation })
62
+ : _jsx(Field, { label: "Your current password", hint: "Required to change sign-in settings.", children: _jsx("input", { className: "input-m3", type: "password", autoComplete: "current-password", required: true, value: currentPassword, onChange: (e) => setCurrentPassword(e.target.value) }) }) }), !locked && _jsx("p", { className: "text-xs", style: { color: 'var(--m3-secondary)' }, children: "Test sign-in uses the saved settings, so save changes first." })] }), _jsxs(ModalFooter, { start: _jsxs(_Fragment, { children: [!locked && (_jsx("button", { type: "button", disabled: busy !== null || !form.issuer || !form.clientId, onClick: () => void test(), className: "btn btn-secondary", children: busy === 'test' ? 'Testing…' : 'Test connection' })), _jsx("button", { type: "button", disabled: !locked && (busy !== null || !data.settings.issuer || !data.settings.clientId), onClick: () => window.open('/api/v1/admin/oidc/test-sign-in', '_blank', 'noopener'), title: "Signs in at the identity provider in a new tab with the saved settings and shows the claims and the outcome, without signing anyone in.", className: "btn btn-secondary", children: "Test sign-in" })] }), children: [_jsx("button", { type: "button", disabled: busy !== null, onClick: onClose, className: "btn btn-secondary", children: locked ? 'Close' : 'Cancel' }), !locked && _jsx("button", { type: "submit", disabled: busy !== null, className: "btn btn-primary", children: busy === 'save' ? 'Saving…' : 'Save' })] })] })] }) });
63
+ }
64
+ //# sourceMappingURL=SingleSignOnModal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SingleSignOnModal.js","sourceRoot":"","sources":["../../src/components/SingleSignOnModal.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /** The confirmation popup reports on this channel. Some providers cut `window.opener`, so no postMessage. */
2
+ export declare const ssoConfirmChannel: () => string;
3
+ /** True when the session signed in with SSO, so sensitive actions are confirmed there instead of with a password. */
4
+ export declare function isSsoSession(): boolean;
5
+ /** True when the API refused an action until the SSO session is confirmed at the identity provider. */
6
+ export declare function isSsoConfirmationRequired(error: unknown): boolean;
7
+ /**
8
+ * Runs sensitive API calls of an SSO session. When the API asks for a confirmation, `run` opens the identity
9
+ * provider in a popup right away and repeats the call once it succeeds; otherwise it throws with the reason.
10
+ * The popup follows the user's click within one API round trip, which browsers still count as user-initiated.
11
+ */
12
+ export declare function useSsoConfirmation(): {
13
+ run: <T>(action: () => Promise<T>) => Promise<T>;
14
+ waiting: boolean;
15
+ cancel: () => void | undefined;
16
+ };
17
+ export type SsoConfirmationState = ReturnType<typeof useSsoConfirmation>;
18
+ /** Takes the place of a "current password" field in an SSO session: a note, or the popup's progress. */
19
+ export declare function SsoConfirmationNote({ confirmation }: {
20
+ confirmation: Pick<SsoConfirmationState, 'waiting' | 'cancel'>;
21
+ }): import("react").JSX.Element;
22
+ //# sourceMappingURL=SsoConfirmation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SsoConfirmation.d.ts","sourceRoot":"","sources":["../../src/components/SsoConfirmation.tsx"],"names":[],"mappings":"AAKA,6GAA6G;AAC7G,eAAO,MAAM,iBAAiB,QAAO,MAAmC,CAAA;AAaxE,qHAAqH;AACrH,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED,uGAAuG;AACvG,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEjE;AAyBD;;;;GAIG;AACH,wBAAgB,kBAAkB;UAKD,CAAC,UAAW,MAAM,OAAO,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,CAAC,CAAC;;;EAkBzE;AAED,MAAM,MAAM,oBAAoB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAExE,wGAAwG;AACxG,wBAAgB,mBAAmB,CAAC,EAAE,YAAY,EAAE,EAAE;IAAE,YAAY,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,GAAG,QAAQ,CAAC,CAAA;CAAE,+BAUvH"}
@@ -0,0 +1,87 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
+ import { getCurrentUser } from '../api/client';
4
+ import { storageKey, uiConfig } from '../config';
5
+ import { Alert } from './ui';
6
+ /** The confirmation popup reports on this channel. Some providers cut `window.opener`, so no postMessage. */
7
+ export const ssoConfirmChannel = () => storageKey('sso-confirm');
8
+ const ssoConfirmUrl = () => `${uiConfig().apiBase}/auth/oidc/confirm`;
9
+ // The server-side flow expires after 10 minutes, so a popup left open longer cannot succeed.
10
+ const SSO_CONFIRM_TIMEOUT_MS = 10 * 60 * 1000;
11
+ const OUTCOME_MESSAGES = {
12
+ failed: 'The single sign-on confirmation did not succeed. Try again.',
13
+ blocked: 'The browser blocked the sign-in window. Allow pop-ups for this site and try again.',
14
+ cancelled: 'The single sign-on confirmation was cancelled.',
15
+ };
16
+ /** True when the session signed in with SSO, so sensitive actions are confirmed there instead of with a password. */
17
+ export function isSsoSession() {
18
+ return getCurrentUser()?.authMethod === 'oidc';
19
+ }
20
+ /** True when the API refused an action until the SSO session is confirmed at the identity provider. */
21
+ export function isSsoConfirmationRequired(error) {
22
+ return error?.code === 'SSO_CONFIRMATION_REQUIRED';
23
+ }
24
+ function openSsoConfirmation() {
25
+ const popup = window.open(ssoConfirmUrl(), ssoConfirmChannel(), 'popup,width=520,height=720');
26
+ if (!popup)
27
+ return { result: Promise.resolve('blocked'), cancel: () => { } };
28
+ let finish = () => { };
29
+ const result = new Promise((resolve) => {
30
+ const channel = new BroadcastChannel(ssoConfirmChannel());
31
+ const timer = setTimeout(() => finish('failed'), SSO_CONFIRM_TIMEOUT_MS);
32
+ finish = (outcome) => {
33
+ channel.close();
34
+ clearTimeout(timer);
35
+ resolve(outcome);
36
+ };
37
+ channel.onmessage = (event) => finish(event.data?.ok === true ? 'ok' : 'failed');
38
+ });
39
+ return {
40
+ result,
41
+ cancel: () => {
42
+ finish('cancelled');
43
+ try {
44
+ popup.close();
45
+ }
46
+ catch { /* already closed or no longer ours */ }
47
+ },
48
+ };
49
+ }
50
+ /**
51
+ * Runs sensitive API calls of an SSO session. When the API asks for a confirmation, `run` opens the identity
52
+ * provider in a popup right away and repeats the call once it succeeds; otherwise it throws with the reason.
53
+ * The popup follows the user's click within one API round trip, which browsers still count as user-initiated.
54
+ */
55
+ export function useSsoConfirmation() {
56
+ const [waiting, setWaiting] = useState(false);
57
+ const cancelRef = useRef(null);
58
+ useEffect(() => () => cancelRef.current?.(), []);
59
+ const run = useCallback(async (action) => {
60
+ try {
61
+ return await action();
62
+ }
63
+ catch (error) {
64
+ if (!isSsoConfirmationRequired(error))
65
+ throw error;
66
+ const { result, cancel } = openSsoConfirmation();
67
+ cancelRef.current = cancel;
68
+ setWaiting(true);
69
+ const outcome = await result;
70
+ cancelRef.current = null;
71
+ setWaiting(false);
72
+ if (outcome !== 'ok')
73
+ throw new Error(OUTCOME_MESSAGES[outcome], { cause: error });
74
+ return action();
75
+ }
76
+ }, []);
77
+ const cancel = useCallback(() => cancelRef.current?.(), []);
78
+ return { run, waiting, cancel };
79
+ }
80
+ /** Takes the place of a "current password" field in an SSO session: a note, or the popup's progress. */
81
+ export function SsoConfirmationNote({ confirmation }) {
82
+ if (!confirmation.waiting) {
83
+ return _jsx("p", { className: "text-xs", style: { color: 'var(--m3-secondary)' }, children: "You signed in with single sign-on, so no password is needed. You may be asked to sign in again in a pop-up window." });
84
+ }
85
+ return (_jsxs(Alert, { tone: "info", title: "Confirm it's you", children: [_jsx("p", { children: "Sign in again with your identity provider in the pop-up window. This continues once you are done." }), _jsx("button", { type: "button", className: "btn btn-secondary btn-sm mt-3", onClick: confirmation.cancel, children: "Cancel" })] }));
86
+ }
87
+ //# sourceMappingURL=SsoConfirmation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SsoConfirmation.js","sourceRoot":"","sources":["../../src/components/SsoConfirmation.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export type VaultTypeValue = 'local' | 'hashicorp';
2
+ /** Only a local vault stores values; every external vault holds references to secrets kept in the provider. */
3
+ export declare const isReferenceVault: (type: VaultTypeValue) => boolean;
4
+ /** Icon per vault type, shared by the picker and the vault list so they always match. */
5
+ export declare const VAULT_TYPE_ICONS: Record<VaultTypeValue, string>;
6
+ interface Props {
7
+ value: VaultTypeValue;
8
+ onChange: (type: VaultTypeValue) => void;
9
+ }
10
+ /** Vault type list in the same dropdown style as the other type pickers. */
11
+ export declare function VaultTypePicker({ value, onChange }: Props): import("react").JSX.Element;
12
+ export {};
13
+ //# sourceMappingURL=VaultTypePicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VaultTypePicker.d.ts","sourceRoot":"","sources":["../../src/components/VaultTypePicker.tsx"],"names":[],"mappings":"AAGA,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,WAAW,CAAA;AAElD,+GAA+G;AAC/G,eAAO,MAAM,gBAAgB,GAAI,MAAM,cAAc,KAAG,OAA2B,CAAA;AAEnF,yFAAyF;AACzF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAA8C,CAAA;AAkB1G,UAAU,KAAK;IACb,KAAK,EAAE,cAAc,CAAA;IACrB,QAAQ,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAA;CACzC;AAUD,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,KAAK,+BAyFzD"}
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useRef, useState } from 'react';
3
+ import { uiConfig } from '../config';
4
+ /** Only a local vault stores values; every external vault holds references to secrets kept in the provider. */
5
+ export const isReferenceVault = (type) => type !== 'local';
6
+ /** Icon per vault type, shared by the picker and the vault list so they always match. */
7
+ export const VAULT_TYPE_ICONS = { local: 'storage', hashicorp: 'vpn_key' };
8
+ /** Vault types in picker order; entries with a null value are announced but cannot be selected yet. */
9
+ const vaultTypeOptions = () => [
10
+ { value: 'local', label: 'Local', hint: `Encrypted in the ${uiConfig().texts.appName} database`, icon: VAULT_TYPE_ICONS.local },
11
+ { value: 'hashicorp', label: 'HashiCorp Vault', hint: 'Read secrets from your own HashiCorp Vault', icon: VAULT_TYPE_ICONS.hashicorp },
12
+ { value: null, label: 'Azure Key Vault', hint: 'Coming soon', icon: 'cloud' },
13
+ { value: null, label: 'GCP Secret Manager', hint: 'Coming soon', icon: 'cloud' },
14
+ { value: null, label: 'AWS Secrets Manager', hint: 'Coming soon', icon: 'cloud' },
15
+ ];
16
+ function TypeIcon({ icon }) {
17
+ return (_jsx("span", { className: "flex-none w-8 h-8 rounded-lg grid place-items-center", style: { background: 'var(--admin-icon-container)', color: 'var(--admin-icon-color)' }, children: _jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", style: { fontSize: 20 }, children: icon }) }));
18
+ }
19
+ /** Vault type list in the same dropdown style as the other type pickers. */
20
+ export function VaultTypePicker({ value, onChange }) {
21
+ const [open, setOpen] = useState(false);
22
+ const rootRef = useRef(null);
23
+ const buttonRef = useRef(null);
24
+ const labelId = useId();
25
+ const buttonId = useId();
26
+ const selected = vaultTypeOptions().find((t) => t.value === value);
27
+ useEffect(() => {
28
+ if (!open)
29
+ return;
30
+ function onPointerDown(e) {
31
+ if (!rootRef.current?.contains(e.target))
32
+ setOpen(false);
33
+ }
34
+ function onKeyDown(e) {
35
+ if (e.key !== 'Escape')
36
+ return;
37
+ e.stopPropagation();
38
+ setOpen(false);
39
+ buttonRef.current?.focus();
40
+ }
41
+ document.addEventListener('mousedown', onPointerDown);
42
+ document.addEventListener('keydown', onKeyDown, true);
43
+ return () => {
44
+ document.removeEventListener('mousedown', onPointerDown);
45
+ document.removeEventListener('keydown', onKeyDown, true);
46
+ };
47
+ }, [open]);
48
+ function pick(option) {
49
+ if (!option.value)
50
+ return;
51
+ setOpen(false);
52
+ buttonRef.current?.focus();
53
+ if (option.value !== value)
54
+ onChange(option.value);
55
+ }
56
+ return (_jsxs("div", { ref: rootRef, className: "relative", children: [_jsx("span", { id: labelId, className: "block font-mono text-xs uppercase tracking-wider mb-2", style: { color: 'var(--m3-secondary)' }, children: "Type" }), _jsxs("button", { ref: buttonRef, id: buttonId, type: "button", "aria-haspopup": "listbox", "aria-expanded": open, "aria-labelledby": `${labelId} ${buttonId}`, onClick: () => setOpen((o) => !o), className: "w-full flex items-center gap-3 text-left rounded-xl px-3 py-2 focus-ring", style: { background: 'var(--m3-surface-container-lowest)', border: '1px solid var(--m3-outline-variant)', color: 'var(--m3-on-surface)' }, children: [_jsx(TypeIcon, { icon: selected.icon }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block text-sm font-semibold", children: selected.label }), _jsx("span", { className: "block text-xs truncate", style: { color: 'var(--m3-secondary)' }, children: selected.hint })] }), _jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", style: { color: 'var(--m3-secondary)' }, children: "unfold_more" })] }), open && (_jsx("div", { className: "absolute z-10 left-0 right-0 mt-1.5 rounded-xl overflow-hidden shadow-xl", style: { background: 'var(--m3-surface-container-low)', border: '1px solid var(--m3-outline-variant)' }, children: _jsx("div", { role: "listbox", "aria-labelledby": labelId, className: "max-h-72 overflow-auto p-1.5", children: vaultTypeOptions().map((t) => {
57
+ const disabled = t.value === null;
58
+ const isSelected = t.value === value;
59
+ return (_jsxs("button", { type: "button", role: "option", "aria-selected": isSelected, "aria-disabled": disabled, onClick: () => pick(t), className: `w-full flex items-center gap-3 text-left rounded-lg px-2 py-1.5 focus-ring ${disabled ? 'opacity-40 cursor-not-allowed' : 'hover:bg-[var(--m3-surface-container)]'} ${isSelected ? 'selection-active' : ''}`, style: { border: '1px solid transparent' }, children: [_jsx(TypeIcon, { icon: t.icon }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block text-sm font-semibold", children: t.label }), _jsx("span", { className: "block text-xs truncate opacity-75", children: t.hint })] })] }, t.label));
60
+ }) }) }))] }));
61
+ }
62
+ //# sourceMappingURL=VaultTypePicker.js.map