@ngbplatform/ui 1.3.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 (217) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +44 -0
  3. package/package.json +61 -0
  4. package/public/favicon.svg +6 -0
  5. package/public/silent-check-sso.html +8 -0
  6. package/src/assets/ngb_logo.svg +17 -0
  7. package/src/assets/ngb_logo_color.svg +17 -0
  8. package/src/index.ts +1023 -0
  9. package/src/ngb/README.md +27 -0
  10. package/src/ngb/accounting/NgbAccountingPeriodClosingPage.vue +1157 -0
  11. package/src/ngb/accounting/NgbChartOfAccountEditor.vue +590 -0
  12. package/src/ngb/accounting/NgbChartOfAccountsPage.vue +420 -0
  13. package/src/ngb/accounting/NgbGeneralJournalEntryEditPage.vue +796 -0
  14. package/src/ngb/accounting/NgbGeneralJournalEntryLinesEditor.vue +454 -0
  15. package/src/ngb/accounting/NgbGeneralJournalEntryListPage.vue +186 -0
  16. package/src/ngb/accounting/api.ts +79 -0
  17. package/src/ngb/accounting/generalJournalEntry.ts +127 -0
  18. package/src/ngb/accounting/generalJournalEntryApi.ts +112 -0
  19. package/src/ngb/accounting/generalJournalEntryTypes.ts +155 -0
  20. package/src/ngb/accounting/navigation.ts +83 -0
  21. package/src/ngb/accounting/periodClosing.ts +75 -0
  22. package/src/ngb/accounting/periodClosingApi.ts +60 -0
  23. package/src/ngb/accounting/periodClosingTypes.ts +73 -0
  24. package/src/ngb/accounting/types.ts +57 -0
  25. package/src/ngb/api/audit.ts +23 -0
  26. package/src/ngb/api/catalogs.ts +50 -0
  27. package/src/ngb/api/contracts.ts +285 -0
  28. package/src/ngb/api/documents.ts +170 -0
  29. package/src/ngb/api/http.ts +446 -0
  30. package/src/ngb/api/lookups.ts +14 -0
  31. package/src/ngb/api/types.ts +6 -0
  32. package/src/ngb/auth/index.ts +13 -0
  33. package/src/ngb/auth/keycloak.ts +374 -0
  34. package/src/ngb/auth/router.ts +35 -0
  35. package/src/ngb/auth/types.ts +12 -0
  36. package/src/ngb/auth/useAuthStore.ts +209 -0
  37. package/src/ngb/command-palette/NgbCommandPaletteDialog.vue +342 -0
  38. package/src/ngb/command-palette/NgbCommandPaletteEmptyState.vue +55 -0
  39. package/src/ngb/command-palette/NgbCommandPaletteFooterHints.vue +29 -0
  40. package/src/ngb/command-palette/api.ts +10 -0
  41. package/src/ngb/command-palette/config.ts +16 -0
  42. package/src/ngb/command-palette/presentation.ts +85 -0
  43. package/src/ngb/command-palette/search.ts +130 -0
  44. package/src/ngb/command-palette/staticItems.ts +243 -0
  45. package/src/ngb/command-palette/storage.ts +45 -0
  46. package/src/ngb/command-palette/store.ts +716 -0
  47. package/src/ngb/command-palette/types.ts +120 -0
  48. package/src/ngb/command-palette/useCommandPaletteHotkeys.ts +27 -0
  49. package/src/ngb/command-palette/useCommandPalettePageContext.ts +18 -0
  50. package/src/ngb/components/NgbConfirmDialog.vue +63 -0
  51. package/src/ngb/components/NgbDialog.vue +44 -0
  52. package/src/ngb/components/NgbDrawer.vue +156 -0
  53. package/src/ngb/components/NgbHeaderActionCluster.vue +140 -0
  54. package/src/ngb/components/NgbModalShell.vue +99 -0
  55. package/src/ngb/components/NgbToolbar.vue +14 -0
  56. package/src/ngb/components/forms/NgbFormLayout.vue +13 -0
  57. package/src/ngb/components/forms/NgbFormRow.vue +21 -0
  58. package/src/ngb/components/forms/NgbFormSection.vue +16 -0
  59. package/src/ngb/components/forms/NgbValidationSummary.vue +18 -0
  60. package/src/ngb/components/navigation/NgbNavigationTree.vue +68 -0
  61. package/src/ngb/components/navigation/NgbTreeNode.vue +94 -0
  62. package/src/ngb/components/register/NgbColumnChooser.vue +101 -0
  63. package/src/ngb/components/register/NgbRegisterGrid.vue +473 -0
  64. package/src/ngb/components/register/registerTypes.ts +51 -0
  65. package/src/ngb/components/register/useRegisterColumnResize.ts +99 -0
  66. package/src/ngb/components/register/useRegisterColumnState.ts +220 -0
  67. package/src/ngb/components/register/useRegisterRows.ts +209 -0
  68. package/src/ngb/components/register/useRegisterViewport.ts +56 -0
  69. package/src/ngb/editor/NgbDocumentEffectsPage.vue +512 -0
  70. package/src/ngb/editor/NgbDocumentFlowPage.vue +409 -0
  71. package/src/ngb/editor/NgbDocumentPrintPage.vue +712 -0
  72. package/src/ngb/editor/NgbEditorDiscardDialog.vue +40 -0
  73. package/src/ngb/editor/NgbEntityAuditSidebar.vue +288 -0
  74. package/src/ngb/editor/NgbEntityEditor.vue +250 -0
  75. package/src/ngb/editor/NgbEntityEditorDrawerActions.vue +88 -0
  76. package/src/ngb/editor/NgbEntityEditorHeader.vue +114 -0
  77. package/src/ngb/editor/catalogNavigation.ts +13 -0
  78. package/src/ngb/editor/config.ts +252 -0
  79. package/src/ngb/editor/documentCopyDraft.ts +245 -0
  80. package/src/ngb/editor/documentEffects.ts +43 -0
  81. package/src/ngb/editor/documentEffectsDateFormatting.ts +18 -0
  82. package/src/ngb/editor/documentNavigation.ts +115 -0
  83. package/src/ngb/editor/documentStatus.ts +45 -0
  84. package/src/ngb/editor/entityEditorErrors.ts +162 -0
  85. package/src/ngb/editor/entityEditorPersistence.ts +241 -0
  86. package/src/ngb/editor/extensions.ts +40 -0
  87. package/src/ngb/editor/types.ts +295 -0
  88. package/src/ngb/editor/useConfiguredEntityEditorDocumentActions.ts +183 -0
  89. package/src/ngb/editor/useDocumentEditorDrawerState.ts +152 -0
  90. package/src/ngb/editor/useEditorDrawerState.ts +88 -0
  91. package/src/ngb/editor/useEntityEditorBusinessContext.ts +97 -0
  92. package/src/ngb/editor/useEntityEditorCapabilities.ts +190 -0
  93. package/src/ngb/editor/useEntityEditorCommandPalette.ts +154 -0
  94. package/src/ngb/editor/useEntityEditorCommitHandlers.ts +102 -0
  95. package/src/ngb/editor/useEntityEditorHeaderActions.ts +253 -0
  96. package/src/ngb/editor/useEntityEditorLeaveGuard.ts +66 -0
  97. package/src/ngb/editor/useEntityEditorNavigationActions.ts +235 -0
  98. package/src/ngb/editor/useEntityEditorOutputs.ts +59 -0
  99. package/src/ngb/editor/useEntityEditorPageActions.ts +57 -0
  100. package/src/ngb/editor/useRouteQueryEditorDrawer.ts +173 -0
  101. package/src/ngb/env/runtimeConfig.ts +25 -0
  102. package/src/ngb/lookup/config.ts +42 -0
  103. package/src/ngb/lookup/defaultConfig.ts +136 -0
  104. package/src/ngb/lookup/navigation.ts +76 -0
  105. package/src/ngb/lookup/prefetch.ts +40 -0
  106. package/src/ngb/lookup/store.ts +261 -0
  107. package/src/ngb/metadata/NgbDocumentListFiltersDrawer.vue +78 -0
  108. package/src/ngb/metadata/NgbDocumentPeriodFilter.vue +53 -0
  109. package/src/ngb/metadata/NgbEntityForm.vue +84 -0
  110. package/src/ngb/metadata/NgbEntityFormFieldsBlock.vue +110 -0
  111. package/src/ngb/metadata/NgbEntityListPageHeader.vue +80 -0
  112. package/src/ngb/metadata/NgbFilterFieldControl.vue +104 -0
  113. package/src/ngb/metadata/NgbMetadataCatalogEditPage.vue +42 -0
  114. package/src/ngb/metadata/NgbMetadataCatalogListPage.vue +269 -0
  115. package/src/ngb/metadata/NgbMetadataDocumentEditPage.vue +68 -0
  116. package/src/ngb/metadata/NgbMetadataDocumentListPage.vue +353 -0
  117. package/src/ngb/metadata/NgbMetadataFieldRenderer.vue +124 -0
  118. package/src/ngb/metadata/NgbMetadataLookupControl.vue +92 -0
  119. package/src/ngb/metadata/NgbRecycleBinFilter.vue +53 -0
  120. package/src/ngb/metadata/NgbRegisterPageLayout.vue +143 -0
  121. package/src/ngb/metadata/config.ts +28 -0
  122. package/src/ngb/metadata/dataTypes.ts +27 -0
  123. package/src/ngb/metadata/entityForm.ts +114 -0
  124. package/src/ngb/metadata/entityModel.ts +34 -0
  125. package/src/ngb/metadata/entityValueFormatting.ts +83 -0
  126. package/src/ngb/metadata/fieldRendererState.ts +84 -0
  127. package/src/ngb/metadata/filtering.ts +153 -0
  128. package/src/ngb/metadata/lookup.ts +25 -0
  129. package/src/ngb/metadata/monthPagedListQuery.ts +108 -0
  130. package/src/ngb/metadata/normalization.ts +100 -0
  131. package/src/ngb/metadata/referenceHydration.ts +99 -0
  132. package/src/ngb/metadata/register.ts +135 -0
  133. package/src/ngb/metadata/routePages.ts +86 -0
  134. package/src/ngb/metadata/store.ts +35 -0
  135. package/src/ngb/metadata/types.ts +276 -0
  136. package/src/ngb/metadata/useMetadataListFilters.ts +306 -0
  137. package/src/ngb/metadata/useMetadataRegisterPageData.ts +246 -0
  138. package/src/ngb/metadata/useValidationFocus.ts +49 -0
  139. package/src/ngb/primitives/NgbBadge.vue +31 -0
  140. package/src/ngb/primitives/NgbButton.vue +53 -0
  141. package/src/ngb/primitives/NgbCheckbox.vue +33 -0
  142. package/src/ngb/primitives/NgbDatePicker.vue +141 -0
  143. package/src/ngb/primitives/NgbIcon.vue +545 -0
  144. package/src/ngb/primitives/NgbInput.vue +57 -0
  145. package/src/ngb/primitives/NgbLookup.vue +264 -0
  146. package/src/ngb/primitives/NgbMonthPicker.vue +132 -0
  147. package/src/ngb/primitives/NgbMultiSelect.vue +179 -0
  148. package/src/ngb/primitives/NgbPeriodPicker.vue +125 -0
  149. package/src/ngb/primitives/NgbPickerNavButton.vue +31 -0
  150. package/src/ngb/primitives/NgbPickerPopover.vue +79 -0
  151. package/src/ngb/primitives/NgbRadioGroup.vue +53 -0
  152. package/src/ngb/primitives/NgbSelect.vue +108 -0
  153. package/src/ngb/primitives/NgbStatusIcon.vue +63 -0
  154. package/src/ngb/primitives/NgbSwitch.vue +42 -0
  155. package/src/ngb/primitives/NgbTabs.vue +116 -0
  156. package/src/ngb/primitives/NgbToastHost.vue +49 -0
  157. package/src/ngb/primitives/NgbTooltip.vue +27 -0
  158. package/src/ngb/primitives/iconNames.ts +83 -0
  159. package/src/ngb/primitives/toast.ts +66 -0
  160. package/src/ngb/primitives/useAsyncComboboxQuery.ts +85 -0
  161. package/src/ngb/primitives/useFloatingDropdownPosition.ts +129 -0
  162. package/src/ngb/reporting/NgbReportComposerCollectionSection.vue +106 -0
  163. package/src/ngb/reporting/NgbReportComposerPanel.vue +1187 -0
  164. package/src/ngb/reporting/NgbReportDateRangeFilter.vue +54 -0
  165. package/src/ngb/reporting/NgbReportPage.vue +1124 -0
  166. package/src/ngb/reporting/NgbReportSheet.vue +467 -0
  167. package/src/ngb/reporting/api.ts +35 -0
  168. package/src/ngb/reporting/composer.ts +904 -0
  169. package/src/ngb/reporting/config.ts +120 -0
  170. package/src/ngb/reporting/defaultConfig.ts +15 -0
  171. package/src/ngb/reporting/lookupFilters.ts +43 -0
  172. package/src/ngb/reporting/navigation.ts +181 -0
  173. package/src/ngb/reporting/pageHelpers.ts +73 -0
  174. package/src/ngb/reporting/pageSession.ts +92 -0
  175. package/src/ngb/reporting/paging.ts +66 -0
  176. package/src/ngb/reporting/types.ts +333 -0
  177. package/src/ngb/router/backNavigation.ts +122 -0
  178. package/src/ngb/router/queryParams.ts +129 -0
  179. package/src/ngb/router/queryState.ts +121 -0
  180. package/src/ngb/router/routeAliases.ts +66 -0
  181. package/src/ngb/router/routeParams.ts +15 -0
  182. package/src/ngb/router/shareLink.ts +39 -0
  183. package/src/ngb/security/NgbAccessAuditPanel.vue +32 -0
  184. package/src/ngb/security/NgbAccessDeniedState.vue +27 -0
  185. package/src/ngb/security/NgbEffectiveAccessPanel.vue +60 -0
  186. package/src/ngb/security/NgbPermissionMatrix.vue +160 -0
  187. package/src/ngb/security/NgbRoleEditorPage.vue +319 -0
  188. package/src/ngb/security/NgbRolesPage.vue +125 -0
  189. package/src/ngb/security/NgbUserEditorPage.vue +615 -0
  190. package/src/ngb/security/NgbUsersPage.vue +125 -0
  191. package/src/ngb/security/api.ts +91 -0
  192. package/src/ngb/security/audit.ts +21 -0
  193. package/src/ngb/security/permissions.ts +65 -0
  194. package/src/ngb/security/types.ts +157 -0
  195. package/src/ngb/security/useAccessStore.ts +69 -0
  196. package/src/ngb/site/NgbDashboardAsOfToolbar.vue +100 -0
  197. package/src/ngb/site/NgbDashboardStatusBanner.vue +91 -0
  198. package/src/ngb/site/NgbLogo.vue +44 -0
  199. package/src/ngb/site/NgbPageHeader.vue +49 -0
  200. package/src/ngb/site/NgbSiteShell.vue +286 -0
  201. package/src/ngb/site/NgbSiteSidebar.vue +270 -0
  202. package/src/ngb/site/NgbTopBar.vue +425 -0
  203. package/src/ngb/site/NgbTrendChart.vue +224 -0
  204. package/src/ngb/site/dashboardData.ts +324 -0
  205. package/src/ngb/site/mainMenuStore.ts +49 -0
  206. package/src/ngb/site/types.ts +31 -0
  207. package/src/ngb/site/useDashboardPageState.ts +98 -0
  208. package/src/ngb/site/useTheme.ts +95 -0
  209. package/src/ngb/utils/clone.ts +43 -0
  210. package/src/ngb/utils/dateValues.ts +107 -0
  211. package/src/ngb/utils/errorMessage.ts +14 -0
  212. package/src/ngb/utils/guid.ts +21 -0
  213. package/src/ngb/utils/stableValue.ts +21 -0
  214. package/src/ngb/utils/storage.ts +135 -0
  215. package/src/styles/tailwind.css +165 -0
  216. package/src/vite/publicAssets.ts +104 -0
  217. package/vite-public-assets.js +68 -0
@@ -0,0 +1,125 @@
1
+ <script setup lang="ts">
2
+ import { computed, onMounted, ref } from 'vue'
3
+ import { useRouter } from 'vue-router'
4
+ import { ApiError } from '../api/http'
5
+ import type { RegisterColumn, RegisterDataRow } from '../components/register/registerTypes'
6
+ import NgbRecycleBinFilter from '../metadata/NgbRecycleBinFilter.vue'
7
+ import NgbRegisterPageLayout from '../metadata/NgbRegisterPageLayout.vue'
8
+ import type { QueryTrashMode } from '../router/queryParams'
9
+ import { toErrorMessage } from '../utils/errorMessage'
10
+ import NgbAccessDeniedState from './NgbAccessDeniedState.vue'
11
+ import { getUsers } from './api'
12
+ import { useAccessStore } from './useAccessStore'
13
+ import type { UserListItemDto } from './types'
14
+
15
+ const router = useRouter()
16
+ const access = useAccessStore()
17
+
18
+ const loading = ref(false)
19
+ const error = ref<string | null>(null)
20
+ const accessDenied = ref(false)
21
+ const users = ref<UserListItemDto[]>([])
22
+ const status = ref<QueryTrashMode>('active')
23
+
24
+ const columns: RegisterColumn[] = [
25
+ {
26
+ key: 'user',
27
+ title: 'User',
28
+ width: 320,
29
+ minWidth: 240,
30
+ pinned: 'left',
31
+ format: (value) => Array.isArray(value) ? value.join('\n') : String(value ?? ''),
32
+ },
33
+ { key: 'roles', title: 'Roles', width: 280, minWidth: 200, wrap: true },
34
+ { key: 'keycloakLabel', title: 'Keycloak', width: 120, minWidth: 100, align: 'center', sortable: false },
35
+ ]
36
+
37
+ const filteredUsers = computed(() => {
38
+ return users.value
39
+ .filter((user) => {
40
+ if (status.value === 'active') return user.isActive
41
+ if (status.value === 'deleted') return !user.isActive
42
+ return true
43
+ })
44
+ .sort((a, b) => (a.displayName ?? a.email ?? '').localeCompare(b.displayName ?? b.email ?? ''))
45
+ })
46
+
47
+ const rows = computed<RegisterDataRow[]>(() => filteredUsers.value.map((user) => ({
48
+ key: user.userId,
49
+ __status: user.isActive ? 'posted' : 'marked',
50
+ __statusTitle: user.isActive ? 'Active' : 'Inactive',
51
+ user: [userTitle(user), user.email || user.authSubject],
52
+ roles: roleSummary(user),
53
+ keycloakLabel: user.keycloakEnabled === true ? 'Yes' : 'No',
54
+ })))
55
+
56
+ function userTitle(user: UserListItemDto): string {
57
+ return user.displayName?.trim() || user.email?.trim() || user.authSubject
58
+ }
59
+
60
+ function roleSummary(user: UserListItemDto): string {
61
+ if (!user.roles.length) return 'No roles'
62
+ return user.roles.map((role) => role.name).join(', ')
63
+ }
64
+
65
+ async function load(): Promise<void> {
66
+ loading.value = true
67
+ error.value = null
68
+ accessDenied.value = false
69
+
70
+ try {
71
+ await access.load()
72
+ users.value = await getUsers()
73
+ } catch (cause) {
74
+ users.value = []
75
+ accessDenied.value = cause instanceof ApiError && cause.status === 403
76
+ error.value = accessDenied.value ? null : toErrorMessage(cause, 'Failed to load users')
77
+ } finally {
78
+ loading.value = false
79
+ }
80
+ }
81
+
82
+ function openUser(userId: string): void {
83
+ void router.push(`/admin/security/users/${encodeURIComponent(userId)}`)
84
+ }
85
+
86
+ function createUser(): void {
87
+ void router.push('/admin/security/users/new')
88
+ }
89
+
90
+ function goBack(): void {
91
+ void router.push('/home')
92
+ }
93
+
94
+ onMounted(() => {
95
+ void load()
96
+ })
97
+ </script>
98
+
99
+ <template>
100
+ <NgbAccessDeniedState v-if="accessDenied" />
101
+
102
+ <NgbRegisterPageLayout
103
+ v-else
104
+ title="Users"
105
+ :items-count="filteredUsers.length"
106
+ :total="users.length"
107
+ :loading="loading"
108
+ :error="error"
109
+ :show-filter="false"
110
+ :disable-create="!access.canManageUsers"
111
+ :disable-prev="true"
112
+ :disable-next="true"
113
+ :columns="columns"
114
+ :rows="rows"
115
+ storage-key="ngb:security:users"
116
+ @back="goBack"
117
+ @refresh="load"
118
+ @create="createUser"
119
+ @rowActivate="openUser"
120
+ >
121
+ <template #filters>
122
+ <NgbRecycleBinFilter v-model="status" />
123
+ </template>
124
+ </NgbRegisterPageLayout>
125
+ </template>
@@ -0,0 +1,91 @@
1
+ import { httpGet, httpPost, httpPut } from '../api/http'
2
+ import type {
3
+ CreateRoleRequestDto,
4
+ CreateUserRequestDto,
5
+ CurrentAccessDto,
6
+ EffectiveAccessDto,
7
+ PermissionDefinitionDto,
8
+ ReplaceRolePermissionsRequestDto,
9
+ ReplaceUserRolesRequestDto,
10
+ RoleDetailsDto,
11
+ RoleListItemDto,
12
+ UpdateRoleRequestDto,
13
+ UpdateUserRequestDto,
14
+ UserDetailsDto,
15
+ UserListItemDto,
16
+ } from './types'
17
+
18
+ export async function getCurrentAccess(): Promise<CurrentAccessDto> {
19
+ return await httpGet<CurrentAccessDto>('/api/security/me/access')
20
+ }
21
+
22
+ export async function getPermissionDefinitions(): Promise<PermissionDefinitionDto[]> {
23
+ return await httpGet<PermissionDefinitionDto[]>('/api/security/permissions/definitions')
24
+ }
25
+
26
+ export async function getUsers(): Promise<UserListItemDto[]> {
27
+ return await httpGet<UserListItemDto[]>('/api/security/users')
28
+ }
29
+
30
+ export async function createUser(request: CreateUserRequestDto): Promise<UserDetailsDto> {
31
+ return await httpPost<UserDetailsDto, CreateUserRequestDto>('/api/security/users', request)
32
+ }
33
+
34
+ export async function getUser(userId: string): Promise<UserDetailsDto> {
35
+ return await httpGet<UserDetailsDto>(`/api/security/users/${encodeURIComponent(userId)}`)
36
+ }
37
+
38
+ export async function updateUser(userId: string, request: UpdateUserRequestDto): Promise<UserDetailsDto> {
39
+ return await httpPut<UserDetailsDto, UpdateUserRequestDto>(`/api/security/users/${encodeURIComponent(userId)}`, request)
40
+ }
41
+
42
+ export async function deactivateUser(userId: string): Promise<void> {
43
+ await httpPost<void>(`/api/security/users/${encodeURIComponent(userId)}/deactivate`)
44
+ }
45
+
46
+ export async function reactivateUser(userId: string): Promise<void> {
47
+ await httpPost<void>(`/api/security/users/${encodeURIComponent(userId)}/reactivate`)
48
+ }
49
+
50
+ export async function replaceUserRoles(userId: string, roleIds: string[]): Promise<void> {
51
+ await httpPut<void, ReplaceUserRolesRequestDto>(
52
+ `/api/security/users/${encodeURIComponent(userId)}/roles`,
53
+ { roleIds },
54
+ )
55
+ }
56
+
57
+ export async function getUserEffectiveAccess(userId: string): Promise<EffectiveAccessDto> {
58
+ return await httpGet<EffectiveAccessDto>(`/api/security/users/${encodeURIComponent(userId)}/effective-access`)
59
+ }
60
+
61
+ export async function getRoles(): Promise<RoleListItemDto[]> {
62
+ return await httpGet<RoleListItemDto[]>('/api/security/roles')
63
+ }
64
+
65
+ export async function createRole(request: CreateRoleRequestDto): Promise<RoleDetailsDto> {
66
+ return await httpPost<RoleDetailsDto, CreateRoleRequestDto>('/api/security/roles', request)
67
+ }
68
+
69
+ export async function getRole(roleId: string): Promise<RoleDetailsDto> {
70
+ return await httpGet<RoleDetailsDto>(`/api/security/roles/${encodeURIComponent(roleId)}`)
71
+ }
72
+
73
+ export async function updateRole(roleId: string, request: UpdateRoleRequestDto): Promise<RoleDetailsDto> {
74
+ return await httpPut<RoleDetailsDto, UpdateRoleRequestDto>(`/api/security/roles/${encodeURIComponent(roleId)}`, request)
75
+ }
76
+
77
+ export async function deactivateRole(roleId: string): Promise<void> {
78
+ await httpPost<void>(`/api/security/roles/${encodeURIComponent(roleId)}/deactivate`)
79
+ }
80
+
81
+ export async function reactivateRole(roleId: string): Promise<void> {
82
+ await httpPost<void>(`/api/security/roles/${encodeURIComponent(roleId)}/reactivate`)
83
+ }
84
+
85
+ export async function replaceRolePermissions(roleId: string, permissions: ReplaceRolePermissionsRequestDto['permissions']): Promise<void> {
86
+ await httpPut<void, ReplaceRolePermissionsRequestDto>(
87
+ `/api/security/roles/${encodeURIComponent(roleId)}/permissions`,
88
+ { permissions },
89
+ )
90
+ }
91
+
@@ -0,0 +1,21 @@
1
+ import type { EditorAuditBehavior } from '../editor/types'
2
+
3
+ export const USER_AUDIT_BEHAVIOR: EditorAuditBehavior = {
4
+ actionTitles: {
5
+ 'security.user.create': 'User created',
6
+ 'security.user.update': 'User updated',
7
+ 'security.user.deactivate': 'User deactivated',
8
+ 'security.user.reactivate': 'User reactivated',
9
+ 'security.user.roles.replace': 'User roles changed',
10
+ },
11
+ }
12
+
13
+ export const ROLE_AUDIT_BEHAVIOR: EditorAuditBehavior = {
14
+ actionTitles: {
15
+ 'security.role.create': 'Role created',
16
+ 'security.role.update': 'Role updated',
17
+ 'security.role.deactivate': 'Role deactivated',
18
+ 'security.role.reactivate': 'Role reactivated',
19
+ 'security.role.permissions.replace': 'Role permissions changed',
20
+ },
21
+ }
@@ -0,0 +1,65 @@
1
+ import type { PermissionAssignmentDto, PermissionDefinitionDto, PermissionGroupDto } from './types'
2
+
3
+ export type PermissionKeyLike = PermissionAssignmentDto | string
4
+
5
+ function normalizeSegment(value: string | null | undefined): string {
6
+ return String(value ?? '').trim().toLowerCase()
7
+ }
8
+
9
+ export function buildPermissionKey(permission: PermissionKeyLike): string {
10
+ if (typeof permission === 'string') return permission.trim().toLowerCase()
11
+ return [
12
+ normalizeSegment(permission.resourceKind),
13
+ normalizeSegment(permission.resourceCode),
14
+ normalizeSegment(permission.actionCode),
15
+ ].join('.')
16
+ }
17
+
18
+ export function buildPermissionAssignment(resourceKind: string, resourceCode: string, actionCode: string): PermissionAssignmentDto {
19
+ return {
20
+ resourceKind: normalizeSegment(resourceKind),
21
+ resourceCode: normalizeSegment(resourceCode),
22
+ actionCode: normalizeSegment(actionCode),
23
+ }
24
+ }
25
+
26
+ export const SYSTEM_PERMISSIONS = {
27
+ usersView: buildPermissionAssignment('system', 'users', 'view'),
28
+ usersManage: buildPermissionAssignment('system', 'users', 'manage'),
29
+ rolesView: buildPermissionAssignment('system', 'roles', 'view'),
30
+ rolesManage: buildPermissionAssignment('system', 'roles', 'manage'),
31
+ permissionsView: buildPermissionAssignment('system', 'permissions', 'view'),
32
+ auditView: buildPermissionAssignment('system', 'audit', 'view'),
33
+ } as const
34
+
35
+ export function hasPermission(permissions: readonly PermissionAssignmentDto[] | null | undefined, permission: PermissionKeyLike): boolean {
36
+ const key = buildPermissionKey(permission)
37
+ if (!key || !permissions?.length) return false
38
+ return permissions.some((entry) => buildPermissionKey(entry) === key)
39
+ }
40
+
41
+ export function toPermissionKeySet(permissions: readonly PermissionAssignmentDto[] | null | undefined): Set<string> {
42
+ return new Set((permissions ?? []).map((entry) => buildPermissionKey(entry)).filter(Boolean))
43
+ }
44
+
45
+ export function groupPermissionDefinitions(definitions: readonly PermissionDefinitionDto[]): PermissionGroupDto[] {
46
+ const groups = new Map<string, PermissionDefinitionDto[]>()
47
+
48
+ for (const definition of definitions) {
49
+ const group = definition.group?.trim() || 'Other'
50
+ const items = groups.get(group) ?? []
51
+ items.push(definition)
52
+ groups.set(group, items)
53
+ }
54
+
55
+ return Array.from(groups.entries())
56
+ .map(([group, permissions]) => ({
57
+ group,
58
+ permissions: permissions
59
+ .slice()
60
+ .sort((a, b) => a.resourceKind.localeCompare(b.resourceKind)
61
+ || a.resourceCode.localeCompare(b.resourceCode)
62
+ || a.actionCode.localeCompare(b.actionCode)),
63
+ }))
64
+ .sort((a, b) => a.group.localeCompare(b.group))
65
+ }
@@ -0,0 +1,157 @@
1
+ export type PermissionAssignmentDto = {
2
+ resourceKind: string
3
+ resourceCode: string
4
+ actionCode: string
5
+ }
6
+
7
+ export type PermissionDefinitionDto = PermissionAssignmentDto & {
8
+ displayName: string
9
+ group: string
10
+ description?: string | null
11
+ }
12
+
13
+ export type PermissionGroupDto = {
14
+ group: string
15
+ permissions: PermissionDefinitionDto[]
16
+ }
17
+
18
+ export type RoleBadgeDto = {
19
+ roleId: string
20
+ code: string
21
+ name: string
22
+ isSystem: boolean
23
+ isActive: boolean
24
+ }
25
+
26
+ export type UserBadgeDto = {
27
+ userId: string
28
+ email?: string | null
29
+ displayName?: string | null
30
+ isActive: boolean
31
+ }
32
+
33
+ export type UserListItemDto = {
34
+ userId: string
35
+ authSubject: string
36
+ email?: string | null
37
+ displayName?: string | null
38
+ isActive: boolean
39
+ keycloakEnabled?: boolean | null
40
+ roles: RoleBadgeDto[]
41
+ createdAtUtc: string
42
+ updatedAtUtc: string
43
+ }
44
+
45
+ export type UserDetailsDto = {
46
+ userId: string
47
+ authSubject: string
48
+ email?: string | null
49
+ firstName?: string | null
50
+ lastName?: string | null
51
+ displayName?: string | null
52
+ isActive: boolean
53
+ keycloakEnabled?: boolean | null
54
+ roles: RoleBadgeDto[]
55
+ accessVersion: number
56
+ createdAtUtc: string
57
+ updatedAtUtc: string
58
+ }
59
+
60
+ export type CreateUserRequestDto = {
61
+ email: string
62
+ firstName?: string | null
63
+ lastName?: string | null
64
+ displayName?: string | null
65
+ enabled: boolean
66
+ temporaryPassword?: string | null
67
+ requirePasswordUpdate: boolean
68
+ roleIds: string[]
69
+ }
70
+
71
+ export type UpdateUserRequestDto = {
72
+ email?: string | null
73
+ firstName?: string | null
74
+ lastName?: string | null
75
+ displayName?: string | null
76
+ enabled: boolean
77
+ temporaryPassword?: string | null
78
+ requirePasswordUpdate: boolean
79
+ roleIds: string[]
80
+ }
81
+
82
+ export type ReplaceUserRolesRequestDto = {
83
+ roleIds: string[]
84
+ }
85
+
86
+ export type RoleListItemDto = {
87
+ roleId: string
88
+ code: string
89
+ name: string
90
+ description?: string | null
91
+ isSystem: boolean
92
+ isActive: boolean
93
+ assignedUsersCount: number
94
+ createdAtUtc: string
95
+ updatedAtUtc: string
96
+ }
97
+
98
+ export type RoleDetailsDto = {
99
+ roleId: string
100
+ code: string
101
+ name: string
102
+ description?: string | null
103
+ isSystem: boolean
104
+ isActive: boolean
105
+ permissions: PermissionAssignmentDto[]
106
+ assignedUsers: UserBadgeDto[]
107
+ createdAtUtc: string
108
+ updatedAtUtc: string
109
+ }
110
+
111
+ export type CreateRoleRequestDto = {
112
+ code: string
113
+ name: string
114
+ description?: string | null
115
+ permissions: PermissionAssignmentDto[]
116
+ }
117
+
118
+ export type UpdateRoleRequestDto = {
119
+ code: string
120
+ name: string
121
+ description?: string | null
122
+ isActive: boolean
123
+ permissions: PermissionAssignmentDto[]
124
+ }
125
+
126
+ export type ReplaceRolePermissionsRequestDto = {
127
+ permissions: PermissionAssignmentDto[]
128
+ }
129
+
130
+ export type CurrentAccessDto = {
131
+ userId?: string | null
132
+ authSubject?: string | null
133
+ isAuthenticated: boolean
134
+ isActive: boolean
135
+ isBootstrapAdmin: boolean
136
+ accessVersion: number
137
+ roles?: RoleBadgeDto[]
138
+ permissions: PermissionAssignmentDto[]
139
+ }
140
+
141
+ export type EffectiveAccessResourceDto = {
142
+ resourceKind: string
143
+ resourceCode: string
144
+ displayName: string
145
+ actions: string[]
146
+ }
147
+
148
+ export type EffectiveAccessGroupDto = {
149
+ group: string
150
+ resources: EffectiveAccessResourceDto[]
151
+ }
152
+
153
+ export type EffectiveAccessDto = {
154
+ userId: string
155
+ accessVersion: number
156
+ groups: EffectiveAccessGroupDto[]
157
+ }
@@ -0,0 +1,69 @@
1
+ import { defineStore } from 'pinia'
2
+ import { toErrorMessage } from '../utils/errorMessage'
3
+ import { getCurrentAccess } from './api'
4
+ import { buildPermissionKey, hasPermission, SYSTEM_PERMISSIONS, toPermissionKeySet, type PermissionKeyLike } from './permissions'
5
+ import type { CurrentAccessDto } from './types'
6
+
7
+ export const useAccessStore = defineStore('access', {
8
+ state: () => ({
9
+ current: null as CurrentAccessDto | null,
10
+ permissionKeys: new Set<string>(),
11
+ isLoading: false,
12
+ error: null as string | null,
13
+ loadedAt: 0,
14
+ }),
15
+ getters: {
16
+ isActive: (state) => state.current?.isActive === true,
17
+ canViewUsers: (state) => state.current?.isBootstrapAdmin === true || hasPermission(state.current?.permissions, SYSTEM_PERMISSIONS.usersView),
18
+ canManageUsers: (state) => state.current?.isBootstrapAdmin === true || hasPermission(state.current?.permissions, SYSTEM_PERMISSIONS.usersManage),
19
+ canViewRoles: (state) => state.current?.isBootstrapAdmin === true || hasPermission(state.current?.permissions, SYSTEM_PERMISSIONS.rolesView),
20
+ canManageRoles: (state) => state.current?.isBootstrapAdmin === true || hasPermission(state.current?.permissions, SYSTEM_PERMISSIONS.rolesManage),
21
+ canViewPermissions: (state) => state.current?.isBootstrapAdmin === true || hasPermission(state.current?.permissions, SYSTEM_PERMISSIONS.permissionsView),
22
+ applicationRoleNames: (state) => {
23
+ const seen = new Set<string>()
24
+ return (state.current?.roles ?? [])
25
+ .filter((role) => role.isActive)
26
+ .map((role) => (role.name || role.code).trim())
27
+ .filter((name) => {
28
+ const key = name.toLowerCase()
29
+ if (!name || seen.has(key)) return false
30
+ seen.add(key)
31
+ return true
32
+ })
33
+ },
34
+ },
35
+ actions: {
36
+ async load(force = false): Promise<CurrentAccessDto | null> {
37
+ if (this.isLoading) return this.current
38
+ if (!force && this.current) return this.current
39
+
40
+ this.isLoading = true
41
+ this.error = null
42
+
43
+ try {
44
+ const access = await getCurrentAccess()
45
+ this.current = access
46
+ this.permissionKeys = toPermissionKeySet(access.permissions)
47
+ this.loadedAt = Date.now()
48
+ return access
49
+ } catch (cause) {
50
+ this.current = null
51
+ this.permissionKeys = new Set()
52
+ this.error = toErrorMessage(cause, 'Failed to load access profile')
53
+ return null
54
+ } finally {
55
+ this.isLoading = false
56
+ }
57
+ },
58
+ hasPermission(permission: PermissionKeyLike): boolean {
59
+ return this.permissionKeys.has(buildPermissionKey(permission))
60
+ },
61
+ reset(): void {
62
+ this.current = null
63
+ this.permissionKeys = new Set()
64
+ this.error = null
65
+ this.loadedAt = 0
66
+ this.isLoading = false
67
+ },
68
+ },
69
+ })
@@ -0,0 +1,100 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+
4
+ import NgbDatePicker from '../primitives/NgbDatePicker.vue'
5
+ import NgbIcon from '../primitives/NgbIcon.vue'
6
+
7
+ const props = withDefaults(defineProps<{
8
+ modelValue: string | null | undefined
9
+ loading?: boolean
10
+ disabled?: boolean
11
+ }>(), {
12
+ loading: false,
13
+ disabled: false,
14
+ })
15
+
16
+ const emit = defineEmits<{
17
+ (e: 'refresh'): void
18
+ (e: 'update:modelValue', value: string | null): void
19
+ }>()
20
+
21
+ const isDisabled = computed(() => props.loading || props.disabled)
22
+
23
+ function onRefresh(): void {
24
+ if (isDisabled.value) return
25
+ emit('refresh')
26
+ }
27
+ </script>
28
+
29
+ <template>
30
+ <div class="ngb-dashboard-toolbar-control">
31
+ <div class="ngb-dashboard-toolbar-picker">
32
+ <NgbDatePicker
33
+ :model-value="modelValue"
34
+ grouped
35
+ :disabled="isDisabled"
36
+ @update:model-value="emit('update:modelValue', $event)"
37
+ />
38
+ </div>
39
+
40
+ <div class="ngb-dashboard-toolbar-divider" aria-hidden="true" />
41
+
42
+ <button
43
+ type="button"
44
+ class="ngb-dashboard-toolbar-button ngb-focus"
45
+ :disabled="isDisabled"
46
+ title="Refresh"
47
+ aria-label="Refresh"
48
+ @click="onRefresh"
49
+ >
50
+ <NgbIcon name="refresh" :size="15" />
51
+ </button>
52
+ </div>
53
+ </template>
54
+
55
+ <style scoped>
56
+ .ngb-dashboard-toolbar-control {
57
+ display: inline-flex;
58
+ align-items: center;
59
+ gap: 0.375rem;
60
+ min-height: 28px;
61
+ border: 1px solid var(--ngb-border);
62
+ border-radius: var(--ngb-radius);
63
+ background: var(--ngb-card);
64
+ padding: 0 0.25rem;
65
+ box-shadow: var(--ngb-shadow-1);
66
+ }
67
+
68
+ .ngb-dashboard-toolbar-picker {
69
+ width: 10rem;
70
+ }
71
+
72
+ .ngb-dashboard-toolbar-divider {
73
+ height: 1rem;
74
+ width: 1px;
75
+ background: var(--ngb-border);
76
+ }
77
+
78
+ .ngb-dashboard-toolbar-button {
79
+ display: inline-flex;
80
+ align-items: center;
81
+ justify-content: center;
82
+ height: 26px;
83
+ width: 26px;
84
+ border: none;
85
+ border-radius: var(--ngb-radius);
86
+ background: transparent;
87
+ color: var(--ngb-muted);
88
+ transition: background-color 140ms ease, color 140ms ease;
89
+ }
90
+
91
+ .ngb-dashboard-toolbar-button:hover:not(:disabled) {
92
+ background: var(--ngb-bg);
93
+ color: var(--ngb-text);
94
+ }
95
+
96
+ .ngb-dashboard-toolbar-button:disabled {
97
+ cursor: not-allowed;
98
+ opacity: 0.45;
99
+ }
100
+ </style>