qdadm 0.13.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.
- package/CHANGELOG.md +270 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/package.json +48 -0
- package/src/assets/logo.svg +6 -0
- package/src/components/BoolCell.vue +28 -0
- package/src/components/dialogs/BulkStatusDialog.vue +43 -0
- package/src/components/dialogs/MultiStepDialog.vue +321 -0
- package/src/components/dialogs/SimpleDialog.vue +108 -0
- package/src/components/dialogs/UnsavedChangesDialog.vue +87 -0
- package/src/components/display/CardsGrid.vue +155 -0
- package/src/components/display/CopyableId.vue +92 -0
- package/src/components/display/EmptyState.vue +114 -0
- package/src/components/display/IntensityBar.vue +171 -0
- package/src/components/display/RichCardsGrid.vue +220 -0
- package/src/components/editors/JsonEditorFoldable.vue +467 -0
- package/src/components/editors/JsonStructuredField.vue +218 -0
- package/src/components/editors/JsonViewer.vue +91 -0
- package/src/components/editors/KeyValueEditor.vue +314 -0
- package/src/components/editors/LanguageEditor.vue +245 -0
- package/src/components/editors/ScopeEditor.vue +341 -0
- package/src/components/editors/VanillaJsonEditor.vue +185 -0
- package/src/components/forms/FormActions.vue +104 -0
- package/src/components/forms/FormField.vue +64 -0
- package/src/components/forms/FormTab.vue +217 -0
- package/src/components/forms/FormTabs.vue +108 -0
- package/src/components/index.js +44 -0
- package/src/components/layout/AppLayout.vue +430 -0
- package/src/components/layout/Breadcrumb.vue +106 -0
- package/src/components/layout/PageHeader.vue +75 -0
- package/src/components/layout/PageLayout.vue +93 -0
- package/src/components/lists/ActionButtons.vue +41 -0
- package/src/components/lists/ActionColumn.vue +37 -0
- package/src/components/lists/FilterBar.vue +53 -0
- package/src/components/lists/ListPage.vue +319 -0
- package/src/composables/index.js +19 -0
- package/src/composables/useApp.js +43 -0
- package/src/composables/useAuth.js +49 -0
- package/src/composables/useBareForm.js +143 -0
- package/src/composables/useBreadcrumb.js +221 -0
- package/src/composables/useDirtyState.js +103 -0
- package/src/composables/useEntityTitle.js +121 -0
- package/src/composables/useForm.js +254 -0
- package/src/composables/useGuardStore.js +37 -0
- package/src/composables/useJsonSyntax.js +101 -0
- package/src/composables/useListPageBuilder.js +1176 -0
- package/src/composables/useNavigation.js +89 -0
- package/src/composables/usePageBuilder.js +334 -0
- package/src/composables/useStatus.js +146 -0
- package/src/composables/useSubEditor.js +165 -0
- package/src/composables/useTabSync.js +110 -0
- package/src/composables/useUnsavedChangesGuard.js +122 -0
- package/src/entity/EntityManager.js +540 -0
- package/src/entity/index.js +11 -0
- package/src/entity/storage/ApiStorage.js +146 -0
- package/src/entity/storage/LocalStorage.js +220 -0
- package/src/entity/storage/MemoryStorage.js +201 -0
- package/src/entity/storage/index.js +10 -0
- package/src/index.js +29 -0
- package/src/kernel/Kernel.js +234 -0
- package/src/kernel/index.js +7 -0
- package/src/module/index.js +16 -0
- package/src/module/moduleRegistry.js +222 -0
- package/src/orchestrator/Orchestrator.js +141 -0
- package/src/orchestrator/index.js +8 -0
- package/src/orchestrator/useOrchestrator.js +61 -0
- package/src/plugin.js +142 -0
- package/src/styles/_alerts.css +48 -0
- package/src/styles/_code.css +33 -0
- package/src/styles/_dialogs.css +17 -0
- package/src/styles/_markdown.css +82 -0
- package/src/styles/_show-pages.css +84 -0
- package/src/styles/index.css +16 -0
- package/src/styles/main.css +845 -0
- package/src/styles/theme/components.css +286 -0
- package/src/styles/theme/index.css +10 -0
- package/src/styles/theme/tokens.css +125 -0
- package/src/styles/theme/utilities.css +172 -0
- package/src/utils/debugInjector.js +261 -0
- package/src/utils/formatters.js +165 -0
- package/src/utils/index.js +35 -0
- package/src/utils/transformers.js +105 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module Registry - Auto-discovery and registration system
|
|
3
|
+
*
|
|
4
|
+
* Each module can provide an init.js that registers:
|
|
5
|
+
* - Routes
|
|
6
|
+
* - Navigation items
|
|
7
|
+
* - Route families (for active state detection)
|
|
8
|
+
*
|
|
9
|
+
* Usage in module (modules/agents/init.js):
|
|
10
|
+
*
|
|
11
|
+
* export function init(registry) {
|
|
12
|
+
* registry.addRoutes('agents', [
|
|
13
|
+
* { path: '', name: 'agents', component: () => import('./pages/AgentList.vue') },
|
|
14
|
+
* { path: 'create', name: 'agent-create', component: () => import('./pages/AgentForm.vue') },
|
|
15
|
+
* { path: ':id/edit', name: 'agent-edit', component: () => import('./pages/AgentForm.vue') }
|
|
16
|
+
* ])
|
|
17
|
+
*
|
|
18
|
+
* registry.addNavItem({
|
|
19
|
+
* section: 'Simulation',
|
|
20
|
+
* route: 'agents',
|
|
21
|
+
* icon: 'pi pi-user',
|
|
22
|
+
* label: 'Agents'
|
|
23
|
+
* })
|
|
24
|
+
*
|
|
25
|
+
* registry.addRouteFamily('agents', ['agent-'])
|
|
26
|
+
* }
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
// Storage
|
|
30
|
+
const routes = []
|
|
31
|
+
const navItems = []
|
|
32
|
+
const routeFamilies = {}
|
|
33
|
+
const navSections = new Map()
|
|
34
|
+
const entityConfigs = new Map() // Entity declarations from modules
|
|
35
|
+
|
|
36
|
+
// Configurable section order (set via setSectionOrder or bootstrap)
|
|
37
|
+
let sectionOrder = []
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Registry API passed to module init functions
|
|
41
|
+
*/
|
|
42
|
+
const registry = {
|
|
43
|
+
/**
|
|
44
|
+
* Add routes for this module
|
|
45
|
+
* @param {string} prefix - Path prefix for all routes (e.g., 'agents')
|
|
46
|
+
* @param {Array} moduleRoutes - Route definitions with relative paths
|
|
47
|
+
* @param {object} options - { entity?: string } - Entity name for permission checking
|
|
48
|
+
*/
|
|
49
|
+
addRoutes(prefix, moduleRoutes, options = {}) {
|
|
50
|
+
const prefixedRoutes = moduleRoutes.map(route => ({
|
|
51
|
+
...route,
|
|
52
|
+
path: route.path ? `${prefix}/${route.path}` : prefix,
|
|
53
|
+
meta: {
|
|
54
|
+
...route.meta,
|
|
55
|
+
...(options.entity && { entity: options.entity })
|
|
56
|
+
}
|
|
57
|
+
}))
|
|
58
|
+
routes.push(...prefixedRoutes)
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Add a navigation item to a section
|
|
63
|
+
* @param {object} item - { section, route, icon, label, exact? }
|
|
64
|
+
*/
|
|
65
|
+
addNavItem(item) {
|
|
66
|
+
const { section, ...navItem } = item
|
|
67
|
+
if (!navSections.has(section)) {
|
|
68
|
+
navSections.set(section, [])
|
|
69
|
+
}
|
|
70
|
+
navSections.get(section).push(navItem)
|
|
71
|
+
navItems.push(item)
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Add route family mapping (for active state detection)
|
|
76
|
+
* @param {string} parent - Parent route name (e.g., 'agents')
|
|
77
|
+
* @param {Array<string>} prefixes - Child route prefixes (e.g., ['agent-'])
|
|
78
|
+
*/
|
|
79
|
+
addRouteFamily(parent, prefixes) {
|
|
80
|
+
if (!routeFamilies[parent]) {
|
|
81
|
+
routeFamilies[parent] = []
|
|
82
|
+
}
|
|
83
|
+
routeFamilies[parent].push(...prefixes)
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Declare an entity managed by this module
|
|
88
|
+
* @param {string} name - Entity name (e.g., 'users')
|
|
89
|
+
* @param {object|EntityManager} config - Entity config or manager instance
|
|
90
|
+
* - If EntityManager instance: used directly
|
|
91
|
+
* - If object: { endpoint, idField, storage?, ... } passed to factory
|
|
92
|
+
*
|
|
93
|
+
* Usage:
|
|
94
|
+
* registry.addEntity('users', { endpoint: '/users' })
|
|
95
|
+
* registry.addEntity('users', new CustomUsersManager())
|
|
96
|
+
*/
|
|
97
|
+
addEntity(name, config) {
|
|
98
|
+
entityConfigs.set(name, config)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Set section order for navigation menu
|
|
104
|
+
* Called from bootstrap config
|
|
105
|
+
* @param {Array<string>} order - Section titles in order
|
|
106
|
+
*/
|
|
107
|
+
export function setSectionOrder(order) {
|
|
108
|
+
sectionOrder = order || []
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Initialize modules from a glob import result
|
|
113
|
+
* The consuming app calls this with its own import.meta.glob
|
|
114
|
+
*
|
|
115
|
+
* Usage in app:
|
|
116
|
+
* const moduleInits = import.meta.glob('./modules/* /init.js', { eager: true })
|
|
117
|
+
* initModules(moduleInits)
|
|
118
|
+
*
|
|
119
|
+
* @param {object} moduleInits - Result of import.meta.glob
|
|
120
|
+
* @param {object} options - { coreNavItems: [] } - Core items not in modules
|
|
121
|
+
*/
|
|
122
|
+
export function initModules(moduleInits, options = {}) {
|
|
123
|
+
// Add core nav items (pages that aren't in modules)
|
|
124
|
+
if (options.coreNavItems) {
|
|
125
|
+
for (const item of options.coreNavItems) {
|
|
126
|
+
registry.addNavItem(item)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Initialize all discovered modules
|
|
131
|
+
for (const path in moduleInits) {
|
|
132
|
+
const module = moduleInits[path]
|
|
133
|
+
if (typeof module.init === 'function') {
|
|
134
|
+
module.init(registry)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get all registered routes
|
|
141
|
+
*/
|
|
142
|
+
export function getRoutes() {
|
|
143
|
+
return routes
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get navigation sections in order
|
|
148
|
+
*/
|
|
149
|
+
export function getNavSections() {
|
|
150
|
+
const sections = []
|
|
151
|
+
|
|
152
|
+
// First add sections in the configured order
|
|
153
|
+
for (const title of sectionOrder) {
|
|
154
|
+
if (navSections.has(title)) {
|
|
155
|
+
sections.push({
|
|
156
|
+
title,
|
|
157
|
+
items: navSections.get(title)
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Add any sections not in the predefined order
|
|
163
|
+
for (const [title, items] of navSections) {
|
|
164
|
+
if (!sectionOrder.includes(title)) {
|
|
165
|
+
sections.push({ title, items })
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return sections
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get route families mapping
|
|
174
|
+
*/
|
|
175
|
+
export function getRouteFamilies() {
|
|
176
|
+
return routeFamilies
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get entity configurations declared by modules
|
|
181
|
+
* @returns {Map<string, object>}
|
|
182
|
+
*/
|
|
183
|
+
export function getEntityConfigs() {
|
|
184
|
+
return entityConfigs
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get a specific entity configuration
|
|
189
|
+
* @param {string} name - Entity name
|
|
190
|
+
* @returns {object|undefined}
|
|
191
|
+
*/
|
|
192
|
+
export function getEntityConfig(name) {
|
|
193
|
+
return entityConfigs.get(name)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Check if a route belongs to a family
|
|
198
|
+
*/
|
|
199
|
+
export function isRouteInFamily(currentRoute, parentRoute) {
|
|
200
|
+
if (currentRoute === parentRoute) return true
|
|
201
|
+
|
|
202
|
+
const prefixes = routeFamilies[parentRoute]
|
|
203
|
+
if (prefixes && currentRoute) {
|
|
204
|
+
return prefixes.some(prefix => currentRoute.startsWith(prefix))
|
|
205
|
+
}
|
|
206
|
+
return false
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Reset registry (for testing)
|
|
211
|
+
*/
|
|
212
|
+
export function resetRegistry() {
|
|
213
|
+
routes.length = 0
|
|
214
|
+
navItems.length = 0
|
|
215
|
+
Object.keys(routeFamilies).forEach(key => delete routeFamilies[key])
|
|
216
|
+
navSections.clear()
|
|
217
|
+
entityConfigs.clear()
|
|
218
|
+
sectionOrder = []
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Export registry for direct access if needed
|
|
222
|
+
export { registry }
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrator - Manager of EntityManagers
|
|
3
|
+
*
|
|
4
|
+
* Central registry that creates and distributes EntityManagers.
|
|
5
|
+
* Uses a factory function provided by the consuming application.
|
|
6
|
+
* Entity configs can be declared by modules via registry.addEntity().
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ```js
|
|
10
|
+
* // Dashboard provides a factory that receives (name, config)
|
|
11
|
+
* const entityFactory = (entityName, entityConfig) => {
|
|
12
|
+
* // entityConfig comes from registry.addEntity() or is undefined
|
|
13
|
+
* if (entityConfig instanceof EntityManager) {
|
|
14
|
+
* return entityConfig // Module provided a custom manager
|
|
15
|
+
* }
|
|
16
|
+
* return new EntityManager({
|
|
17
|
+
* name: entityName,
|
|
18
|
+
* storage: new ApiStorage({
|
|
19
|
+
* endpoint: entityConfig?.endpoint || `/${entityName}`,
|
|
20
|
+
* client
|
|
21
|
+
* })
|
|
22
|
+
* })
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* const orchestrator = new Orchestrator({ entityFactory })
|
|
26
|
+
*
|
|
27
|
+
* // Get managers (created lazily)
|
|
28
|
+
* const usersManager = orchestrator.get('users')
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
import { getEntityConfig } from '../module/moduleRegistry.js'
|
|
32
|
+
|
|
33
|
+
export class Orchestrator {
|
|
34
|
+
constructor(options = {}) {
|
|
35
|
+
const {
|
|
36
|
+
entityFactory = null,
|
|
37
|
+
// Optional: pre-registered managers (for special cases)
|
|
38
|
+
managers = {}
|
|
39
|
+
} = options
|
|
40
|
+
|
|
41
|
+
this._entityFactory = entityFactory
|
|
42
|
+
this._managers = new Map()
|
|
43
|
+
|
|
44
|
+
// Register pre-provided managers
|
|
45
|
+
for (const [name, manager] of Object.entries(managers)) {
|
|
46
|
+
this.register(name, manager)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Set the entity factory
|
|
52
|
+
* @param {function} factory - (entityName, entityConfig) => EntityManager
|
|
53
|
+
*/
|
|
54
|
+
setFactory(factory) {
|
|
55
|
+
this._entityFactory = factory
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Register an EntityManager manually
|
|
60
|
+
* Use this for special managers that don't follow the factory pattern
|
|
61
|
+
*
|
|
62
|
+
* @param {string} name - Entity name
|
|
63
|
+
* @param {EntityManager} manager - Manager instance
|
|
64
|
+
*/
|
|
65
|
+
register(name, manager) {
|
|
66
|
+
this._managers.set(name, manager)
|
|
67
|
+
if (manager.onRegister) {
|
|
68
|
+
manager.onRegister(this)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check if a manager exists (registered or can be created)
|
|
74
|
+
* @param {string} name - Entity name
|
|
75
|
+
* @returns {boolean}
|
|
76
|
+
*/
|
|
77
|
+
has(name) {
|
|
78
|
+
return this._managers.has(name) || !!this._entityFactory
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get an EntityManager by name
|
|
83
|
+
* Creates it via factory if not already registered
|
|
84
|
+
*
|
|
85
|
+
* @param {string} name - Entity name
|
|
86
|
+
* @returns {EntityManager}
|
|
87
|
+
*/
|
|
88
|
+
get(name) {
|
|
89
|
+
// Return existing manager
|
|
90
|
+
if (this._managers.has(name)) {
|
|
91
|
+
return this._managers.get(name)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Get entity config from module registry (if declared)
|
|
95
|
+
const entityConfig = getEntityConfig(name)
|
|
96
|
+
|
|
97
|
+
// If config is already an EntityManager, use it directly
|
|
98
|
+
if (entityConfig && typeof entityConfig.get === 'function') {
|
|
99
|
+
this.register(name, entityConfig)
|
|
100
|
+
return entityConfig
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Create via factory
|
|
104
|
+
if (this._entityFactory) {
|
|
105
|
+
const manager = this._entityFactory(name, entityConfig)
|
|
106
|
+
if (manager) {
|
|
107
|
+
this.register(name, manager)
|
|
108
|
+
return manager
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
throw new Error(`[Orchestrator] No manager for entity "${name}" and no factory provided`)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get all registered manager names
|
|
117
|
+
* @returns {string[]}
|
|
118
|
+
*/
|
|
119
|
+
getRegisteredNames() {
|
|
120
|
+
return Array.from(this._managers.keys())
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Dispose all managers
|
|
125
|
+
*/
|
|
126
|
+
dispose() {
|
|
127
|
+
for (const [, manager] of this._managers) {
|
|
128
|
+
if (manager.onDispose) {
|
|
129
|
+
manager.onDispose()
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
this._managers.clear()
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Factory function to create an Orchestrator
|
|
138
|
+
*/
|
|
139
|
+
export function createOrchestrator(options) {
|
|
140
|
+
return new Orchestrator(options)
|
|
141
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useOrchestrator - Composable to access the Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* ```js
|
|
6
|
+
* const { orchestrator, getManager } = useOrchestrator()
|
|
7
|
+
*
|
|
8
|
+
* // Get a specific manager
|
|
9
|
+
* const usersManager = getManager('users')
|
|
10
|
+
*
|
|
11
|
+
* // Use it
|
|
12
|
+
* const { items } = await usersManager.list({ page: 1 })
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
import { inject } from 'vue'
|
|
16
|
+
|
|
17
|
+
export function useOrchestrator() {
|
|
18
|
+
const orchestrator = inject('qdadmOrchestrator')
|
|
19
|
+
|
|
20
|
+
if (!orchestrator) {
|
|
21
|
+
throw new Error('[qdadm] Orchestrator not provided. Make sure to use createQdadm() with entityFactory.')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get an EntityManager by name
|
|
26
|
+
* @param {string} name - Entity name
|
|
27
|
+
* @returns {EntityManager}
|
|
28
|
+
*/
|
|
29
|
+
function getManager(name) {
|
|
30
|
+
return orchestrator.get(name)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check if a manager exists
|
|
35
|
+
* @param {string} name - Entity name
|
|
36
|
+
* @returns {boolean}
|
|
37
|
+
*/
|
|
38
|
+
function hasManager(name) {
|
|
39
|
+
return orchestrator.has(name)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
orchestrator,
|
|
44
|
+
getManager,
|
|
45
|
+
hasManager
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* useEntity - Shorthand composable to get a specific EntityManager
|
|
51
|
+
*
|
|
52
|
+
* Usage:
|
|
53
|
+
* ```js
|
|
54
|
+
* const users = useEntity('users')
|
|
55
|
+
* const { items } = await users.list()
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export function useEntity(name) {
|
|
59
|
+
const { getManager } = useOrchestrator()
|
|
60
|
+
return getManager(name)
|
|
61
|
+
}
|
package/src/plugin.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qdadm Vue Plugin
|
|
3
|
+
*
|
|
4
|
+
* Creates and configures the qdadm framework for Vue applications.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Orchestrator } from './orchestrator/Orchestrator.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates the qdadm plugin with the given configuration
|
|
11
|
+
*
|
|
12
|
+
* @param {object} options - Configuration options
|
|
13
|
+
* @param {function} options.entityFactory - Factory function: (entityName, entityConfig) => EntityManager
|
|
14
|
+
* @param {object} options.managers - Pre-registered managers to override defaults (e.g., { users: myUsersManager })
|
|
15
|
+
* @param {object} options.orchestrator - Or provide a pre-configured Orchestrator instance
|
|
16
|
+
* @param {object} options.authAdapter - Optional: Auth adapter for login/logout
|
|
17
|
+
* @param {object} options.router - Required: Vue Router instance
|
|
18
|
+
* @param {object} options.toast - Required: Toast notification service
|
|
19
|
+
* @param {object} options.app - Optional: App branding configuration
|
|
20
|
+
* @param {object} options.modules - Optional: Module system configuration
|
|
21
|
+
* @param {object} options.features - Optional: Feature toggles (auth, poweredBy)
|
|
22
|
+
* @param {object} options.builtinModules - Optional: Builtin modules configuration
|
|
23
|
+
* @param {object} options.endpoints - Optional: API endpoints configuration
|
|
24
|
+
* @returns {object} Vue plugin
|
|
25
|
+
*/
|
|
26
|
+
export function createQdadm(options) {
|
|
27
|
+
return {
|
|
28
|
+
install(app) {
|
|
29
|
+
// Validation des contrats requis
|
|
30
|
+
if (!options.entityFactory && !options.orchestrator && !options.managers) {
|
|
31
|
+
throw new Error('[qdadm] entityFactory, orchestrator, or managers required')
|
|
32
|
+
}
|
|
33
|
+
if (!options.router) {
|
|
34
|
+
throw new Error('[qdadm] router required')
|
|
35
|
+
}
|
|
36
|
+
if (!options.toast) {
|
|
37
|
+
throw new Error('[qdadm] toast required')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Create or use provided Orchestrator
|
|
41
|
+
let orchestrator = options.orchestrator
|
|
42
|
+
if (!orchestrator) {
|
|
43
|
+
orchestrator = new Orchestrator({
|
|
44
|
+
entityFactory: options.entityFactory,
|
|
45
|
+
managers: options.managers || {}
|
|
46
|
+
})
|
|
47
|
+
} else if (options.managers) {
|
|
48
|
+
// Register additional managers on existing orchestrator
|
|
49
|
+
for (const [name, manager] of Object.entries(options.managers)) {
|
|
50
|
+
orchestrator.register(name, manager)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Features config avec défauts
|
|
55
|
+
const features = {
|
|
56
|
+
auth: true,
|
|
57
|
+
poweredBy: true,
|
|
58
|
+
...options.features
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// App config avec défauts
|
|
62
|
+
const appConfig = {
|
|
63
|
+
name: 'Admin',
|
|
64
|
+
shortName: 'Admin',
|
|
65
|
+
logo: null,
|
|
66
|
+
logoSmall: null,
|
|
67
|
+
favicon: null,
|
|
68
|
+
version: null,
|
|
69
|
+
theme: {
|
|
70
|
+
primary: '#3B82F6',
|
|
71
|
+
surface: '#1E293B',
|
|
72
|
+
...options.app?.theme
|
|
73
|
+
},
|
|
74
|
+
...options.app
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Endpoints config
|
|
78
|
+
const endpoints = {
|
|
79
|
+
users: '/users',
|
|
80
|
+
roles: '/roles',
|
|
81
|
+
...options.endpoints
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Builtin modules config
|
|
85
|
+
const builtinModules = {
|
|
86
|
+
users: features.auth,
|
|
87
|
+
roles: features.auth,
|
|
88
|
+
...options.builtinModules
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Validation conditionnelle
|
|
92
|
+
if (features.auth && !options.authAdapter) {
|
|
93
|
+
throw new Error('[qdadm] authAdapter required when features.auth is true')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Provide all dependencies
|
|
97
|
+
app.provide('qdadmOrchestrator', orchestrator)
|
|
98
|
+
app.provide('qdadmRouter', options.router)
|
|
99
|
+
app.provide('qdadmToast', options.toast)
|
|
100
|
+
app.provide('qdadmFeatures', features)
|
|
101
|
+
app.provide('qdadmApp', appConfig)
|
|
102
|
+
app.provide('qdadmEndpoints', endpoints)
|
|
103
|
+
app.provide('qdadmBuiltinModules', builtinModules)
|
|
104
|
+
|
|
105
|
+
if (options.authAdapter) {
|
|
106
|
+
app.provide('authAdapter', options.authAdapter)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (options.modules?.sectionOrder) {
|
|
110
|
+
app.provide('qdadmSectionOrder', options.modules.sectionOrder)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Add route guard for entity permissions
|
|
114
|
+
options.router.beforeEach((to, from, next) => {
|
|
115
|
+
const entity = to.meta?.entity
|
|
116
|
+
if (entity) {
|
|
117
|
+
const manager = orchestrator.get(entity)
|
|
118
|
+
if (manager && !manager.canRead()) {
|
|
119
|
+
// Redirect to home or show forbidden
|
|
120
|
+
console.warn(`[qdadm] Access denied to ${to.path} (entity: ${entity})`)
|
|
121
|
+
return next({ path: '/' })
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
next()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// Appliquer le thème via CSS variables
|
|
128
|
+
if (appConfig.theme) {
|
|
129
|
+
document.documentElement.style.setProperty('--qdadm-primary', appConfig.theme.primary)
|
|
130
|
+
document.documentElement.style.setProperty('--qdadm-surface', appConfig.theme.surface)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Appliquer le favicon
|
|
134
|
+
if (appConfig.favicon) {
|
|
135
|
+
const link = document.querySelector("link[rel~='icon']") || document.createElement('link')
|
|
136
|
+
link.rel = 'icon'
|
|
137
|
+
link.href = appConfig.favicon
|
|
138
|
+
document.head.appendChild(link)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alert and Notice Boxes
|
|
3
|
+
* Warning, info, and system notice styles
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* Warning banner (yellow) - for system notices */
|
|
7
|
+
.alert-warning {
|
|
8
|
+
background: var(--p-yellow-50);
|
|
9
|
+
padding: 1rem;
|
|
10
|
+
margin-bottom: 1rem;
|
|
11
|
+
border-radius: 0.5rem;
|
|
12
|
+
border: 1px solid var(--p-yellow-200);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.alert-warning-icon {
|
|
16
|
+
color: var(--p-yellow-600);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.alert-warning-text {
|
|
20
|
+
margin-left: 0.5rem;
|
|
21
|
+
color: var(--p-yellow-700);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Info banner (orange) - for important notices */
|
|
25
|
+
.alert-info {
|
|
26
|
+
color: var(--p-orange-600);
|
|
27
|
+
margin-bottom: 1rem;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Key info panel - for displaying read-only metadata */
|
|
31
|
+
.key-info-panel {
|
|
32
|
+
background: var(--p-surface-50);
|
|
33
|
+
padding: 1rem;
|
|
34
|
+
margin-bottom: 1rem;
|
|
35
|
+
border-radius: 0.5rem;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.key-info-grid {
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-wrap: wrap;
|
|
41
|
+
gap: 2rem;
|
|
42
|
+
align-items: center;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.key-info-item small {
|
|
46
|
+
color: var(--p-surface-500);
|
|
47
|
+
display: block;
|
|
48
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code and Key Display
|
|
3
|
+
* Monospace text, code boxes, and key displays
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* Code box - for displaying keys, code, or technical values */
|
|
7
|
+
.code-box {
|
|
8
|
+
word-break: break-all;
|
|
9
|
+
font-family: monospace;
|
|
10
|
+
background: var(--p-surface-100);
|
|
11
|
+
padding: 1rem;
|
|
12
|
+
border-radius: 0.25rem;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Small code display */
|
|
16
|
+
.code-box--sm {
|
|
17
|
+
padding: 0.5rem;
|
|
18
|
+
font-size: 0.875rem;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Tag with small font for compact display */
|
|
22
|
+
.tag-sm {
|
|
23
|
+
font-size: 0.7rem;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.tag-xs {
|
|
27
|
+
font-size: 0.75rem;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Constrained width for tag previews */
|
|
31
|
+
.tags-constrained {
|
|
32
|
+
max-width: 300px;
|
|
33
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dialog Content Styles
|
|
3
|
+
* Styles for modal dialog content
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* Dialog content spacing */
|
|
7
|
+
.dialog-content {
|
|
8
|
+
margin-bottom: 1rem;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* Dialog section title */
|
|
12
|
+
.dialog-section-title {
|
|
13
|
+
font-size: 0.875rem;
|
|
14
|
+
font-weight: 600;
|
|
15
|
+
margin-bottom: 0.5rem;
|
|
16
|
+
color: var(--p-surface-600);
|
|
17
|
+
}
|