@burdenoff/microfe-admin 2026.519.1 → 2026.520.1

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 (39) hide show
  1. package/dist/admin/AdminRoot.d.ts.map +1 -1
  2. package/dist/admin/AdminRoot.js +54 -46
  3. package/dist/admin/AdminRoot.js.map +1 -1
  4. package/dist/admin/AdminRoutes.d.ts.map +1 -1
  5. package/dist/admin/AdminRoutes.js +22 -18
  6. package/dist/admin/AdminRoutes.js.map +1 -1
  7. package/dist/admin/components/ConfirmDialog.js +81 -4
  8. package/dist/admin/components/ConfirmDialog.js.map +1 -0
  9. package/dist/admin/components/TenantSsoPanel.d.ts +7 -0
  10. package/dist/admin/components/TenantSsoPanel.d.ts.map +1 -0
  11. package/dist/admin/components/TenantSsoPanel.js +286 -0
  12. package/dist/admin/components/TenantSsoPanel.js.map +1 -0
  13. package/dist/admin/constants/permissions.d.ts +2 -0
  14. package/dist/admin/constants/permissions.d.ts.map +1 -1
  15. package/dist/admin/constants/permissions.js +2 -0
  16. package/dist/admin/constants/permissions.js.map +1 -1
  17. package/dist/admin/hooks/useAdminPermissions.d.ts +2 -0
  18. package/dist/admin/hooks/useAdminPermissions.d.ts.map +1 -1
  19. package/dist/admin/hooks/useAdminPermissions.js +2 -0
  20. package/dist/admin/hooks/useAdminPermissions.js.map +1 -1
  21. package/dist/admin/pages/SyncFailuresPage.d.ts +14 -0
  22. package/dist/admin/pages/SyncFailuresPage.d.ts.map +1 -0
  23. package/dist/admin/pages/SyncFailuresPage.js +297 -0
  24. package/dist/admin/pages/SyncFailuresPage.js.map +1 -0
  25. package/dist/admin/pages/TenantDetailPage.d.ts.map +1 -1
  26. package/dist/admin/pages/TenantDetailPage.js +28 -23
  27. package/dist/admin/pages/TenantDetailPage.js.map +1 -1
  28. package/dist/admin/pages/index.d.ts +1 -0
  29. package/dist/admin/pages/index.d.ts.map +1 -1
  30. package/dist/admin/pages/index.js +1 -0
  31. package/dist/generated/global-operations.d.ts +252 -0
  32. package/dist/generated/global-operations.d.ts.map +1 -1
  33. package/dist/generated/global-operations.js +156 -34
  34. package/dist/generated/global-operations.js.map +1 -1
  35. package/dist/generated/global-types.d.ts +566 -1393
  36. package/dist/generated/global-types.d.ts.map +1 -1
  37. package/dist/generated/global-types.js +4 -2
  38. package/dist/generated/global-types.js.map +1 -1
  39. package/package.json +1 -1
@@ -0,0 +1,286 @@
1
+ import { useToast as e } from "./Toast.js";
2
+ import { Button as t, Card as n, CardContent as r, CardHeader as i, CardTitle as a, Input as o, Separator as s } from "./ui.js";
3
+ import { tWithFallback as c } from "../utils/i18n.js";
4
+ import { ConfirmDialog as l } from "./ConfirmDialog.js";
5
+ import { useAdminDeleteSsoConfigMutation as u, useAdminGetSsoConfigQuery as d, useAdminSaveSsoConfigMutation as f } from "../../generated/global-operations.js";
6
+ import { useEffect as p, useState as m } from "react";
7
+ import { ChevronDown as h, ChevronUp as g, Loader2 as _, Save as v, ShieldCheck as y, Trash2 as b } from "lucide-react";
8
+ import { Fragment as x, jsx as S, jsxs as C } from "react/jsx-runtime";
9
+ import { useI18n as w } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
10
+ //#region src/admin/components/TenantSsoPanel.tsx
11
+ var T = {
12
+ entityId: "",
13
+ ssoUrl: "",
14
+ sloUrl: "",
15
+ certificate: "",
16
+ enabled: !0
17
+ };
18
+ function E({ tenantId: E, canManage: D }) {
19
+ let { t: O } = w(), k = (e, t, n) => c(O, e, t, n), A = e(), [j, M] = m(!1), [N, P] = m(!1), [F, I] = m(T), [L, R] = m(!1), { data: z, loading: B, error: V, refetch: H } = d({
20
+ variables: { tenantId: E },
21
+ fetchPolicy: "cache-and-network"
22
+ }), [U, { loading: W }] = f(), [G, { loading: K }] = u(), q = z?.getSSOConfig;
23
+ return p(() => {
24
+ I(q ? {
25
+ entityId: q.entityId,
26
+ ssoUrl: q.ssoUrl,
27
+ sloUrl: q.sloUrl ?? "",
28
+ certificate: q.certificate,
29
+ enabled: q.enabled
30
+ } : T);
31
+ }, [q]), /* @__PURE__ */ C(x, { children: [/* @__PURE__ */ C(n, { children: [/* @__PURE__ */ S(i, { children: /* @__PURE__ */ C("div", {
32
+ className: "flex items-center justify-between",
33
+ children: [/* @__PURE__ */ C(a, {
34
+ className: "flex items-center gap-2 text-lg",
35
+ children: [/* @__PURE__ */ S(y, { className: "h-5 w-5" }), k("admin.tenantDetail.sso.title", "SSO Configuration")]
36
+ }), /* @__PURE__ */ S("button", {
37
+ type: "button",
38
+ onClick: () => M((e) => !e),
39
+ className: "flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors",
40
+ "aria-expanded": j,
41
+ "aria-label": j ? k("admin.tenantDetail.sso.collapse", "Collapse SSO configuration") : k("admin.tenantDetail.sso.expand", "Expand SSO configuration"),
42
+ children: S(j ? g : h, { className: "h-4 w-4" })
43
+ })]
44
+ }) }), j && /* @__PURE__ */ S(r, {
45
+ className: "space-y-4",
46
+ children: B ? /* @__PURE__ */ S("div", {
47
+ className: "flex items-center justify-center py-6",
48
+ children: /* @__PURE__ */ S(_, { className: "h-6 w-6 animate-spin text-muted-foreground" })
49
+ }) : V ? /* @__PURE__ */ S("p", {
50
+ className: "text-sm text-destructive",
51
+ children: V.message
52
+ }) : q && !N ? /* @__PURE__ */ C("div", {
53
+ className: "space-y-3",
54
+ children: [
55
+ /* @__PURE__ */ S("div", {
56
+ className: "flex items-center gap-2",
57
+ children: /* @__PURE__ */ S("span", {
58
+ className: `inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${q.enabled ? "bg-status-success-bg-subtle text-status-success-text" : "bg-bg-sunken text-text-secondary"}`,
59
+ children: q.enabled ? k("admin.tenantDetail.sso.enabled", "Enabled") : k("admin.tenantDetail.sso.disabled", "Disabled")
60
+ })
61
+ }),
62
+ /* @__PURE__ */ C("dl", {
63
+ className: "grid gap-2 text-sm",
64
+ children: [
65
+ /* @__PURE__ */ C("div", {
66
+ className: "flex flex-col gap-0.5",
67
+ children: [/* @__PURE__ */ S("dt", {
68
+ className: "text-xs text-muted-foreground",
69
+ children: k("admin.tenantDetail.sso.entityId", "Entity ID")
70
+ }), /* @__PURE__ */ S("dd", {
71
+ className: "font-mono text-xs break-all",
72
+ children: q.entityId
73
+ })]
74
+ }),
75
+ /* @__PURE__ */ C("div", {
76
+ className: "flex flex-col gap-0.5",
77
+ children: [/* @__PURE__ */ S("dt", {
78
+ className: "text-xs text-muted-foreground",
79
+ children: k("admin.tenantDetail.sso.ssoUrl", "SSO URL")
80
+ }), /* @__PURE__ */ S("dd", {
81
+ className: "font-mono text-xs break-all",
82
+ children: q.ssoUrl
83
+ })]
84
+ }),
85
+ q.sloUrl && /* @__PURE__ */ C("div", {
86
+ className: "flex flex-col gap-0.5",
87
+ children: [/* @__PURE__ */ S("dt", {
88
+ className: "text-xs text-muted-foreground",
89
+ children: k("admin.tenantDetail.sso.sloUrl", "SLO URL")
90
+ }), /* @__PURE__ */ S("dd", {
91
+ className: "font-mono text-xs break-all",
92
+ children: q.sloUrl
93
+ })]
94
+ })
95
+ ]
96
+ }),
97
+ D && /* @__PURE__ */ C("div", {
98
+ className: "flex gap-2 pt-2",
99
+ children: [/* @__PURE__ */ S(t, {
100
+ variant: "outline",
101
+ size: "sm",
102
+ onClick: () => P(!0),
103
+ children: k("admin.tenantDetail.sso.editButton", "Edit")
104
+ }), /* @__PURE__ */ C(t, {
105
+ variant: "outline",
106
+ size: "sm",
107
+ className: "text-destructive hover:text-destructive",
108
+ onClick: () => R(!0),
109
+ children: [/* @__PURE__ */ S(b, { className: "mr-1.5 h-3.5 w-3.5" }), k("admin.tenantDetail.sso.deleteButton", "Remove")]
110
+ })]
111
+ })
112
+ ]
113
+ }) : /* @__PURE__ */ C("form", {
114
+ onSubmit: async (e) => {
115
+ e.preventDefault();
116
+ try {
117
+ await U({
118
+ variables: {
119
+ tenantId: E,
120
+ input: {
121
+ entityId: F.entityId,
122
+ ssoUrl: F.ssoUrl,
123
+ sloUrl: F.sloUrl || void 0,
124
+ certificate: F.certificate,
125
+ enabled: F.enabled
126
+ }
127
+ },
128
+ context: { headers: { "x-workspace-id": "" } }
129
+ }), A.success(k("admin.tenantDetail.sso.savedTitle", "SSO configuration saved"), k("admin.tenantDetail.sso.savedDescription", "SAML SSO has been configured for this tenant.")), P(!1), H();
130
+ } catch (e) {
131
+ A.error(k("admin.tenantDetail.sso.errorTitle", "Failed to save SSO config"), e instanceof Error ? e.message : k("admin.tenantDetail.sso.unknownError", "Unknown error"));
132
+ }
133
+ },
134
+ className: "space-y-4",
135
+ children: [
136
+ !q && /* @__PURE__ */ S("p", {
137
+ className: "text-sm text-muted-foreground",
138
+ children: k("admin.tenantDetail.sso.notConfigured", "No SSO configuration. Fill in the SAML details below to enable enterprise SSO.")
139
+ }),
140
+ /* @__PURE__ */ S(s, {}),
141
+ /* @__PURE__ */ C("div", {
142
+ className: "grid gap-4 sm:grid-cols-2",
143
+ children: [
144
+ /* @__PURE__ */ C("div", {
145
+ className: "sm:col-span-2 space-y-1.5",
146
+ children: [/* @__PURE__ */ C("label", {
147
+ className: "text-sm font-medium",
148
+ htmlFor: `sso-entityId-${E}`,
149
+ children: [k("admin.tenantDetail.sso.entityId", "Entity ID"), /* @__PURE__ */ S("span", {
150
+ className: "ml-1 text-destructive",
151
+ children: "*"
152
+ })]
153
+ }), /* @__PURE__ */ S(o, {
154
+ id: `sso-entityId-${E}`,
155
+ value: F.entityId,
156
+ onChange: (e) => I((t) => ({
157
+ ...t,
158
+ entityId: e.target.value
159
+ })),
160
+ placeholder: "https://idp.example.com/entity",
161
+ required: !0
162
+ })]
163
+ }),
164
+ /* @__PURE__ */ C("div", {
165
+ className: "sm:col-span-2 space-y-1.5",
166
+ children: [/* @__PURE__ */ C("label", {
167
+ className: "text-sm font-medium",
168
+ htmlFor: `sso-ssoUrl-${E}`,
169
+ children: [k("admin.tenantDetail.sso.ssoUrl", "SSO URL"), /* @__PURE__ */ S("span", {
170
+ className: "ml-1 text-destructive",
171
+ children: "*"
172
+ })]
173
+ }), /* @__PURE__ */ S(o, {
174
+ id: `sso-ssoUrl-${E}`,
175
+ value: F.ssoUrl,
176
+ onChange: (e) => I((t) => ({
177
+ ...t,
178
+ ssoUrl: e.target.value
179
+ })),
180
+ placeholder: "https://idp.example.com/sso",
181
+ required: !0
182
+ })]
183
+ }),
184
+ /* @__PURE__ */ C("div", {
185
+ className: "sm:col-span-2 space-y-1.5",
186
+ children: [/* @__PURE__ */ C("label", {
187
+ className: "text-sm font-medium",
188
+ htmlFor: `sso-sloUrl-${E}`,
189
+ children: [k("admin.tenantDetail.sso.sloUrl", "SLO URL"), /* @__PURE__ */ S("span", {
190
+ className: "ml-0.5 text-xs text-muted-foreground",
191
+ children: k("admin.tenantDetail.sso.optional", "(optional)")
192
+ })]
193
+ }), /* @__PURE__ */ S(o, {
194
+ id: `sso-sloUrl-${E}`,
195
+ value: F.sloUrl,
196
+ onChange: (e) => I((t) => ({
197
+ ...t,
198
+ sloUrl: e.target.value
199
+ })),
200
+ placeholder: "https://idp.example.com/slo"
201
+ })]
202
+ }),
203
+ /* @__PURE__ */ C("div", {
204
+ className: "sm:col-span-2 space-y-1.5",
205
+ children: [/* @__PURE__ */ C("label", {
206
+ className: "text-sm font-medium",
207
+ htmlFor: `sso-cert-${E}`,
208
+ children: [k("admin.tenantDetail.sso.certificate", "X.509 Certificate"), /* @__PURE__ */ S("span", {
209
+ className: "ml-1 text-destructive",
210
+ children: "*"
211
+ })]
212
+ }), /* @__PURE__ */ S("textarea", {
213
+ id: `sso-cert-${E}`,
214
+ value: F.certificate,
215
+ onChange: (e) => I((t) => ({
216
+ ...t,
217
+ certificate: e.target.value
218
+ })),
219
+ placeholder: "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
220
+ required: !0,
221
+ rows: 5,
222
+ className: "w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 resize-none"
223
+ })]
224
+ }),
225
+ /* @__PURE__ */ C("div", {
226
+ className: "sm:col-span-2 flex items-center gap-3",
227
+ children: [/* @__PURE__ */ S("button", {
228
+ type: "button",
229
+ role: "switch",
230
+ "aria-checked": F.enabled,
231
+ "aria-label": k("admin.tenantDetail.sso.enabledLabel", "Enable SSO for this tenant"),
232
+ onClick: () => I((e) => ({
233
+ ...e,
234
+ enabled: !e.enabled
235
+ })),
236
+ className: `relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ${F.enabled ? "bg-primary" : "bg-muted"}`,
237
+ children: /* @__PURE__ */ S("span", { className: `pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow-lg transition-transform ${F.enabled ? "translate-x-4" : "translate-x-0"}` })
238
+ }), /* @__PURE__ */ S("span", {
239
+ className: "text-sm",
240
+ children: k("admin.tenantDetail.sso.enabledLabel", "Enable SSO for this tenant")
241
+ })]
242
+ })
243
+ ]
244
+ }),
245
+ /* @__PURE__ */ S(s, {}),
246
+ /* @__PURE__ */ C("div", {
247
+ className: "flex gap-2",
248
+ children: [/* @__PURE__ */ C(t, {
249
+ type: "submit",
250
+ size: "sm",
251
+ disabled: W,
252
+ children: [W ? /* @__PURE__ */ S(_, { className: "mr-1.5 h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ S(v, { className: "mr-1.5 h-3.5 w-3.5" }), k("admin.tenantDetail.sso.saveButton", "Save SSO Config")]
253
+ }), N && /* @__PURE__ */ S(t, {
254
+ type: "button",
255
+ variant: "outline",
256
+ size: "sm",
257
+ onClick: () => P(!1),
258
+ children: k("admin.tenantDetail.sso.cancelButton", "Cancel")
259
+ })]
260
+ })
261
+ ]
262
+ })
263
+ })] }), /* @__PURE__ */ S(l, {
264
+ open: L,
265
+ onClose: () => R(!1),
266
+ title: k("admin.tenantDetail.sso.deleteTitle", "Remove SSO configuration?"),
267
+ description: k("admin.tenantDetail.sso.deleteDescription", "This will remove the SAML SSO configuration. Users will no longer be able to sign in via SSO."),
268
+ confirmLabel: k("admin.tenantDetail.sso.deleteConfirm", "Remove SSO"),
269
+ variant: "destructive",
270
+ onConfirm: async () => {
271
+ try {
272
+ await G({
273
+ variables: { tenantId: E },
274
+ context: { headers: { "x-workspace-id": "" } }
275
+ }), A.success(k("admin.tenantDetail.sso.deletedTitle", "SSO configuration removed"), k("admin.tenantDetail.sso.deletedDescription", "SSO has been disabled for this tenant.")), R(!1), P(!1), H();
276
+ } catch (e) {
277
+ A.error(k("admin.tenantDetail.sso.errorTitle", "Failed to delete SSO config"), e instanceof Error ? e.message : k("admin.tenantDetail.sso.unknownError", "Unknown error"));
278
+ }
279
+ },
280
+ loading: K
281
+ })] });
282
+ }
283
+ //#endregion
284
+ export { E as TenantSsoPanel };
285
+
286
+ //# sourceMappingURL=TenantSsoPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TenantSsoPanel.js","names":[],"sources":["../../../src/admin/components/TenantSsoPanel.tsx"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport { ShieldCheck, Loader2, Trash2, Save, ChevronDown, ChevronUp } from 'lucide-react';\nimport {\n useAdminGetSsoConfigQuery,\n useAdminSaveSsoConfigMutation,\n useAdminDeleteSsoConfigMutation,\n} from '../../generated/global-operations';\nimport { Card, CardHeader, CardTitle, CardContent, Button, Input, Separator } from './ui';\nimport { useToast } from './Toast';\nimport { ConfirmDialog } from './ConfirmDialog';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\n\ninterface TenantSsoPanelProps {\n tenantId: string;\n canManage: boolean;\n}\n\ninterface SsoFormData {\n entityId: string;\n ssoUrl: string;\n sloUrl: string;\n certificate: string;\n enabled: boolean;\n}\n\nconst EMPTY_FORM: SsoFormData = {\n entityId: '',\n ssoUrl: '',\n sloUrl: '',\n certificate: '',\n enabled: true,\n};\n\nexport function TenantSsoPanel({ tenantId, canManage }: TenantSsoPanelProps) {\n const { t } = useI18n();\n const tr = (key: string, fallback: string, params?: Record<string, string | number>): string =>\n tWithFallback(t, key, fallback, params);\n const toast = useToast();\n\n const [expanded, setExpanded] = useState(false);\n const [editing, setEditing] = useState(false);\n const [form, setForm] = useState<SsoFormData>(EMPTY_FORM);\n const [confirmDelete, setConfirmDelete] = useState(false);\n\n const { data, loading, error, refetch } = useAdminGetSsoConfigQuery({\n variables: { tenantId },\n fetchPolicy: 'cache-and-network',\n });\n\n const [saveSso, { loading: saving }] = useAdminSaveSsoConfigMutation();\n const [deleteSso, { loading: deleting }] = useAdminDeleteSsoConfigMutation();\n\n const config = data?.getSSOConfig;\n\n useEffect(() => {\n if (config) {\n setForm({\n entityId: config.entityId,\n ssoUrl: config.ssoUrl,\n sloUrl: config.sloUrl ?? '',\n certificate: config.certificate,\n enabled: config.enabled,\n });\n } else {\n setForm(EMPTY_FORM);\n }\n }, [config]);\n\n const handleSave = async (e: React.FormEvent) => {\n e.preventDefault();\n try {\n await saveSso({\n variables: {\n tenantId,\n input: {\n entityId: form.entityId,\n ssoUrl: form.ssoUrl,\n sloUrl: form.sloUrl || undefined,\n certificate: form.certificate,\n enabled: form.enabled,\n },\n },\n context: { headers: { 'x-workspace-id': '' } },\n });\n toast.success(\n tr('admin.tenantDetail.sso.savedTitle', 'SSO configuration saved'),\n tr(\n 'admin.tenantDetail.sso.savedDescription',\n 'SAML SSO has been configured for this tenant.'\n )\n );\n setEditing(false);\n void refetch();\n } catch (err) {\n toast.error(\n tr('admin.tenantDetail.sso.errorTitle', 'Failed to save SSO config'),\n err instanceof Error\n ? err.message\n : tr('admin.tenantDetail.sso.unknownError', 'Unknown error')\n );\n }\n };\n\n const handleDelete = async () => {\n try {\n await deleteSso({\n variables: { tenantId },\n context: { headers: { 'x-workspace-id': '' } },\n });\n toast.success(\n tr('admin.tenantDetail.sso.deletedTitle', 'SSO configuration removed'),\n tr('admin.tenantDetail.sso.deletedDescription', 'SSO has been disabled for this tenant.')\n );\n setConfirmDelete(false);\n setEditing(false);\n void refetch();\n } catch (err) {\n toast.error(\n tr('admin.tenantDetail.sso.errorTitle', 'Failed to delete SSO config'),\n err instanceof Error\n ? err.message\n : tr('admin.tenantDetail.sso.unknownError', 'Unknown error')\n );\n }\n };\n\n return (\n <>\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"flex items-center gap-2 text-lg\">\n <ShieldCheck className=\"h-5 w-5\" />\n {tr('admin.tenantDetail.sso.title', 'SSO Configuration')}\n </CardTitle>\n <button\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n className=\"flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors\"\n aria-expanded={expanded}\n aria-label={\n expanded\n ? tr('admin.tenantDetail.sso.collapse', 'Collapse SSO configuration')\n : tr('admin.tenantDetail.sso.expand', 'Expand SSO configuration')\n }\n >\n {expanded ? <ChevronUp className=\"h-4 w-4\" /> : <ChevronDown className=\"h-4 w-4\" />}\n </button>\n </div>\n </CardHeader>\n\n {expanded && (\n <CardContent className=\"space-y-4\">\n {loading ? (\n <div className=\"flex items-center justify-center py-6\">\n <Loader2 className=\"h-6 w-6 animate-spin text-muted-foreground\" />\n </div>\n ) : error ? (\n <p className=\"text-sm text-destructive\">{error.message}</p>\n ) : config && !editing ? (\n /* ── Configured state — read-only view ── */\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <span\n className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${\n config.enabled\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-sunken text-text-secondary'\n }`}\n >\n {config.enabled\n ? tr('admin.tenantDetail.sso.enabled', 'Enabled')\n : tr('admin.tenantDetail.sso.disabled', 'Disabled')}\n </span>\n </div>\n\n <dl className=\"grid gap-2 text-sm\">\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"text-xs text-muted-foreground\">\n {tr('admin.tenantDetail.sso.entityId', 'Entity ID')}\n </dt>\n <dd className=\"font-mono text-xs break-all\">{config.entityId}</dd>\n </div>\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"text-xs text-muted-foreground\">\n {tr('admin.tenantDetail.sso.ssoUrl', 'SSO URL')}\n </dt>\n <dd className=\"font-mono text-xs break-all\">{config.ssoUrl}</dd>\n </div>\n {config.sloUrl && (\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"text-xs text-muted-foreground\">\n {tr('admin.tenantDetail.sso.sloUrl', 'SLO URL')}\n </dt>\n <dd className=\"font-mono text-xs break-all\">{config.sloUrl}</dd>\n </div>\n )}\n </dl>\n\n {canManage && (\n <div className=\"flex gap-2 pt-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => setEditing(true)}>\n {tr('admin.tenantDetail.sso.editButton', 'Edit')}\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"text-destructive hover:text-destructive\"\n onClick={() => setConfirmDelete(true)}\n >\n <Trash2 className=\"mr-1.5 h-3.5 w-3.5\" />\n {tr('admin.tenantDetail.sso.deleteButton', 'Remove')}\n </Button>\n </div>\n )}\n </div>\n ) : (\n /* ── Not configured or editing — show form ── */\n <form onSubmit={handleSave} className=\"space-y-4\">\n {!config && (\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'admin.tenantDetail.sso.notConfigured',\n 'No SSO configuration. Fill in the SAML details below to enable enterprise SSO.'\n )}\n </p>\n )}\n\n <Separator />\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-entityId-${tenantId}`}>\n {tr('admin.tenantDetail.sso.entityId', 'Entity ID')}\n <span className=\"ml-1 text-destructive\">*</span>\n </label>\n <Input\n id={`sso-entityId-${tenantId}`}\n value={form.entityId}\n onChange={(e) => setForm((f) => ({ ...f, entityId: e.target.value }))}\n placeholder=\"https://idp.example.com/entity\"\n required\n />\n </div>\n\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-ssoUrl-${tenantId}`}>\n {tr('admin.tenantDetail.sso.ssoUrl', 'SSO URL')}\n <span className=\"ml-1 text-destructive\">*</span>\n </label>\n <Input\n id={`sso-ssoUrl-${tenantId}`}\n value={form.ssoUrl}\n onChange={(e) => setForm((f) => ({ ...f, ssoUrl: e.target.value }))}\n placeholder=\"https://idp.example.com/sso\"\n required\n />\n </div>\n\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-sloUrl-${tenantId}`}>\n {tr('admin.tenantDetail.sso.sloUrl', 'SLO URL')}\n <span className=\"ml-0.5 text-xs text-muted-foreground\">\n {tr('admin.tenantDetail.sso.optional', '(optional)')}\n </span>\n </label>\n <Input\n id={`sso-sloUrl-${tenantId}`}\n value={form.sloUrl}\n onChange={(e) => setForm((f) => ({ ...f, sloUrl: e.target.value }))}\n placeholder=\"https://idp.example.com/slo\"\n />\n </div>\n\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-cert-${tenantId}`}>\n {tr('admin.tenantDetail.sso.certificate', 'X.509 Certificate')}\n <span className=\"ml-1 text-destructive\">*</span>\n </label>\n <textarea\n id={`sso-cert-${tenantId}`}\n value={form.certificate}\n onChange={(e) => setForm((f) => ({ ...f, certificate: e.target.value }))}\n placeholder=\"-----BEGIN CERTIFICATE-----&#10;...&#10;-----END CERTIFICATE-----\"\n required\n rows={5}\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 resize-none\"\n />\n </div>\n\n <div className=\"sm:col-span-2 flex items-center gap-3\">\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={form.enabled}\n aria-label={tr(\n 'admin.tenantDetail.sso.enabledLabel',\n 'Enable SSO for this tenant'\n )}\n onClick={() => setForm((f) => ({ ...f, enabled: !f.enabled }))}\n className={`relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ${\n form.enabled ? 'bg-primary' : 'bg-muted'\n }`}\n >\n <span\n className={`pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow-lg transition-transform ${\n form.enabled ? 'translate-x-4' : 'translate-x-0'\n }`}\n />\n </button>\n <span className=\"text-sm\">\n {tr('admin.tenantDetail.sso.enabledLabel', 'Enable SSO for this tenant')}\n </span>\n </div>\n </div>\n\n <Separator />\n\n <div className=\"flex gap-2\">\n <Button type=\"submit\" size=\"sm\" disabled={saving}>\n {saving ? (\n <Loader2 className=\"mr-1.5 h-3.5 w-3.5 animate-spin\" />\n ) : (\n <Save className=\"mr-1.5 h-3.5 w-3.5\" />\n )}\n {tr('admin.tenantDetail.sso.saveButton', 'Save SSO Config')}\n </Button>\n {editing && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setEditing(false)}\n >\n {tr('admin.tenantDetail.sso.cancelButton', 'Cancel')}\n </Button>\n )}\n </div>\n </form>\n )}\n </CardContent>\n )}\n </Card>\n\n <ConfirmDialog\n open={confirmDelete}\n onClose={() => setConfirmDelete(false)}\n title={tr('admin.tenantDetail.sso.deleteTitle', 'Remove SSO configuration?')}\n description={tr(\n 'admin.tenantDetail.sso.deleteDescription',\n 'This will remove the SAML SSO configuration. Users will no longer be able to sign in via SSO.'\n )}\n confirmLabel={tr('admin.tenantDetail.sso.deleteConfirm', 'Remove SSO')}\n variant=\"destructive\"\n onConfirm={handleDelete}\n loading={deleting}\n />\n </>\n );\n}\n"],"mappings":"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"}
@@ -18,6 +18,8 @@ export declare const ADMIN_PERMISSIONS: {
18
18
  readonly NOTIFICATION_READ: "notification:read";
19
19
  readonly RBAC_AUDIT_READ: "rbac:audit:read";
20
20
  readonly WORKSPACE_ADMIN: "workspace:admin";
21
+ readonly SYNC_OUTBOX_READ: "sync-outbox:read";
22
+ readonly SYNC_OUTBOX_MANAGE: "sync-outbox:manage";
21
23
  readonly PRODUCT_READ: "product:read";
22
24
  readonly PRODUCT_WRITE: "product:write";
23
25
  readonly WAITLIST_READ: "newsletter-waitlist:read";
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../src/admin/constants/permissions.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;CAgCpB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAC"}
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../src/admin/constants/permissions.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;CAoCpB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAC"}
@@ -12,6 +12,8 @@ var e = {
12
12
  NOTIFICATION_READ: "notification:read",
13
13
  RBAC_AUDIT_READ: "rbac:audit:read",
14
14
  WORKSPACE_ADMIN: "workspace:admin",
15
+ SYNC_OUTBOX_READ: "sync-outbox:read",
16
+ SYNC_OUTBOX_MANAGE: "sync-outbox:manage",
15
17
  PRODUCT_READ: "product:read",
16
18
  PRODUCT_WRITE: "product:write",
17
19
  WAITLIST_READ: "newsletter-waitlist:read",
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.js","names":[],"sources":["../../../src/admin/constants/permissions.ts"],"sourcesContent":["/**\n * Admin Console Permission Constants\n *\n * Maps to resource types and actions in global-rbac-svc seed.\n * The admin console uses global-layer resources: tenant, user, billing,\n * notification, rbac.\n */\n\nexport const ADMIN_PERMISSIONS = {\n // Tenant management\n TENANT_READ: 'tenant:read',\n TENANT_WRITE: 'tenant:write',\n TENANT_DELETE: 'tenant:delete',\n TENANT_ADMIN: 'tenant:admin',\n\n // User management\n USER_READ: 'user:read',\n USER_UPDATE: 'user:update',\n USER_DELETE: 'user:delete',\n USER_ADMIN: 'user:admin',\n\n // Billing\n BILLING_READ: 'billing:read',\n\n // Notifications / Communications\n NOTIFICATION_READ: 'notification:read',\n\n // Audit logs\n RBAC_AUDIT_READ: 'rbac:audit:read',\n\n // Workspace / Sandbox\n WORKSPACE_ADMIN: 'workspace:admin',\n\n // Product management\n PRODUCT_READ: 'product:read',\n PRODUCT_WRITE: 'product:write',\n\n // Waitlist management\n WAITLIST_READ: 'newsletter-waitlist:read',\n WAITLIST_MANAGE: 'newsletter-waitlist:approve',\n} as const;\n\nexport type AdminPermission = (typeof ADMIN_PERMISSIONS)[keyof typeof ADMIN_PERMISSIONS];\n"],"mappings":";AAQA,IAAa,IAAoB;CAE/B,aAAa;CACb,cAAc;CACd,eAAe;CACf,cAAc;CAGd,WAAW;CACX,aAAa;CACb,aAAa;CACb,YAAY;CAGZ,cAAc;CAGd,mBAAmB;CAGnB,iBAAiB;CAGjB,iBAAiB;CAGjB,cAAc;CACd,eAAe;CAGf,eAAe;CACf,iBAAiB;CAClB"}
1
+ {"version":3,"file":"permissions.js","names":[],"sources":["../../../src/admin/constants/permissions.ts"],"sourcesContent":["/**\n * Admin Console Permission Constants\n *\n * Maps to resource types and actions in global-rbac-svc seed.\n * The admin console uses global-layer resources: tenant, user, billing,\n * notification, rbac.\n */\n\nexport const ADMIN_PERMISSIONS = {\n // Tenant management\n TENANT_READ: 'tenant:read',\n TENANT_WRITE: 'tenant:write',\n TENANT_DELETE: 'tenant:delete',\n TENANT_ADMIN: 'tenant:admin',\n\n // User management\n USER_READ: 'user:read',\n USER_UPDATE: 'user:update',\n USER_DELETE: 'user:delete',\n USER_ADMIN: 'user:admin',\n\n // Billing\n BILLING_READ: 'billing:read',\n\n // Notifications / Communications\n NOTIFICATION_READ: 'notification:read',\n\n // Audit logs\n RBAC_AUDIT_READ: 'rbac:audit:read',\n\n // Workspace / Sandbox\n WORKSPACE_ADMIN: 'workspace:admin',\n\n // Cross-layer sync outbox (super-admin only)\n SYNC_OUTBOX_READ: 'sync-outbox:read',\n SYNC_OUTBOX_MANAGE: 'sync-outbox:manage',\n\n // Product management\n PRODUCT_READ: 'product:read',\n PRODUCT_WRITE: 'product:write',\n\n // Waitlist management\n WAITLIST_READ: 'newsletter-waitlist:read',\n WAITLIST_MANAGE: 'newsletter-waitlist:approve',\n} as const;\n\nexport type AdminPermission = (typeof ADMIN_PERMISSIONS)[keyof typeof ADMIN_PERMISSIONS];\n"],"mappings":";AAQA,IAAa,IAAoB;CAE/B,aAAa;CACb,cAAc;CACd,eAAe;CACf,cAAc;CAGd,WAAW;CACX,aAAa;CACb,aAAa;CACb,YAAY;CAGZ,cAAc;CAGd,mBAAmB;CAGnB,iBAAiB;CAGjB,iBAAiB;CAGjB,kBAAkB;CAClB,oBAAoB;CAGpB,cAAc;CACd,eAAe;CAGf,eAAe;CACf,iBAAiB;CAClB"}
@@ -19,6 +19,8 @@ export declare function useAdminPermissions(): {
19
19
  canViewAuditLogs: boolean;
20
20
  canManageSettings: boolean;
21
21
  canViewSRE: boolean;
22
+ canViewSyncFailures: boolean;
23
+ canManageSyncFailures: boolean;
22
24
  canManageSandbox: boolean;
23
25
  canViewProducts: boolean;
24
26
  canManageProducts: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"useAdminPermissions.d.ts","sourceRoot":"","sources":["../../../src/admin/hooks/useAdminPermissions.ts"],"names":[],"mappings":"AA+BA;;;;;;GAMG;AACH,wBAAgB,mBAAmB;;;;;;;;;;;;;;;;;;;;;EA8ElC"}
1
+ {"version":3,"file":"useAdminPermissions.d.ts","sourceRoot":"","sources":["../../../src/admin/hooks/useAdminPermissions.ts"],"names":[],"mappings":"AA+BA;;;;;;GAMG;AACH,wBAAgB,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;EAkFlC"}
@@ -41,6 +41,8 @@ function i() {
41
41
  canViewAuditLogs: u(e.RBAC_AUDIT_READ),
42
42
  canManageSettings: u(e.TENANT_ADMIN),
43
43
  canViewSRE: u(e.TENANT_ADMIN),
44
+ canViewSyncFailures: l || i(e.SYNC_OUTBOX_READ),
45
+ canManageSyncFailures: l || i(e.SYNC_OUTBOX_MANAGE),
44
46
  canManageSandbox: u(e.WORKSPACE_ADMIN),
45
47
  canViewProducts: d,
46
48
  canManageProducts: u(e.PRODUCT_WRITE),
@@ -1 +1 @@
1
- {"version":3,"file":"useAdminPermissions.js","names":[],"sources":["../../../src/admin/hooks/useAdminPermissions.ts"],"sourcesContent":["import { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useAuth } from '@burdenoff/fe-libs/shared/providers/shell/AuthProvider';\nimport { ADMIN_PERMISSIONS } from '../constants/permissions';\n\ninterface JwtAccessClaims {\n role: string | null;\n actorType: string | null;\n}\n\nfunction decodeJwtAccessClaims(token: string | undefined): JwtAccessClaims {\n if (!token) return { role: null, actorType: null };\n const parts = token.split('.');\n if (parts.length !== 3) return { role: null, actorType: null };\n try {\n const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n const normalized = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');\n const payload = JSON.parse(atob(normalized)) as Record<string, unknown>;\n return {\n role: typeof payload.role === 'string' ? payload.role : null,\n actorType:\n typeof payload.actor_type === 'string'\n ? payload.actor_type\n : typeof payload.actorType === 'string'\n ? payload.actorType\n : null,\n };\n } catch {\n return { role: null, actorType: null };\n }\n}\n\n/**\n * Hook for admin console RBAC permissions.\n *\n * SUPER_ADMIN role / actor type (verified from profile or JWT) bypasses all\n * permission checks.\n * Other users require granular permissions from the global RBAC service.\n */\nexport function useAdminPermissions() {\n const { hasPermission, isLoading } = usePermissions();\n const auth = useAuth();\n\n const jwtClaims = decodeJwtAccessClaims(auth.tokens?.accessToken);\n const userActorType =\n typeof auth.user?.actorType === 'string'\n ? auth.user.actorType\n : typeof auth.user?.actor_type === 'string'\n ? auth.user.actor_type\n : null;\n const isSuperAdmin =\n auth.user?.role === 'SUPER_ADMIN' ||\n auth.user?.role === 'super_admin' ||\n userActorType === 'super_admin' ||\n jwtClaims.role === 'SUPER_ADMIN' ||\n jwtClaims.role === 'super_admin' ||\n jwtClaims.actorType === 'super_admin';\n\n const check = (permission: string) => isSuperAdmin || hasPermission(permission);\n\n // True if the user holds any admin-level permission (read or admin grants)\n const hasAnyAdminAccess =\n isSuperAdmin ||\n hasPermission(ADMIN_PERMISSIONS.TENANT_READ) ||\n hasPermission(ADMIN_PERMISSIONS.TENANT_ADMIN) ||\n hasPermission(ADMIN_PERMISSIONS.USER_READ) ||\n hasPermission(ADMIN_PERMISSIONS.USER_ADMIN) ||\n hasPermission(ADMIN_PERMISSIONS.BILLING_READ) ||\n hasPermission(ADMIN_PERMISSIONS.NOTIFICATION_READ) ||\n hasPermission(ADMIN_PERMISSIONS.RBAC_AUDIT_READ) ||\n hasPermission(ADMIN_PERMISSIONS.WORKSPACE_ADMIN);\n\n return {\n // Tenant management\n canViewTenants: check(ADMIN_PERMISSIONS.TENANT_READ),\n canManageTenants: check(ADMIN_PERMISSIONS.TENANT_WRITE),\n canDeleteTenants: check(ADMIN_PERMISSIONS.TENANT_DELETE),\n isTenantAdmin: check(ADMIN_PERMISSIONS.TENANT_ADMIN),\n\n // User management\n canViewUsers: check(ADMIN_PERMISSIONS.USER_READ),\n canUpdateUsers: check(ADMIN_PERMISSIONS.USER_UPDATE),\n canDeleteUsers: check(ADMIN_PERMISSIONS.USER_DELETE),\n isUserAdmin: check(ADMIN_PERMISSIONS.USER_ADMIN),\n\n // Billing\n canViewBilling: check(ADMIN_PERMISSIONS.BILLING_READ),\n\n // Communications\n canViewCommunications: check(ADMIN_PERMISSIONS.NOTIFICATION_READ),\n\n // Audit\n canViewAuditLogs: check(ADMIN_PERMISSIONS.RBAC_AUDIT_READ),\n\n // Platform settings / SRE\n canManageSettings: check(ADMIN_PERMISSIONS.TENANT_ADMIN),\n canViewSRE: check(ADMIN_PERMISSIONS.TENANT_ADMIN),\n\n // Workspace / Sandbox\n canManageSandbox: check(ADMIN_PERMISSIONS.WORKSPACE_ADMIN),\n\n // Products\n canViewProducts: hasAnyAdminAccess,\n canManageProducts: check(ADMIN_PERMISSIONS.PRODUCT_WRITE),\n\n // Waitlist\n canViewWaitlist: hasAnyAdminAccess,\n canManageWaitlist:\n isSuperAdmin ||\n hasPermission(ADMIN_PERMISSIONS.WAITLIST_MANAGE) ||\n hasPermission(ADMIN_PERMISSIONS.TENANT_ADMIN),\n\n // General pages — any admin role can access\n canViewDashboard: hasAnyAdminAccess,\n\n isLoading: isSuperAdmin ? false : isLoading,\n };\n}\n"],"mappings":";;;;AASA,SAAS,EAAsB,GAA4C;AACzE,KAAI,CAAC,EAAO,QAAO;EAAE,MAAM;EAAM,WAAW;EAAM;CAClD,IAAM,IAAQ,EAAM,MAAM,IAAI;AAC9B,KAAI,EAAM,WAAW,EAAG,QAAO;EAAE,MAAM;EAAM,WAAW;EAAM;AAC9D,KAAI;EACF,IAAM,IAAS,EAAM,GAAG,QAAQ,MAAM,IAAI,CAAC,QAAQ,MAAM,IAAI,EACvD,IAAa,EAAO,OAAO,KAAK,KAAK,EAAO,SAAS,EAAE,GAAG,GAAG,IAAI,EACjE,IAAU,KAAK,MAAM,KAAK,EAAW,CAAC;AAC5C,SAAO;GACL,MAAM,OAAO,EAAQ,QAAS,WAAW,EAAQ,OAAO;GACxD,WACE,OAAO,EAAQ,cAAe,WAC1B,EAAQ,aACR,OAAO,EAAQ,aAAc,WAC3B,EAAQ,YACR;GACT;SACK;AACN,SAAO;GAAE,MAAM;GAAM,WAAW;GAAM;;;AAW1C,SAAgB,IAAsB;CACpC,IAAM,EAAE,kBAAe,iBAAc,GAAgB,EAC/C,IAAO,GAAS,EAEhB,IAAY,EAAsB,EAAK,QAAQ,YAAY,EAC3D,IACJ,OAAO,EAAK,MAAM,aAAc,WAC5B,EAAK,KAAK,YACV,OAAO,EAAK,MAAM,cAAe,WAC/B,EAAK,KAAK,aACV,MACF,IACJ,EAAK,MAAM,SAAS,iBACpB,EAAK,MAAM,SAAS,iBACpB,MAAkB,iBAClB,EAAU,SAAS,iBACnB,EAAU,SAAS,iBACnB,EAAU,cAAc,eAEpB,KAAS,MAAuB,KAAgB,EAAc,EAAW,EAGzE,IACJ,KACA,EAAc,EAAkB,YAAY,IAC5C,EAAc,EAAkB,aAAa,IAC7C,EAAc,EAAkB,UAAU,IAC1C,EAAc,EAAkB,WAAW,IAC3C,EAAc,EAAkB,aAAa,IAC7C,EAAc,EAAkB,kBAAkB,IAClD,EAAc,EAAkB,gBAAgB,IAChD,EAAc,EAAkB,gBAAgB;AAElD,QAAO;EAEL,gBAAgB,EAAM,EAAkB,YAAY;EACpD,kBAAkB,EAAM,EAAkB,aAAa;EACvD,kBAAkB,EAAM,EAAkB,cAAc;EACxD,eAAe,EAAM,EAAkB,aAAa;EAGpD,cAAc,EAAM,EAAkB,UAAU;EAChD,gBAAgB,EAAM,EAAkB,YAAY;EACpD,gBAAgB,EAAM,EAAkB,YAAY;EACpD,aAAa,EAAM,EAAkB,WAAW;EAGhD,gBAAgB,EAAM,EAAkB,aAAa;EAGrD,uBAAuB,EAAM,EAAkB,kBAAkB;EAGjE,kBAAkB,EAAM,EAAkB,gBAAgB;EAG1D,mBAAmB,EAAM,EAAkB,aAAa;EACxD,YAAY,EAAM,EAAkB,aAAa;EAGjD,kBAAkB,EAAM,EAAkB,gBAAgB;EAG1D,iBAAiB;EACjB,mBAAmB,EAAM,EAAkB,cAAc;EAGzD,iBAAiB;EACjB,mBACE,KACA,EAAc,EAAkB,gBAAgB,IAChD,EAAc,EAAkB,aAAa;EAG/C,kBAAkB;EAElB,WAAW,IAAe,KAAQ;EACnC"}
1
+ {"version":3,"file":"useAdminPermissions.js","names":[],"sources":["../../../src/admin/hooks/useAdminPermissions.ts"],"sourcesContent":["import { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useAuth } from '@burdenoff/fe-libs/shared/providers/shell/AuthProvider';\nimport { ADMIN_PERMISSIONS } from '../constants/permissions';\n\ninterface JwtAccessClaims {\n role: string | null;\n actorType: string | null;\n}\n\nfunction decodeJwtAccessClaims(token: string | undefined): JwtAccessClaims {\n if (!token) return { role: null, actorType: null };\n const parts = token.split('.');\n if (parts.length !== 3) return { role: null, actorType: null };\n try {\n const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n const normalized = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');\n const payload = JSON.parse(atob(normalized)) as Record<string, unknown>;\n return {\n role: typeof payload.role === 'string' ? payload.role : null,\n actorType:\n typeof payload.actor_type === 'string'\n ? payload.actor_type\n : typeof payload.actorType === 'string'\n ? payload.actorType\n : null,\n };\n } catch {\n return { role: null, actorType: null };\n }\n}\n\n/**\n * Hook for admin console RBAC permissions.\n *\n * SUPER_ADMIN role / actor type (verified from profile or JWT) bypasses all\n * permission checks.\n * Other users require granular permissions from the global RBAC service.\n */\nexport function useAdminPermissions() {\n const { hasPermission, isLoading } = usePermissions();\n const auth = useAuth();\n\n const jwtClaims = decodeJwtAccessClaims(auth.tokens?.accessToken);\n const userActorType =\n typeof auth.user?.actorType === 'string'\n ? auth.user.actorType\n : typeof auth.user?.actor_type === 'string'\n ? auth.user.actor_type\n : null;\n const isSuperAdmin =\n auth.user?.role === 'SUPER_ADMIN' ||\n auth.user?.role === 'super_admin' ||\n userActorType === 'super_admin' ||\n jwtClaims.role === 'SUPER_ADMIN' ||\n jwtClaims.role === 'super_admin' ||\n jwtClaims.actorType === 'super_admin';\n\n const check = (permission: string) => isSuperAdmin || hasPermission(permission);\n\n // True if the user holds any admin-level permission (read or admin grants)\n const hasAnyAdminAccess =\n isSuperAdmin ||\n hasPermission(ADMIN_PERMISSIONS.TENANT_READ) ||\n hasPermission(ADMIN_PERMISSIONS.TENANT_ADMIN) ||\n hasPermission(ADMIN_PERMISSIONS.USER_READ) ||\n hasPermission(ADMIN_PERMISSIONS.USER_ADMIN) ||\n hasPermission(ADMIN_PERMISSIONS.BILLING_READ) ||\n hasPermission(ADMIN_PERMISSIONS.NOTIFICATION_READ) ||\n hasPermission(ADMIN_PERMISSIONS.RBAC_AUDIT_READ) ||\n hasPermission(ADMIN_PERMISSIONS.WORKSPACE_ADMIN);\n\n return {\n // Tenant management\n canViewTenants: check(ADMIN_PERMISSIONS.TENANT_READ),\n canManageTenants: check(ADMIN_PERMISSIONS.TENANT_WRITE),\n canDeleteTenants: check(ADMIN_PERMISSIONS.TENANT_DELETE),\n isTenantAdmin: check(ADMIN_PERMISSIONS.TENANT_ADMIN),\n\n // User management\n canViewUsers: check(ADMIN_PERMISSIONS.USER_READ),\n canUpdateUsers: check(ADMIN_PERMISSIONS.USER_UPDATE),\n canDeleteUsers: check(ADMIN_PERMISSIONS.USER_DELETE),\n isUserAdmin: check(ADMIN_PERMISSIONS.USER_ADMIN),\n\n // Billing\n canViewBilling: check(ADMIN_PERMISSIONS.BILLING_READ),\n\n // Communications\n canViewCommunications: check(ADMIN_PERMISSIONS.NOTIFICATION_READ),\n\n // Audit\n canViewAuditLogs: check(ADMIN_PERMISSIONS.RBAC_AUDIT_READ),\n\n // Platform settings / SRE\n canManageSettings: check(ADMIN_PERMISSIONS.TENANT_ADMIN),\n canViewSRE: check(ADMIN_PERMISSIONS.TENANT_ADMIN),\n\n // Cross-layer sync outbox monitoring (super-admin only)\n canViewSyncFailures: isSuperAdmin || hasPermission(ADMIN_PERMISSIONS.SYNC_OUTBOX_READ),\n canManageSyncFailures: isSuperAdmin || hasPermission(ADMIN_PERMISSIONS.SYNC_OUTBOX_MANAGE),\n\n // Workspace / Sandbox\n canManageSandbox: check(ADMIN_PERMISSIONS.WORKSPACE_ADMIN),\n\n // Products\n canViewProducts: hasAnyAdminAccess,\n canManageProducts: check(ADMIN_PERMISSIONS.PRODUCT_WRITE),\n\n // Waitlist\n canViewWaitlist: hasAnyAdminAccess,\n canManageWaitlist:\n isSuperAdmin ||\n hasPermission(ADMIN_PERMISSIONS.WAITLIST_MANAGE) ||\n hasPermission(ADMIN_PERMISSIONS.TENANT_ADMIN),\n\n // General pages — any admin role can access\n canViewDashboard: hasAnyAdminAccess,\n\n isLoading: isSuperAdmin ? false : isLoading,\n };\n}\n"],"mappings":";;;;AASA,SAAS,EAAsB,GAA4C;AACzE,KAAI,CAAC,EAAO,QAAO;EAAE,MAAM;EAAM,WAAW;EAAM;CAClD,IAAM,IAAQ,EAAM,MAAM,IAAI;AAC9B,KAAI,EAAM,WAAW,EAAG,QAAO;EAAE,MAAM;EAAM,WAAW;EAAM;AAC9D,KAAI;EACF,IAAM,IAAS,EAAM,GAAG,QAAQ,MAAM,IAAI,CAAC,QAAQ,MAAM,IAAI,EACvD,IAAa,EAAO,OAAO,KAAK,KAAK,EAAO,SAAS,EAAE,GAAG,GAAG,IAAI,EACjE,IAAU,KAAK,MAAM,KAAK,EAAW,CAAC;AAC5C,SAAO;GACL,MAAM,OAAO,EAAQ,QAAS,WAAW,EAAQ,OAAO;GACxD,WACE,OAAO,EAAQ,cAAe,WAC1B,EAAQ,aACR,OAAO,EAAQ,aAAc,WAC3B,EAAQ,YACR;GACT;SACK;AACN,SAAO;GAAE,MAAM;GAAM,WAAW;GAAM;;;AAW1C,SAAgB,IAAsB;CACpC,IAAM,EAAE,kBAAe,iBAAc,GAAgB,EAC/C,IAAO,GAAS,EAEhB,IAAY,EAAsB,EAAK,QAAQ,YAAY,EAC3D,IACJ,OAAO,EAAK,MAAM,aAAc,WAC5B,EAAK,KAAK,YACV,OAAO,EAAK,MAAM,cAAe,WAC/B,EAAK,KAAK,aACV,MACF,IACJ,EAAK,MAAM,SAAS,iBACpB,EAAK,MAAM,SAAS,iBACpB,MAAkB,iBAClB,EAAU,SAAS,iBACnB,EAAU,SAAS,iBACnB,EAAU,cAAc,eAEpB,KAAS,MAAuB,KAAgB,EAAc,EAAW,EAGzE,IACJ,KACA,EAAc,EAAkB,YAAY,IAC5C,EAAc,EAAkB,aAAa,IAC7C,EAAc,EAAkB,UAAU,IAC1C,EAAc,EAAkB,WAAW,IAC3C,EAAc,EAAkB,aAAa,IAC7C,EAAc,EAAkB,kBAAkB,IAClD,EAAc,EAAkB,gBAAgB,IAChD,EAAc,EAAkB,gBAAgB;AAElD,QAAO;EAEL,gBAAgB,EAAM,EAAkB,YAAY;EACpD,kBAAkB,EAAM,EAAkB,aAAa;EACvD,kBAAkB,EAAM,EAAkB,cAAc;EACxD,eAAe,EAAM,EAAkB,aAAa;EAGpD,cAAc,EAAM,EAAkB,UAAU;EAChD,gBAAgB,EAAM,EAAkB,YAAY;EACpD,gBAAgB,EAAM,EAAkB,YAAY;EACpD,aAAa,EAAM,EAAkB,WAAW;EAGhD,gBAAgB,EAAM,EAAkB,aAAa;EAGrD,uBAAuB,EAAM,EAAkB,kBAAkB;EAGjE,kBAAkB,EAAM,EAAkB,gBAAgB;EAG1D,mBAAmB,EAAM,EAAkB,aAAa;EACxD,YAAY,EAAM,EAAkB,aAAa;EAGjD,qBAAqB,KAAgB,EAAc,EAAkB,iBAAiB;EACtF,uBAAuB,KAAgB,EAAc,EAAkB,mBAAmB;EAG1F,kBAAkB,EAAM,EAAkB,gBAAgB;EAG1D,iBAAiB;EACjB,mBAAmB,EAAM,EAAkB,cAAc;EAGzD,iBAAiB;EACjB,mBACE,KACA,EAAc,EAAkB,gBAAgB,IAChD,EAAc,EAAkB,aAAa;EAG/C,kBAAkB;EAElB,WAAW,IAAe,KAAQ;EACnC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Sync Failures admin page.
3
+ *
4
+ * Shows the state of the cross-layer sync outbox so a super-admin can spot
5
+ * users/workspaces that didn't get fully provisioned (e.g. signup happened
6
+ * while a downstream service was down) and manually requeue them once the
7
+ * underlying problem is fixed.
8
+ *
9
+ * Manual retry only flips the row to RETRYING; the background reconciler in
10
+ * global-tenant-svc actually performs the sync on its next tick. That keeps
11
+ * the existing single-writer retry semantics intact.
12
+ */
13
+ export declare function SyncFailuresPage(): import("react/jsx-runtime").JSX.Element;
14
+ //# sourceMappingURL=SyncFailuresPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SyncFailuresPage.d.ts","sourceRoot":"","sources":["../../../src/admin/pages/SyncFailuresPage.tsx"],"names":[],"mappings":"AAiFA;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,4CAmW/B"}