@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,584 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PersistableRoleProvider - Roles provider with load/persist callbacks
|
|
3
|
+
*
|
|
4
|
+
* Flexible adapter that can load role→permissions mapping from any source
|
|
5
|
+
* (localStorage, API, entity) and persist changes back.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Fixed permissions (incompressible, always applied, never overridden)
|
|
9
|
+
* - Default mapping as fallback
|
|
10
|
+
* - Async load/persist callbacks
|
|
11
|
+
* - Merge strategy for defaults + loaded data
|
|
12
|
+
* - Cache invalidation on persist
|
|
13
|
+
*
|
|
14
|
+
* Permission priority (highest to lowest):
|
|
15
|
+
* 1. fixed - System permissions, always present, never overridden
|
|
16
|
+
* 2. loaded - Data from load() callback
|
|
17
|
+
* 3. defaults - Fallback when load returns null/fails
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // With fixed system permissions + API loading
|
|
21
|
+
* const adapter = new PersistableRoleProvider({
|
|
22
|
+
* // Fixed: ALWAYS present, even if API returns different data
|
|
23
|
+
* fixed: {
|
|
24
|
+
* role_permissions: {
|
|
25
|
+
* ROLE_ANONYMOUS: ['auth:login', 'auth:register'],
|
|
26
|
+
* ROLE_USER: ['auth:authenticated', 'auth:logout', 'profile:read']
|
|
27
|
+
* }
|
|
28
|
+
* },
|
|
29
|
+
* // Defaults: used before load() or if load fails
|
|
30
|
+
* defaults: {
|
|
31
|
+
* role_permissions: {
|
|
32
|
+
* ROLE_USER: ['entity:*:read'],
|
|
33
|
+
* ROLE_ADMIN: ['entity:**']
|
|
34
|
+
* }
|
|
35
|
+
* },
|
|
36
|
+
* // Load from API (requires auth, called after login)
|
|
37
|
+
* load: async () => {
|
|
38
|
+
* const res = await fetch('/api/config/roles')
|
|
39
|
+
* return res.json()
|
|
40
|
+
* },
|
|
41
|
+
* autoLoad: false // Load manually after auth
|
|
42
|
+
* })
|
|
43
|
+
*
|
|
44
|
+
* // After user authenticates
|
|
45
|
+
* signals.on('auth:login', () => adapter.load())
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* // With localStorage
|
|
49
|
+
* const adapter = new PersistableRoleProvider({
|
|
50
|
+
* load: () => JSON.parse(localStorage.getItem('roles') || 'null'),
|
|
51
|
+
* persist: (data) => localStorage.setItem('roles', JSON.stringify(data)),
|
|
52
|
+
* defaults: {
|
|
53
|
+
* role_permissions: {
|
|
54
|
+
* ROLE_USER: ['entity:*:read', 'entity:*:list'],
|
|
55
|
+
* ROLE_ADMIN: ['entity:**', 'admin:**']
|
|
56
|
+
* }
|
|
57
|
+
* }
|
|
58
|
+
* })
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
import { RoleProvider } from './RolesProvider'
|
|
62
|
+
import type { RoleData, RoleHierarchyMap } from './RolesProvider'
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Role configuration structure
|
|
66
|
+
*/
|
|
67
|
+
export interface RoleConfig {
|
|
68
|
+
role_hierarchy?: Record<string, string[]>
|
|
69
|
+
role_permissions?: Record<string, string[]>
|
|
70
|
+
role_labels?: Record<string, string>
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Merge strategy for combining defaults with loaded data
|
|
75
|
+
*/
|
|
76
|
+
export type MergeStrategy = 'extend' | 'replace' | 'defaults-only'
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Load callback type
|
|
80
|
+
*/
|
|
81
|
+
export type LoadCallback = () => Promise<RoleConfig | null> | RoleConfig | null
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Persist callback type
|
|
85
|
+
*/
|
|
86
|
+
export type PersistCallback = (data: RoleConfig) => Promise<void> | void
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Options for PersistableRoleProvider
|
|
90
|
+
*/
|
|
91
|
+
export interface PersistableRolesProviderOptions {
|
|
92
|
+
load?: LoadCallback
|
|
93
|
+
persist?: PersistCallback
|
|
94
|
+
fixed?: RoleConfig
|
|
95
|
+
defaults?: RoleConfig
|
|
96
|
+
mergeStrategy?: MergeStrategy
|
|
97
|
+
autoLoad?: boolean
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Options for createLocalStorageRolesProvider
|
|
102
|
+
*/
|
|
103
|
+
export interface LocalStorageRolesProviderOptions {
|
|
104
|
+
key?: string
|
|
105
|
+
fixed?: RoleConfig
|
|
106
|
+
defaults?: RoleConfig
|
|
107
|
+
mergeStrategy?: MergeStrategy
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export class PersistableRoleProvider extends RoleProvider {
|
|
111
|
+
private _loadFn: LoadCallback | null
|
|
112
|
+
private _persistFn: PersistCallback | null
|
|
113
|
+
private _mergeStrategy: MergeStrategy
|
|
114
|
+
private _autoLoad: boolean
|
|
115
|
+
|
|
116
|
+
// Fixed (incompressible, always applied on top)
|
|
117
|
+
private _fixed: Required<RoleConfig>
|
|
118
|
+
|
|
119
|
+
// Defaults (fallback)
|
|
120
|
+
private _defaults: Required<RoleConfig>
|
|
121
|
+
|
|
122
|
+
// Current state (starts with defaults)
|
|
123
|
+
protected _hierarchy: Record<string, string[]>
|
|
124
|
+
protected _permissions: Record<string, string[]>
|
|
125
|
+
protected _labels: Record<string, string>
|
|
126
|
+
|
|
127
|
+
// Loading state
|
|
128
|
+
// Note: _loaded is public to allow createLocalStorageRolesProvider to set it
|
|
129
|
+
public _loaded: boolean = false
|
|
130
|
+
private _loading: Promise<void> | null = null
|
|
131
|
+
private _dirty: boolean = false
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Create a persistable roles provider
|
|
135
|
+
*/
|
|
136
|
+
constructor(options: PersistableRolesProviderOptions = {}) {
|
|
137
|
+
super()
|
|
138
|
+
|
|
139
|
+
this._loadFn = options.load || null
|
|
140
|
+
this._persistFn = options.persist || null
|
|
141
|
+
this._mergeStrategy = options.mergeStrategy || 'extend'
|
|
142
|
+
this._autoLoad = options.autoLoad ?? true
|
|
143
|
+
|
|
144
|
+
// Fixed (incompressible, always applied on top)
|
|
145
|
+
this._fixed = {
|
|
146
|
+
role_hierarchy: options.fixed?.role_hierarchy || {},
|
|
147
|
+
role_permissions: options.fixed?.role_permissions || {},
|
|
148
|
+
role_labels: options.fixed?.role_labels || {},
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Defaults (fallback)
|
|
152
|
+
this._defaults = {
|
|
153
|
+
role_hierarchy: options.defaults?.role_hierarchy || {},
|
|
154
|
+
role_permissions: options.defaults?.role_permissions || {},
|
|
155
|
+
role_labels: options.defaults?.role_labels || {},
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Current state (starts with defaults)
|
|
159
|
+
this._hierarchy = { ...this._defaults.role_hierarchy }
|
|
160
|
+
this._permissions = { ...this._defaults.role_permissions }
|
|
161
|
+
this._labels = { ...this._defaults.role_labels }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Load configuration from source
|
|
166
|
+
*/
|
|
167
|
+
async load(): Promise<void> {
|
|
168
|
+
if (!this._loadFn) {
|
|
169
|
+
this._loaded = true
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Prevent concurrent loads
|
|
174
|
+
if (this._loading) {
|
|
175
|
+
return this._loading
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
this._loading = this._doLoad()
|
|
179
|
+
try {
|
|
180
|
+
await this._loading
|
|
181
|
+
} finally {
|
|
182
|
+
this._loading = null
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Internal load implementation
|
|
188
|
+
*/
|
|
189
|
+
private async _doLoad(): Promise<void> {
|
|
190
|
+
try {
|
|
191
|
+
const data = await this._loadFn!()
|
|
192
|
+
|
|
193
|
+
if (data) {
|
|
194
|
+
this._applyData(data)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
this._loaded = true
|
|
198
|
+
this._dirty = false
|
|
199
|
+
} catch (err) {
|
|
200
|
+
console.error('[PersistableRoleProvider] Load failed:', err)
|
|
201
|
+
// Keep defaults on error
|
|
202
|
+
this._loaded = true
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Apply loaded data according to merge strategy
|
|
208
|
+
*/
|
|
209
|
+
_applyData(data: RoleConfig): void {
|
|
210
|
+
switch (this._mergeStrategy) {
|
|
211
|
+
case 'replace':
|
|
212
|
+
// Loaded data replaces everything
|
|
213
|
+
this._hierarchy = data.role_hierarchy || {}
|
|
214
|
+
this._permissions = data.role_permissions || {}
|
|
215
|
+
this._labels = data.role_labels || {}
|
|
216
|
+
break
|
|
217
|
+
|
|
218
|
+
case 'defaults-only':
|
|
219
|
+
// Ignore loaded data, use defaults
|
|
220
|
+
break
|
|
221
|
+
|
|
222
|
+
case 'extend':
|
|
223
|
+
default:
|
|
224
|
+
// Loaded extends defaults (loaded takes priority)
|
|
225
|
+
this._hierarchy = {
|
|
226
|
+
...this._defaults.role_hierarchy,
|
|
227
|
+
...(data.role_hierarchy || {}),
|
|
228
|
+
}
|
|
229
|
+
this._permissions = {
|
|
230
|
+
...this._defaults.role_permissions,
|
|
231
|
+
...(data.role_permissions || {}),
|
|
232
|
+
}
|
|
233
|
+
this._labels = {
|
|
234
|
+
...this._defaults.role_labels,
|
|
235
|
+
...(data.role_labels || {}),
|
|
236
|
+
}
|
|
237
|
+
break
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Persist current configuration to source
|
|
243
|
+
*/
|
|
244
|
+
async persist(): Promise<void> {
|
|
245
|
+
if (!this._persistFn) {
|
|
246
|
+
console.warn('[PersistableRoleProvider] No persist function configured')
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const data: RoleConfig = {
|
|
251
|
+
role_hierarchy: this._hierarchy,
|
|
252
|
+
role_permissions: this._permissions,
|
|
253
|
+
role_labels: this._labels,
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
await this._persistFn(data)
|
|
258
|
+
this._dirty = false
|
|
259
|
+
} catch (err) {
|
|
260
|
+
console.error('[PersistableRoleProvider] Persist failed:', err)
|
|
261
|
+
throw err
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Ensure data is loaded (auto-load if needed)
|
|
267
|
+
*/
|
|
268
|
+
private async _ensureLoaded(): Promise<void> {
|
|
269
|
+
if (!this._loaded && this._autoLoad) {
|
|
270
|
+
await this.load()
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
275
|
+
// RoleProvider interface
|
|
276
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Get permissions for a role
|
|
280
|
+
*
|
|
281
|
+
* Merges: current permissions + fixed permissions (fixed always wins)
|
|
282
|
+
*/
|
|
283
|
+
getPermissions(role: string): string[] {
|
|
284
|
+
// Note: This is synchronous. For async sources, call load() first
|
|
285
|
+
// or use autoLoad + await ensureReady() before first use
|
|
286
|
+
const base = this._permissions[role] || []
|
|
287
|
+
const fixed = this._fixed.role_permissions[role] || []
|
|
288
|
+
|
|
289
|
+
// Merge: base + fixed (deduplicated)
|
|
290
|
+
if (fixed.length === 0) return base
|
|
291
|
+
if (base.length === 0) return fixed
|
|
292
|
+
|
|
293
|
+
const merged = [...base]
|
|
294
|
+
for (const perm of fixed) {
|
|
295
|
+
if (!merged.includes(perm)) {
|
|
296
|
+
merged.push(perm)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return merged
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Get all defined roles
|
|
304
|
+
*
|
|
305
|
+
* Includes roles from: current + fixed
|
|
306
|
+
*/
|
|
307
|
+
getRoles(): string[] {
|
|
308
|
+
const roles = new Set<string>([
|
|
309
|
+
...Object.keys(this._permissions),
|
|
310
|
+
...Object.keys(this._fixed.role_permissions),
|
|
311
|
+
])
|
|
312
|
+
return [...roles]
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Get role hierarchy
|
|
317
|
+
*
|
|
318
|
+
* Merges: current hierarchy + fixed hierarchy
|
|
319
|
+
*/
|
|
320
|
+
getHierarchy(): RoleHierarchyMap {
|
|
321
|
+
return {
|
|
322
|
+
...this._hierarchy,
|
|
323
|
+
...this._fixed.role_hierarchy,
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Get role labels for display
|
|
329
|
+
*
|
|
330
|
+
* Merges: current labels + fixed labels
|
|
331
|
+
*/
|
|
332
|
+
getLabels(): Record<string, string> {
|
|
333
|
+
return {
|
|
334
|
+
...this._labels,
|
|
335
|
+
...this._fixed.role_labels,
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// getAnonymousRole() inherited from RoleProvider (returns 'ROLE_ANONYMOUS')
|
|
340
|
+
|
|
341
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
342
|
+
// Role query methods
|
|
343
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Check if a role exists (in current or fixed)
|
|
347
|
+
*/
|
|
348
|
+
roleExists(role: string): boolean {
|
|
349
|
+
return (
|
|
350
|
+
this._permissions[role] !== undefined || this._fixed.role_permissions[role] !== undefined
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Get complete role object
|
|
356
|
+
*/
|
|
357
|
+
getRole(role: string): RoleData | null {
|
|
358
|
+
if (!this.roleExists(role)) {
|
|
359
|
+
return null
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
name: role,
|
|
363
|
+
label: this._labels[role] || this._fixed.role_labels[role] || role,
|
|
364
|
+
permissions: this.getPermissions(role),
|
|
365
|
+
inherits: this._hierarchy[role] || this._fixed.role_hierarchy[role] || [],
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
370
|
+
// Mutation methods
|
|
371
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Create a new role (async, auto-persists)
|
|
375
|
+
*/
|
|
376
|
+
async createRole(
|
|
377
|
+
name: string,
|
|
378
|
+
{ label, permissions = [], inherits = [] }: { label?: string; permissions?: string[]; inherits?: string[] } = {}
|
|
379
|
+
): Promise<RoleData> {
|
|
380
|
+
if (this.roleExists(name)) {
|
|
381
|
+
throw new Error(`Role '${name}' already exists`)
|
|
382
|
+
}
|
|
383
|
+
this._permissions[name] = [...permissions]
|
|
384
|
+
if (label) this._labels[name] = label
|
|
385
|
+
if (inherits.length > 0) this._hierarchy[name] = [...inherits]
|
|
386
|
+
this._dirty = true
|
|
387
|
+
|
|
388
|
+
if (this._persistFn) {
|
|
389
|
+
await this.persist()
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return this.getRole(name)!
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Update an existing role (async, auto-persists)
|
|
397
|
+
*/
|
|
398
|
+
async updateRole(
|
|
399
|
+
name: string,
|
|
400
|
+
{ label, permissions, inherits }: { label?: string; permissions?: string[]; inherits?: string[] } = {}
|
|
401
|
+
): Promise<RoleData> {
|
|
402
|
+
if (!this.roleExists(name)) {
|
|
403
|
+
throw new Error(`Role '${name}' does not exist`)
|
|
404
|
+
}
|
|
405
|
+
if (permissions !== undefined) this._permissions[name] = [...permissions]
|
|
406
|
+
if (label !== undefined) this._labels[name] = label
|
|
407
|
+
if (inherits !== undefined) this._hierarchy[name] = [...inherits]
|
|
408
|
+
this._dirty = true
|
|
409
|
+
|
|
410
|
+
if (this._persistFn) {
|
|
411
|
+
await this.persist()
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return this.getRole(name)!
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Set permissions for a role
|
|
419
|
+
*/
|
|
420
|
+
setRolePermissions(role: string, permissions: string[]): this {
|
|
421
|
+
this._permissions[role] = [...permissions]
|
|
422
|
+
this._dirty = true
|
|
423
|
+
return this
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Add permissions to a role
|
|
428
|
+
*/
|
|
429
|
+
addRolePermissions(role: string, permissions: string[]): this {
|
|
430
|
+
const current = this._permissions[role] || []
|
|
431
|
+
const newPerms = permissions.filter((p) => !current.includes(p))
|
|
432
|
+
this._permissions[role] = [...current, ...newPerms]
|
|
433
|
+
this._dirty = true
|
|
434
|
+
return this
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Remove permissions from a role
|
|
439
|
+
*/
|
|
440
|
+
removeRolePermissions(role: string, permissions: string[]): this {
|
|
441
|
+
const current = this._permissions[role] || []
|
|
442
|
+
this._permissions[role] = current.filter((p) => !permissions.includes(p))
|
|
443
|
+
this._dirty = true
|
|
444
|
+
return this
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Set role hierarchy
|
|
449
|
+
*/
|
|
450
|
+
setRoleHierarchy(role: string, inherits: string[]): this {
|
|
451
|
+
this._hierarchy[role] = [...inherits]
|
|
452
|
+
this._dirty = true
|
|
453
|
+
return this
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Set role label
|
|
458
|
+
*/
|
|
459
|
+
setRoleLabel(role: string, label: string): this {
|
|
460
|
+
this._labels[role] = label
|
|
461
|
+
this._dirty = true
|
|
462
|
+
return this
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Delete a role entirely (async, auto-persists)
|
|
467
|
+
*/
|
|
468
|
+
async deleteRole(name: string): Promise<void> {
|
|
469
|
+
if (!this.roleExists(name)) {
|
|
470
|
+
throw new Error(`Role '${name}' does not exist`)
|
|
471
|
+
}
|
|
472
|
+
delete this._permissions[name]
|
|
473
|
+
delete this._hierarchy[name]
|
|
474
|
+
delete this._labels[name]
|
|
475
|
+
this._dirty = true
|
|
476
|
+
|
|
477
|
+
if (this._persistFn) {
|
|
478
|
+
await this.persist()
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Reset to defaults
|
|
484
|
+
*/
|
|
485
|
+
reset(): this {
|
|
486
|
+
this._hierarchy = { ...this._defaults.role_hierarchy }
|
|
487
|
+
this._permissions = { ...this._defaults.role_permissions }
|
|
488
|
+
this._labels = { ...this._defaults.role_labels }
|
|
489
|
+
this._dirty = true
|
|
490
|
+
return this
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
494
|
+
// State inspection
|
|
495
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Check if data has been loaded
|
|
499
|
+
*/
|
|
500
|
+
get isLoaded(): boolean {
|
|
501
|
+
return this._loaded
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Check if there are unsaved changes
|
|
506
|
+
*/
|
|
507
|
+
get isDirty(): boolean {
|
|
508
|
+
return this._dirty
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Check if adapter supports persistence
|
|
513
|
+
* Returns true if a persist function was provided
|
|
514
|
+
*/
|
|
515
|
+
get canPersist(): boolean {
|
|
516
|
+
return !!this._persistFn
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Get current configuration as object
|
|
521
|
+
*/
|
|
522
|
+
toJSON(): RoleConfig {
|
|
523
|
+
return {
|
|
524
|
+
role_hierarchy: this._hierarchy,
|
|
525
|
+
role_permissions: this._permissions,
|
|
526
|
+
role_labels: this._labels,
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Get a ready promise (resolves when loaded)
|
|
532
|
+
*/
|
|
533
|
+
async ensureReady(): Promise<this> {
|
|
534
|
+
await this._ensureLoaded()
|
|
535
|
+
return this
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Create a localStorage-backed roles provider
|
|
541
|
+
*
|
|
542
|
+
* Unlike async sources, localStorage is synchronous so data is loaded
|
|
543
|
+
* immediately at construction time. No need to call ensureReady().
|
|
544
|
+
*/
|
|
545
|
+
export function createLocalStorageRolesProvider(
|
|
546
|
+
options: LocalStorageRolesProviderOptions = {}
|
|
547
|
+
): PersistableRoleProvider {
|
|
548
|
+
const key = options.key || 'qdadm_roles'
|
|
549
|
+
|
|
550
|
+
// Load data synchronously from localStorage (localStorage is sync)
|
|
551
|
+
let initialData: RoleConfig | null = null
|
|
552
|
+
try {
|
|
553
|
+
const stored = localStorage.getItem(key)
|
|
554
|
+
initialData = stored ? JSON.parse(stored) : null
|
|
555
|
+
} catch {
|
|
556
|
+
// Ignore parse errors, will use defaults
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const adapter = new PersistableRoleProvider({
|
|
560
|
+
load: () => {
|
|
561
|
+
try {
|
|
562
|
+
const stored = localStorage.getItem(key)
|
|
563
|
+
return stored ? JSON.parse(stored) : null
|
|
564
|
+
} catch {
|
|
565
|
+
return null
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
persist: (data) => {
|
|
569
|
+
localStorage.setItem(key, JSON.stringify(data))
|
|
570
|
+
},
|
|
571
|
+
fixed: options.fixed,
|
|
572
|
+
defaults: options.defaults,
|
|
573
|
+
mergeStrategy: options.mergeStrategy,
|
|
574
|
+
autoLoad: false, // We load synchronously below
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
// Apply initial data immediately (synchronous load)
|
|
578
|
+
if (initialData) {
|
|
579
|
+
adapter._applyData(initialData)
|
|
580
|
+
}
|
|
581
|
+
adapter._loaded = true
|
|
582
|
+
|
|
583
|
+
return adapter
|
|
584
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RolesManager - System entity manager for roles
|
|
3
|
+
*
|
|
4
|
+
* Uses RoleProviderStorage as bridge to rolesProvider.
|
|
5
|
+
* Allows SecurityModule to use standard ctx.crud() pattern.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
9
|
+
import { EntityManager } from '../entity/EntityManager'
|
|
10
|
+
import { RoleProviderStorage } from './RolesProviderStorage'
|
|
11
|
+
import type { RoleProvider } from './RolesProvider'
|
|
12
|
+
import type { PermissionRegistry } from './PermissionRegistry'
|
|
13
|
+
import type { IStorage, EntityRecord } from '../types'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* RoleData as EntityRecord (with id mapped to name)
|
|
17
|
+
*/
|
|
18
|
+
interface RoleRecord extends EntityRecord {
|
|
19
|
+
name: string
|
|
20
|
+
label: string
|
|
21
|
+
permissions: string[]
|
|
22
|
+
inherits: string[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Options for RolesManager
|
|
27
|
+
*/
|
|
28
|
+
export interface RolesManagerOptions {
|
|
29
|
+
rolesProvider?: RoleProvider | null
|
|
30
|
+
permissionRegistry?: PermissionRegistry | null
|
|
31
|
+
adminPermission?: string
|
|
32
|
+
[key: string]: unknown
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Type for EntityManager base class
|
|
36
|
+
interface EntityManagerInstance {
|
|
37
|
+
_hasSecurityChecker(): boolean
|
|
38
|
+
authAdapter: {
|
|
39
|
+
isGranted(permission: string): boolean
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class RolesManager extends EntityManager<RoleRecord> {
|
|
44
|
+
private _rolesProvider: RoleProvider | null
|
|
45
|
+
private _permissionRegistry: PermissionRegistry | null
|
|
46
|
+
private _adminPermission: string
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Protected system roles that cannot be deleted
|
|
50
|
+
*/
|
|
51
|
+
static PROTECTED_ROLES = ['ROLE_ANONYMOUS']
|
|
52
|
+
|
|
53
|
+
constructor(options: RolesManagerOptions = {}) {
|
|
54
|
+
const {
|
|
55
|
+
rolesProvider = null,
|
|
56
|
+
permissionRegistry = null,
|
|
57
|
+
adminPermission = 'security:roles:manage',
|
|
58
|
+
...rest
|
|
59
|
+
} = options
|
|
60
|
+
|
|
61
|
+
const storage = new RoleProviderStorage(rolesProvider) as unknown as IStorage<RoleRecord>
|
|
62
|
+
|
|
63
|
+
super({
|
|
64
|
+
name: 'roles',
|
|
65
|
+
labelField: 'label',
|
|
66
|
+
idField: 'name',
|
|
67
|
+
system: true, // System-provided entity
|
|
68
|
+
fields: {
|
|
69
|
+
name: { type: 'text', label: 'Role Name', required: true },
|
|
70
|
+
label: { type: 'text', label: 'Display Label' },
|
|
71
|
+
permissions: { type: 'array', label: 'Permissions', default: [] },
|
|
72
|
+
inherits: { type: 'array', label: 'Inherits From', default: [] },
|
|
73
|
+
},
|
|
74
|
+
storage,
|
|
75
|
+
...rest,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
this._rolesProvider = rolesProvider
|
|
79
|
+
this._permissionRegistry = permissionRegistry
|
|
80
|
+
this._adminPermission = adminPermission
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get rolesProvider (for RoleForm permission picker)
|
|
85
|
+
*/
|
|
86
|
+
get rolesProvider(): RoleProvider | null {
|
|
87
|
+
return this._rolesProvider
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get permission registry (for RoleForm permission picker)
|
|
92
|
+
*/
|
|
93
|
+
get permissionRegistry(): PermissionRegistry | null {
|
|
94
|
+
return this._permissionRegistry
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get admin permission (for external registration)
|
|
99
|
+
*/
|
|
100
|
+
get adminPermission(): string {
|
|
101
|
+
return this._adminPermission
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private _isAdmin(): boolean {
|
|
105
|
+
const self = this as unknown as EntityManagerInstance
|
|
106
|
+
if (self._hasSecurityChecker()) {
|
|
107
|
+
return self.authAdapter.isGranted(this._adminPermission)
|
|
108
|
+
}
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
canRead(): boolean {
|
|
113
|
+
return this._isAdmin()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
canCreate(): boolean {
|
|
117
|
+
return this._isAdmin()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
canUpdate(): boolean {
|
|
121
|
+
return this._isAdmin()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Check if can delete (general or row-specific)
|
|
126
|
+
* Protected roles cannot be deleted even with permission
|
|
127
|
+
*/
|
|
128
|
+
canDelete(item?: RoleRecord | null): boolean {
|
|
129
|
+
if (!this._isAdmin()) return false
|
|
130
|
+
if (item) {
|
|
131
|
+
// Protected system roles cannot be deleted
|
|
132
|
+
return !RolesManager.PROTECTED_ROLES.includes(item.name)
|
|
133
|
+
}
|
|
134
|
+
return true
|
|
135
|
+
}
|
|
136
|
+
}
|