@keycloakify/keycloak-account-ui 25.0.1-rc.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 (349) hide show
  1. package/KeycloakAccountUi.d.ts +3 -0
  2. package/KeycloakAccountUi.js +29 -0
  3. package/KeycloakAccountUi.js.map +1 -0
  4. package/LICENSE +2 -0
  5. package/README.md +39 -0
  6. package/account-security/AccountRow.d.ts +8 -0
  7. package/account-security/AccountRow.js +36 -0
  8. package/account-security/AccountRow.js.map +1 -0
  9. package/account-security/DeviceActivity.d.ts +2 -0
  10. package/account-security/DeviceActivity.js +70 -0
  11. package/account-security/DeviceActivity.js.map +1 -0
  12. package/account-security/LinkedAccounts.d.ts +2 -0
  13. package/account-security/LinkedAccounts.js +25 -0
  14. package/account-security/LinkedAccounts.js.map +1 -0
  15. package/account-security/SigningIn.d.ts +2 -0
  16. package/account-security/SigningIn.js +65 -0
  17. package/account-security/SigningIn.js.map +1 -0
  18. package/api/constants.d.ts +2 -0
  19. package/api/constants.js +3 -0
  20. package/api/constants.js.map +1 -0
  21. package/api/methods.d.ts +25 -0
  22. package/api/methods.js +73 -0
  23. package/api/methods.js.map +1 -0
  24. package/api/parse-links.d.ts +5 -0
  25. package/api/parse-links.js +23 -0
  26. package/api/parse-links.js.map +1 -0
  27. package/api/parse-response.d.ts +3 -0
  28. package/api/parse-response.js +40 -0
  29. package/api/parse-response.js.map +1 -0
  30. package/api/representations.d.ts +202 -0
  31. package/api/representations.js +3 -0
  32. package/api/representations.js.map +1 -0
  33. package/api/request.d.ts +12 -0
  34. package/api/request.js +33 -0
  35. package/api/request.js.map +1 -0
  36. package/api.d.ts +13 -0
  37. package/api.js +52 -0
  38. package/api.js.map +1 -0
  39. package/applications/Applications.d.ts +2 -0
  40. package/applications/Applications.js +52 -0
  41. package/applications/Applications.js.map +1 -0
  42. package/components/datalist/EmptyRow.d.ts +5 -0
  43. package/components/datalist/EmptyRow.js +20 -0
  44. package/components/datalist/EmptyRow.js.map +1 -0
  45. package/components/page/Page.d.ts +7 -0
  46. package/components/page/Page.js +6 -0
  47. package/components/page/Page.js.map +1 -0
  48. package/content/ContentComponent.d.ts +2 -0
  49. package/content/ContentComponent.js +36 -0
  50. package/content/ContentComponent.js.map +1 -0
  51. package/content/fetchContent.d.ts +3 -0
  52. package/content/fetchContent.js +6 -0
  53. package/content/fetchContent.js.map +1 -0
  54. package/environment.d.ts +25 -0
  55. package/environment.js +3 -0
  56. package/environment.js.map +1 -0
  57. package/groups/Groups.d.ts +2 -0
  58. package/groups/Groups.js +45 -0
  59. package/groups/Groups.js.map +1 -0
  60. package/i18n.d.ts +4 -0
  61. package/i18n.js +31 -0
  62. package/i18n.js.map +1 -0
  63. package/oid4vci/Oid4Vci.d.ts +2 -0
  64. package/oid4vci/Oid4Vci.js +54 -0
  65. package/oid4vci/Oid4Vci.js.map +1 -0
  66. package/package.json +428 -0
  67. package/personal-info/PersonalInfo.d.ts +2 -0
  68. package/personal-info/PersonalInfo.js +63 -0
  69. package/personal-info/PersonalInfo.js.map +1 -0
  70. package/resources/EditTheResource.d.ts +8 -0
  71. package/resources/EditTheResource.js +36 -0
  72. package/resources/EditTheResource.js.map +1 -0
  73. package/resources/PermissionRequest.d.ts +7 -0
  74. package/resources/PermissionRequest.js +39 -0
  75. package/resources/PermissionRequest.js.map +1 -0
  76. package/resources/ResourceToolbar.d.ts +12 -0
  77. package/resources/ResourceToolbar.js +24 -0
  78. package/resources/ResourceToolbar.js.map +1 -0
  79. package/resources/Resources.d.ts +2 -0
  80. package/resources/Resources.js +13 -0
  81. package/resources/Resources.js.map +1 -0
  82. package/resources/ResourcesTab.d.ts +5 -0
  83. package/resources/ResourcesTab.js +103 -0
  84. package/resources/ResourcesTab.js.map +1 -0
  85. package/resources/ShareTheResource.d.ts +9 -0
  86. package/resources/ShareTheResource.js +70 -0
  87. package/resources/ShareTheResource.js.map +1 -0
  88. package/resources/SharedWith.d.ts +6 -0
  89. package/resources/SharedWith.js +8 -0
  90. package/resources/SharedWith.js.map +1 -0
  91. package/root/ErrorPage.d.ts +5 -0
  92. package/root/ErrorPage.js +29 -0
  93. package/root/ErrorPage.js.map +1 -0
  94. package/root/Header.d.ts +1 -0
  95. package/root/Header.js +31 -0
  96. package/root/Header.js.map +1 -0
  97. package/root/PageNav.d.ts +23 -0
  98. package/root/PageNav.js +44 -0
  99. package/root/PageNav.js.map +1 -0
  100. package/root/Root.d.ts +1 -0
  101. package/root/Root.js +12 -0
  102. package/root/Root.js.map +1 -0
  103. package/root/header.module.css +3 -0
  104. package/routes.d.ts +15 -0
  105. package/routes.js +69 -0
  106. package/routes.js.map +1 -0
  107. package/src/KeycloakAccountUi.tsx +36 -0
  108. package/src/account-security/AccountRow.tsx +136 -0
  109. package/src/account-security/DeviceActivity.tsx +253 -0
  110. package/src/account-security/LinkedAccounts.tsx +86 -0
  111. package/src/account-security/SigningIn.tsx +251 -0
  112. package/src/api/constants.ts +2 -0
  113. package/src/api/methods.ts +158 -0
  114. package/src/api/parse-links.ts +28 -0
  115. package/src/api/parse-response.ts +53 -0
  116. package/src/api/representations.ts +219 -0
  117. package/src/api/request.ts +74 -0
  118. package/src/api.ts +127 -0
  119. package/src/applications/Applications.tsx +282 -0
  120. package/src/components/datalist/EmptyRow.tsx +26 -0
  121. package/src/components/page/Page.tsx +27 -0
  122. package/src/content/ContentComponent.tsx +62 -0
  123. package/src/content/fetchContent.ts +13 -0
  124. package/src/environment.ts +31 -0
  125. package/src/global.d.ts +4 -0
  126. package/src/groups/Groups.tsx +142 -0
  127. package/src/i18n.ts +47 -0
  128. package/src/i18next.d.ts +10 -0
  129. package/src/oid4vci/Oid4Vci.tsx +136 -0
  130. package/src/personal-info/PersonalInfo.tsx +184 -0
  131. package/src/resources/EditTheResource.tsx +101 -0
  132. package/src/resources/PermissionRequest.tsx +129 -0
  133. package/src/resources/ResourceToolbar.tsx +87 -0
  134. package/src/resources/Resources.tsx +39 -0
  135. package/src/resources/ResourcesTab.tsx +410 -0
  136. package/src/resources/ShareTheResource.tsx +217 -0
  137. package/src/resources/SharedWith.tsx +26 -0
  138. package/src/root/ErrorPage.tsx +65 -0
  139. package/src/root/Header.tsx +62 -0
  140. package/src/root/PageNav.tsx +154 -0
  141. package/src/root/Root.tsx +20 -0
  142. package/src/root/header.module.css +3 -0
  143. package/src/routes.tsx +85 -0
  144. package/src/ui-shared/alerts/Alerts.tsx +86 -0
  145. package/src/ui-shared/buttons/FormSubmitButton.tsx +47 -0
  146. package/src/ui-shared/context/ErrorPage.tsx +60 -0
  147. package/src/ui-shared/context/HelpContext.tsx +30 -0
  148. package/src/ui-shared/context/KeycloakContext.tsx +97 -0
  149. package/src/ui-shared/context/environment.ts +50 -0
  150. package/src/ui-shared/continue-cancel/ContinueCancelModal.tsx +75 -0
  151. package/src/ui-shared/controls/FormErrorText.tsx +23 -0
  152. package/src/ui-shared/controls/FormLabel.tsx +40 -0
  153. package/src/ui-shared/controls/HelpItem.tsx +43 -0
  154. package/src/ui-shared/controls/NumberControl.tsx +89 -0
  155. package/src/ui-shared/controls/PasswordControl.tsx +71 -0
  156. package/src/ui-shared/controls/PasswordInput.tsx +50 -0
  157. package/src/ui-shared/controls/SwitchControl.tsx +67 -0
  158. package/src/ui-shared/controls/TextAreaControl.tsx +60 -0
  159. package/src/ui-shared/controls/TextControl.tsx +74 -0
  160. package/src/ui-shared/controls/keycloak-text-area/KeycloakTextArea.tsx +23 -0
  161. package/src/ui-shared/controls/select-control/SelectControl.tsx +75 -0
  162. package/src/ui-shared/controls/select-control/SingleSelectControl.tsx +107 -0
  163. package/src/ui-shared/controls/select-control/TypeaheadSelectControl.tsx +283 -0
  164. package/src/ui-shared/icons/IconMapper.tsx +63 -0
  165. package/src/ui-shared/index.ts +1 -0
  166. package/src/ui-shared/main.ts +62 -0
  167. package/src/ui-shared/masthead/DefaultAvatar.tsx +109 -0
  168. package/src/ui-shared/masthead/KeycloakDropdown.tsx +47 -0
  169. package/src/ui-shared/masthead/Masthead.tsx +145 -0
  170. package/src/ui-shared/scroll-form/FormPanel.tsx +29 -0
  171. package/src/ui-shared/scroll-form/FormTitle.tsx +28 -0
  172. package/src/ui-shared/scroll-form/ScrollForm.tsx +98 -0
  173. package/src/ui-shared/scroll-form/ScrollPanel.tsx +21 -0
  174. package/src/ui-shared/scroll-form/form-title.module.css +4 -0
  175. package/src/ui-shared/scroll-form/scroll-form.module.css +8 -0
  176. package/src/ui-shared/select/KeycloakSelect.tsx +49 -0
  177. package/src/ui-shared/select/SingleSelect.tsx +85 -0
  178. package/src/ui-shared/select/TypeaheadSelect.tsx +196 -0
  179. package/src/ui-shared/user-profile/LocaleSelector.tsx +51 -0
  180. package/src/ui-shared/user-profile/MultiInputComponent.tsx +146 -0
  181. package/src/ui-shared/user-profile/OptionsComponent.tsx +60 -0
  182. package/src/ui-shared/user-profile/SelectComponent.tsx +106 -0
  183. package/src/ui-shared/user-profile/TextAreaComponent.tsx +23 -0
  184. package/src/ui-shared/user-profile/TextComponent.tsx +30 -0
  185. package/src/ui-shared/user-profile/UserProfileFields.tsx +246 -0
  186. package/src/ui-shared/user-profile/UserProfileGroup.tsx +70 -0
  187. package/src/ui-shared/user-profile/utils.ts +161 -0
  188. package/src/ui-shared/utils/createNamedContext.ts +11 -0
  189. package/src/ui-shared/utils/isDefined.ts +3 -0
  190. package/src/ui-shared/utils/useRequiredContext.ts +24 -0
  191. package/src/ui-shared/utils/useStorageItem.ts +51 -0
  192. package/src/ui-shared/utils/useStoredState.ts +38 -0
  193. package/src/utils/formatDate.ts +18 -0
  194. package/src/utils/isRecord.ts +2 -0
  195. package/src/utils/joinPath.ts +22 -0
  196. package/src/utils/usePromise.ts +74 -0
  197. package/ui-shared/alerts/Alerts.d.ts +17 -0
  198. package/ui-shared/alerts/Alerts.js +27 -0
  199. package/ui-shared/alerts/Alerts.js.map +1 -0
  200. package/ui-shared/buttons/FormSubmitButton.d.ts +10 -0
  201. package/ui-shared/buttons/FormSubmitButton.js +26 -0
  202. package/ui-shared/buttons/FormSubmitButton.js.map +1 -0
  203. package/ui-shared/context/ErrorPage.d.ts +5 -0
  204. package/ui-shared/context/ErrorPage.js +24 -0
  205. package/ui-shared/context/ErrorPage.js.map +1 -0
  206. package/ui-shared/context/HelpContext.d.ts +9 -0
  207. package/ui-shared/context/HelpContext.js +14 -0
  208. package/ui-shared/context/HelpContext.js.map +1 -0
  209. package/ui-shared/context/KeycloakContext.d.ts +12 -0
  210. package/ui-shared/context/KeycloakContext.js +53 -0
  211. package/ui-shared/context/KeycloakContext.js.map +1 -0
  212. package/ui-shared/context/environment.d.ts +36 -0
  213. package/ui-shared/context/environment.js +27 -0
  214. package/ui-shared/context/environment.js.map +1 -0
  215. package/ui-shared/continue-cancel/ContinueCancelModal.d.ts +15 -0
  216. package/ui-shared/continue-cancel/ContinueCancelModal.js +27 -0
  217. package/ui-shared/continue-cancel/ContinueCancelModal.js.map +1 -0
  218. package/ui-shared/controls/FormErrorText.d.ts +5 -0
  219. package/ui-shared/controls/FormErrorText.js +19 -0
  220. package/ui-shared/controls/FormErrorText.js.map +1 -0
  221. package/ui-shared/controls/FormLabel.d.ts +13 -0
  222. package/ui-shared/controls/FormLabel.js +20 -0
  223. package/ui-shared/controls/FormLabel.js.map +1 -0
  224. package/ui-shared/controls/HelpItem.d.ts +9 -0
  225. package/ui-shared/controls/HelpItem.js +9 -0
  226. package/ui-shared/controls/HelpItem.js.map +1 -0
  227. package/ui-shared/controls/NumberControl.d.ts +13 -0
  228. package/ui-shared/controls/NumberControl.js +32 -0
  229. package/ui-shared/controls/NumberControl.js.map +1 -0
  230. package/ui-shared/controls/PasswordControl.d.ts +9 -0
  231. package/ui-shared/controls/PasswordControl.js +25 -0
  232. package/ui-shared/controls/PasswordControl.js.map +1 -0
  233. package/ui-shared/controls/PasswordInput.d.ts +7 -0
  234. package/ui-shared/controls/PasswordInput.js +25 -0
  235. package/ui-shared/controls/PasswordInput.js.map +1 -0
  236. package/ui-shared/controls/SwitchControl.d.ts +11 -0
  237. package/ui-shared/controls/SwitchControl.js +29 -0
  238. package/ui-shared/controls/SwitchControl.js.map +1 -0
  239. package/ui-shared/controls/TextAreaControl.d.ts +8 -0
  240. package/ui-shared/controls/TextAreaControl.js +12 -0
  241. package/ui-shared/controls/TextAreaControl.js.map +1 -0
  242. package/ui-shared/controls/TextControl.d.ts +10 -0
  243. package/ui-shared/controls/TextControl.js +24 -0
  244. package/ui-shared/controls/TextControl.js.map +1 -0
  245. package/ui-shared/controls/keycloak-text-area/KeycloakTextArea.d.ts +4 -0
  246. package/ui-shared/controls/keycloak-text-area/KeycloakTextArea.js +5 -0
  247. package/ui-shared/controls/keycloak-text-area/KeycloakTextArea.js.map +1 -0
  248. package/ui-shared/controls/select-control/SelectControl.d.ts +31 -0
  249. package/ui-shared/controls/select-control/SelectControl.js +28 -0
  250. package/ui-shared/controls/select-control/SelectControl.js.map +1 -0
  251. package/ui-shared/controls/select-control/SingleSelectControl.d.ts +3 -0
  252. package/ui-shared/controls/select-control/SingleSelectControl.js +41 -0
  253. package/ui-shared/controls/select-control/SingleSelectControl.js.map +1 -0
  254. package/ui-shared/controls/select-control/TypeaheadSelectControl.d.ts +3 -0
  255. package/ui-shared/controls/select-control/TypeaheadSelectControl.js +138 -0
  256. package/ui-shared/controls/select-control/TypeaheadSelectControl.js.map +1 -0
  257. package/ui-shared/icons/IconMapper.d.ts +5 -0
  258. package/ui-shared/icons/IconMapper.js +40 -0
  259. package/ui-shared/icons/IconMapper.js.map +1 -0
  260. package/ui-shared/index.d.ts +1 -0
  261. package/ui-shared/index.js +2 -0
  262. package/ui-shared/index.js.map +1 -0
  263. package/ui-shared/main.d.ts +31 -0
  264. package/ui-shared/main.js +29 -0
  265. package/ui-shared/main.js.map +1 -0
  266. package/ui-shared/masthead/DefaultAvatar.d.ts +7 -0
  267. package/ui-shared/masthead/DefaultAvatar.js +26 -0
  268. package/ui-shared/masthead/DefaultAvatar.js.map +1 -0
  269. package/ui-shared/masthead/KeycloakDropdown.d.ts +10 -0
  270. package/ui-shared/masthead/KeycloakDropdown.js +23 -0
  271. package/ui-shared/masthead/KeycloakDropdown.js.map +1 -0
  272. package/ui-shared/masthead/Masthead.d.ts +22 -0
  273. package/ui-shared/masthead/Masthead.js +55 -0
  274. package/ui-shared/masthead/Masthead.js.map +1 -0
  275. package/ui-shared/scroll-form/FormPanel.d.ts +8 -0
  276. package/ui-shared/scroll-form/FormPanel.js +9 -0
  277. package/ui-shared/scroll-form/FormPanel.js.map +1 -0
  278. package/ui-shared/scroll-form/FormTitle.d.ts +8 -0
  279. package/ui-shared/scroll-form/FormTitle.js +19 -0
  280. package/ui-shared/scroll-form/FormTitle.js.map +1 -0
  281. package/ui-shared/scroll-form/ScrollForm.d.ts +15 -0
  282. package/ui-shared/scroll-form/ScrollForm.js +38 -0
  283. package/ui-shared/scroll-form/ScrollForm.js.map +1 -0
  284. package/ui-shared/scroll-form/ScrollPanel.d.ts +7 -0
  285. package/ui-shared/scroll-form/ScrollPanel.js +18 -0
  286. package/ui-shared/scroll-form/ScrollPanel.js.map +1 -0
  287. package/ui-shared/select/KeycloakSelect.d.ts +30 -0
  288. package/ui-shared/select/KeycloakSelect.js +31 -0
  289. package/ui-shared/select/KeycloakSelect.js.map +1 -0
  290. package/ui-shared/select/SingleSelect.d.ts +4 -0
  291. package/ui-shared/select/SingleSelect.js +46 -0
  292. package/ui-shared/select/SingleSelect.js.map +1 -0
  293. package/ui-shared/select/TypeaheadSelect.d.ts +2 -0
  294. package/ui-shared/select/TypeaheadSelect.js +96 -0
  295. package/ui-shared/select/TypeaheadSelect.js.map +1 -0
  296. package/ui-shared/user-profile/LocaleSelector.d.ts +7 -0
  297. package/ui-shared/user-profile/LocaleSelector.js +28 -0
  298. package/ui-shared/user-profile/LocaleSelector.js.map +1 -0
  299. package/ui-shared/user-profile/MultiInputComponent.d.ts +15 -0
  300. package/ui-shared/user-profile/MultiInputComponent.js +61 -0
  301. package/ui-shared/user-profile/MultiInputComponent.js.map +1 -0
  302. package/ui-shared/user-profile/OptionsComponent.d.ts +2 -0
  303. package/ui-shared/user-profile/OptionsComponent.js +28 -0
  304. package/ui-shared/user-profile/OptionsComponent.js.map +1 -0
  305. package/ui-shared/user-profile/SelectComponent.d.ts +2 -0
  306. package/ui-shared/user-profile/SelectComponent.js +55 -0
  307. package/ui-shared/user-profile/SelectComponent.js.map +1 -0
  308. package/ui-shared/user-profile/TextAreaComponent.d.ts +2 -0
  309. package/ui-shared/user-profile/TextAreaComponent.js +11 -0
  310. package/ui-shared/user-profile/TextAreaComponent.js.map +1 -0
  311. package/ui-shared/user-profile/TextComponent.d.ts +2 -0
  312. package/ui-shared/user-profile/TextComponent.js +14 -0
  313. package/ui-shared/user-profile/TextComponent.js.map +1 -0
  314. package/ui-shared/user-profile/UserProfileFields.d.ts +39 -0
  315. package/ui-shared/user-profile/UserProfileFields.js +112 -0
  316. package/ui-shared/user-profile/UserProfileFields.js.map +1 -0
  317. package/ui-shared/user-profile/UserProfileGroup.d.ts +12 -0
  318. package/ui-shared/user-profile/UserProfileGroup.js +15 -0
  319. package/ui-shared/user-profile/UserProfileGroup.js.map +1 -0
  320. package/ui-shared/user-profile/utils.d.ts +32 -0
  321. package/ui-shared/user-profile/utils.js +79 -0
  322. package/ui-shared/user-profile/utils.js.map +1 -0
  323. package/ui-shared/utils/createNamedContext.d.ts +3 -0
  324. package/ui-shared/utils/createNamedContext.js +7 -0
  325. package/ui-shared/utils/createNamedContext.js.map +1 -0
  326. package/ui-shared/utils/isDefined.d.ts +1 -0
  327. package/ui-shared/utils/isDefined.js +4 -0
  328. package/ui-shared/utils/isDefined.js.map +1 -0
  329. package/ui-shared/utils/useRequiredContext.d.ts +9 -0
  330. package/ui-shared/utils/useRequiredContext.js +17 -0
  331. package/ui-shared/utils/useRequiredContext.js.map +1 -0
  332. package/ui-shared/utils/useStorageItem.d.ts +10 -0
  333. package/ui-shared/utils/useStorageItem.js +40 -0
  334. package/ui-shared/utils/useStorageItem.js.map +1 -0
  335. package/ui-shared/utils/useStoredState.d.ts +13 -0
  336. package/ui-shared/utils/useStoredState.js +21 -0
  337. package/ui-shared/utils/useStoredState.js.map +1 -0
  338. package/utils/formatDate.d.ts +4 -0
  339. package/utils/formatDate.js +12 -0
  340. package/utils/formatDate.js.map +1 -0
  341. package/utils/isRecord.d.ts +1 -0
  342. package/utils/isRecord.js +2 -0
  343. package/utils/isRecord.js.map +1 -0
  344. package/utils/joinPath.d.ts +1 -0
  345. package/utils/joinPath.js +18 -0
  346. package/utils/joinPath.js.map +1 -0
  347. package/utils/usePromise.d.ts +41 -0
  348. package/utils/usePromise.js +55 -0
  349. package/utils/usePromise.js.map +1 -0
@@ -0,0 +1,129 @@
1
+ import {
2
+ Badge,
3
+ Button,
4
+ Chip,
5
+ Icon,
6
+ Modal,
7
+ ModalVariant,
8
+ Text,
9
+ } from "@patternfly/react-core";
10
+ import { UserCheckIcon } from "@patternfly/react-icons";
11
+ import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table";
12
+ import { useState } from "react";
13
+ import { useTranslation } from "react-i18next";
14
+ import { useAlerts, useEnvironment } from "@keycloakify/keycloak-account-ui/ui-shared";
15
+ import { fetchPermission, updateRequest } from "@keycloakify/keycloak-account-ui/api";
16
+ import { Permission, Resource } from "@keycloakify/keycloak-account-ui/api/representations";
17
+
18
+ type PermissionRequestProps = {
19
+ resource: Resource;
20
+ refresh: () => void;
21
+ };
22
+
23
+ export const PermissionRequest = ({
24
+ resource,
25
+ refresh,
26
+ }: PermissionRequestProps) => {
27
+ const { t } = useTranslation();
28
+ const context = useEnvironment();
29
+ const { addAlert, addError } = useAlerts();
30
+
31
+ const [open, setOpen] = useState(false);
32
+
33
+ const toggle = () => setOpen(!open);
34
+
35
+ const approveDeny = async (
36
+ shareRequest: Permission,
37
+ approve: boolean = false,
38
+ ) => {
39
+ try {
40
+ const permissions = await fetchPermission({ context }, resource._id);
41
+ const { scopes, username } = permissions.find(
42
+ (p) => p.username === shareRequest.username,
43
+ ) || { scopes: [], username: shareRequest.username };
44
+
45
+ await updateRequest(
46
+ context,
47
+ resource._id,
48
+ username,
49
+ approve
50
+ ? [...(scopes as string[]), ...(shareRequest.scopes as string[])]
51
+ : scopes,
52
+ );
53
+ addAlert(t("shareSuccess"));
54
+ toggle();
55
+ refresh();
56
+ } catch (error) {
57
+ addError(t("shareError", { error }).toString());
58
+ }
59
+ };
60
+
61
+ return (
62
+ <>
63
+ <Button variant="link" onClick={toggle}>
64
+ <Icon size="lg">
65
+ <UserCheckIcon />
66
+ </Icon>
67
+ <Badge>{resource.shareRequests?.length}</Badge>
68
+ </Button>
69
+ <Modal
70
+ title={t("permissionRequest", { name: resource.name })}
71
+ variant={ModalVariant.large}
72
+ isOpen={open}
73
+ onClose={toggle}
74
+ actions={[
75
+ <Button key="close" variant="link" onClick={toggle}>
76
+ {t("close")}
77
+ </Button>,
78
+ ]}
79
+ >
80
+ <Table aria-label={t("resources")}>
81
+ <Thead>
82
+ <Tr>
83
+ <Th>{t("requestor")}</Th>
84
+ <Th>{t("permissionRequests")}</Th>
85
+ <Th aria-hidden="true"></Th>
86
+ </Tr>
87
+ </Thead>
88
+ <Tbody>
89
+ {resource.shareRequests?.map((shareRequest) => (
90
+ <Tr key={shareRequest.username}>
91
+ <Td>
92
+ {shareRequest.firstName} {shareRequest.lastName}{" "}
93
+ {shareRequest.lastName ? "" : shareRequest.username}
94
+ <br />
95
+ <Text component="small">{shareRequest.email}</Text>
96
+ </Td>
97
+ <Td>
98
+ {shareRequest.scopes.map((scope) => (
99
+ <Chip key={scope.toString()} isReadOnly>
100
+ {scope as string}
101
+ </Chip>
102
+ ))}
103
+ </Td>
104
+ <Td>
105
+ <Button
106
+ onClick={() => {
107
+ approveDeny(shareRequest, true);
108
+ }}
109
+ >
110
+ {t("accept")}
111
+ </Button>
112
+ <Button
113
+ onClick={() => {
114
+ approveDeny(shareRequest);
115
+ }}
116
+ className="pf-v5-u-ml-sm"
117
+ variant="danger"
118
+ >
119
+ {t("deny")}
120
+ </Button>
121
+ </Td>
122
+ </Tr>
123
+ ))}
124
+ </Tbody>
125
+ </Table>
126
+ </Modal>
127
+ </>
128
+ );
129
+ };
@@ -0,0 +1,87 @@
1
+ import { useState } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import {
4
+ Pagination,
5
+ SearchInput,
6
+ PaginationToggleTemplateProps,
7
+ Toolbar,
8
+ ToolbarContent,
9
+ ToolbarItem,
10
+ } from "@patternfly/react-core";
11
+
12
+ type ResourceToolbarProps = {
13
+ onFilter: (nameFilter: string) => void;
14
+ count: number;
15
+ first: number;
16
+ max: number;
17
+ onNextClick: (page: number) => void;
18
+ onPreviousClick: (page: number) => void;
19
+ onPerPageSelect: (max: number, first: number) => void;
20
+ hasNext: boolean;
21
+ };
22
+
23
+ export const ResourceToolbar = ({
24
+ count,
25
+ first,
26
+ max,
27
+ onNextClick,
28
+ onPreviousClick,
29
+ onPerPageSelect,
30
+ onFilter,
31
+ hasNext,
32
+ }: ResourceToolbarProps) => {
33
+ const { t } = useTranslation();
34
+ const [nameFilter, setNameFilter] = useState("");
35
+
36
+ const page = Math.round(first / max) + 1;
37
+ return (
38
+ <Toolbar>
39
+ <ToolbarContent>
40
+ <ToolbarItem>
41
+ <SearchInput
42
+ placeholder={t("filterByName")}
43
+ aria-label={t("filterByName")}
44
+ value={nameFilter}
45
+ onChange={(_, value) => {
46
+ setNameFilter(value);
47
+ }}
48
+ onSearch={() => onFilter(nameFilter)}
49
+ onKeyDown={(e) => {
50
+ if (e.key === "Enter") {
51
+ onFilter(nameFilter);
52
+ }
53
+ }}
54
+ onClear={() => {
55
+ setNameFilter("");
56
+ onFilter("");
57
+ }}
58
+ />
59
+ </ToolbarItem>
60
+ <ToolbarItem variant="pagination">
61
+ <Pagination
62
+ isCompact
63
+ perPageOptions={[
64
+ { title: "5", value: 5 },
65
+ { title: "10", value: 10 },
66
+ { title: "20", value: 20 },
67
+ ]}
68
+ toggleTemplate={({
69
+ firstIndex,
70
+ lastIndex,
71
+ }: PaginationToggleTemplateProps) => (
72
+ <b>
73
+ {firstIndex} - {lastIndex}
74
+ </b>
75
+ )}
76
+ itemCount={count + (page - 1) * max + (hasNext ? 1 : 0)}
77
+ page={page}
78
+ perPage={max}
79
+ onNextClick={(_, p) => onNextClick((p - 1) * max)}
80
+ onPreviousClick={(_, p) => onPreviousClick((p - 1) * max)}
81
+ onPerPageSelect={(_, m, f) => onPerPageSelect(f - 1, m)}
82
+ />
83
+ </ToolbarItem>
84
+ </ToolbarContent>
85
+ </Toolbar>
86
+ );
87
+ };
@@ -0,0 +1,39 @@
1
+ import { useState } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import { Tab, Tabs, TabTitleText } from "@patternfly/react-core";
4
+
5
+ import { ResourcesTab } from "@keycloakify/keycloak-account-ui/resources/ResourcesTab";
6
+ import { Page } from "@keycloakify/keycloak-account-ui/components/page/Page";
7
+
8
+ export const Resources = () => {
9
+ const { t } = useTranslation();
10
+ const [activeTabKey, setActiveTabKey] = useState(0);
11
+
12
+ return (
13
+ <Page title={t("resources")} description={t("resourceIntroMessage")}>
14
+ <Tabs
15
+ activeKey={activeTabKey}
16
+ onSelect={(_, key) => setActiveTabKey(key as number)}
17
+ mountOnEnter
18
+ unmountOnExit
19
+ >
20
+ <Tab
21
+ data-testid="myResources"
22
+ eventKey={0}
23
+ title={<TabTitleText>{t("myResources")}</TabTitleText>}
24
+ >
25
+ <ResourcesTab />
26
+ </Tab>
27
+ <Tab
28
+ data-testid="sharedWithMe"
29
+ eventKey={1}
30
+ title={<TabTitleText>{t("sharedWithMe")}</TabTitleText>}
31
+ >
32
+ <ResourcesTab isShared />
33
+ </Tab>
34
+ </Tabs>
35
+ </Page>
36
+ );
37
+ };
38
+
39
+ export default Resources;
@@ -0,0 +1,410 @@
1
+ import {
2
+ Button,
3
+ Chip,
4
+ ChipGroup,
5
+ Dropdown,
6
+ DropdownItem,
7
+ DropdownList,
8
+ MenuToggle,
9
+ OverflowMenu,
10
+ OverflowMenuContent,
11
+ OverflowMenuControl,
12
+ OverflowMenuDropdownItem,
13
+ OverflowMenuGroup,
14
+ OverflowMenuItem,
15
+ Spinner,
16
+ } from "@patternfly/react-core";
17
+ import {
18
+ EditAltIcon,
19
+ EllipsisVIcon,
20
+ ExternalLinkAltIcon,
21
+ Remove2Icon,
22
+ ShareAltIcon,
23
+ } from "@patternfly/react-icons";
24
+ import {
25
+ ExpandableRowContent,
26
+ Table,
27
+ Tbody,
28
+ Td,
29
+ Th,
30
+ Thead,
31
+ Tr,
32
+ } from "@patternfly/react-table";
33
+ import { useState } from "react";
34
+ import { useTranslation } from "react-i18next";
35
+ import {
36
+ ContinueCancelModal,
37
+ useAlerts,
38
+ useEnvironment,
39
+ } from "@keycloakify/keycloak-account-ui/ui-shared";
40
+ import { fetchPermission, fetchResources, updatePermissions } from "@keycloakify/keycloak-account-ui/api";
41
+ import { getPermissionRequests } from "@keycloakify/keycloak-account-ui/api/methods";
42
+ import { Links } from "@keycloakify/keycloak-account-ui/api/parse-links";
43
+ import { Permission, Resource } from "@keycloakify/keycloak-account-ui/api/representations";
44
+ import { usePromise } from "@keycloakify/keycloak-account-ui/utils/usePromise";
45
+ import { EditTheResource } from "@keycloakify/keycloak-account-ui/resources/EditTheResource";
46
+ import { PermissionRequest } from "@keycloakify/keycloak-account-ui/resources/PermissionRequest";
47
+ import { ResourceToolbar } from "@keycloakify/keycloak-account-ui/resources/ResourceToolbar";
48
+ import { ShareTheResource } from "@keycloakify/keycloak-account-ui/resources/ShareTheResource";
49
+ import { SharedWith } from "@keycloakify/keycloak-account-ui/resources/SharedWith";
50
+
51
+ type PermissionDetail = {
52
+ contextOpen?: boolean;
53
+ rowOpen?: boolean;
54
+ shareDialogOpen?: boolean;
55
+ editDialogOpen?: boolean;
56
+ permissions?: Permission[];
57
+ };
58
+
59
+ type ResourcesTabProps = {
60
+ isShared?: boolean;
61
+ };
62
+
63
+ export const ResourcesTab = ({ isShared = false }: ResourcesTabProps) => {
64
+ const { t } = useTranslation();
65
+ const context = useEnvironment();
66
+ const { addAlert, addError } = useAlerts();
67
+
68
+ const [params, setParams] = useState<Record<string, string>>({
69
+ first: "0",
70
+ max: "5",
71
+ });
72
+ const [links, setLinks] = useState<Links | undefined>();
73
+ const [resources, setResources] = useState<Resource[]>();
74
+ const [details, setDetails] = useState<
75
+ Record<string, PermissionDetail | undefined>
76
+ >({});
77
+ const [key, setKey] = useState(1);
78
+ const refresh = () => setKey(key + 1);
79
+
80
+ usePromise(
81
+ async (signal) => {
82
+ const result = await fetchResources(
83
+ { signal, context },
84
+ params,
85
+ isShared,
86
+ );
87
+ if (!isShared)
88
+ await Promise.all(
89
+ result.data.map(
90
+ async (r) =>
91
+ (r.shareRequests = await getPermissionRequests(r._id, {
92
+ signal,
93
+ context,
94
+ })),
95
+ ),
96
+ );
97
+ return result;
98
+ },
99
+ ({ data, links }) => {
100
+ setResources(data);
101
+ setLinks(links);
102
+ },
103
+ [params, key],
104
+ );
105
+
106
+ if (!resources) {
107
+ return <Spinner />;
108
+ }
109
+
110
+ const fetchPermissions = async (id: string) => {
111
+ let permissions = details[id]?.permissions || [];
112
+ if (!details[id]) {
113
+ permissions = await fetchPermission({ context }, id);
114
+ }
115
+ return permissions;
116
+ };
117
+
118
+ const removeShare = async (resource: Resource) => {
119
+ try {
120
+ const permissions = (await fetchPermissions(resource._id)).map(
121
+ ({ username }) =>
122
+ ({
123
+ username,
124
+ scopes: [],
125
+ }) as Permission,
126
+ )!;
127
+ await updatePermissions(context, resource._id, permissions);
128
+ setDetails({});
129
+ addAlert(t("unShareSuccess"));
130
+ } catch (error) {
131
+ addError(t("unShareError", { error }).toString());
132
+ }
133
+ };
134
+
135
+ const toggleOpen = async (
136
+ id: string,
137
+ field: keyof PermissionDetail,
138
+ open: boolean,
139
+ ) => {
140
+ const permissions = await fetchPermissions(id);
141
+
142
+ setDetails({
143
+ ...details,
144
+ [id]: { ...details[id], [field]: open, permissions },
145
+ });
146
+ };
147
+
148
+ return (
149
+ <>
150
+ <ResourceToolbar
151
+ onFilter={(name) => setParams({ ...params, name })}
152
+ count={resources.length}
153
+ first={parseInt(params["first"])}
154
+ max={parseInt(params["max"])}
155
+ onNextClick={() => setParams(links?.next || {})}
156
+ onPreviousClick={() => setParams(links?.prev || {})}
157
+ onPerPageSelect={(first, max) =>
158
+ setParams({ first: `${first}`, max: `${max}` })
159
+ }
160
+ hasNext={!!links?.next}
161
+ />
162
+ <Table aria-label={t("resources")}>
163
+ <Thead>
164
+ <Tr>
165
+ <Th aria-hidden="true" />
166
+ <Th>{t("resourceName")}</Th>
167
+ <Th>{t("application")}</Th>
168
+ <Th aria-hidden={isShared}>
169
+ {!isShared ? t("permissionRequests") : ""}
170
+ </Th>
171
+ </Tr>
172
+ </Thead>
173
+ {resources.map((resource, index) => (
174
+ <Tbody
175
+ key={resource.name}
176
+ isExpanded={details[resource._id]?.rowOpen}
177
+ >
178
+ <Tr>
179
+ <Td
180
+ data-testid={`expand-${resource.name}`}
181
+ expand={
182
+ !isShared
183
+ ? {
184
+ isExpanded: details[resource._id]?.rowOpen || false,
185
+ rowIndex: index,
186
+ onToggle: () =>
187
+ toggleOpen(
188
+ resource._id,
189
+ "rowOpen",
190
+ !details[resource._id]?.rowOpen,
191
+ ),
192
+ }
193
+ : undefined
194
+ }
195
+ />
196
+ <Td
197
+ dataLabel={t("resourceName")}
198
+ data-testid={`row[${index}].name`}
199
+ >
200
+ {resource.name}
201
+ </Td>
202
+ <Td dataLabel={t("application")}>
203
+ <a href={resource.client.baseUrl}>
204
+ {resource.client.name || resource.client.clientId}{" "}
205
+ <ExternalLinkAltIcon />
206
+ </a>
207
+ </Td>
208
+ <Td dataLabel={t("permissionRequests")}>
209
+ {resource.shareRequests &&
210
+ resource.shareRequests.length > 0 && (
211
+ <PermissionRequest
212
+ resource={resource}
213
+ refresh={() => refresh()}
214
+ />
215
+ )}
216
+ <ShareTheResource
217
+ resource={resource}
218
+ permissions={details[resource._id]?.permissions}
219
+ open={details[resource._id]?.shareDialogOpen || false}
220
+ onClose={() => setDetails({})}
221
+ />
222
+ {details[resource._id]?.editDialogOpen && (
223
+ <EditTheResource
224
+ resource={resource}
225
+ permissions={details[resource._id]?.permissions}
226
+ onClose={() => setDetails({})}
227
+ />
228
+ )}
229
+ </Td>
230
+ {isShared ? (
231
+ <Td>
232
+ {resource.scopes.length > 0 && (
233
+ <ChipGroup categoryName={t("permissions")}>
234
+ {resource.scopes.map((scope) => (
235
+ <Chip key={scope.name} isReadOnly>
236
+ {scope.displayName || scope.name}
237
+ </Chip>
238
+ ))}
239
+ </ChipGroup>
240
+ )}
241
+ </Td>
242
+ ) : (
243
+ <Td isActionCell>
244
+ <OverflowMenu breakpoint="lg">
245
+ <OverflowMenuContent>
246
+ <OverflowMenuGroup groupType="button">
247
+ <OverflowMenuItem>
248
+ <Button
249
+ data-testid={`share-${resource.name}`}
250
+ variant="link"
251
+ onClick={() =>
252
+ toggleOpen(resource._id, "shareDialogOpen", true)
253
+ }
254
+ >
255
+ <ShareAltIcon /> {t("share")}
256
+ </Button>
257
+ </OverflowMenuItem>
258
+ <OverflowMenuItem>
259
+ <Dropdown
260
+ popperProps={{
261
+ position: "right",
262
+ }}
263
+ onOpenChange={(isOpen) =>
264
+ toggleOpen(resource._id, "contextOpen", isOpen)
265
+ }
266
+ toggle={(ref) => (
267
+ <MenuToggle
268
+ variant="plain"
269
+ ref={ref}
270
+ onClick={() =>
271
+ toggleOpen(
272
+ resource._id,
273
+ "contextOpen",
274
+ !details[resource._id]?.contextOpen,
275
+ )
276
+ }
277
+ isExpanded={details[resource._id]?.contextOpen}
278
+ >
279
+ <EllipsisVIcon />
280
+ </MenuToggle>
281
+ )}
282
+ isOpen={!!details[resource._id]?.contextOpen}
283
+ >
284
+ <DropdownList>
285
+ <DropdownItem
286
+ isDisabled={
287
+ details[resource._id]?.permissions?.length ===
288
+ 0
289
+ }
290
+ onClick={() =>
291
+ toggleOpen(
292
+ resource._id,
293
+ "editDialogOpen",
294
+ true,
295
+ )
296
+ }
297
+ >
298
+ <EditAltIcon /> {t("edit")}
299
+ </DropdownItem>
300
+ <ContinueCancelModal
301
+ buttonTitle={
302
+ <>
303
+ <Remove2Icon /> {t("unShare")}
304
+ </>
305
+ }
306
+ modalTitle={t("unShare")}
307
+ continueLabel={t("confirm")}
308
+ cancelLabel={t("cancel")}
309
+ component={DropdownItem}
310
+ onContinue={() => removeShare(resource)}
311
+ isDisabled={
312
+ details[resource._id]?.permissions?.length ===
313
+ 0
314
+ }
315
+ >
316
+ {t("unShareAllConfirm")}
317
+ </ContinueCancelModal>
318
+ </DropdownList>
319
+ </Dropdown>
320
+ </OverflowMenuItem>
321
+ </OverflowMenuGroup>
322
+ </OverflowMenuContent>
323
+ <OverflowMenuControl>
324
+ <Dropdown
325
+ popperProps={{
326
+ position: "right",
327
+ }}
328
+ onOpenChange={(isOpen) =>
329
+ toggleOpen(resource._id, "contextOpen", isOpen)
330
+ }
331
+ toggle={(ref) => (
332
+ <MenuToggle
333
+ variant="plain"
334
+ ref={ref}
335
+ isExpanded={details[resource._id]?.contextOpen}
336
+ onClick={() =>
337
+ toggleOpen(
338
+ resource._id,
339
+ "contextOpen",
340
+ !details[resource._id]?.contextOpen,
341
+ )
342
+ }
343
+ >
344
+ <EllipsisVIcon />
345
+ </MenuToggle>
346
+ )}
347
+ isOpen={!!details[resource._id]?.contextOpen}
348
+ >
349
+ <DropdownList>
350
+ <OverflowMenuDropdownItem
351
+ key="share"
352
+ isShared
353
+ onClick={() =>
354
+ toggleOpen(resource._id, "shareDialogOpen", true)
355
+ }
356
+ >
357
+ <ShareAltIcon /> {t("share")}
358
+ </OverflowMenuDropdownItem>
359
+ <OverflowMenuDropdownItem
360
+ key="edit"
361
+ isShared
362
+ onClick={() =>
363
+ toggleOpen(resource._id, "editDialogOpen", true)
364
+ }
365
+ isDisabled={
366
+ details[resource._id]?.permissions?.length === 0
367
+ }
368
+ >
369
+ <EditAltIcon /> {t("edit")}
370
+ </OverflowMenuDropdownItem>
371
+ <ContinueCancelModal
372
+ key="unShare"
373
+ buttonTitle={
374
+ <>
375
+ <Remove2Icon /> {t("unShare")}
376
+ </>
377
+ }
378
+ modalTitle={t("unShare")}
379
+ continueLabel={t("confirm")}
380
+ cancelLabel={t("cancel")}
381
+ component={OverflowMenuDropdownItem}
382
+ onContinue={() => removeShare(resource)}
383
+ isDisabled={
384
+ details[resource._id]?.permissions?.length === 0
385
+ }
386
+ >
387
+ {t("unShareAllConfirm")}
388
+ </ContinueCancelModal>
389
+ </DropdownList>
390
+ </Dropdown>
391
+ </OverflowMenuControl>
392
+ </OverflowMenu>
393
+ </Td>
394
+ )}
395
+ </Tr>
396
+ <Tr isExpanded={details[resource._id]?.rowOpen || false}>
397
+ <Td colSpan={4} textCenter>
398
+ <ExpandableRowContent>
399
+ <SharedWith
400
+ permissions={details[resource._id]?.permissions}
401
+ />
402
+ </ExpandableRowContent>
403
+ </Td>
404
+ </Tr>
405
+ </Tbody>
406
+ ))}
407
+ </Table>
408
+ </>
409
+ );
410
+ };