@quazardous/qdadm 1.19.3
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.
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/package.json +101 -0
- package/src/assets/logo.svg +6 -0
- package/src/auth/SessionAuthAdapter.ts +459 -0
- package/src/auth/index.ts +15 -0
- package/src/chain/ActiveStack.ts +154 -0
- package/src/chain/StackHydrator.ts +292 -0
- package/src/chain/index.ts +25 -0
- package/src/chain/useActiveStack.ts +120 -0
- package/src/chain/useStackHydrator.ts +155 -0
- package/src/components/BoolCell.vue +34 -0
- package/src/components/InfoBanner.vue +73 -0
- package/src/components/QdadmRoot.vue +52 -0
- package/src/components/SeverityTag.vue +86 -0
- package/src/components/dialogs/BulkStatusDialog.vue +60 -0
- package/src/components/dialogs/MultiStepDialog.vue +331 -0
- package/src/components/dialogs/SimpleDialog.vue +122 -0
- package/src/components/dialogs/UnsavedChangesDialog.vue +80 -0
- package/src/components/display/BannerZone.vue +65 -0
- package/src/components/display/CardsGrid.vue +72 -0
- package/src/components/display/CopyableId.vue +84 -0
- package/src/components/display/EmptyState.vue +62 -0
- package/src/components/display/IntensityBar.vue +190 -0
- package/src/components/display/RichCardsGrid.vue +101 -0
- package/src/components/edit/FormActions.vue +78 -0
- package/src/components/edit/FormField.vue +82 -0
- package/src/components/edit/FormInput.vue +158 -0
- package/src/components/edit/FormPage.vue +260 -0
- package/src/components/edit/FormTab.vue +217 -0
- package/src/components/edit/FormTabs.vue +120 -0
- package/src/components/edit/LookupField.vue +406 -0
- package/src/components/edit/LookupPickerDialog.vue +230 -0
- package/src/components/editors/JsonEditorFoldable.vue +470 -0
- package/src/components/editors/JsonStructuredField.vue +264 -0
- package/src/components/editors/JsonViewer.vue +51 -0
- package/src/components/editors/KeyValueEditor.vue +314 -0
- package/src/components/editors/LanguageEditor.vue +259 -0
- package/src/components/editors/PermissionEditor.vue +558 -0
- package/src/components/editors/ScopeEditor.vue +372 -0
- package/src/components/editors/VanillaJsonEditor.vue +183 -0
- package/src/components/index.ts +83 -0
- package/src/components/item/FieldGroups.vue +392 -0
- package/src/components/layout/AppLayout.vue +1032 -0
- package/src/components/layout/BaseLayout.vue +190 -0
- package/src/components/layout/Breadcrumb.vue +55 -0
- package/src/components/layout/DashboardLayout.vue +104 -0
- package/src/components/layout/FormLayout.vue +256 -0
- package/src/components/layout/ListLayout.vue +278 -0
- package/src/components/layout/PageHeader.vue +156 -0
- package/src/components/layout/PageLayout.vue +126 -0
- package/src/components/layout/PageNav.vue +192 -0
- package/src/components/layout/SidebarBox.vue +87 -0
- package/src/components/layout/Zone.vue +182 -0
- package/src/components/layout/defaults/DefaultBreadcrumb.vue +149 -0
- package/src/components/layout/defaults/DefaultFooter.vue +89 -0
- package/src/components/layout/defaults/DefaultFormActions.vue +67 -0
- package/src/components/layout/defaults/DefaultHeader.vue +78 -0
- package/src/components/layout/defaults/DefaultMenu.vue +188 -0
- package/src/components/layout/defaults/DefaultPagination.vue +75 -0
- package/src/components/layout/defaults/DefaultTable.vue +137 -0
- package/src/components/layout/defaults/DefaultUserInfo.vue +111 -0
- package/src/components/layout/defaults/index.ts +16 -0
- package/src/components/lists/ActionButtons.vue +49 -0
- package/src/components/lists/ActionColumn.vue +40 -0
- package/src/components/lists/FilterBar.vue +54 -0
- package/src/components/lists/ListPage.vue +467 -0
- package/src/components/pages/LoginPage.vue +334 -0
- package/src/components/pages/NotFoundPage.vue +98 -0
- package/src/components/show/ShowDisplay.vue +343 -0
- package/src/components/show/ShowField.vue +67 -0
- package/src/components/show/ShowPage.vue +431 -0
- package/src/composables/index.ts +155 -0
- package/src/composables/useApp.ts +67 -0
- package/src/composables/useAuth.ts +98 -0
- package/src/composables/useBareForm.ts +319 -0
- package/src/composables/useBreadcrumb.ts +252 -0
- package/src/composables/useChildPage.ts +113 -0
- package/src/composables/useCurrentEntity.ts +60 -0
- package/src/composables/useDeferred.ts +94 -0
- package/src/composables/useDirtyState.ts +138 -0
- package/src/composables/useEntityItemFormPage.ts +942 -0
- package/src/composables/useEntityItemFormPage.types.ts +451 -0
- package/src/composables/useEntityItemPage.ts +438 -0
- package/src/composables/useEntityItemShowPage.ts +722 -0
- package/src/composables/useFieldManager.ts +692 -0
- package/src/composables/useForm.ts +544 -0
- package/src/composables/useGuardStore.ts +53 -0
- package/src/composables/useHooks.ts +56 -0
- package/src/composables/useInfoBanner.ts +217 -0
- package/src/composables/useJsonSyntax.ts +122 -0
- package/src/composables/useLayoutResolver.ts +329 -0
- package/src/composables/useListPage.ts +1535 -0
- package/src/composables/useListPage.types.ts +437 -0
- package/src/composables/useListPage.utils.ts +101 -0
- package/src/composables/useNavContext.ts +439 -0
- package/src/composables/useNavigation.ts +216 -0
- package/src/composables/useOptionsLookup.ts +323 -0
- package/src/composables/usePageTitle.ts +84 -0
- package/src/composables/useSSEBridge.ts +142 -0
- package/src/composables/useSemanticBreadcrumb.ts +222 -0
- package/src/composables/useSidebarState.ts +24 -0
- package/src/composables/useSignals.ts +52 -0
- package/src/composables/useUnsavedChangesGuard.ts +155 -0
- package/src/composables/useUserImpersonator.ts +317 -0
- package/src/composables/useZoneRegistry.ts +252 -0
- package/src/core/bundles.ts +558 -0
- package/src/core/decorator.ts +420 -0
- package/src/core/extension.ts +470 -0
- package/src/core/index.ts +59 -0
- package/src/deferred/DeferredRegistry.ts +353 -0
- package/src/deferred/index.ts +15 -0
- package/src/editors/index.ts +12 -0
- package/src/entity/EntityManager.cache.ts +530 -0
- package/src/entity/EntityManager.crud.ts +615 -0
- package/src/entity/EntityManager.interface.ts +78 -0
- package/src/entity/EntityManager.query.ts +221 -0
- package/src/entity/EntityManager.relations.ts +151 -0
- package/src/entity/EntityManager.ts +944 -0
- package/src/entity/EntityManager.types.ts +288 -0
- package/src/entity/auth/CompositeAuthAdapter.ts +194 -0
- package/src/entity/auth/EntityAuthAdapter.ts +119 -0
- package/src/entity/auth/PermissiveAdapter.ts +43 -0
- package/src/entity/auth/RoleHierarchy.ts +128 -0
- package/src/entity/auth/SecurityChecker.ts +202 -0
- package/src/entity/auth/factory.ts +203 -0
- package/src/entity/auth/index.ts +47 -0
- package/src/entity/factory.test.js +189 -0
- package/src/entity/factory.ts +146 -0
- package/src/entity/index.ts +49 -0
- package/src/entity/storage/ApiStorage.ts +228 -0
- package/src/entity/storage/IStorage.ts +100 -0
- package/src/entity/storage/LocalStorage.ts +200 -0
- package/src/entity/storage/MemoryStorage.ts +184 -0
- package/src/entity/storage/MockApiStorage.ts +318 -0
- package/src/entity/storage/SdkStorage.ts +377 -0
- package/src/entity/storage/factory.test.js +159 -0
- package/src/entity/storage/factory.ts +209 -0
- package/src/entity/storage/index.ts +75 -0
- package/src/gen/FieldMapper.ts +122 -0
- package/src/gen/StorageProfileFactory.ts +93 -0
- package/src/gen/connectors/BaseConnector.ts +150 -0
- package/src/gen/connectors/ManualConnector.test.js +499 -0
- package/src/gen/connectors/ManualConnector.ts +414 -0
- package/src/gen/connectors/OpenAPIConnector.test.js +836 -0
- package/src/gen/connectors/OpenAPIConnector.ts +693 -0
- package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
- package/src/gen/connectors/index.ts +27 -0
- package/src/gen/createManagers.ts +245 -0
- package/src/gen/decorators.ts +134 -0
- package/src/gen/generateManagers.test.js +540 -0
- package/src/gen/generateManagers.ts +398 -0
- package/src/gen/index.ts +66 -0
- package/src/gen/schema.ts +248 -0
- package/src/gen/vite-plugin.ts +114 -0
- package/src/generated/managers/testManager.js +45 -0
- package/src/generated/managers/testManager.ts +46 -0
- package/src/hooks/HookRegistry.ts +16 -0
- package/src/hooks/index.ts +15 -0
- package/src/i18n/I18n.ts +344 -0
- package/src/i18n/IncrementalDomainProvider.ts +153 -0
- package/src/i18n/InlineTranslationProvider.ts +4 -0
- package/src/i18n/LazyTranslationProvider.ts +102 -0
- package/src/i18n/MessagesRegistry.ts +4 -0
- package/src/i18n/Resolver.ts +4 -0
- package/src/i18n/__tests__/I18n.test.ts +169 -0
- package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
- package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
- package/src/i18n/__tests__/Resolver.test.ts +271 -0
- package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
- package/src/i18n/defaults/core.en.yml +55 -0
- package/src/i18n/defaults/core.fr.yml +55 -0
- package/src/i18n/index.ts +55 -0
- package/src/i18n/loaders/raw-modules.d.ts +15 -0
- package/src/i18n/loaders/yaml.ts +35 -0
- package/src/i18n/strategies.ts +4 -0
- package/src/i18n/types.ts +34 -0
- package/src/i18n/useI18n.ts +34 -0
- package/src/index.ts +453 -0
- package/src/kernel/EventRouter.ts +23 -0
- package/src/kernel/Kernel.api.ts +79 -0
- package/src/kernel/Kernel.auth.ts +120 -0
- package/src/kernel/Kernel.i18n.ts +29 -0
- package/src/kernel/Kernel.modules.ts +170 -0
- package/src/kernel/Kernel.registries.ts +237 -0
- package/src/kernel/Kernel.routing.ts +334 -0
- package/src/kernel/Kernel.ts +460 -0
- package/src/kernel/Kernel.types.ts +263 -0
- package/src/kernel/Kernel.vue.ts +304 -0
- package/src/kernel/KernelContext.entities.ts +80 -0
- package/src/kernel/KernelContext.events.ts +57 -0
- package/src/kernel/KernelContext.i18n.ts +37 -0
- package/src/kernel/KernelContext.permissions.ts +38 -0
- package/src/kernel/KernelContext.routing.ts +280 -0
- package/src/kernel/KernelContext.ts +252 -0
- package/src/kernel/KernelContext.types.ts +173 -0
- package/src/kernel/KernelContext.zones.ts +54 -0
- package/src/kernel/Module.ts +169 -0
- package/src/kernel/ModuleLoader.ts +645 -0
- package/src/kernel/SSEBridge.ts +11 -0
- package/src/kernel/SignalBus.ts +48 -0
- package/src/kernel/index.ts +56 -0
- package/src/module/index.ts +33 -0
- package/src/module/moduleRegistry.ts +492 -0
- package/src/modules/debug/AuthCollector.ts +501 -0
- package/src/modules/debug/Collector.ts +20 -0
- package/src/modules/debug/DebugBridge.ts +14 -0
- package/src/modules/debug/DebugModule.ts +220 -0
- package/src/modules/debug/EntitiesCollector.ts +839 -0
- package/src/modules/debug/ErrorCollector.ts +4 -0
- package/src/modules/debug/I18nCollector.ts +9 -0
- package/src/modules/debug/LocalStorageAdapter.ts +7 -0
- package/src/modules/debug/RouterCollector.ts +449 -0
- package/src/modules/debug/SignalCollector.ts +4 -0
- package/src/modules/debug/ToastCollector.ts +4 -0
- package/src/modules/debug/ZonesCollector.ts +447 -0
- package/src/modules/debug/components/DebugBar.vue +31 -0
- package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
- package/src/modules/debug/components/index.ts +9 -0
- package/src/modules/debug/components/panels/AuthPanel.vue +267 -0
- package/src/modules/debug/components/panels/EntitiesPanel.vue +572 -0
- package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
- package/src/modules/debug/components/panels/RouterPanel.vue +440 -0
- package/src/modules/debug/components/panels/ZonesPanel.vue +133 -0
- package/src/modules/debug/components/panels/index.ts +16 -0
- package/src/modules/debug/index.ts +58 -0
- package/src/modules/debug/styles.scss +2671 -0
- package/src/notifications/NotificationBadge.vue +35 -0
- package/src/notifications/NotificationListener.vue +71 -0
- package/src/notifications/NotificationModule.ts +76 -0
- package/src/notifications/NotificationPanel.vue +478 -0
- package/src/notifications/NotificationStore.ts +288 -0
- package/src/notifications/index.ts +42 -0
- package/src/notifications/styles.scss +32 -0
- package/src/orchestrator/Orchestrator.ts +427 -0
- package/src/orchestrator/index.ts +8 -0
- package/src/orchestrator/useOrchestrator.ts +80 -0
- package/src/plugin.ts +251 -0
- package/src/query/FilterQuery.ts +306 -0
- package/src/query/QueryExecutor.ts +341 -0
- package/src/query/index.ts +16 -0
- package/src/security/EntityRolesProvider.ts +385 -0
- package/src/security/PermissionMatcher.ts +152 -0
- package/src/security/PermissionRegistry.ts +273 -0
- package/src/security/PersistableRolesProvider.ts +584 -0
- package/src/security/RolesManager.ts +136 -0
- package/src/security/RolesProvider.ts +167 -0
- package/src/security/RolesProviderStorage.ts +211 -0
- package/src/security/SecurityModule.ts +94 -0
- package/src/security/StaticRolesProvider.ts +117 -0
- package/src/security/UsersManager.ts +163 -0
- package/src/security/index.ts +77 -0
- package/src/security/pages/RoleForm.vue +276 -0
- package/src/security/pages/RoleList.vue +123 -0
- package/src/styles/_alerts.scss +95 -0
- package/src/styles/_buttons.scss +391 -0
- package/src/styles/_cards.scss +241 -0
- package/src/styles/_code.scss +78 -0
- package/src/styles/_dialogs.scss +55 -0
- package/src/styles/_filter-bar.scss +70 -0
- package/src/styles/_forms.scss +434 -0
- package/src/styles/_helpers.scss +153 -0
- package/src/styles/_lists.scss +225 -0
- package/src/styles/_main.scss +356 -0
- package/src/styles/_markdown.scss +88 -0
- package/src/styles/_responsive.scss +255 -0
- package/src/styles/_show-pages.scss +186 -0
- package/src/styles/_sidebar.scss +380 -0
- package/src/styles/_states.scss +115 -0
- package/src/styles/_stats.scss +47 -0
- package/src/styles/_variables.scss +224 -0
- package/src/styles/index.scss +38 -0
- package/src/styles/theme/_components.scss +331 -0
- package/src/styles/theme/_index.scss +10 -0
- package/src/styles/theme/_tokens.scss +147 -0
- package/src/styles/theme/_utilities.scss +175 -0
- package/src/toast/ToastBridgeModule.ts +68 -0
- package/src/toast/ToastListener.vue +55 -0
- package/src/toast/index.ts +26 -0
- package/src/toast/useSignalToast.ts +154 -0
- package/src/types/index.ts +285 -0
- package/src/types/pluralize.d.ts +19 -0
- package/src/types/quarkernel.d.ts +50 -0
- package/src/utils/formatters.ts +174 -0
- package/src/utils/index.ts +35 -0
- package/src/utils/transformers.ts +131 -0
- package/src/vite/qdadmDebugPlugin.ts +401 -0
- package/src/vite-env.d.ts +50 -0
- package/src/zones/ZoneRegistry.ts +934 -0
- package/src/zones/index.ts +29 -0
- package/src/zones/zones.ts +191 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UsersManager - System entity manager for users
|
|
3
|
+
*
|
|
4
|
+
* Provides standard user entity with:
|
|
5
|
+
* - username, password, role fields
|
|
6
|
+
* - role linked to roles entity via reference
|
|
7
|
+
* - Admin-only access by default (customizable)
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* // In a module:
|
|
11
|
+
* ctx.userEntity({
|
|
12
|
+
* storage: new ApiStorage({ endpoint: '/api/users' }),
|
|
13
|
+
* extraFields: {
|
|
14
|
+
* email: { type: 'email', label: 'Email' }
|
|
15
|
+
* }
|
|
16
|
+
* })
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
20
|
+
import { EntityManager } from '../entity/EntityManager'
|
|
21
|
+
import type { FieldConfig } from '../types'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* User entity structure
|
|
25
|
+
*/
|
|
26
|
+
export interface UserEntity {
|
|
27
|
+
id: string | number
|
|
28
|
+
username: string
|
|
29
|
+
password?: string
|
|
30
|
+
role?: string
|
|
31
|
+
[key: string]: unknown
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Field definitions for users
|
|
36
|
+
*/
|
|
37
|
+
export type FieldDefinitions = Record<string, FieldConfig>
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Storage interface for users
|
|
41
|
+
*/
|
|
42
|
+
export interface UserStorage {
|
|
43
|
+
list(params?: unknown): Promise<{ items: UserEntity[]; total: number }>
|
|
44
|
+
get(id: string | number): Promise<UserEntity | null>
|
|
45
|
+
create(data: Partial<UserEntity>): Promise<UserEntity>
|
|
46
|
+
update(id: string | number, data: Partial<UserEntity>): Promise<UserEntity>
|
|
47
|
+
delete(id: string | number): Promise<void>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Options for UsersManager
|
|
52
|
+
*/
|
|
53
|
+
export interface UsersManagerOptions {
|
|
54
|
+
storage: UserStorage
|
|
55
|
+
extraFields?: FieldDefinitions
|
|
56
|
+
adminPermission?: string
|
|
57
|
+
fieldOverrides?: {
|
|
58
|
+
username?: Partial<FieldConfig>
|
|
59
|
+
password?: Partial<FieldConfig>
|
|
60
|
+
role?: Partial<FieldConfig>
|
|
61
|
+
}
|
|
62
|
+
[key: string]: unknown
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Type for EntityManager base class
|
|
66
|
+
interface EntityManagerInstance {
|
|
67
|
+
_hasSecurityChecker(): boolean
|
|
68
|
+
authAdapter: {
|
|
69
|
+
isGranted(permission: string): boolean
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class UsersManager extends EntityManager {
|
|
74
|
+
private _adminPermission: string
|
|
75
|
+
|
|
76
|
+
constructor(options: UsersManagerOptions) {
|
|
77
|
+
const {
|
|
78
|
+
storage,
|
|
79
|
+
extraFields = {},
|
|
80
|
+
adminPermission = 'security:users:manage',
|
|
81
|
+
fieldOverrides = {},
|
|
82
|
+
...rest
|
|
83
|
+
} = options
|
|
84
|
+
|
|
85
|
+
if (!storage) {
|
|
86
|
+
throw new Error('[UsersManager] storage is required')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Default fields for users
|
|
90
|
+
const defaultFields: FieldDefinitions = {
|
|
91
|
+
username: {
|
|
92
|
+
type: 'text',
|
|
93
|
+
label: 'Username',
|
|
94
|
+
required: true,
|
|
95
|
+
default: '',
|
|
96
|
+
...fieldOverrides.username,
|
|
97
|
+
},
|
|
98
|
+
password: {
|
|
99
|
+
type: 'password',
|
|
100
|
+
label: 'Password',
|
|
101
|
+
required: true,
|
|
102
|
+
default: '',
|
|
103
|
+
listable: false, // Don't show in list view
|
|
104
|
+
...fieldOverrides.password,
|
|
105
|
+
},
|
|
106
|
+
role: {
|
|
107
|
+
type: 'select',
|
|
108
|
+
label: 'Role',
|
|
109
|
+
reference: { entity: 'roles' }, // Links to roles entity
|
|
110
|
+
default: 'ROLE_USER',
|
|
111
|
+
...fieldOverrides.role,
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
super({
|
|
116
|
+
name: 'users',
|
|
117
|
+
labelField: 'username',
|
|
118
|
+
system: true, // System-provided entity
|
|
119
|
+
fields: {
|
|
120
|
+
...defaultFields,
|
|
121
|
+
...extraFields,
|
|
122
|
+
},
|
|
123
|
+
storage,
|
|
124
|
+
...rest,
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
this._adminPermission = adminPermission
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get admin permission (for external registration)
|
|
132
|
+
*/
|
|
133
|
+
get adminPermission(): string {
|
|
134
|
+
return this._adminPermission
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private _isAdmin(): boolean {
|
|
138
|
+
const self = this as unknown as EntityManagerInstance
|
|
139
|
+
if (self._hasSecurityChecker()) {
|
|
140
|
+
return self.authAdapter.isGranted(this._adminPermission)
|
|
141
|
+
}
|
|
142
|
+
return false
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
canRead(): boolean {
|
|
146
|
+
return this._isAdmin()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
canCreate(): boolean {
|
|
150
|
+
// little dumb but should work for simple storages
|
|
151
|
+
return this._isAdmin()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
canUpdate(): boolean {
|
|
155
|
+
// little dumb but should work for simple storages
|
|
156
|
+
return this._isAdmin()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
canDelete(): boolean {
|
|
160
|
+
// little dumb but should work for simple storages
|
|
161
|
+
return this._isAdmin()
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security module - Permission and role management
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - PermissionMatcher: Wildcard pattern matching (*, **)
|
|
6
|
+
* - PermissionRegistry: Central registry for module permissions
|
|
7
|
+
* - RoleProvider: Interface for role → permissions mapping
|
|
8
|
+
* - StaticRoleProvider: Config-based roles provider (default)
|
|
9
|
+
* - EntityRoleProvider: Entity-based roles provider (for UI management)
|
|
10
|
+
* - PersistableRoleProvider: Load/persist from any source (localStorage, API)
|
|
11
|
+
* - SecurityModule: System module for roles UI (uses RolesManager)
|
|
12
|
+
* - RolesManager: System entity manager for roles
|
|
13
|
+
* - UsersManager: System entity manager for users (linked to roles)
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* import {
|
|
17
|
+
* PermissionMatcher,
|
|
18
|
+
* PermissionRegistry,
|
|
19
|
+
* StaticRoleProvider,
|
|
20
|
+
* EntityRoleProvider,
|
|
21
|
+
* PersistableRoleProvider,
|
|
22
|
+
* createLocalStorageRolesProvider,
|
|
23
|
+
* UsersManager
|
|
24
|
+
* } from '@quazardous/qdadm/security'
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export { PermissionMatcher } from './PermissionMatcher'
|
|
28
|
+
export {
|
|
29
|
+
PermissionRegistry,
|
|
30
|
+
type PermissionDefinition,
|
|
31
|
+
type PermissionMeta,
|
|
32
|
+
type RegisterOptions,
|
|
33
|
+
type RegisterEntityOptions,
|
|
34
|
+
type GroupedPermissions,
|
|
35
|
+
} from './PermissionRegistry'
|
|
36
|
+
export {
|
|
37
|
+
RoleProvider,
|
|
38
|
+
type RoleMeta,
|
|
39
|
+
type RoleData,
|
|
40
|
+
type RoleHierarchyMap,
|
|
41
|
+
type RolePermissionsMap,
|
|
42
|
+
type RoleLabelsMap,
|
|
43
|
+
type RoleProviderContext,
|
|
44
|
+
} from './RolesProvider'
|
|
45
|
+
export {
|
|
46
|
+
StaticRoleProvider,
|
|
47
|
+
type StaticRolesProviderConfig,
|
|
48
|
+
} from './StaticRolesProvider'
|
|
49
|
+
export {
|
|
50
|
+
EntityRoleProvider,
|
|
51
|
+
type EntityRolesProviderOptions,
|
|
52
|
+
} from './EntityRolesProvider'
|
|
53
|
+
export {
|
|
54
|
+
PersistableRoleProvider,
|
|
55
|
+
createLocalStorageRolesProvider,
|
|
56
|
+
type RoleConfig,
|
|
57
|
+
type MergeStrategy,
|
|
58
|
+
type LoadCallback,
|
|
59
|
+
type PersistCallback,
|
|
60
|
+
type PersistableRolesProviderOptions,
|
|
61
|
+
type LocalStorageRolesProviderOptions,
|
|
62
|
+
} from './PersistableRolesProvider'
|
|
63
|
+
export { SecurityModule } from './SecurityModule'
|
|
64
|
+
export { RolesManager, type RolesManagerOptions } from './RolesManager'
|
|
65
|
+
export { UsersManager, type UsersManagerOptions, type UserEntity } from './UsersManager'
|
|
66
|
+
export {
|
|
67
|
+
RoleProviderStorage,
|
|
68
|
+
type RoleListParams,
|
|
69
|
+
type RoleListResult,
|
|
70
|
+
type RoleInput,
|
|
71
|
+
} from './RolesProviderStorage'
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Standard entity actions for CRUD operations
|
|
75
|
+
* Used by PermissionRegistry.registerEntity()
|
|
76
|
+
*/
|
|
77
|
+
export const ENTITY_ACTIONS = ['read', 'list', 'create', 'update', 'delete'] as const
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* RoleForm - Role create/edit form (standard FormPage pattern)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ref, computed, inject } from 'vue'
|
|
7
|
+
import { useEntityItemFormPage, FormPage, useOrchestrator, PermissionEditor } from '../../index.js'
|
|
8
|
+
import InputText from 'primevue/inputtext'
|
|
9
|
+
import AutoComplete from 'primevue/autocomplete'
|
|
10
|
+
import Chip from 'primevue/chip'
|
|
11
|
+
import type { PermissionRegistry } from '../PermissionRegistry'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Role option for autocomplete
|
|
15
|
+
*/
|
|
16
|
+
interface RoleOption {
|
|
17
|
+
name: string
|
|
18
|
+
label: string
|
|
19
|
+
display: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Role data from roles provider
|
|
24
|
+
*/
|
|
25
|
+
interface RoleData {
|
|
26
|
+
label?: string
|
|
27
|
+
[key: string]: unknown
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* RolesProvider interface
|
|
32
|
+
*/
|
|
33
|
+
interface RolesProviderInterface {
|
|
34
|
+
getRoles: () => string[]
|
|
35
|
+
getRole?: (name: string) => RoleData | null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Roles manager interface
|
|
40
|
+
*/
|
|
41
|
+
interface RolesManager {
|
|
42
|
+
rolesProvider?: RolesProviderInterface
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Autocomplete search event
|
|
47
|
+
*/
|
|
48
|
+
interface AutocompleteSearchEvent {
|
|
49
|
+
query?: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Autocomplete select event
|
|
54
|
+
*/
|
|
55
|
+
interface AutocompleteSelectEvent {
|
|
56
|
+
value: RoleOption
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Role form data structure
|
|
61
|
+
*/
|
|
62
|
+
interface RoleFormData {
|
|
63
|
+
name: string
|
|
64
|
+
label: string
|
|
65
|
+
inherits: string[]
|
|
66
|
+
permissions: string[]
|
|
67
|
+
[key: string]: unknown
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ============ FORM BUILDER ============
|
|
71
|
+
const form = useEntityItemFormPage({ entity: 'roles' })
|
|
72
|
+
|
|
73
|
+
// ============ HELPERS ============
|
|
74
|
+
const { getManager } = useOrchestrator()
|
|
75
|
+
const manager = getManager('roles') as RolesManager | null
|
|
76
|
+
|
|
77
|
+
// Get permissionRegistry directly from Kernel (via provide/inject)
|
|
78
|
+
const permissionRegistry = inject<PermissionRegistry | null>('qdadmPermissionRegistry', null)
|
|
79
|
+
|
|
80
|
+
// Role options for inheritance (exclude self)
|
|
81
|
+
const allRoles = computed<RoleOption[]>(() => {
|
|
82
|
+
const currentName = form.data.value?.name as string | undefined
|
|
83
|
+
const roles = manager?.rolesProvider?.getRoles() || []
|
|
84
|
+
return roles
|
|
85
|
+
.filter((name: string) => name !== currentName)
|
|
86
|
+
.map((name: string) => {
|
|
87
|
+
const role = manager?.rolesProvider?.getRole?.(name)
|
|
88
|
+
return {
|
|
89
|
+
name,
|
|
90
|
+
label: role?.label || name,
|
|
91
|
+
display: `${name} (${role?.label || name})`
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// Autocomplete for roles
|
|
97
|
+
const roleInput = ref<string>('')
|
|
98
|
+
const roleSuggestions = ref<RoleOption[]>([])
|
|
99
|
+
|
|
100
|
+
function searchRoles(event: AutocompleteSearchEvent): void {
|
|
101
|
+
const query = (event.query || '').toLowerCase()
|
|
102
|
+
const selected = (form.data.value.inherits as string[]) || []
|
|
103
|
+
|
|
104
|
+
roleSuggestions.value = allRoles.value
|
|
105
|
+
.filter((r: RoleOption) => !selected.includes(r.name))
|
|
106
|
+
.filter((r: RoleOption) =>
|
|
107
|
+
query === '' ||
|
|
108
|
+
r.name.toLowerCase().includes(query) ||
|
|
109
|
+
r.label.toLowerCase().includes(query)
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function onRoleSelect(event: AutocompleteSelectEvent): void {
|
|
114
|
+
const role = event.value
|
|
115
|
+
const inherits = (form.data.value.inherits as string[]) || []
|
|
116
|
+
if (role && !inherits.includes(role.name)) {
|
|
117
|
+
form.data.value.inherits = [...inherits, role.name]
|
|
118
|
+
}
|
|
119
|
+
roleInput.value = ''
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function removeRole(roleName: string): void {
|
|
123
|
+
const inherits = (form.data.value.inherits as string[]) || []
|
|
124
|
+
form.data.value.inherits = inherits.filter((r: string) => r !== roleName)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getRoleLabel(roleName: string): string {
|
|
128
|
+
const role = allRoles.value.find((r: RoleOption) => r.name === roleName)
|
|
129
|
+
return role?.label || roleName
|
|
130
|
+
}
|
|
131
|
+
</script>
|
|
132
|
+
|
|
133
|
+
<template>
|
|
134
|
+
<FormPage v-bind="form.props.value" v-on="form.events">
|
|
135
|
+
<template #fields>
|
|
136
|
+
<div class="role-form-fields">
|
|
137
|
+
<!-- Role Name -->
|
|
138
|
+
<div class="form-field">
|
|
139
|
+
<label class="font-medium">Role Name</label>
|
|
140
|
+
<InputText
|
|
141
|
+
v-model="(form.data.value as RoleFormData).name"
|
|
142
|
+
:disabled="form.isEdit.value"
|
|
143
|
+
placeholder="ROLE_ADMIN"
|
|
144
|
+
class="w-full"
|
|
145
|
+
/>
|
|
146
|
+
<small class="field-hint">Convention: ROLE_UPPERCASE_NAME</small>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<!-- Label -->
|
|
150
|
+
<div class="form-field">
|
|
151
|
+
<label class="font-medium">Display Label</label>
|
|
152
|
+
<InputText
|
|
153
|
+
v-model="(form.data.value as RoleFormData).label"
|
|
154
|
+
placeholder="Administrator"
|
|
155
|
+
class="w-full"
|
|
156
|
+
/>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<!-- Inherits -->
|
|
160
|
+
<div class="form-field">
|
|
161
|
+
<label class="font-medium">Inherits From</label>
|
|
162
|
+
<div class="inherits-editor">
|
|
163
|
+
<div v-if="((form.data.value as RoleFormData).inherits || []).length > 0" class="inherits-chips">
|
|
164
|
+
<Chip
|
|
165
|
+
v-for="roleName in (form.data.value as RoleFormData).inherits"
|
|
166
|
+
:key="roleName"
|
|
167
|
+
removable
|
|
168
|
+
@remove="removeRole(roleName)"
|
|
169
|
+
class="inherits-chip"
|
|
170
|
+
>
|
|
171
|
+
<span class="chip-name">{{ roleName }}</span>
|
|
172
|
+
<span class="chip-label">({{ getRoleLabel(roleName) }})</span>
|
|
173
|
+
</Chip>
|
|
174
|
+
</div>
|
|
175
|
+
<AutoComplete
|
|
176
|
+
v-model="roleInput"
|
|
177
|
+
:suggestions="roleSuggestions"
|
|
178
|
+
optionLabel="display"
|
|
179
|
+
placeholder="Type role name..."
|
|
180
|
+
:minLength="0"
|
|
181
|
+
completeOnFocus
|
|
182
|
+
@complete="searchRoles"
|
|
183
|
+
@item-select="onRoleSelect"
|
|
184
|
+
dropdown
|
|
185
|
+
class="w-full"
|
|
186
|
+
>
|
|
187
|
+
<template #option="{ option }">
|
|
188
|
+
<div class="role-option">
|
|
189
|
+
<span class="role-name">{{ option.name }}</span>
|
|
190
|
+
<span class="role-label">{{ option.label }}</span>
|
|
191
|
+
</div>
|
|
192
|
+
</template>
|
|
193
|
+
</AutoComplete>
|
|
194
|
+
</div>
|
|
195
|
+
<small class="field-hint">
|
|
196
|
+
This role inherits all permissions from selected roles
|
|
197
|
+
</small>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<!-- Permissions -->
|
|
201
|
+
<div class="form-field">
|
|
202
|
+
<label class="font-medium">
|
|
203
|
+
Permissions ({{ ((form.data.value as RoleFormData).permissions || []).length }})
|
|
204
|
+
</label>
|
|
205
|
+
<PermissionEditor
|
|
206
|
+
v-model="(form.data.value as RoleFormData).permissions"
|
|
207
|
+
:permissionRegistry="permissionRegistry"
|
|
208
|
+
placeholder="Type namespace:action..."
|
|
209
|
+
/>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
</template>
|
|
213
|
+
</FormPage>
|
|
214
|
+
</template>
|
|
215
|
+
|
|
216
|
+
<style scoped>
|
|
217
|
+
/*
|
|
218
|
+
* Only keep styles here that REQUIRE scoping (:deep, dynamic binding, component-specific overrides).
|
|
219
|
+
* Generic/reusable styles belong in src/styles/ partials (see _forms.scss, _cards.scss, etc.).
|
|
220
|
+
*/
|
|
221
|
+
/* Form layout - .form-field and .field-hint are global (main.css) */
|
|
222
|
+
.role-form-fields {
|
|
223
|
+
display: flex;
|
|
224
|
+
flex-direction: column;
|
|
225
|
+
gap: 1.5rem;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* Inherits editor - uses .editor-box pattern */
|
|
229
|
+
.inherits-editor {
|
|
230
|
+
border: 1px solid var(--p-surface-200);
|
|
231
|
+
border-radius: 0.5rem;
|
|
232
|
+
padding: 0.75rem;
|
|
233
|
+
background: var(--p-surface-50);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/* Inherits chips - uses .editor-chips pattern */
|
|
237
|
+
.inherits-chips {
|
|
238
|
+
display: flex;
|
|
239
|
+
flex-wrap: wrap;
|
|
240
|
+
gap: 0.5rem;
|
|
241
|
+
margin-bottom: 0.75rem;
|
|
242
|
+
padding-bottom: 0.75rem;
|
|
243
|
+
border-bottom: 1px solid var(--p-surface-200);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.inherits-chip {
|
|
247
|
+
font-family: monospace;
|
|
248
|
+
font-size: 0.875rem;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.chip-name {
|
|
252
|
+
font-weight: 500;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.chip-label {
|
|
256
|
+
color: var(--p-surface-500);
|
|
257
|
+
margin-left: 0.25rem;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.role-option {
|
|
261
|
+
display: flex;
|
|
262
|
+
justify-content: space-between;
|
|
263
|
+
align-items: center;
|
|
264
|
+
gap: 1rem;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.role-name {
|
|
268
|
+
font-family: monospace;
|
|
269
|
+
font-weight: 500;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.role-label {
|
|
273
|
+
color: var(--p-surface-500);
|
|
274
|
+
font-size: 0.875rem;
|
|
275
|
+
}
|
|
276
|
+
</style>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* RoleList - Role listing page (standard ListPage pattern)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useListPage, ListPage, useOrchestrator } from '../../index.js'
|
|
7
|
+
import Column from 'primevue/column'
|
|
8
|
+
import Message from 'primevue/message'
|
|
9
|
+
import Chip from 'primevue/chip'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* RolesProvider interface
|
|
13
|
+
*/
|
|
14
|
+
interface RolesProviderInterface {
|
|
15
|
+
canPersist?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Roles manager interface
|
|
20
|
+
*/
|
|
21
|
+
interface RolesManager {
|
|
22
|
+
rolesProvider?: RolesProviderInterface
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ============ LIST BUILDER ============
|
|
26
|
+
const list = useListPage({ entity: 'roles' })
|
|
27
|
+
|
|
28
|
+
// ============ SEARCH ============
|
|
29
|
+
list.setSearch({
|
|
30
|
+
placeholder: 'Search roles...',
|
|
31
|
+
fields: ['name', 'label']
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// ============ HEADER ACTIONS ============
|
|
35
|
+
list.addCreateAction('New Role')
|
|
36
|
+
|
|
37
|
+
// ============ ROW ACTIONS ============
|
|
38
|
+
list.addEditAction()
|
|
39
|
+
list.addDeleteAction({ labelField: 'label' })
|
|
40
|
+
|
|
41
|
+
// ============ HELPERS ============
|
|
42
|
+
const { getManager } = useOrchestrator()
|
|
43
|
+
const manager = getManager('roles') as RolesManager | null
|
|
44
|
+
// Storage capability (not user permission) - for showing read-only warning
|
|
45
|
+
const storageCanPersist: boolean = manager?.rolesProvider?.canPersist ?? false
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<template>
|
|
49
|
+
<ListPage v-bind="list.props.value" v-on="list.events">
|
|
50
|
+
<!-- Read-only Warning -->
|
|
51
|
+
<template #before-table>
|
|
52
|
+
<Message v-if="!storageCanPersist" severity="info" :closable="false" class="mb-4">
|
|
53
|
+
<div class="flex align-items-center gap-2">
|
|
54
|
+
<i class="pi pi-info-circle text-xl"></i>
|
|
55
|
+
<span>
|
|
56
|
+
<strong>Read-only:</strong> Roles are configured statically.
|
|
57
|
+
Use a PersistableRoleProvider for editing.
|
|
58
|
+
</span>
|
|
59
|
+
</div>
|
|
60
|
+
</Message>
|
|
61
|
+
</template>
|
|
62
|
+
|
|
63
|
+
<template #columns>
|
|
64
|
+
<Column field="name" header="Role" sortable style="width: 25%">
|
|
65
|
+
<template #body="{ data }">
|
|
66
|
+
<div>
|
|
67
|
+
<span class="p-role-label">{{ data.name }}</span>
|
|
68
|
+
<div v-if="data.label && data.label !== data.name" class="text-sm text-color-secondary mt-1">
|
|
69
|
+
{{ data.label }}
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</template>
|
|
73
|
+
</Column>
|
|
74
|
+
|
|
75
|
+
<Column header="Inherits" style="width: 20%">
|
|
76
|
+
<template #body="{ data }">
|
|
77
|
+
<div v-if="data.inherits?.length" class="p-role-labels">
|
|
78
|
+
<span
|
|
79
|
+
v-for="parent in data.inherits"
|
|
80
|
+
:key="parent"
|
|
81
|
+
class="p-role-label"
|
|
82
|
+
>{{ parent }}</span>
|
|
83
|
+
</div>
|
|
84
|
+
<span v-else class="text-color-secondary">-</span>
|
|
85
|
+
</template>
|
|
86
|
+
</Column>
|
|
87
|
+
|
|
88
|
+
<Column header="Permissions" style="width: 40%">
|
|
89
|
+
<template #body="{ data }">
|
|
90
|
+
<div v-if="data.permissions?.length" class="chips-container">
|
|
91
|
+
<Chip
|
|
92
|
+
v-for="perm in data.permissions.slice(0, 5)"
|
|
93
|
+
:key="perm"
|
|
94
|
+
:label="perm"
|
|
95
|
+
/>
|
|
96
|
+
<Chip
|
|
97
|
+
v-if="data.permissions.length > 5"
|
|
98
|
+
:label="`+${data.permissions.length - 5}`"
|
|
99
|
+
class="chip-more"
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
<span v-else class="text-color-secondary">No permissions</span>
|
|
103
|
+
</template>
|
|
104
|
+
</Column>
|
|
105
|
+
</template>
|
|
106
|
+
</ListPage>
|
|
107
|
+
</template>
|
|
108
|
+
|
|
109
|
+
<style scoped>
|
|
110
|
+
/*
|
|
111
|
+
* Only keep styles here that REQUIRE scoping (:deep, dynamic binding, component-specific overrides).
|
|
112
|
+
* Generic/reusable styles belong in src/styles/ partials (see _forms.scss, _cards.scss, etc.).
|
|
113
|
+
*/
|
|
114
|
+
.chips-container {
|
|
115
|
+
display: flex;
|
|
116
|
+
flex-wrap: wrap;
|
|
117
|
+
gap: 0.25rem;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.chip-more {
|
|
121
|
+
border-style: dashed;
|
|
122
|
+
}
|
|
123
|
+
</style>
|