strapi-plugin-keycloak-realm-users 1.0.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 (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +485 -0
  3. package/__tests__/constants.test.mjs +207 -0
  4. package/__tests__/mocks/strapi.mjs +182 -0
  5. package/__tests__/services/audit-log.test.mjs +283 -0
  6. package/__tests__/services/keycloak-client.test.mjs +651 -0
  7. package/__tests__/services/permission.test.mjs +374 -0
  8. package/__tests__/services/realm.test.mjs +415 -0
  9. package/__tests__/services/user.test.mjs +487 -0
  10. package/__tests__/utils/errors.test.mjs +109 -0
  11. package/admin/src/components/Initializer.jsx +14 -0
  12. package/admin/src/components/RealmBadge.jsx +17 -0
  13. package/admin/src/constants.js +14 -0
  14. package/admin/src/hooks/useAuditLogs.js +142 -0
  15. package/admin/src/hooks/useKeycloakRoles.js +182 -0
  16. package/admin/src/hooks/useKeycloakUsers.js +477 -0
  17. package/admin/src/hooks/useRealmAdmins.js +249 -0
  18. package/admin/src/hooks/useRealms.js +269 -0
  19. package/admin/src/index.js +46 -0
  20. package/admin/src/pages/App.jsx +21 -0
  21. package/admin/src/pages/AuditPage/index.jsx +213 -0
  22. package/admin/src/pages/RealmsPage/RealmEditPage.jsx +791 -0
  23. package/admin/src/pages/RealmsPage/RealmListPage.jsx +231 -0
  24. package/admin/src/pages/RealmsPage/index.jsx +7 -0
  25. package/admin/src/pages/UsersPage/UserEditPage.jsx +313 -0
  26. package/admin/src/pages/UsersPage/UserListPage.jsx +437 -0
  27. package/admin/src/pages/UsersPage/index.jsx +7 -0
  28. package/admin/src/pluginId.js +2 -0
  29. package/admin/src/translations/en.json +77 -0
  30. package/admin/src/translations/fr.json +77 -0
  31. package/babel.config.cjs +17 -0
  32. package/coverage/clover.xml +422 -0
  33. package/coverage/coverage-final.json +8 -0
  34. package/coverage/lcov-report/base.css +224 -0
  35. package/coverage/lcov-report/block-navigation.js +87 -0
  36. package/coverage/lcov-report/favicon.png +0 -0
  37. package/coverage/lcov-report/index.html +146 -0
  38. package/coverage/lcov-report/prettify.css +1 -0
  39. package/coverage/lcov-report/prettify.js +2 -0
  40. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  41. package/coverage/lcov-report/sorter.js +210 -0
  42. package/coverage/lcov-report/src/bootstrap.js.html +346 -0
  43. package/coverage/lcov-report/src/config/index.html +116 -0
  44. package/coverage/lcov-report/src/config/index.js.html +106 -0
  45. package/coverage/lcov-report/src/constants.js.html +850 -0
  46. package/coverage/lcov-report/src/content-types/audit-log/index.html +116 -0
  47. package/coverage/lcov-report/src/content-types/audit-log/index.js.html +94 -0
  48. package/coverage/lcov-report/src/content-types/index.html +116 -0
  49. package/coverage/lcov-report/src/content-types/index.js.html +112 -0
  50. package/coverage/lcov-report/src/content-types/realm-admin/index.html +116 -0
  51. package/coverage/lcov-report/src/content-types/realm-admin/index.js.html +94 -0
  52. package/coverage/lcov-report/src/content-types/realm-config/index.html +116 -0
  53. package/coverage/lcov-report/src/content-types/realm-config/index.js.html +94 -0
  54. package/coverage/lcov-report/src/controllers/audit.js.html +517 -0
  55. package/coverage/lcov-report/src/controllers/index.html +161 -0
  56. package/coverage/lcov-report/src/controllers/index.js.html +112 -0
  57. package/coverage/lcov-report/src/controllers/realm.js.html +1057 -0
  58. package/coverage/lcov-report/src/controllers/user.js.html +1324 -0
  59. package/coverage/lcov-report/src/destroy.js.html +100 -0
  60. package/coverage/lcov-report/src/index.html +116 -0
  61. package/coverage/lcov-report/src/policies/can-access-realm.js.html +163 -0
  62. package/coverage/lcov-report/src/policies/index.html +146 -0
  63. package/coverage/lcov-report/src/policies/index.js.html +106 -0
  64. package/coverage/lcov-report/src/policies/is-authenticated.js.html +100 -0
  65. package/coverage/lcov-report/src/register.js.html +106 -0
  66. package/coverage/lcov-report/src/routes/admin.js.html +844 -0
  67. package/coverage/lcov-report/src/routes/index.html +131 -0
  68. package/coverage/lcov-report/src/routes/index.js.html +109 -0
  69. package/coverage/lcov-report/src/services/audit-log.js.html +673 -0
  70. package/coverage/lcov-report/src/services/index.html +176 -0
  71. package/coverage/lcov-report/src/services/index.js.html +124 -0
  72. package/coverage/lcov-report/src/services/keycloak-client.js.html +2359 -0
  73. package/coverage/lcov-report/src/services/permission.js.html +955 -0
  74. package/coverage/lcov-report/src/services/realm.js.html +1207 -0
  75. package/coverage/lcov-report/src/services/user.js.html +1924 -0
  76. package/coverage/lcov-report/src/utils/errors.js.html +274 -0
  77. package/coverage/lcov-report/src/utils/index.html +116 -0
  78. package/coverage/lcov-report/src/utils/index.js.html +103 -0
  79. package/coverage/lcov.info +804 -0
  80. package/dist/_chunks/App-BaKrvCeS.mjs +1975 -0
  81. package/dist/_chunks/App-DO6syS77.js +1975 -0
  82. package/dist/_chunks/en-Li-XBDe9.mjs +72 -0
  83. package/dist/_chunks/en-aCyfgNfr.js +72 -0
  84. package/dist/_chunks/fr-Cj33Q8jI.js +72 -0
  85. package/dist/_chunks/fr-vLrXph-Z.mjs +72 -0
  86. package/dist/_chunks/index-DwDO4-0C.js +69 -0
  87. package/dist/_chunks/index-jTVd7LdQ.mjs +70 -0
  88. package/dist/admin/index.js +3 -0
  89. package/dist/admin/index.mjs +4 -0
  90. package/dist/server/index.js +3003 -0
  91. package/dist/server/index.mjs +3004 -0
  92. package/jest.config.cjs +50 -0
  93. package/package.json +55 -0
  94. package/server/src/bootstrap.js +87 -0
  95. package/server/src/config/index.js +7 -0
  96. package/server/src/constants.js +255 -0
  97. package/server/src/content-types/audit-log/index.js +3 -0
  98. package/server/src/content-types/audit-log/schema.json +61 -0
  99. package/server/src/content-types/index.js +9 -0
  100. package/server/src/content-types/realm-admin/index.js +3 -0
  101. package/server/src/content-types/realm-admin/schema.json +45 -0
  102. package/server/src/content-types/realm-config/index.js +3 -0
  103. package/server/src/content-types/realm-config/schema.json +56 -0
  104. package/server/src/controllers/audit.js +144 -0
  105. package/server/src/controllers/index.js +9 -0
  106. package/server/src/controllers/realm.js +324 -0
  107. package/server/src/controllers/user.js +413 -0
  108. package/server/src/destroy.js +5 -0
  109. package/server/src/index.js +21 -0
  110. package/server/src/policies/can-access-realm.js +26 -0
  111. package/server/src/policies/index.js +7 -0
  112. package/server/src/policies/is-authenticated.js +5 -0
  113. package/server/src/register.js +7 -0
  114. package/server/src/routes/admin.js +253 -0
  115. package/server/src/routes/index.js +8 -0
  116. package/server/src/services/audit-log.js +196 -0
  117. package/server/src/services/index.js +13 -0
  118. package/server/src/services/keycloak-client.js +758 -0
  119. package/server/src/services/permission.js +290 -0
  120. package/server/src/services/realm.js +374 -0
  121. package/server/src/services/user.js +613 -0
  122. package/server/src/utils/errors.js +63 -0
  123. package/server/src/utils/index.js +6 -0
@@ -0,0 +1,1975 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const reactRouterDom = require("react-router-dom");
5
+ const react = require("react");
6
+ const reactIntl = require("react-intl");
7
+ const designSystem = require("@strapi/design-system");
8
+ const admin = require("@strapi/strapi/admin");
9
+ const icons = require("@strapi/icons");
10
+ const index = require("./index-DwDO4-0C.js");
11
+ const useRealms = () => {
12
+ const client = admin.useFetchClient();
13
+ const { toggleNotification } = admin.useNotification();
14
+ const [realms, setRealms] = react.useState([]);
15
+ const [isLoading, setIsLoading] = react.useState(true);
16
+ const fetchAll = react.useCallback(async () => {
17
+ setIsLoading(true);
18
+ try {
19
+ const { data } = await client.get(`${index.API_BASE_PATH}/realms`);
20
+ setRealms(data.data || []);
21
+ } catch (err) {
22
+ toggleNotification({
23
+ type: "danger",
24
+ message: err.response?.data?.error?.message || "Failed to fetch realms"
25
+ });
26
+ } finally {
27
+ setIsLoading(false);
28
+ }
29
+ }, [client, toggleNotification]);
30
+ const fetchOne = react.useCallback(
31
+ async (id) => {
32
+ try {
33
+ const { data } = await client.get(`${index.API_BASE_PATH}/realms/${id}`);
34
+ return data.data;
35
+ } catch (err) {
36
+ toggleNotification({
37
+ type: "danger",
38
+ message: err.response?.data?.error?.message || "Failed to fetch realm"
39
+ });
40
+ throw err;
41
+ }
42
+ },
43
+ [client, toggleNotification]
44
+ );
45
+ const create = react.useCallback(
46
+ async (realmData) => {
47
+ try {
48
+ const { data } = await client.post(`${index.API_BASE_PATH}/realms`, { data: realmData });
49
+ toggleNotification({
50
+ type: "success",
51
+ message: "Realm created successfully"
52
+ });
53
+ await fetchAll();
54
+ return data.data;
55
+ } catch (err) {
56
+ toggleNotification({
57
+ type: "danger",
58
+ message: err.response?.data?.error?.message || "Failed to create realm"
59
+ });
60
+ throw err;
61
+ }
62
+ },
63
+ [client, toggleNotification, fetchAll]
64
+ );
65
+ const update = react.useCallback(
66
+ async (id, realmData) => {
67
+ try {
68
+ const { data } = await client.put(`${index.API_BASE_PATH}/realms/${id}`, { data: realmData });
69
+ toggleNotification({
70
+ type: "success",
71
+ message: "Realm updated successfully"
72
+ });
73
+ await fetchAll();
74
+ return data.data;
75
+ } catch (err) {
76
+ toggleNotification({
77
+ type: "danger",
78
+ message: err.response?.data?.error?.message || "Failed to update realm"
79
+ });
80
+ throw err;
81
+ }
82
+ },
83
+ [client, toggleNotification, fetchAll]
84
+ );
85
+ const remove = react.useCallback(
86
+ async (id) => {
87
+ try {
88
+ await client.del(`${index.API_BASE_PATH}/realms/${id}`);
89
+ toggleNotification({
90
+ type: "success",
91
+ message: "Realm deleted successfully"
92
+ });
93
+ await fetchAll();
94
+ } catch (err) {
95
+ toggleNotification({
96
+ type: "danger",
97
+ message: err.response?.data?.error?.message || "Failed to delete realm"
98
+ });
99
+ throw err;
100
+ }
101
+ },
102
+ [client, toggleNotification, fetchAll]
103
+ );
104
+ const testConnection = react.useCallback(
105
+ async (id) => {
106
+ try {
107
+ const { data } = await client.post(`${index.API_BASE_PATH}/realms/${id}/test`);
108
+ return data.data;
109
+ } catch (err) {
110
+ return { success: false, message: err.response?.data?.error?.message || "Connection failed" };
111
+ }
112
+ },
113
+ [client]
114
+ );
115
+ const testConnectionRaw = react.useCallback(
116
+ async (config) => {
117
+ try {
118
+ const { data } = await client.post(`${index.API_BASE_PATH}/realms/test-connection`, { data: config });
119
+ return data.data;
120
+ } catch (err) {
121
+ return { success: false, message: err.response?.data?.error?.message || "Connection failed" };
122
+ }
123
+ },
124
+ [client]
125
+ );
126
+ react.useEffect(() => {
127
+ fetchAll();
128
+ }, [fetchAll]);
129
+ return {
130
+ realms,
131
+ isLoading,
132
+ fetchAll,
133
+ fetchOne,
134
+ create,
135
+ update,
136
+ remove,
137
+ testConnection,
138
+ testConnectionRaw
139
+ };
140
+ };
141
+ const RealmListPage = () => {
142
+ const { formatMessage } = reactIntl.useIntl();
143
+ const navigate = reactRouterDom.useNavigate();
144
+ const { realms, isLoading, remove, testConnection } = useRealms();
145
+ const [deleteId, setDeleteId] = react.useState(null);
146
+ const [testingId, setTestingId] = react.useState(null);
147
+ const [testResults, setTestResults] = react.useState({});
148
+ const handleDelete = async () => {
149
+ if (deleteId) {
150
+ await remove(deleteId);
151
+ setDeleteId(null);
152
+ }
153
+ };
154
+ const handleTest = async (id) => {
155
+ setTestingId(id);
156
+ const result = await testConnection(id);
157
+ setTestResults((prev) => ({ ...prev, [id]: result }));
158
+ setTestingId(null);
159
+ };
160
+ if (isLoading) {
161
+ return /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Root, { children: /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, {}) }) }) });
162
+ }
163
+ return /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Root, { children: [
164
+ /* @__PURE__ */ jsxRuntime.jsx(
165
+ admin.Layouts.Header,
166
+ {
167
+ title: formatMessage({ id: index.getTrad("realms.title"), defaultMessage: "Keycloak Realms" }),
168
+ subtitle: formatMessage({
169
+ id: index.getTrad("realms.subtitle"),
170
+ defaultMessage: "Configure Keycloak realm connections"
171
+ }),
172
+ primaryAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}), onClick: () => navigate(`/settings/${index.PLUGIN_ID}/realms/create`), children: formatMessage({ id: index.getTrad("realms.create"), defaultMessage: "Add Realm" }) })
173
+ }
174
+ ),
175
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: realms.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
176
+ designSystem.EmptyStateLayout,
177
+ {
178
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.User, { width: "10rem", height: "10rem" }),
179
+ content: formatMessage({
180
+ id: index.getTrad("realms.empty.description"),
181
+ defaultMessage: "Add your first Keycloak realm to start managing users."
182
+ }),
183
+ action: /* @__PURE__ */ jsxRuntime.jsx(
184
+ designSystem.Button,
185
+ {
186
+ variant: "secondary",
187
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
188
+ onClick: () => navigate(`/settings/${index.PLUGIN_ID}/realms/create`),
189
+ children: formatMessage({ id: index.getTrad("realms.create"), defaultMessage: "Add Realm" })
190
+ }
191
+ )
192
+ }
193
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 6, rowCount: realms.length + 1, children: [
194
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
195
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({ id: index.getTrad("realm.displayName"), defaultMessage: "Display Name" }) }) }),
196
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({ id: index.getTrad("realm.name"), defaultMessage: "Name" }) }) }),
197
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({ id: index.getTrad("realm.realmName"), defaultMessage: "Realm Name" }) }) }),
198
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({ id: index.getTrad("realm.enabled"), defaultMessage: "Enabled" }) }) }),
199
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Status" }) }),
200
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Actions" }) })
201
+ ] }) }),
202
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: realms.map((realm) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
203
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, children: [
204
+ /* @__PURE__ */ jsxRuntime.jsx(
205
+ designSystem.Box,
206
+ {
207
+ width: "12px",
208
+ height: "12px",
209
+ borderRadius: "50%",
210
+ background: realm.color || "primary600"
211
+ }
212
+ ),
213
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", fontWeight: "bold", children: realm.displayName })
214
+ ] }) }),
215
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: realm.name }) }),
216
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: realm.realmName }) }),
217
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: realm.enabled ? "success100" : "neutral150", children: realm.enabled ? "Enabled" : "Disabled" }) }),
218
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: testResults[realm.documentId] ? /* @__PURE__ */ jsxRuntime.jsx(
219
+ designSystem.Badge,
220
+ {
221
+ backgroundColor: testResults[realm.documentId].success ? "success100" : "danger100",
222
+ children: testResults[realm.documentId].success ? "Connected" : "Failed"
223
+ }
224
+ ) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral400", children: "-" }) }),
225
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, children: [
226
+ /* @__PURE__ */ jsxRuntime.jsx(
227
+ designSystem.IconButton,
228
+ {
229
+ withTooltip: false,
230
+ label: "Test Connection",
231
+ onClick: () => handleTest(realm.documentId),
232
+ loading: testingId === realm.documentId,
233
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Play, {})
234
+ }
235
+ ),
236
+ /* @__PURE__ */ jsxRuntime.jsx(
237
+ designSystem.IconButton,
238
+ {
239
+ withTooltip: false,
240
+ label: "Manage Users",
241
+ onClick: () => navigate(`/settings/${index.PLUGIN_ID}/realms/${realm.documentId}/users`),
242
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.User, {})
243
+ }
244
+ ),
245
+ /* @__PURE__ */ jsxRuntime.jsx(
246
+ designSystem.IconButton,
247
+ {
248
+ withTooltip: false,
249
+ label: "Edit",
250
+ onClick: () => navigate(`/settings/${index.PLUGIN_ID}/realms/${realm.documentId}`),
251
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Pencil, {})
252
+ }
253
+ ),
254
+ /* @__PURE__ */ jsxRuntime.jsx(
255
+ designSystem.IconButton,
256
+ {
257
+ withTooltip: false,
258
+ label: "Delete",
259
+ onClick: () => setDeleteId(realm.documentId),
260
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
261
+ }
262
+ )
263
+ ] }) })
264
+ ] }, realm.documentId)) })
265
+ ] }) }),
266
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Root, { open: !!deleteId, onOpenChange: () => setDeleteId(null), children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Content, { children: [
267
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Header, { children: "Delete Realm" }),
268
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Body, { children: formatMessage({
269
+ id: index.getTrad("realm.delete.confirm"),
270
+ defaultMessage: "Are you sure you want to delete this realm configuration?"
271
+ }) }),
272
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Footer, { children: [
273
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Cancel, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", children: "Cancel" }) }),
274
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Action, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "danger-light", onClick: handleDelete, children: "Delete" }) })
275
+ ] })
276
+ ] }) })
277
+ ] });
278
+ };
279
+ const useRealmAdmins = (realmId) => {
280
+ const client = admin.useFetchClient();
281
+ const { toggleNotification } = admin.useNotification();
282
+ const [admins, setAdmins] = react.useState([]);
283
+ const [strapiUsers, setStrapiUsers] = react.useState([]);
284
+ const [isLoading, setIsLoading] = react.useState(false);
285
+ const fetchAdmins = react.useCallback(async () => {
286
+ if (!realmId) return;
287
+ setIsLoading(true);
288
+ try {
289
+ const { data } = await client.get(`${index.API_BASE_PATH}/realms/${realmId}/admins`);
290
+ setAdmins(data.data || []);
291
+ } catch (err) {
292
+ toggleNotification({
293
+ type: "danger",
294
+ message: err.response?.data?.error?.message || "Failed to fetch realm admins"
295
+ });
296
+ } finally {
297
+ setIsLoading(false);
298
+ }
299
+ }, [realmId, client, toggleNotification]);
300
+ const fetchStrapiUsers = react.useCallback(async () => {
301
+ try {
302
+ const { data } = await client.get("/admin/users");
303
+ setStrapiUsers(data.data?.results || data.data || []);
304
+ } catch (err) {
305
+ toggleNotification({
306
+ type: "danger",
307
+ message: "Failed to fetch Strapi users"
308
+ });
309
+ }
310
+ }, [client, toggleNotification]);
311
+ const addAdmin = react.useCallback(
312
+ async (strapiUserId, strapiUserEmail, permissions) => {
313
+ try {
314
+ const { data } = await client.post(`${index.API_BASE_PATH}/realms/${realmId}/admins`, {
315
+ data: {
316
+ strapiUserId,
317
+ strapiUserEmail,
318
+ permissions
319
+ }
320
+ });
321
+ toggleNotification({
322
+ type: "success",
323
+ message: "Admin added successfully"
324
+ });
325
+ await fetchAdmins();
326
+ return data.data;
327
+ } catch (err) {
328
+ toggleNotification({
329
+ type: "danger",
330
+ message: err.response?.data?.error?.message || "Failed to add admin"
331
+ });
332
+ throw err;
333
+ }
334
+ },
335
+ [realmId, client, toggleNotification, fetchAdmins]
336
+ );
337
+ const updateAdmin = react.useCallback(
338
+ async (adminId, permissions) => {
339
+ try {
340
+ const { data } = await client.put(`${index.API_BASE_PATH}/realms/${realmId}/admins/${adminId}`, {
341
+ data: { permissions }
342
+ });
343
+ toggleNotification({
344
+ type: "success",
345
+ message: "Admin permissions updated"
346
+ });
347
+ await fetchAdmins();
348
+ return data.data;
349
+ } catch (err) {
350
+ toggleNotification({
351
+ type: "danger",
352
+ message: err.response?.data?.error?.message || "Failed to update admin"
353
+ });
354
+ throw err;
355
+ }
356
+ },
357
+ [realmId, client, toggleNotification, fetchAdmins]
358
+ );
359
+ const removeAdmin = react.useCallback(
360
+ async (adminId) => {
361
+ try {
362
+ await client.del(`${index.API_BASE_PATH}/realms/${realmId}/admins/${adminId}`);
363
+ toggleNotification({
364
+ type: "success",
365
+ message: "Admin removed successfully"
366
+ });
367
+ await fetchAdmins();
368
+ } catch (err) {
369
+ toggleNotification({
370
+ type: "danger",
371
+ message: err.response?.data?.error?.message || "Failed to remove admin"
372
+ });
373
+ throw err;
374
+ }
375
+ },
376
+ [realmId, client, toggleNotification, fetchAdmins]
377
+ );
378
+ return {
379
+ admins,
380
+ strapiUsers,
381
+ isLoading,
382
+ fetchAdmins,
383
+ fetchStrapiUsers,
384
+ addAdmin,
385
+ updateAdmin,
386
+ removeAdmin
387
+ };
388
+ };
389
+ const PERMISSIONS = [
390
+ { key: "canRead", label: "Read", description: "View users" },
391
+ { key: "canCreate", label: "Create", description: "Add new users" },
392
+ { key: "canUpdate", label: "Update", description: "Edit user details" },
393
+ { key: "canDelete", label: "Delete", description: "Remove users" },
394
+ { key: "canResetPassword", label: "Reset Password", description: "Change user passwords" },
395
+ { key: "canManageRoles", label: "Manage Roles", description: "Assign/remove Keycloak roles" }
396
+ ];
397
+ const RealmEditPage = () => {
398
+ const { id } = reactRouterDom.useParams();
399
+ const navigate = reactRouterDom.useNavigate();
400
+ const { formatMessage } = reactIntl.useIntl();
401
+ const { fetchOne, create, update, testConnectionRaw } = useRealms();
402
+ const {
403
+ admins,
404
+ strapiUsers,
405
+ isLoading: isLoadingAdmins,
406
+ fetchAdmins,
407
+ fetchStrapiUsers,
408
+ addAdmin,
409
+ updateAdmin,
410
+ removeAdmin
411
+ } = useRealmAdmins(id);
412
+ const isEditMode = !!id;
413
+ const [activeTab, setActiveTab] = react.useState("configuration");
414
+ const [isLoading, setIsLoading] = react.useState(isEditMode);
415
+ const [isSaving, setIsSaving] = react.useState(false);
416
+ const [isTesting, setIsTesting] = react.useState(false);
417
+ const [testResult, setTestResult] = react.useState(null);
418
+ const [formData, setFormData] = react.useState({
419
+ name: "",
420
+ displayName: "",
421
+ serverUrl: "",
422
+ realmName: "",
423
+ clientId: "",
424
+ clientSecret: "",
425
+ enabled: true,
426
+ color: "#4945ff"
427
+ });
428
+ const [errors, setErrors] = react.useState({});
429
+ const [showAddModal, setShowAddModal] = react.useState(false);
430
+ const [showEditModal, setShowEditModal] = react.useState(false);
431
+ const [selectedAdmin, setSelectedAdmin] = react.useState(null);
432
+ const [deleteAdminId, setDeleteAdminId] = react.useState(null);
433
+ const [newAdminUserId, setNewAdminUserId] = react.useState("");
434
+ const [adminPermissions, setAdminPermissions] = react.useState({
435
+ canRead: true,
436
+ canCreate: false,
437
+ canUpdate: false,
438
+ canDelete: false,
439
+ canResetPassword: false,
440
+ canManageRoles: false
441
+ });
442
+ react.useEffect(() => {
443
+ if (isEditMode) {
444
+ fetchOne(id).then((realm) => {
445
+ setFormData({
446
+ name: realm.name || "",
447
+ displayName: realm.displayName || "",
448
+ serverUrl: realm.serverUrl || "",
449
+ realmName: realm.realmName || "",
450
+ clientId: realm.clientId || "",
451
+ clientSecret: "",
452
+ enabled: realm.enabled !== false,
453
+ color: realm.color || "#4945ff"
454
+ });
455
+ }).catch(() => {
456
+ navigate(`/settings/${index.PLUGIN_ID}`);
457
+ }).finally(() => {
458
+ setIsLoading(false);
459
+ });
460
+ }
461
+ }, [id, isEditMode, fetchOne, navigate]);
462
+ react.useEffect(() => {
463
+ if (isEditMode && activeTab === "admins") {
464
+ fetchAdmins();
465
+ fetchStrapiUsers();
466
+ }
467
+ }, [isEditMode, activeTab, fetchAdmins, fetchStrapiUsers]);
468
+ const handleChange = (field) => (e) => {
469
+ const value = e.target ? e.target.value : e;
470
+ setFormData((prev) => ({ ...prev, [field]: value }));
471
+ setErrors((prev) => ({ ...prev, [field]: null }));
472
+ setTestResult(null);
473
+ };
474
+ const handleToggle = (field) => () => {
475
+ setFormData((prev) => ({ ...prev, [field]: !prev[field] }));
476
+ };
477
+ const validate = () => {
478
+ const newErrors = {};
479
+ if (!formData.name) {
480
+ newErrors.name = "Name is required";
481
+ } else if (!/^[a-z0-9-]+$/.test(formData.name)) {
482
+ newErrors.name = "Name must contain only lowercase letters, numbers, and hyphens";
483
+ }
484
+ if (!formData.displayName) {
485
+ newErrors.displayName = "Display name is required";
486
+ }
487
+ if (!formData.serverUrl) {
488
+ newErrors.serverUrl = "Server URL is required";
489
+ }
490
+ if (!formData.realmName) {
491
+ newErrors.realmName = "Realm name is required";
492
+ }
493
+ if (!formData.clientId) {
494
+ newErrors.clientId = "Client ID is required";
495
+ }
496
+ if (!isEditMode && !formData.clientSecret) {
497
+ newErrors.clientSecret = "Client secret is required for new realms";
498
+ }
499
+ setErrors(newErrors);
500
+ return Object.keys(newErrors).length === 0;
501
+ };
502
+ const handleTestConnection = async () => {
503
+ if (!formData.serverUrl || !formData.realmName || !formData.clientId) {
504
+ setTestResult({ success: false, message: "Please fill in server URL, realm name, and client ID" });
505
+ return;
506
+ }
507
+ setIsTesting(true);
508
+ setTestResult(null);
509
+ try {
510
+ const result = await testConnectionRaw({
511
+ serverUrl: formData.serverUrl,
512
+ realmName: formData.realmName,
513
+ clientId: formData.clientId,
514
+ clientSecret: formData.clientSecret || void 0
515
+ });
516
+ setTestResult(result);
517
+ } catch {
518
+ setTestResult({ success: false, message: "Connection test failed" });
519
+ } finally {
520
+ setIsTesting(false);
521
+ }
522
+ };
523
+ const handleSubmit = async () => {
524
+ if (!validate()) return;
525
+ setIsSaving(true);
526
+ try {
527
+ const dataToSave = { ...formData };
528
+ if (!dataToSave.clientSecret) {
529
+ delete dataToSave.clientSecret;
530
+ }
531
+ if (isEditMode) {
532
+ await update(id, dataToSave);
533
+ } else {
534
+ await create(dataToSave);
535
+ }
536
+ navigate(`/settings/${index.PLUGIN_ID}`);
537
+ } catch {
538
+ } finally {
539
+ setIsSaving(false);
540
+ }
541
+ };
542
+ const handleOpenAddModal = () => {
543
+ setNewAdminUserId("");
544
+ setAdminPermissions({
545
+ canRead: true,
546
+ canCreate: false,
547
+ canUpdate: false,
548
+ canDelete: false,
549
+ canResetPassword: false,
550
+ canManageRoles: false
551
+ });
552
+ setShowAddModal(true);
553
+ };
554
+ const handleOpenEditModal = (admin2) => {
555
+ setSelectedAdmin(admin2);
556
+ setAdminPermissions({
557
+ canRead: admin2.canRead || false,
558
+ canCreate: admin2.canCreate || false,
559
+ canUpdate: admin2.canUpdate || false,
560
+ canDelete: admin2.canDelete || false,
561
+ canResetPassword: admin2.canResetPassword || false,
562
+ canManageRoles: admin2.canManageRoles || false
563
+ });
564
+ setShowEditModal(true);
565
+ };
566
+ const handleAddAdmin = async () => {
567
+ if (!newAdminUserId) return;
568
+ const selectedUser = strapiUsers.find((u) => u.id === parseInt(newAdminUserId, 10));
569
+ try {
570
+ await addAdmin(parseInt(newAdminUserId, 10), selectedUser?.email, adminPermissions);
571
+ setShowAddModal(false);
572
+ } catch {
573
+ }
574
+ };
575
+ const handleUpdateAdmin = async () => {
576
+ if (!selectedAdmin) return;
577
+ try {
578
+ await updateAdmin(selectedAdmin.strapiUserId, adminPermissions);
579
+ setShowEditModal(false);
580
+ setSelectedAdmin(null);
581
+ } catch {
582
+ }
583
+ };
584
+ const handleDeleteAdmin = async () => {
585
+ if (!deleteAdminId) return;
586
+ try {
587
+ await removeAdmin(deleteAdminId);
588
+ setDeleteAdminId(null);
589
+ } catch {
590
+ }
591
+ };
592
+ const handlePermissionChange = (key) => () => {
593
+ setAdminPermissions((prev) => ({ ...prev, [key]: !prev[key] }));
594
+ };
595
+ const availableUsers = strapiUsers.filter(
596
+ (user) => !admins.some((admin2) => admin2.strapiUserId === user.id)
597
+ );
598
+ if (isLoading) {
599
+ return /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Root, { children: /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, {}) }) }) });
600
+ }
601
+ return /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Root, { children: [
602
+ /* @__PURE__ */ jsxRuntime.jsx(
603
+ admin.Layouts.Header,
604
+ {
605
+ title: isEditMode ? "Edit Realm" : "Create Realm",
606
+ subtitle: isEditMode && formData.displayName,
607
+ navigationAction: /* @__PURE__ */ jsxRuntime.jsx(
608
+ designSystem.Button,
609
+ {
610
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}),
611
+ variant: "ghost",
612
+ onClick: () => navigate(`/settings/${index.PLUGIN_ID}`),
613
+ children: formatMessage({ id: index.getTrad("common.back"), defaultMessage: "Back" })
614
+ }
615
+ ),
616
+ primaryAction: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
617
+ activeTab === "configuration" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
618
+ /* @__PURE__ */ jsxRuntime.jsx(
619
+ designSystem.Button,
620
+ {
621
+ variant: "secondary",
622
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Play, {}),
623
+ onClick: handleTestConnection,
624
+ loading: isTesting,
625
+ children: formatMessage({ id: index.getTrad("realm.testConnection"), defaultMessage: "Test Connection" })
626
+ }
627
+ ),
628
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Check, {}), onClick: handleSubmit, loading: isSaving, children: formatMessage({ id: index.getTrad("common.save"), defaultMessage: "Save" }) })
629
+ ] }),
630
+ activeTab === "admins" && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}), onClick: handleOpenAddModal, children: "Add Admin" })
631
+ ] })
632
+ }
633
+ ),
634
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: isEditMode ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs.Root, { value: activeTab, onValueChange: setActiveTab, children: [
635
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs.List, { children: [
636
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Trigger, { value: "configuration", children: "Configuration" }),
637
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Trigger, { value: "admins", children: "Admins" })
638
+ ] }),
639
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginTop: 4, children: [
640
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "configuration", children: /* @__PURE__ */ jsxRuntime.jsx(
641
+ ConfigurationTab,
642
+ {
643
+ formData,
644
+ errors,
645
+ testResult,
646
+ isEditMode,
647
+ handleChange,
648
+ handleToggle,
649
+ setTestResult,
650
+ formatMessage
651
+ }
652
+ ) }),
653
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "admins", children: /* @__PURE__ */ jsxRuntime.jsx(
654
+ AdminsTab,
655
+ {
656
+ admins,
657
+ isLoading: isLoadingAdmins,
658
+ onEdit: handleOpenEditModal,
659
+ onDelete: setDeleteAdminId
660
+ }
661
+ ) })
662
+ ] })
663
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
664
+ ConfigurationTab,
665
+ {
666
+ formData,
667
+ errors,
668
+ testResult,
669
+ isEditMode,
670
+ handleChange,
671
+ handleToggle,
672
+ setTestResult,
673
+ formatMessage
674
+ }
675
+ ) }),
676
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Root, { open: showAddModal, onOpenChange: () => setShowAddModal(false), children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Content, { children: [
677
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Title, { children: "Add Realm Admin" }) }),
678
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 4, children: [
679
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
680
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { required: true, children: "Select User" }),
681
+ /* @__PURE__ */ jsxRuntime.jsx(
682
+ designSystem.SingleSelect,
683
+ {
684
+ value: newAdminUserId,
685
+ onChange: setNewAdminUserId,
686
+ placeholder: "Select a Strapi user...",
687
+ children: availableUsers.map((user) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.SingleSelectOption, { value: String(user.id), children: [
688
+ user.firstname,
689
+ " ",
690
+ user.lastname,
691
+ " (",
692
+ user.email,
693
+ ")"
694
+ ] }, user.id))
695
+ }
696
+ )
697
+ ] }),
698
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
699
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: "Permissions" }),
700
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 2, children: PERMISSIONS.map((perm) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, marginBottom: 2, children: [
701
+ /* @__PURE__ */ jsxRuntime.jsx(
702
+ designSystem.Checkbox,
703
+ {
704
+ checked: adminPermissions[perm.key],
705
+ onCheckedChange: handlePermissionChange(perm.key)
706
+ }
707
+ ),
708
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
709
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "semiBold", children: perm.label }),
710
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: perm.description })
711
+ ] })
712
+ ] }, perm.key)) })
713
+ ] })
714
+ ] }) }),
715
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Footer, { children: [
716
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Close, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", children: "Cancel" }) }),
717
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleAddAdmin, disabled: !newAdminUserId, children: "Add Admin" })
718
+ ] })
719
+ ] }) }),
720
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Root, { open: showEditModal, onOpenChange: () => setShowEditModal(false), children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Content, { children: [
721
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Title, { children: "Edit Admin Permissions" }) }),
722
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 4, children: [
723
+ selectedAdmin && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral100", padding: 3, hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: selectedAdmin.strapiUserEmail }) }),
724
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
725
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: "Permissions" }),
726
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 2, children: PERMISSIONS.map((perm) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, marginBottom: 2, children: [
727
+ /* @__PURE__ */ jsxRuntime.jsx(
728
+ designSystem.Checkbox,
729
+ {
730
+ checked: adminPermissions[perm.key],
731
+ onCheckedChange: handlePermissionChange(perm.key)
732
+ }
733
+ ),
734
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
735
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "semiBold", children: perm.label }),
736
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: perm.description })
737
+ ] })
738
+ ] }, perm.key)) })
739
+ ] })
740
+ ] }) }),
741
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Footer, { children: [
742
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Close, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", children: "Cancel" }) }),
743
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleUpdateAdmin, children: "Save Permissions" })
744
+ ] })
745
+ ] }) }),
746
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Root, { open: !!deleteAdminId, onOpenChange: () => setDeleteAdminId(null), children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Content, { children: [
747
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Header, { children: "Remove Admin" }),
748
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Body, { children: "Are you sure you want to remove this admin from the realm? They will lose all permissions." }),
749
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Footer, { children: [
750
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Cancel, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", children: "Cancel" }) }),
751
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Action, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "danger-light", onClick: handleDeleteAdmin, children: "Remove" }) })
752
+ ] })
753
+ ] }) })
754
+ ] });
755
+ };
756
+ const ConfigurationTab = ({
757
+ formData,
758
+ errors,
759
+ testResult,
760
+ isEditMode,
761
+ handleChange,
762
+ handleToggle,
763
+ setTestResult,
764
+ formatMessage
765
+ }) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", padding: 6, shadow: "filterShadow", hasRadius: true, children: [
766
+ testResult && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginBottom: 4, children: /* @__PURE__ */ jsxRuntime.jsx(
767
+ designSystem.Alert,
768
+ {
769
+ variant: testResult.success ? "success" : "danger",
770
+ title: testResult.success ? "Connection Successful" : "Connection Failed",
771
+ onClose: () => setTestResult(null),
772
+ closeLabel: "Close",
773
+ children: testResult.success ? `Connected to realm: ${testResult.realmDisplayName || formData.realmName}` : testResult.message
774
+ }
775
+ ) }),
776
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Root, { gap: 4, children: [
777
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { error: errors.name, children: [
778
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { required: true, children: formatMessage({ id: index.getTrad("realm.name"), defaultMessage: "Name" }) }),
779
+ /* @__PURE__ */ jsxRuntime.jsx(
780
+ designSystem.TextInput,
781
+ {
782
+ name: "name",
783
+ value: formData.name,
784
+ onChange: handleChange("name"),
785
+ disabled: isEditMode
786
+ }
787
+ ),
788
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, { children: formatMessage({
789
+ id: index.getTrad("realm.name.hint"),
790
+ defaultMessage: "Unique identifier (lowercase, numbers, hyphens only)"
791
+ }) }),
792
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {})
793
+ ] }) }),
794
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { error: errors.displayName, children: [
795
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { required: true, children: formatMessage({ id: index.getTrad("realm.displayName"), defaultMessage: "Display Name" }) }),
796
+ /* @__PURE__ */ jsxRuntime.jsx(
797
+ designSystem.TextInput,
798
+ {
799
+ name: "displayName",
800
+ value: formData.displayName,
801
+ onChange: handleChange("displayName")
802
+ }
803
+ ),
804
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {})
805
+ ] }) }),
806
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { error: errors.serverUrl, children: [
807
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { required: true, children: formatMessage({ id: index.getTrad("realm.serverUrl"), defaultMessage: "Server URL" }) }),
808
+ /* @__PURE__ */ jsxRuntime.jsx(
809
+ designSystem.TextInput,
810
+ {
811
+ name: "serverUrl",
812
+ value: formData.serverUrl,
813
+ onChange: handleChange("serverUrl")
814
+ }
815
+ ),
816
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, { children: formatMessage({
817
+ id: index.getTrad("realm.serverUrl.hint"),
818
+ defaultMessage: "e.g., https://keycloak.example.com"
819
+ }) }),
820
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {})
821
+ ] }) }),
822
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { error: errors.realmName, children: [
823
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { required: true, children: formatMessage({ id: index.getTrad("realm.realmName"), defaultMessage: "Realm Name" }) }),
824
+ /* @__PURE__ */ jsxRuntime.jsx(
825
+ designSystem.TextInput,
826
+ {
827
+ name: "realmName",
828
+ value: formData.realmName,
829
+ onChange: handleChange("realmName")
830
+ }
831
+ ),
832
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, { children: formatMessage({
833
+ id: index.getTrad("realm.realmName.hint"),
834
+ defaultMessage: "The Keycloak realm name"
835
+ }) }),
836
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {})
837
+ ] }) }),
838
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { error: errors.clientId, children: [
839
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { required: true, children: formatMessage({ id: index.getTrad("realm.clientId"), defaultMessage: "Client ID" }) }),
840
+ /* @__PURE__ */ jsxRuntime.jsx(
841
+ designSystem.TextInput,
842
+ {
843
+ name: "clientId",
844
+ value: formData.clientId,
845
+ onChange: handleChange("clientId")
846
+ }
847
+ ),
848
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, { children: formatMessage({
849
+ id: index.getTrad("realm.clientId.hint"),
850
+ defaultMessage: "Service account client ID with admin permissions"
851
+ }) }),
852
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {})
853
+ ] }) }),
854
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { error: errors.clientSecret, children: [
855
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { required: !isEditMode, children: formatMessage({ id: index.getTrad("realm.clientSecret"), defaultMessage: "Client Secret" }) }),
856
+ /* @__PURE__ */ jsxRuntime.jsx(
857
+ designSystem.TextInput,
858
+ {
859
+ name: "clientSecret",
860
+ type: "password",
861
+ value: formData.clientSecret,
862
+ onChange: handleChange("clientSecret"),
863
+ placeholder: isEditMode ? "Leave empty to keep current secret" : ""
864
+ }
865
+ ),
866
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {})
867
+ ] }) }),
868
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
869
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({ id: index.getTrad("realm.color"), defaultMessage: "Color" }) }),
870
+ /* @__PURE__ */ jsxRuntime.jsx(
871
+ designSystem.TextInput,
872
+ {
873
+ name: "color",
874
+ type: "color",
875
+ value: formData.color,
876
+ onChange: handleChange("color")
877
+ }
878
+ )
879
+ ] }) }),
880
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 1, children: [
881
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({ id: index.getTrad("realm.enabled"), defaultMessage: "Enabled" }) }),
882
+ /* @__PURE__ */ jsxRuntime.jsx(
883
+ designSystem.Toggle,
884
+ {
885
+ checked: formData.enabled,
886
+ onChange: handleToggle("enabled"),
887
+ onLabel: "On",
888
+ offLabel: "Off"
889
+ }
890
+ )
891
+ ] }) })
892
+ ] })
893
+ ] });
894
+ const AdminsTab = ({ admins, isLoading, onEdit, onDelete }) => {
895
+ if (isLoading) {
896
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, {}) });
897
+ }
898
+ if (admins.length === 0) {
899
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral0", padding: 6, shadow: "filterShadow", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsx(
900
+ designSystem.EmptyStateLayout,
901
+ {
902
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.User, { width: "6rem", height: "6rem" }),
903
+ content: "No admins assigned to this realm yet. Add an admin to allow them to manage users."
904
+ }
905
+ ) });
906
+ }
907
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral0", padding: 6, shadow: "filterShadow", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 8, rowCount: admins.length + 1, children: [
908
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
909
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "User" }) }),
910
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Read" }) }),
911
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Create" }) }),
912
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Update" }) }),
913
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Delete" }) }),
914
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Password" }) }),
915
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Roles" }) }),
916
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Actions" }) })
917
+ ] }) }),
918
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: admins.map((admin2) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
919
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: admin2.strapiUserEmail }) }),
920
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(PermissionBadge, { enabled: admin2.canRead }) }),
921
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(PermissionBadge, { enabled: admin2.canCreate }) }),
922
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(PermissionBadge, { enabled: admin2.canUpdate }) }),
923
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(PermissionBadge, { enabled: admin2.canDelete }) }),
924
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(PermissionBadge, { enabled: admin2.canResetPassword }) }),
925
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(PermissionBadge, { enabled: admin2.canManageRoles }) }),
926
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, children: [
927
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.IconButton, { withTooltip: false, label: "Edit", onClick: () => onEdit(admin2), children: /* @__PURE__ */ jsxRuntime.jsx(icons.Pencil, {}) }),
928
+ /* @__PURE__ */ jsxRuntime.jsx(
929
+ designSystem.IconButton,
930
+ {
931
+ withTooltip: false,
932
+ label: "Remove",
933
+ onClick: () => onDelete(admin2.strapiUserId),
934
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
935
+ }
936
+ )
937
+ ] }) })
938
+ ] }, admin2.documentId)) })
939
+ ] }) });
940
+ };
941
+ const PermissionBadge = ({ enabled }) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: enabled ? "success100" : "neutral150", children: enabled ? "Yes" : "No" });
942
+ const RealmsPage = {
943
+ List: RealmListPage,
944
+ Edit: RealmEditPage
945
+ };
946
+ const useKeycloakUsers = (realmId) => {
947
+ const client = admin.useFetchClient();
948
+ const { toggleNotification } = admin.useNotification();
949
+ const [users, setUsers] = react.useState([]);
950
+ const [isLoading, setIsLoading] = react.useState(false);
951
+ const [pagination, setPagination] = react.useState({
952
+ page: 1,
953
+ pageSize: 25,
954
+ total: 0,
955
+ pageCount: 0
956
+ });
957
+ const fetchUsers = react.useCallback(
958
+ async ({ search = "", page = 1, pageSize = 25 } = {}) => {
959
+ if (!realmId) return;
960
+ setIsLoading(true);
961
+ try {
962
+ const { data } = await client.get(`${index.API_BASE_PATH}/realms/${realmId}/users`, {
963
+ params: { search, page, pageSize }
964
+ });
965
+ setUsers(data.data?.users || []);
966
+ setPagination(data.data?.pagination || { page: 1, pageSize: 25, total: 0, pageCount: 0 });
967
+ } catch (err) {
968
+ toggleNotification({
969
+ type: "danger",
970
+ message: err.response?.data?.error?.message || "Failed to fetch users"
971
+ });
972
+ } finally {
973
+ setIsLoading(false);
974
+ }
975
+ },
976
+ [realmId, client, toggleNotification]
977
+ );
978
+ const getUser = react.useCallback(
979
+ async (userId) => {
980
+ try {
981
+ const { data } = await client.get(`${index.API_BASE_PATH}/realms/${realmId}/users/${userId}`);
982
+ return data.data;
983
+ } catch (err) {
984
+ toggleNotification({
985
+ type: "danger",
986
+ message: err.response?.data?.error?.message || "Failed to fetch user"
987
+ });
988
+ throw err;
989
+ }
990
+ },
991
+ [realmId, client, toggleNotification]
992
+ );
993
+ const createUser = react.useCallback(
994
+ async (userData) => {
995
+ try {
996
+ const { data } = await client.post(`${index.API_BASE_PATH}/realms/${realmId}/users`, {
997
+ data: userData
998
+ });
999
+ toggleNotification({
1000
+ type: "success",
1001
+ message: "User created successfully"
1002
+ });
1003
+ await fetchUsers();
1004
+ return data.data;
1005
+ } catch (err) {
1006
+ toggleNotification({
1007
+ type: "danger",
1008
+ message: err.response?.data?.error?.message || "Failed to create user"
1009
+ });
1010
+ throw err;
1011
+ }
1012
+ },
1013
+ [realmId, client, toggleNotification, fetchUsers]
1014
+ );
1015
+ const updateUser = react.useCallback(
1016
+ async (userId, userData) => {
1017
+ try {
1018
+ const { data } = await client.put(`${index.API_BASE_PATH}/realms/${realmId}/users/${userId}`, {
1019
+ data: userData
1020
+ });
1021
+ toggleNotification({
1022
+ type: "success",
1023
+ message: "User updated successfully"
1024
+ });
1025
+ await fetchUsers();
1026
+ return data.data;
1027
+ } catch (err) {
1028
+ toggleNotification({
1029
+ type: "danger",
1030
+ message: err.response?.data?.error?.message || "Failed to update user"
1031
+ });
1032
+ throw err;
1033
+ }
1034
+ },
1035
+ [realmId, client, toggleNotification, fetchUsers]
1036
+ );
1037
+ const deleteUser = react.useCallback(
1038
+ async (userId) => {
1039
+ try {
1040
+ await client.del(`${index.API_BASE_PATH}/realms/${realmId}/users/${userId}`);
1041
+ toggleNotification({
1042
+ type: "success",
1043
+ message: "User deleted successfully"
1044
+ });
1045
+ await fetchUsers();
1046
+ } catch (err) {
1047
+ toggleNotification({
1048
+ type: "danger",
1049
+ message: err.response?.data?.error?.message || "Failed to delete user"
1050
+ });
1051
+ throw err;
1052
+ }
1053
+ },
1054
+ [realmId, client, toggleNotification, fetchUsers]
1055
+ );
1056
+ const resetPassword = react.useCallback(
1057
+ async (userId, password, temporary = true) => {
1058
+ try {
1059
+ await client.post(`${index.API_BASE_PATH}/realms/${realmId}/users/${userId}/reset-password`, {
1060
+ data: { password, temporary }
1061
+ });
1062
+ toggleNotification({
1063
+ type: "success",
1064
+ message: "Password reset successfully"
1065
+ });
1066
+ } catch (err) {
1067
+ toggleNotification({
1068
+ type: "danger",
1069
+ message: err.response?.data?.error?.message || "Failed to reset password"
1070
+ });
1071
+ throw err;
1072
+ }
1073
+ },
1074
+ [realmId, client, toggleNotification]
1075
+ );
1076
+ const enableUser = react.useCallback(
1077
+ async (userId) => {
1078
+ try {
1079
+ await client.post(`${index.API_BASE_PATH}/realms/${realmId}/users/${userId}/enable`);
1080
+ toggleNotification({
1081
+ type: "success",
1082
+ message: "User enabled successfully"
1083
+ });
1084
+ await fetchUsers();
1085
+ } catch (err) {
1086
+ toggleNotification({
1087
+ type: "danger",
1088
+ message: err.response?.data?.error?.message || "Failed to enable user"
1089
+ });
1090
+ throw err;
1091
+ }
1092
+ },
1093
+ [realmId, client, toggleNotification, fetchUsers]
1094
+ );
1095
+ const disableUser = react.useCallback(
1096
+ async (userId) => {
1097
+ try {
1098
+ await client.post(`${index.API_BASE_PATH}/realms/${realmId}/users/${userId}/disable`);
1099
+ toggleNotification({
1100
+ type: "success",
1101
+ message: "User disabled successfully"
1102
+ });
1103
+ await fetchUsers();
1104
+ } catch (err) {
1105
+ toggleNotification({
1106
+ type: "danger",
1107
+ message: err.response?.data?.error?.message || "Failed to disable user"
1108
+ });
1109
+ throw err;
1110
+ }
1111
+ },
1112
+ [realmId, client, toggleNotification, fetchUsers]
1113
+ );
1114
+ const sendVerificationEmail = react.useCallback(
1115
+ async (userId) => {
1116
+ try {
1117
+ await client.post(`${index.API_BASE_PATH}/realms/${realmId}/users/${userId}/send-verify-email`);
1118
+ toggleNotification({
1119
+ type: "success",
1120
+ message: "Verification email sent"
1121
+ });
1122
+ } catch (err) {
1123
+ toggleNotification({
1124
+ type: "danger",
1125
+ message: err.response?.data?.error?.message || "Failed to send verification email"
1126
+ });
1127
+ throw err;
1128
+ }
1129
+ },
1130
+ [realmId, client, toggleNotification]
1131
+ );
1132
+ const sendResetPasswordEmail = react.useCallback(
1133
+ async (userId) => {
1134
+ try {
1135
+ await client.post(`${index.API_BASE_PATH}/realms/${realmId}/users/${userId}/send-reset-password-email`);
1136
+ toggleNotification({
1137
+ type: "success",
1138
+ message: "Password reset email sent"
1139
+ });
1140
+ } catch (err) {
1141
+ toggleNotification({
1142
+ type: "danger",
1143
+ message: err.response?.data?.error?.message || "Failed to send reset email"
1144
+ });
1145
+ throw err;
1146
+ }
1147
+ },
1148
+ [realmId, client, toggleNotification]
1149
+ );
1150
+ const bulkImport = react.useCallback(
1151
+ async (usersData) => {
1152
+ try {
1153
+ const { data } = await client.post(`${index.API_BASE_PATH}/realms/${realmId}/users/import`, {
1154
+ data: { users: usersData }
1155
+ });
1156
+ const result = data.data;
1157
+ toggleNotification({
1158
+ type: result.failed.length > 0 ? "warning" : "success",
1159
+ message: `Imported ${result.success.length} users. ${result.failed.length} failed.`
1160
+ });
1161
+ await fetchUsers();
1162
+ return result;
1163
+ } catch (err) {
1164
+ toggleNotification({
1165
+ type: "danger",
1166
+ message: err.response?.data?.error?.message || "Failed to import users"
1167
+ });
1168
+ throw err;
1169
+ }
1170
+ },
1171
+ [realmId, client, toggleNotification, fetchUsers]
1172
+ );
1173
+ const exportUsers = react.useCallback(
1174
+ async (format = "json") => {
1175
+ try {
1176
+ const { data } = await client.get(`${index.API_BASE_PATH}/realms/${realmId}/users/export`, {
1177
+ params: { format }
1178
+ });
1179
+ return format === "csv" ? data : data.data;
1180
+ } catch (err) {
1181
+ toggleNotification({
1182
+ type: "danger",
1183
+ message: err.response?.data?.error?.message || "Failed to export users"
1184
+ });
1185
+ throw err;
1186
+ }
1187
+ },
1188
+ [realmId, client, toggleNotification]
1189
+ );
1190
+ react.useEffect(() => {
1191
+ if (realmId) {
1192
+ fetchUsers();
1193
+ }
1194
+ }, [realmId]);
1195
+ return {
1196
+ users,
1197
+ isLoading,
1198
+ pagination,
1199
+ fetchUsers,
1200
+ getUser,
1201
+ createUser,
1202
+ updateUser,
1203
+ deleteUser,
1204
+ resetPassword,
1205
+ enableUser,
1206
+ disableUser,
1207
+ sendVerificationEmail,
1208
+ sendResetPasswordEmail,
1209
+ bulkImport,
1210
+ exportUsers
1211
+ };
1212
+ };
1213
+ const UserListPage = () => {
1214
+ const { realmId } = reactRouterDom.useParams();
1215
+ const navigate = reactRouterDom.useNavigate();
1216
+ const { formatMessage } = reactIntl.useIntl();
1217
+ const { fetchOne: fetchRealm } = useRealms();
1218
+ const {
1219
+ users,
1220
+ isLoading,
1221
+ pagination,
1222
+ fetchUsers,
1223
+ deleteUser,
1224
+ resetPassword,
1225
+ enableUser,
1226
+ disableUser,
1227
+ sendVerificationEmail,
1228
+ exportUsers
1229
+ } = useKeycloakUsers(realmId);
1230
+ const [realm, setRealm] = react.useState(null);
1231
+ const [search, setSearch] = react.useState("");
1232
+ const [deleteId, setDeleteId] = react.useState(null);
1233
+ const [resetPasswordUser, setResetPasswordUser] = react.useState(null);
1234
+ const [newPassword, setNewPassword] = react.useState("");
1235
+ const [temporaryPassword, setTemporaryPassword] = react.useState(true);
1236
+ react.useEffect(() => {
1237
+ fetchRealm(realmId).then(setRealm).catch(() => navigate(`/settings/${index.PLUGIN_ID}`));
1238
+ }, [realmId, fetchRealm, navigate]);
1239
+ const debounceRef = react.useRef(null);
1240
+ const handleSearch = react.useCallback(
1241
+ (value) => {
1242
+ setSearch(value);
1243
+ if (debounceRef.current) {
1244
+ clearTimeout(debounceRef.current);
1245
+ }
1246
+ debounceRef.current = setTimeout(() => {
1247
+ fetchUsers({ search: value, page: 1 });
1248
+ }, 300);
1249
+ },
1250
+ [fetchUsers]
1251
+ );
1252
+ const handlePageChange = (page) => {
1253
+ fetchUsers({ search, page });
1254
+ };
1255
+ const handleDelete = async () => {
1256
+ if (deleteId) {
1257
+ await deleteUser(deleteId);
1258
+ setDeleteId(null);
1259
+ }
1260
+ };
1261
+ const handleResetPassword = async () => {
1262
+ if (resetPasswordUser && newPassword) {
1263
+ await resetPassword(resetPasswordUser.id, newPassword, temporaryPassword);
1264
+ setResetPasswordUser(null);
1265
+ setNewPassword("");
1266
+ setTemporaryPassword(true);
1267
+ }
1268
+ };
1269
+ const handleExport = async (format) => {
1270
+ try {
1271
+ const data = await exportUsers(format);
1272
+ const blob = new Blob(
1273
+ [format === "csv" ? data : JSON.stringify(data, null, 2)],
1274
+ { type: format === "csv" ? "text/csv" : "application/json" }
1275
+ );
1276
+ const url = URL.createObjectURL(blob);
1277
+ const a = document.createElement("a");
1278
+ a.href = url;
1279
+ a.download = `keycloak-users-${realm?.name || realmId}.${format}`;
1280
+ a.click();
1281
+ URL.revokeObjectURL(url);
1282
+ } catch {
1283
+ }
1284
+ };
1285
+ if (!realm) {
1286
+ return /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Root, { children: /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, {}) }) }) });
1287
+ }
1288
+ const canCreate = realm.permissions?.canCreate;
1289
+ const canUpdate = realm.permissions?.canUpdate;
1290
+ const canDelete = realm.permissions?.canDelete;
1291
+ const canResetPassword = realm.permissions?.canResetPassword;
1292
+ return /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Root, { children: [
1293
+ /* @__PURE__ */ jsxRuntime.jsx(
1294
+ admin.Layouts.Header,
1295
+ {
1296
+ title: formatMessage({ id: index.getTrad("users.title"), defaultMessage: "Users" }),
1297
+ subtitle: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, children: [
1298
+ /* @__PURE__ */ jsxRuntime.jsx(
1299
+ designSystem.Box,
1300
+ {
1301
+ width: "12px",
1302
+ height: "12px",
1303
+ borderRadius: "50%",
1304
+ background: realm.color || "primary600"
1305
+ }
1306
+ ),
1307
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: realm.displayName })
1308
+ ] }),
1309
+ navigationAction: /* @__PURE__ */ jsxRuntime.jsx(
1310
+ designSystem.Button,
1311
+ {
1312
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}),
1313
+ variant: "ghost",
1314
+ onClick: () => navigate(`/settings/${index.PLUGIN_ID}`),
1315
+ children: formatMessage({ id: index.getTrad("common.back"), defaultMessage: "Back" })
1316
+ }
1317
+ ),
1318
+ primaryAction: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
1319
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Download, {}), onClick: () => handleExport("json"), children: formatMessage({ id: index.getTrad("users.export"), defaultMessage: "Export" }) }),
1320
+ canCreate && /* @__PURE__ */ jsxRuntime.jsx(
1321
+ designSystem.Button,
1322
+ {
1323
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
1324
+ onClick: () => navigate(`/settings/${index.PLUGIN_ID}/realms/${realmId}/users/create`),
1325
+ children: formatMessage({ id: index.getTrad("users.create"), defaultMessage: "Create User" })
1326
+ }
1327
+ )
1328
+ ] })
1329
+ }
1330
+ ),
1331
+ /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Content, { children: [
1332
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginBottom: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Root, { children: /* @__PURE__ */ jsxRuntime.jsx(
1333
+ designSystem.TextInput,
1334
+ {
1335
+ name: "search",
1336
+ placeholder: formatMessage({
1337
+ id: index.getTrad("users.search"),
1338
+ defaultMessage: "Search users..."
1339
+ }),
1340
+ value: search,
1341
+ onChange: (e) => handleSearch(e.target.value),
1342
+ startAction: /* @__PURE__ */ jsxRuntime.jsx(icons.Search, {})
1343
+ }
1344
+ ) }) }),
1345
+ users.length === 0 && !isLoading ? /* @__PURE__ */ jsxRuntime.jsx(
1346
+ designSystem.EmptyStateLayout,
1347
+ {
1348
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.User, { width: "10rem", height: "10rem" }),
1349
+ content: formatMessage({
1350
+ id: index.getTrad("users.empty.description"),
1351
+ defaultMessage: "Create your first user or adjust your search."
1352
+ }),
1353
+ action: canCreate && /* @__PURE__ */ jsxRuntime.jsx(
1354
+ designSystem.Button,
1355
+ {
1356
+ variant: "secondary",
1357
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
1358
+ onClick: () => navigate(`/settings/${index.PLUGIN_ID}/realms/${realmId}/users/create`),
1359
+ children: formatMessage({ id: index.getTrad("users.create"), defaultMessage: "Create User" })
1360
+ }
1361
+ )
1362
+ }
1363
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { opacity: isLoading ? 0.5 : 1, transition: "opacity 0.2s" }, children: [
1364
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 7, rowCount: users.length + 1, children: [
1365
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
1366
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({ id: index.getTrad("user.username"), defaultMessage: "Username" }) }) }),
1367
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({ id: index.getTrad("user.email"), defaultMessage: "Email" }) }) }),
1368
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({ id: index.getTrad("user.firstName"), defaultMessage: "First Name" }) }) }),
1369
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({ id: index.getTrad("user.lastName"), defaultMessage: "Last Name" }) }) }),
1370
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({ id: index.getTrad("user.enabled"), defaultMessage: "Enabled" }) }) }),
1371
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({ id: index.getTrad("user.emailVerified"), defaultMessage: "Verified" }) }) }),
1372
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Actions" }) })
1373
+ ] }) }),
1374
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: users.map((user) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
1375
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", fontWeight: "bold", children: user.username }) }),
1376
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: user.email || "-" }) }),
1377
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: user.firstName || "-" }) }),
1378
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: user.lastName || "-" }) }),
1379
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: user.enabled ? "success100" : "danger100", children: user.enabled ? "Yes" : "No" }) }),
1380
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: user.emailVerified ? "success100" : "neutral150", children: user.emailVerified ? "Yes" : "No" }) }),
1381
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, children: [
1382
+ canResetPassword && /* @__PURE__ */ jsxRuntime.jsx(
1383
+ designSystem.IconButton,
1384
+ {
1385
+ withTooltip: false,
1386
+ label: "Reset Password",
1387
+ onClick: () => setResetPasswordUser(user),
1388
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Key, {})
1389
+ }
1390
+ ),
1391
+ canUpdate && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1392
+ /* @__PURE__ */ jsxRuntime.jsx(
1393
+ designSystem.IconButton,
1394
+ {
1395
+ withTooltip: false,
1396
+ label: user.enabled ? "Disable" : "Enable",
1397
+ onClick: () => user.enabled ? disableUser(user.id) : enableUser(user.id),
1398
+ children: user.enabled ? /* @__PURE__ */ jsxRuntime.jsx(icons.Cross, {}) : /* @__PURE__ */ jsxRuntime.jsx(icons.Check, {})
1399
+ }
1400
+ ),
1401
+ !user.emailVerified && /* @__PURE__ */ jsxRuntime.jsx(
1402
+ designSystem.IconButton,
1403
+ {
1404
+ withTooltip: false,
1405
+ label: "Send Verification Email",
1406
+ onClick: () => sendVerificationEmail(user.id),
1407
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Mail, {})
1408
+ }
1409
+ ),
1410
+ /* @__PURE__ */ jsxRuntime.jsx(
1411
+ designSystem.IconButton,
1412
+ {
1413
+ withTooltip: false,
1414
+ label: "Edit",
1415
+ onClick: () => navigate(`/settings/${index.PLUGIN_ID}/realms/${realmId}/users/${user.id}`),
1416
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Pencil, {})
1417
+ }
1418
+ )
1419
+ ] }),
1420
+ canDelete && /* @__PURE__ */ jsxRuntime.jsx(designSystem.IconButton, { withTooltip: false, label: "Delete", onClick: () => setDeleteId(user.id), children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}) })
1421
+ ] }) })
1422
+ ] }, user.id)) })
1423
+ ] }),
1424
+ pagination.pageCount > 1 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx(
1425
+ designSystem.Pagination,
1426
+ {
1427
+ activePage: pagination.page,
1428
+ pageCount: pagination.pageCount,
1429
+ onChangePage: handlePageChange
1430
+ }
1431
+ ) })
1432
+ ] })
1433
+ ] }),
1434
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Root, { open: !!deleteId, onOpenChange: () => setDeleteId(null), children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Content, { children: [
1435
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Header, { children: "Delete User" }),
1436
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Body, { children: formatMessage({
1437
+ id: index.getTrad("user.delete.confirm"),
1438
+ defaultMessage: "Are you sure you want to delete this user?"
1439
+ }) }),
1440
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Footer, { children: [
1441
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Cancel, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", children: "Cancel" }) }),
1442
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Action, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "danger-light", onClick: handleDelete, children: "Delete" }) })
1443
+ ] })
1444
+ ] }) }),
1445
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Root, { open: !!resetPasswordUser, onOpenChange: () => setResetPasswordUser(null), children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Content, { children: [
1446
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Title, { children: formatMessage({ id: index.getTrad("user.resetPassword"), defaultMessage: "Reset Password" }) }) }),
1447
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 4, children: [
1448
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { children: [
1449
+ "Reset password for user: ",
1450
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: resetPasswordUser?.username })
1451
+ ] }),
1452
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
1453
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { required: true, children: formatMessage({ id: index.getTrad("password.new"), defaultMessage: "New Password" }) }),
1454
+ /* @__PURE__ */ jsxRuntime.jsx(
1455
+ designSystem.TextInput,
1456
+ {
1457
+ type: "password",
1458
+ value: newPassword,
1459
+ onChange: (e) => setNewPassword(e.target.value)
1460
+ }
1461
+ )
1462
+ ] }),
1463
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, children: [
1464
+ /* @__PURE__ */ jsxRuntime.jsx(
1465
+ designSystem.Toggle,
1466
+ {
1467
+ checked: temporaryPassword,
1468
+ onChange: () => setTemporaryPassword(!temporaryPassword),
1469
+ onLabel: "Yes",
1470
+ offLabel: "No"
1471
+ }
1472
+ ),
1473
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage({
1474
+ id: index.getTrad("password.temporary"),
1475
+ defaultMessage: "Temporary (user must change on login)"
1476
+ }) })
1477
+ ] })
1478
+ ] }) }),
1479
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Footer, { children: [
1480
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Close, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", children: "Cancel" }) }),
1481
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleResetPassword, disabled: !newPassword, children: "Reset Password" })
1482
+ ] })
1483
+ ] }) })
1484
+ ] });
1485
+ };
1486
+ const useKeycloakRoles = (realmId) => {
1487
+ const client = admin.useFetchClient();
1488
+ const { toggleNotification } = admin.useNotification();
1489
+ const [roles, setRoles] = react.useState([]);
1490
+ const [isLoading, setIsLoading] = react.useState(false);
1491
+ const fetchRoles = react.useCallback(async () => {
1492
+ if (!realmId) return;
1493
+ setIsLoading(true);
1494
+ try {
1495
+ const { data } = await client.get(`${index.API_BASE_PATH}/realms/${realmId}/roles`);
1496
+ setRoles(data.data || []);
1497
+ } catch (err) {
1498
+ toggleNotification({
1499
+ type: "danger",
1500
+ message: err.response?.data?.error?.message || "Failed to fetch roles"
1501
+ });
1502
+ } finally {
1503
+ setIsLoading(false);
1504
+ }
1505
+ }, [realmId, client, toggleNotification]);
1506
+ const getUserRoles = react.useCallback(
1507
+ async (userId) => {
1508
+ try {
1509
+ const { data } = await client.get(`${index.API_BASE_PATH}/realms/${realmId}/users/${userId}/roles`);
1510
+ return data.data || [];
1511
+ } catch (err) {
1512
+ toggleNotification({
1513
+ type: "danger",
1514
+ message: err.response?.data?.error?.message || "Failed to fetch user roles"
1515
+ });
1516
+ throw err;
1517
+ }
1518
+ },
1519
+ [realmId, client, toggleNotification]
1520
+ );
1521
+ const assignRoles = react.useCallback(
1522
+ async (userId, rolesToAssign) => {
1523
+ try {
1524
+ await client.post(`${index.API_BASE_PATH}/realms/${realmId}/users/${userId}/roles`, {
1525
+ data: { roles: rolesToAssign }
1526
+ });
1527
+ toggleNotification({
1528
+ type: "success",
1529
+ message: "Roles assigned successfully"
1530
+ });
1531
+ } catch (err) {
1532
+ toggleNotification({
1533
+ type: "danger",
1534
+ message: err.response?.data?.error?.message || "Failed to assign roles"
1535
+ });
1536
+ throw err;
1537
+ }
1538
+ },
1539
+ [realmId, client, toggleNotification]
1540
+ );
1541
+ const removeRoles = react.useCallback(
1542
+ async (userId, rolesToRemove) => {
1543
+ try {
1544
+ await client.del(`${index.API_BASE_PATH}/realms/${realmId}/users/${userId}/roles`, {
1545
+ data: { roles: rolesToRemove }
1546
+ });
1547
+ toggleNotification({
1548
+ type: "success",
1549
+ message: "Roles removed successfully"
1550
+ });
1551
+ } catch (err) {
1552
+ toggleNotification({
1553
+ type: "danger",
1554
+ message: err.response?.data?.error?.message || "Failed to remove roles"
1555
+ });
1556
+ throw err;
1557
+ }
1558
+ },
1559
+ [realmId, client, toggleNotification]
1560
+ );
1561
+ react.useEffect(() => {
1562
+ if (realmId) {
1563
+ fetchRoles();
1564
+ }
1565
+ }, [realmId, fetchRoles]);
1566
+ return {
1567
+ roles,
1568
+ isLoading,
1569
+ fetchRoles,
1570
+ getUserRoles,
1571
+ assignRoles,
1572
+ removeRoles
1573
+ };
1574
+ };
1575
+ const UserEditPage = () => {
1576
+ const { realmId, userId } = reactRouterDom.useParams();
1577
+ const navigate = reactRouterDom.useNavigate();
1578
+ const { formatMessage } = reactIntl.useIntl();
1579
+ const { fetchOne: fetchRealm } = useRealms();
1580
+ const { getUser, createUser, updateUser } = useKeycloakUsers(realmId);
1581
+ const { roles, getUserRoles, assignRoles, removeRoles } = useKeycloakRoles(realmId);
1582
+ const isEditMode = !!userId;
1583
+ const [realm, setRealm] = react.useState(null);
1584
+ const [isLoading, setIsLoading] = react.useState(true);
1585
+ const [isSaving, setIsSaving] = react.useState(false);
1586
+ const [formData, setFormData] = react.useState({
1587
+ username: "",
1588
+ email: "",
1589
+ firstName: "",
1590
+ lastName: "",
1591
+ enabled: true,
1592
+ emailVerified: false
1593
+ });
1594
+ const [selectedRoles, setSelectedRoles] = react.useState([]);
1595
+ const [originalRoles, setOriginalRoles] = react.useState([]);
1596
+ const [errors, setErrors] = react.useState({});
1597
+ react.useEffect(() => {
1598
+ const loadData = async () => {
1599
+ try {
1600
+ const realmData = await fetchRealm(realmId);
1601
+ setRealm(realmData);
1602
+ if (isEditMode) {
1603
+ const [userData, userRoles] = await Promise.all([
1604
+ getUser(userId),
1605
+ getUserRoles(userId)
1606
+ ]);
1607
+ setFormData({
1608
+ username: userData.username || "",
1609
+ email: userData.email || "",
1610
+ firstName: userData.firstName || "",
1611
+ lastName: userData.lastName || "",
1612
+ enabled: userData.enabled !== false,
1613
+ emailVerified: userData.emailVerified === true
1614
+ });
1615
+ const roleNames = userRoles.map((r) => r.name);
1616
+ setSelectedRoles(roleNames);
1617
+ setOriginalRoles(roleNames);
1618
+ }
1619
+ } catch {
1620
+ navigate(`/settings/${index.PLUGIN_ID}/realms/${realmId}/users`);
1621
+ } finally {
1622
+ setIsLoading(false);
1623
+ }
1624
+ };
1625
+ loadData();
1626
+ }, [realmId, userId, isEditMode, fetchRealm, getUser, getUserRoles, navigate]);
1627
+ const handleChange = (field) => (e) => {
1628
+ const value = e.target ? e.target.value : e;
1629
+ setFormData((prev) => ({ ...prev, [field]: value }));
1630
+ setErrors((prev) => ({ ...prev, [field]: null }));
1631
+ };
1632
+ const handleToggle = (field) => () => {
1633
+ setFormData((prev) => ({ ...prev, [field]: !prev[field] }));
1634
+ };
1635
+ const validate = () => {
1636
+ const newErrors = {};
1637
+ if (!formData.username) {
1638
+ newErrors.username = "Username is required";
1639
+ }
1640
+ if (formData.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
1641
+ newErrors.email = "Invalid email format";
1642
+ }
1643
+ setErrors(newErrors);
1644
+ return Object.keys(newErrors).length === 0;
1645
+ };
1646
+ const handleSubmit = async () => {
1647
+ if (!validate()) return;
1648
+ setIsSaving(true);
1649
+ try {
1650
+ if (isEditMode) {
1651
+ await updateUser(userId, formData);
1652
+ const rolesToAssign = selectedRoles.filter((r) => !originalRoles.includes(r));
1653
+ const rolesToRemove = originalRoles.filter((r) => !selectedRoles.includes(r));
1654
+ if (rolesToAssign.length > 0) {
1655
+ const roleObjects = roles.filter((r) => rolesToAssign.includes(r.name)).map((r) => ({ id: r.id, name: r.name }));
1656
+ await assignRoles(userId, roleObjects);
1657
+ }
1658
+ if (rolesToRemove.length > 0) {
1659
+ const roleObjects = roles.filter((r) => rolesToRemove.includes(r.name)).map((r) => ({ id: r.id, name: r.name }));
1660
+ await removeRoles(userId, roleObjects);
1661
+ }
1662
+ } else {
1663
+ const newUser = await createUser(formData);
1664
+ if (selectedRoles.length > 0 && newUser?.id) {
1665
+ const roleObjects = roles.filter((r) => selectedRoles.includes(r.name)).map((r) => ({ id: r.id, name: r.name }));
1666
+ await assignRoles(newUser.id, roleObjects);
1667
+ }
1668
+ }
1669
+ navigate(`/settings/${index.PLUGIN_ID}/realms/${realmId}/users`);
1670
+ } catch {
1671
+ } finally {
1672
+ setIsSaving(false);
1673
+ }
1674
+ };
1675
+ if (isLoading) {
1676
+ return /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Root, { children: /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, {}) }) }) });
1677
+ }
1678
+ return /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Root, { children: [
1679
+ /* @__PURE__ */ jsxRuntime.jsx(
1680
+ admin.Layouts.Header,
1681
+ {
1682
+ title: isEditMode ? "Edit User" : "Create User",
1683
+ subtitle: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, children: [
1684
+ /* @__PURE__ */ jsxRuntime.jsx(
1685
+ designSystem.Box,
1686
+ {
1687
+ width: "12px",
1688
+ height: "12px",
1689
+ borderRadius: "50%",
1690
+ background: realm?.color || "primary600"
1691
+ }
1692
+ ),
1693
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: realm?.displayName })
1694
+ ] }),
1695
+ navigationAction: /* @__PURE__ */ jsxRuntime.jsx(
1696
+ designSystem.Button,
1697
+ {
1698
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}),
1699
+ variant: "ghost",
1700
+ onClick: () => navigate(`/settings/${index.PLUGIN_ID}/realms/${realmId}/users`),
1701
+ children: formatMessage({ id: index.getTrad("common.back"), defaultMessage: "Back" })
1702
+ }
1703
+ ),
1704
+ primaryAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Check, {}), onClick: handleSubmit, loading: isSaving, children: formatMessage({ id: index.getTrad("common.save"), defaultMessage: "Save" }) })
1705
+ }
1706
+ ),
1707
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral0", padding: 6, shadow: "filterShadow", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Root, { gap: 4, children: [
1708
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { error: errors.username, children: [
1709
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { required: true, children: formatMessage({ id: index.getTrad("user.username"), defaultMessage: "Username" }) }),
1710
+ /* @__PURE__ */ jsxRuntime.jsx(
1711
+ designSystem.TextInput,
1712
+ {
1713
+ name: "username",
1714
+ value: formData.username,
1715
+ onChange: handleChange("username"),
1716
+ disabled: isEditMode
1717
+ }
1718
+ ),
1719
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {})
1720
+ ] }) }),
1721
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { error: errors.email, children: [
1722
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({ id: index.getTrad("user.email"), defaultMessage: "Email" }) }),
1723
+ /* @__PURE__ */ jsxRuntime.jsx(
1724
+ designSystem.TextInput,
1725
+ {
1726
+ name: "email",
1727
+ type: "email",
1728
+ value: formData.email,
1729
+ onChange: handleChange("email")
1730
+ }
1731
+ ),
1732
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {})
1733
+ ] }) }),
1734
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
1735
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({ id: index.getTrad("user.firstName"), defaultMessage: "First Name" }) }),
1736
+ /* @__PURE__ */ jsxRuntime.jsx(
1737
+ designSystem.TextInput,
1738
+ {
1739
+ name: "firstName",
1740
+ value: formData.firstName,
1741
+ onChange: handleChange("firstName")
1742
+ }
1743
+ )
1744
+ ] }) }),
1745
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
1746
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({ id: index.getTrad("user.lastName"), defaultMessage: "Last Name" }) }),
1747
+ /* @__PURE__ */ jsxRuntime.jsx(
1748
+ designSystem.TextInput,
1749
+ {
1750
+ name: "lastName",
1751
+ value: formData.lastName,
1752
+ onChange: handleChange("lastName")
1753
+ }
1754
+ )
1755
+ ] }) }),
1756
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 1, children: [
1757
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({ id: index.getTrad("user.enabled"), defaultMessage: "Enabled" }) }),
1758
+ /* @__PURE__ */ jsxRuntime.jsx(
1759
+ designSystem.Toggle,
1760
+ {
1761
+ checked: formData.enabled,
1762
+ onChange: handleToggle("enabled"),
1763
+ onLabel: "Yes",
1764
+ offLabel: "No"
1765
+ }
1766
+ )
1767
+ ] }) }),
1768
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 1, children: [
1769
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", children: formatMessage({ id: index.getTrad("user.emailVerified"), defaultMessage: "Email Verified" }) }),
1770
+ /* @__PURE__ */ jsxRuntime.jsx(
1771
+ designSystem.Toggle,
1772
+ {
1773
+ checked: formData.emailVerified,
1774
+ onChange: handleToggle("emailVerified"),
1775
+ onLabel: "Yes",
1776
+ offLabel: "No"
1777
+ }
1778
+ )
1779
+ ] }) }),
1780
+ roles.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
1781
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({ id: index.getTrad("user.roles"), defaultMessage: "Roles" }) }),
1782
+ /* @__PURE__ */ jsxRuntime.jsx(
1783
+ designSystem.MultiSelect,
1784
+ {
1785
+ value: selectedRoles,
1786
+ onChange: setSelectedRoles,
1787
+ placeholder: "Select roles...",
1788
+ children: roles.map((role) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.MultiSelectOption, { value: role.name, children: role.name }, role.id))
1789
+ }
1790
+ )
1791
+ ] }) })
1792
+ ] }) }) })
1793
+ ] });
1794
+ };
1795
+ const UsersPage = {
1796
+ List: UserListPage,
1797
+ Edit: UserEditPage
1798
+ };
1799
+ const useAuditLogs = (options = {}) => {
1800
+ const { realmId, keycloakUserId } = options;
1801
+ const client = admin.useFetchClient();
1802
+ const { toggleNotification } = admin.useNotification();
1803
+ const [logs, setLogs] = react.useState([]);
1804
+ const [isLoading, setIsLoading] = react.useState(false);
1805
+ const [total, setTotal] = react.useState(0);
1806
+ const fetchLogs = react.useCallback(
1807
+ async ({ limit = 50, offset = 0, action, realmName } = {}) => {
1808
+ setIsLoading(true);
1809
+ try {
1810
+ let url = `${index.API_BASE_PATH}/audit-logs`;
1811
+ const params = { limit, offset };
1812
+ if (realmId) {
1813
+ url = `${index.API_BASE_PATH}/audit-logs/realm/${realmId}`;
1814
+ } else if (keycloakUserId) {
1815
+ url = `${index.API_BASE_PATH}/audit-logs/user/${keycloakUserId}`;
1816
+ } else {
1817
+ if (action) params.action = action;
1818
+ if (realmName) params.realmName = realmName;
1819
+ }
1820
+ const { data } = await client.get(url, { params });
1821
+ setLogs(data.data || []);
1822
+ setTotal(data.meta?.total || data.data?.length || 0);
1823
+ } catch (err) {
1824
+ toggleNotification({
1825
+ type: "danger",
1826
+ message: err.response?.data?.error?.message || "Failed to fetch audit logs"
1827
+ });
1828
+ } finally {
1829
+ setIsLoading(false);
1830
+ }
1831
+ },
1832
+ [client, toggleNotification, realmId, keycloakUserId]
1833
+ );
1834
+ react.useEffect(() => {
1835
+ fetchLogs();
1836
+ }, [fetchLogs]);
1837
+ return {
1838
+ logs,
1839
+ isLoading,
1840
+ total,
1841
+ fetchLogs
1842
+ };
1843
+ };
1844
+ const ACTION_COLORS = {
1845
+ CREATE_USER: "success100",
1846
+ UPDATE_USER: "primary100",
1847
+ DELETE_USER: "danger100",
1848
+ RESET_PASSWORD: "warning100",
1849
+ ASSIGN_ROLE: "success100",
1850
+ REMOVE_ROLE: "warning100",
1851
+ ENABLE_USER: "success100",
1852
+ DISABLE_USER: "warning100",
1853
+ SEND_VERIFY_EMAIL: "primary100",
1854
+ SEND_RESET_PASSWORD_EMAIL: "primary100",
1855
+ BULK_IMPORT: "success100"
1856
+ };
1857
+ const formatDate = (dateString) => {
1858
+ const date = new Date(dateString);
1859
+ return date.toLocaleString();
1860
+ };
1861
+ const AuditPage = () => {
1862
+ const navigate = reactRouterDom.useNavigate();
1863
+ const { formatMessage } = reactIntl.useIntl();
1864
+ const { realms } = useRealms();
1865
+ const [selectedRealm, setSelectedRealm] = react.useState("");
1866
+ const [selectedAction, setSelectedAction] = react.useState("");
1867
+ const { logs, isLoading, fetchLogs } = useAuditLogs();
1868
+ const handleRealmChange = (value) => {
1869
+ setSelectedRealm(value);
1870
+ fetchLogs({ realmName: value || void 0, action: selectedAction || void 0 });
1871
+ };
1872
+ const handleActionChange = (value) => {
1873
+ setSelectedAction(value);
1874
+ fetchLogs({ realmName: selectedRealm || void 0, action: value || void 0 });
1875
+ };
1876
+ return /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Root, { children: [
1877
+ /* @__PURE__ */ jsxRuntime.jsx(
1878
+ admin.Layouts.Header,
1879
+ {
1880
+ title: formatMessage({ id: index.getTrad("audit.title"), defaultMessage: "Audit Log" }),
1881
+ subtitle: formatMessage({
1882
+ id: index.getTrad("audit.subtitle"),
1883
+ defaultMessage: "Track user management actions"
1884
+ }),
1885
+ navigationAction: /* @__PURE__ */ jsxRuntime.jsx(
1886
+ designSystem.Button,
1887
+ {
1888
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}),
1889
+ variant: "ghost",
1890
+ onClick: () => navigate(`/settings/${index.PLUGIN_ID}`),
1891
+ children: formatMessage({ id: index.getTrad("common.back"), defaultMessage: "Back" })
1892
+ }
1893
+ )
1894
+ }
1895
+ ),
1896
+ /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Content, { children: [
1897
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginBottom: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, children: [
1898
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "200px", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
1899
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Filter by Realm" }),
1900
+ /* @__PURE__ */ jsxRuntime.jsxs(
1901
+ designSystem.SingleSelect,
1902
+ {
1903
+ value: selectedRealm,
1904
+ onChange: handleRealmChange,
1905
+ placeholder: "All realms",
1906
+ children: [
1907
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "", children: "All realms" }),
1908
+ realms.map((realm) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: realm.name, children: realm.displayName }, realm.documentId))
1909
+ ]
1910
+ }
1911
+ )
1912
+ ] }) }),
1913
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "200px", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
1914
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Filter by Action" }),
1915
+ /* @__PURE__ */ jsxRuntime.jsxs(
1916
+ designSystem.SingleSelect,
1917
+ {
1918
+ value: selectedAction,
1919
+ onChange: handleActionChange,
1920
+ placeholder: "All actions",
1921
+ children: [
1922
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "", children: "All actions" }),
1923
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "CREATE_USER", children: "Create User" }),
1924
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "UPDATE_USER", children: "Update User" }),
1925
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "DELETE_USER", children: "Delete User" }),
1926
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "RESET_PASSWORD", children: "Reset Password" }),
1927
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "ASSIGN_ROLE", children: "Assign Role" }),
1928
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "REMOVE_ROLE", children: "Remove Role" }),
1929
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "ENABLE_USER", children: "Enable User" }),
1930
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "DISABLE_USER", children: "Disable User" }),
1931
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "BULK_IMPORT", children: "Bulk Import" })
1932
+ ]
1933
+ }
1934
+ )
1935
+ ] }) })
1936
+ ] }) }),
1937
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, {}) }) : logs.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
1938
+ designSystem.EmptyStateLayout,
1939
+ {
1940
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.File, { width: "10rem", height: "10rem" }),
1941
+ content: "No audit logs found"
1942
+ }
1943
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 6, rowCount: logs.length + 1, children: [
1944
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
1945
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({ id: index.getTrad("audit.date"), defaultMessage: "Date" }) }) }),
1946
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({ id: index.getTrad("audit.action"), defaultMessage: "Action" }) }) }),
1947
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({ id: index.getTrad("audit.realm"), defaultMessage: "Realm" }) }) }),
1948
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({ id: index.getTrad("audit.user"), defaultMessage: "Keycloak User" }) }) }),
1949
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({ id: index.getTrad("audit.performedBy"), defaultMessage: "Performed By" }) }) }),
1950
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Details" }) })
1951
+ ] }) }),
1952
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: logs.map((log) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
1953
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: formatDate(log.createdAt) }) }),
1954
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: ACTION_COLORS[log.action] || "neutral150", children: log.action.replace(/_/g, " ") }) }),
1955
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", children: log.realmDisplayName || log.realmName }) }),
1956
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", children: log.keycloakUsername || log.keycloakUserId || "-" }) }),
1957
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: log.performedByEmail || "-" }) }),
1958
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral500", ellipsis: true, children: log.details ? JSON.stringify(log.details).substring(0, 50) : "-" }) })
1959
+ ] }, log.documentId)) })
1960
+ ] })
1961
+ ] })
1962
+ ] });
1963
+ };
1964
+ const App = () => {
1965
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Routes, { children: [
1966
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { index: true, element: /* @__PURE__ */ jsxRuntime.jsx(RealmsPage.List, {}) }),
1967
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "realms/create", element: /* @__PURE__ */ jsxRuntime.jsx(RealmsPage.Edit, {}) }),
1968
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "realms/:id", element: /* @__PURE__ */ jsxRuntime.jsx(RealmsPage.Edit, {}) }),
1969
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "realms/:realmId/users", element: /* @__PURE__ */ jsxRuntime.jsx(UsersPage.List, {}) }),
1970
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "realms/:realmId/users/create", element: /* @__PURE__ */ jsxRuntime.jsx(UsersPage.Edit, {}) }),
1971
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "realms/:realmId/users/:userId", element: /* @__PURE__ */ jsxRuntime.jsx(UsersPage.Edit, {}) }),
1972
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "audit", element: /* @__PURE__ */ jsxRuntime.jsx(AuditPage, {}) })
1973
+ ] });
1974
+ };
1975
+ exports.default = App;