@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.
Files changed (291) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -0
  3. package/package.json +101 -0
  4. package/src/assets/logo.svg +6 -0
  5. package/src/auth/SessionAuthAdapter.ts +459 -0
  6. package/src/auth/index.ts +15 -0
  7. package/src/chain/ActiveStack.ts +154 -0
  8. package/src/chain/StackHydrator.ts +292 -0
  9. package/src/chain/index.ts +25 -0
  10. package/src/chain/useActiveStack.ts +120 -0
  11. package/src/chain/useStackHydrator.ts +155 -0
  12. package/src/components/BoolCell.vue +34 -0
  13. package/src/components/InfoBanner.vue +73 -0
  14. package/src/components/QdadmRoot.vue +52 -0
  15. package/src/components/SeverityTag.vue +86 -0
  16. package/src/components/dialogs/BulkStatusDialog.vue +60 -0
  17. package/src/components/dialogs/MultiStepDialog.vue +331 -0
  18. package/src/components/dialogs/SimpleDialog.vue +122 -0
  19. package/src/components/dialogs/UnsavedChangesDialog.vue +80 -0
  20. package/src/components/display/BannerZone.vue +65 -0
  21. package/src/components/display/CardsGrid.vue +72 -0
  22. package/src/components/display/CopyableId.vue +84 -0
  23. package/src/components/display/EmptyState.vue +62 -0
  24. package/src/components/display/IntensityBar.vue +190 -0
  25. package/src/components/display/RichCardsGrid.vue +101 -0
  26. package/src/components/edit/FormActions.vue +78 -0
  27. package/src/components/edit/FormField.vue +82 -0
  28. package/src/components/edit/FormInput.vue +158 -0
  29. package/src/components/edit/FormPage.vue +260 -0
  30. package/src/components/edit/FormTab.vue +217 -0
  31. package/src/components/edit/FormTabs.vue +120 -0
  32. package/src/components/edit/LookupField.vue +406 -0
  33. package/src/components/edit/LookupPickerDialog.vue +230 -0
  34. package/src/components/editors/JsonEditorFoldable.vue +470 -0
  35. package/src/components/editors/JsonStructuredField.vue +264 -0
  36. package/src/components/editors/JsonViewer.vue +51 -0
  37. package/src/components/editors/KeyValueEditor.vue +314 -0
  38. package/src/components/editors/LanguageEditor.vue +259 -0
  39. package/src/components/editors/PermissionEditor.vue +558 -0
  40. package/src/components/editors/ScopeEditor.vue +372 -0
  41. package/src/components/editors/VanillaJsonEditor.vue +183 -0
  42. package/src/components/index.ts +83 -0
  43. package/src/components/item/FieldGroups.vue +392 -0
  44. package/src/components/layout/AppLayout.vue +1032 -0
  45. package/src/components/layout/BaseLayout.vue +190 -0
  46. package/src/components/layout/Breadcrumb.vue +55 -0
  47. package/src/components/layout/DashboardLayout.vue +104 -0
  48. package/src/components/layout/FormLayout.vue +256 -0
  49. package/src/components/layout/ListLayout.vue +278 -0
  50. package/src/components/layout/PageHeader.vue +156 -0
  51. package/src/components/layout/PageLayout.vue +126 -0
  52. package/src/components/layout/PageNav.vue +192 -0
  53. package/src/components/layout/SidebarBox.vue +87 -0
  54. package/src/components/layout/Zone.vue +182 -0
  55. package/src/components/layout/defaults/DefaultBreadcrumb.vue +149 -0
  56. package/src/components/layout/defaults/DefaultFooter.vue +89 -0
  57. package/src/components/layout/defaults/DefaultFormActions.vue +67 -0
  58. package/src/components/layout/defaults/DefaultHeader.vue +78 -0
  59. package/src/components/layout/defaults/DefaultMenu.vue +188 -0
  60. package/src/components/layout/defaults/DefaultPagination.vue +75 -0
  61. package/src/components/layout/defaults/DefaultTable.vue +137 -0
  62. package/src/components/layout/defaults/DefaultUserInfo.vue +111 -0
  63. package/src/components/layout/defaults/index.ts +16 -0
  64. package/src/components/lists/ActionButtons.vue +49 -0
  65. package/src/components/lists/ActionColumn.vue +40 -0
  66. package/src/components/lists/FilterBar.vue +54 -0
  67. package/src/components/lists/ListPage.vue +467 -0
  68. package/src/components/pages/LoginPage.vue +334 -0
  69. package/src/components/pages/NotFoundPage.vue +98 -0
  70. package/src/components/show/ShowDisplay.vue +343 -0
  71. package/src/components/show/ShowField.vue +67 -0
  72. package/src/components/show/ShowPage.vue +431 -0
  73. package/src/composables/index.ts +155 -0
  74. package/src/composables/useApp.ts +67 -0
  75. package/src/composables/useAuth.ts +98 -0
  76. package/src/composables/useBareForm.ts +319 -0
  77. package/src/composables/useBreadcrumb.ts +252 -0
  78. package/src/composables/useChildPage.ts +113 -0
  79. package/src/composables/useCurrentEntity.ts +60 -0
  80. package/src/composables/useDeferred.ts +94 -0
  81. package/src/composables/useDirtyState.ts +138 -0
  82. package/src/composables/useEntityItemFormPage.ts +942 -0
  83. package/src/composables/useEntityItemFormPage.types.ts +451 -0
  84. package/src/composables/useEntityItemPage.ts +438 -0
  85. package/src/composables/useEntityItemShowPage.ts +722 -0
  86. package/src/composables/useFieldManager.ts +692 -0
  87. package/src/composables/useForm.ts +544 -0
  88. package/src/composables/useGuardStore.ts +53 -0
  89. package/src/composables/useHooks.ts +56 -0
  90. package/src/composables/useInfoBanner.ts +217 -0
  91. package/src/composables/useJsonSyntax.ts +122 -0
  92. package/src/composables/useLayoutResolver.ts +329 -0
  93. package/src/composables/useListPage.ts +1535 -0
  94. package/src/composables/useListPage.types.ts +437 -0
  95. package/src/composables/useListPage.utils.ts +101 -0
  96. package/src/composables/useNavContext.ts +439 -0
  97. package/src/composables/useNavigation.ts +216 -0
  98. package/src/composables/useOptionsLookup.ts +323 -0
  99. package/src/composables/usePageTitle.ts +84 -0
  100. package/src/composables/useSSEBridge.ts +142 -0
  101. package/src/composables/useSemanticBreadcrumb.ts +222 -0
  102. package/src/composables/useSidebarState.ts +24 -0
  103. package/src/composables/useSignals.ts +52 -0
  104. package/src/composables/useUnsavedChangesGuard.ts +155 -0
  105. package/src/composables/useUserImpersonator.ts +317 -0
  106. package/src/composables/useZoneRegistry.ts +252 -0
  107. package/src/core/bundles.ts +558 -0
  108. package/src/core/decorator.ts +420 -0
  109. package/src/core/extension.ts +470 -0
  110. package/src/core/index.ts +59 -0
  111. package/src/deferred/DeferredRegistry.ts +353 -0
  112. package/src/deferred/index.ts +15 -0
  113. package/src/editors/index.ts +12 -0
  114. package/src/entity/EntityManager.cache.ts +530 -0
  115. package/src/entity/EntityManager.crud.ts +615 -0
  116. package/src/entity/EntityManager.interface.ts +78 -0
  117. package/src/entity/EntityManager.query.ts +221 -0
  118. package/src/entity/EntityManager.relations.ts +151 -0
  119. package/src/entity/EntityManager.ts +944 -0
  120. package/src/entity/EntityManager.types.ts +288 -0
  121. package/src/entity/auth/CompositeAuthAdapter.ts +194 -0
  122. package/src/entity/auth/EntityAuthAdapter.ts +119 -0
  123. package/src/entity/auth/PermissiveAdapter.ts +43 -0
  124. package/src/entity/auth/RoleHierarchy.ts +128 -0
  125. package/src/entity/auth/SecurityChecker.ts +202 -0
  126. package/src/entity/auth/factory.ts +203 -0
  127. package/src/entity/auth/index.ts +47 -0
  128. package/src/entity/factory.test.js +189 -0
  129. package/src/entity/factory.ts +146 -0
  130. package/src/entity/index.ts +49 -0
  131. package/src/entity/storage/ApiStorage.ts +228 -0
  132. package/src/entity/storage/IStorage.ts +100 -0
  133. package/src/entity/storage/LocalStorage.ts +200 -0
  134. package/src/entity/storage/MemoryStorage.ts +184 -0
  135. package/src/entity/storage/MockApiStorage.ts +318 -0
  136. package/src/entity/storage/SdkStorage.ts +377 -0
  137. package/src/entity/storage/factory.test.js +159 -0
  138. package/src/entity/storage/factory.ts +209 -0
  139. package/src/entity/storage/index.ts +75 -0
  140. package/src/gen/FieldMapper.ts +122 -0
  141. package/src/gen/StorageProfileFactory.ts +93 -0
  142. package/src/gen/connectors/BaseConnector.ts +150 -0
  143. package/src/gen/connectors/ManualConnector.test.js +499 -0
  144. package/src/gen/connectors/ManualConnector.ts +414 -0
  145. package/src/gen/connectors/OpenAPIConnector.test.js +836 -0
  146. package/src/gen/connectors/OpenAPIConnector.ts +693 -0
  147. package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
  148. package/src/gen/connectors/index.ts +27 -0
  149. package/src/gen/createManagers.ts +245 -0
  150. package/src/gen/decorators.ts +134 -0
  151. package/src/gen/generateManagers.test.js +540 -0
  152. package/src/gen/generateManagers.ts +398 -0
  153. package/src/gen/index.ts +66 -0
  154. package/src/gen/schema.ts +248 -0
  155. package/src/gen/vite-plugin.ts +114 -0
  156. package/src/generated/managers/testManager.js +45 -0
  157. package/src/generated/managers/testManager.ts +46 -0
  158. package/src/hooks/HookRegistry.ts +16 -0
  159. package/src/hooks/index.ts +15 -0
  160. package/src/i18n/I18n.ts +344 -0
  161. package/src/i18n/IncrementalDomainProvider.ts +153 -0
  162. package/src/i18n/InlineTranslationProvider.ts +4 -0
  163. package/src/i18n/LazyTranslationProvider.ts +102 -0
  164. package/src/i18n/MessagesRegistry.ts +4 -0
  165. package/src/i18n/Resolver.ts +4 -0
  166. package/src/i18n/__tests__/I18n.test.ts +169 -0
  167. package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
  168. package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
  169. package/src/i18n/__tests__/Resolver.test.ts +271 -0
  170. package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
  171. package/src/i18n/defaults/core.en.yml +55 -0
  172. package/src/i18n/defaults/core.fr.yml +55 -0
  173. package/src/i18n/index.ts +55 -0
  174. package/src/i18n/loaders/raw-modules.d.ts +15 -0
  175. package/src/i18n/loaders/yaml.ts +35 -0
  176. package/src/i18n/strategies.ts +4 -0
  177. package/src/i18n/types.ts +34 -0
  178. package/src/i18n/useI18n.ts +34 -0
  179. package/src/index.ts +453 -0
  180. package/src/kernel/EventRouter.ts +23 -0
  181. package/src/kernel/Kernel.api.ts +79 -0
  182. package/src/kernel/Kernel.auth.ts +120 -0
  183. package/src/kernel/Kernel.i18n.ts +29 -0
  184. package/src/kernel/Kernel.modules.ts +170 -0
  185. package/src/kernel/Kernel.registries.ts +237 -0
  186. package/src/kernel/Kernel.routing.ts +334 -0
  187. package/src/kernel/Kernel.ts +460 -0
  188. package/src/kernel/Kernel.types.ts +263 -0
  189. package/src/kernel/Kernel.vue.ts +304 -0
  190. package/src/kernel/KernelContext.entities.ts +80 -0
  191. package/src/kernel/KernelContext.events.ts +57 -0
  192. package/src/kernel/KernelContext.i18n.ts +37 -0
  193. package/src/kernel/KernelContext.permissions.ts +38 -0
  194. package/src/kernel/KernelContext.routing.ts +280 -0
  195. package/src/kernel/KernelContext.ts +252 -0
  196. package/src/kernel/KernelContext.types.ts +173 -0
  197. package/src/kernel/KernelContext.zones.ts +54 -0
  198. package/src/kernel/Module.ts +169 -0
  199. package/src/kernel/ModuleLoader.ts +645 -0
  200. package/src/kernel/SSEBridge.ts +11 -0
  201. package/src/kernel/SignalBus.ts +48 -0
  202. package/src/kernel/index.ts +56 -0
  203. package/src/module/index.ts +33 -0
  204. package/src/module/moduleRegistry.ts +492 -0
  205. package/src/modules/debug/AuthCollector.ts +501 -0
  206. package/src/modules/debug/Collector.ts +20 -0
  207. package/src/modules/debug/DebugBridge.ts +14 -0
  208. package/src/modules/debug/DebugModule.ts +220 -0
  209. package/src/modules/debug/EntitiesCollector.ts +839 -0
  210. package/src/modules/debug/ErrorCollector.ts +4 -0
  211. package/src/modules/debug/I18nCollector.ts +9 -0
  212. package/src/modules/debug/LocalStorageAdapter.ts +7 -0
  213. package/src/modules/debug/RouterCollector.ts +449 -0
  214. package/src/modules/debug/SignalCollector.ts +4 -0
  215. package/src/modules/debug/ToastCollector.ts +4 -0
  216. package/src/modules/debug/ZonesCollector.ts +447 -0
  217. package/src/modules/debug/components/DebugBar.vue +31 -0
  218. package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
  219. package/src/modules/debug/components/index.ts +9 -0
  220. package/src/modules/debug/components/panels/AuthPanel.vue +267 -0
  221. package/src/modules/debug/components/panels/EntitiesPanel.vue +572 -0
  222. package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
  223. package/src/modules/debug/components/panels/RouterPanel.vue +440 -0
  224. package/src/modules/debug/components/panels/ZonesPanel.vue +133 -0
  225. package/src/modules/debug/components/panels/index.ts +16 -0
  226. package/src/modules/debug/index.ts +58 -0
  227. package/src/modules/debug/styles.scss +2671 -0
  228. package/src/notifications/NotificationBadge.vue +35 -0
  229. package/src/notifications/NotificationListener.vue +71 -0
  230. package/src/notifications/NotificationModule.ts +76 -0
  231. package/src/notifications/NotificationPanel.vue +478 -0
  232. package/src/notifications/NotificationStore.ts +288 -0
  233. package/src/notifications/index.ts +42 -0
  234. package/src/notifications/styles.scss +32 -0
  235. package/src/orchestrator/Orchestrator.ts +427 -0
  236. package/src/orchestrator/index.ts +8 -0
  237. package/src/orchestrator/useOrchestrator.ts +80 -0
  238. package/src/plugin.ts +251 -0
  239. package/src/query/FilterQuery.ts +306 -0
  240. package/src/query/QueryExecutor.ts +341 -0
  241. package/src/query/index.ts +16 -0
  242. package/src/security/EntityRolesProvider.ts +385 -0
  243. package/src/security/PermissionMatcher.ts +152 -0
  244. package/src/security/PermissionRegistry.ts +273 -0
  245. package/src/security/PersistableRolesProvider.ts +584 -0
  246. package/src/security/RolesManager.ts +136 -0
  247. package/src/security/RolesProvider.ts +167 -0
  248. package/src/security/RolesProviderStorage.ts +211 -0
  249. package/src/security/SecurityModule.ts +94 -0
  250. package/src/security/StaticRolesProvider.ts +117 -0
  251. package/src/security/UsersManager.ts +163 -0
  252. package/src/security/index.ts +77 -0
  253. package/src/security/pages/RoleForm.vue +276 -0
  254. package/src/security/pages/RoleList.vue +123 -0
  255. package/src/styles/_alerts.scss +95 -0
  256. package/src/styles/_buttons.scss +391 -0
  257. package/src/styles/_cards.scss +241 -0
  258. package/src/styles/_code.scss +78 -0
  259. package/src/styles/_dialogs.scss +55 -0
  260. package/src/styles/_filter-bar.scss +70 -0
  261. package/src/styles/_forms.scss +434 -0
  262. package/src/styles/_helpers.scss +153 -0
  263. package/src/styles/_lists.scss +225 -0
  264. package/src/styles/_main.scss +356 -0
  265. package/src/styles/_markdown.scss +88 -0
  266. package/src/styles/_responsive.scss +255 -0
  267. package/src/styles/_show-pages.scss +186 -0
  268. package/src/styles/_sidebar.scss +380 -0
  269. package/src/styles/_states.scss +115 -0
  270. package/src/styles/_stats.scss +47 -0
  271. package/src/styles/_variables.scss +224 -0
  272. package/src/styles/index.scss +38 -0
  273. package/src/styles/theme/_components.scss +331 -0
  274. package/src/styles/theme/_index.scss +10 -0
  275. package/src/styles/theme/_tokens.scss +147 -0
  276. package/src/styles/theme/_utilities.scss +175 -0
  277. package/src/toast/ToastBridgeModule.ts +68 -0
  278. package/src/toast/ToastListener.vue +55 -0
  279. package/src/toast/index.ts +26 -0
  280. package/src/toast/useSignalToast.ts +154 -0
  281. package/src/types/index.ts +285 -0
  282. package/src/types/pluralize.d.ts +19 -0
  283. package/src/types/quarkernel.d.ts +50 -0
  284. package/src/utils/formatters.ts +174 -0
  285. package/src/utils/index.ts +35 -0
  286. package/src/utils/transformers.ts +131 -0
  287. package/src/vite/qdadmDebugPlugin.ts +401 -0
  288. package/src/vite-env.d.ts +50 -0
  289. package/src/zones/ZoneRegistry.ts +934 -0
  290. package/src/zones/index.ts +29 -0
  291. package/src/zones/zones.ts +191 -0
@@ -0,0 +1,372 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed, onMounted, watch, inject } from 'vue'
3
+ import AutoComplete from 'primevue/autocomplete'
4
+ import Select from 'primevue/select'
5
+ import Button from 'primevue/button'
6
+
7
+ interface ScopeRow {
8
+ resource: string
9
+ action: string
10
+ }
11
+
12
+ interface ScopeDefinition {
13
+ resources: string[]
14
+ actions: string[]
15
+ }
16
+
17
+ interface ScopeConfig {
18
+ endpoint?: string
19
+ prefix?: string
20
+ resources?: string[]
21
+ actions?: string[]
22
+ }
23
+
24
+ interface ApiAdapter {
25
+ request: (method: string, url: string) => Promise<ScopeDefinition>
26
+ }
27
+
28
+ interface Props {
29
+ modelValue?: string[]
30
+ disabled?: boolean
31
+ scopeEndpoint?: string | null
32
+ scopePrefix?: string | null
33
+ defaultResources?: string[] | null
34
+ defaultActions?: string[] | null
35
+ }
36
+
37
+ const props = withDefaults(defineProps<Props>(), {
38
+ modelValue: () => [],
39
+ disabled: false,
40
+ scopeEndpoint: null,
41
+ scopePrefix: null,
42
+ defaultResources: null,
43
+ defaultActions: null
44
+ })
45
+
46
+ // Get API adapter (optional)
47
+ const api = inject<ApiAdapter | null>('apiAdapter', null)
48
+
49
+ // Get global scope config (optional) - allows app-level configuration
50
+ const globalScopeConfig = inject<ScopeConfig>('scopeConfig', {})
51
+
52
+ // Computed config with priority: props > globalConfig > defaults
53
+ const config = computed<Required<ScopeConfig>>(() => ({
54
+ endpoint: props.scopeEndpoint ?? globalScopeConfig.endpoint ?? '/reference/scopes',
55
+ prefix: props.scopePrefix ?? globalScopeConfig.prefix ?? 'app',
56
+ resources: props.defaultResources ?? globalScopeConfig.resources ?? ['api', 'users', 'roles', 'apikeys'],
57
+ actions: props.defaultActions ?? globalScopeConfig.actions ?? ['read', 'write', 'grant']
58
+ }))
59
+
60
+ const emit = defineEmits<{
61
+ 'update:modelValue': [value: string[]]
62
+ }>()
63
+
64
+ // Scope structure from API
65
+ const scopeDefinition = ref<ScopeDefinition>({
66
+ resources: [],
67
+ actions: [],
68
+ })
69
+ const loading = ref<boolean>(true)
70
+
71
+ // Local scopes state
72
+ const scopeRows = ref<ScopeRow[]>([])
73
+
74
+ // Resource suggestions for autocomplete
75
+ const resourceSuggestions = ref<string[]>([])
76
+
77
+ // All resources with * prefix
78
+ const allResources = computed<string[]>(() => ['*', ...scopeDefinition.value.resources])
79
+
80
+ // All actions with access prefix
81
+ const allActions = computed<string[]>(() => ['access', ...scopeDefinition.value.actions])
82
+
83
+ /**
84
+ * Search resources for autocomplete
85
+ */
86
+ function searchResources(event: { query: string }): void {
87
+ const query = (event.query || '').toLowerCase()
88
+ if (!query) {
89
+ resourceSuggestions.value = [...allResources.value]
90
+ } else {
91
+ resourceSuggestions.value = allResources.value.filter(r =>
92
+ r.toLowerCase().includes(query)
93
+ )
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Parse a scope string into resource and action
99
+ */
100
+ function parseScope(scope: string): ScopeRow {
101
+ if (!scope) return { resource: '', action: '' }
102
+ // prefix.resource:action
103
+ const regex = new RegExp(`^${config.value.prefix}\\.([^:]+):(.+)$`)
104
+ const match = scope.match(regex)
105
+ if (match && match[1] && match[2]) {
106
+ return { resource: match[1], action: match[2] }
107
+ }
108
+ return { resource: '', action: '' }
109
+ }
110
+
111
+ /**
112
+ * Build scope string from resource and action
113
+ */
114
+ function buildScope(resource: string, action: string): string {
115
+ if (!resource || !action) return ''
116
+ return `${config.value.prefix}.${resource}:${action}`
117
+ }
118
+
119
+ // Initialize from modelValue
120
+ function initFromValue(): void {
121
+ if (!props.modelValue || props.modelValue.length === 0) {
122
+ scopeRows.value = []
123
+ return
124
+ }
125
+ scopeRows.value = props.modelValue.map(scope => {
126
+ const { resource, action } = parseScope(scope)
127
+ return { resource, action }
128
+ })
129
+ }
130
+
131
+ // Emit changes - only complete scopes
132
+ function emitChanges(): void {
133
+ const scopes = scopeRows.value
134
+ .map(row => buildScope(row.resource, row.action))
135
+ .filter(s => s) // Filter out empty
136
+ emit('update:modelValue', scopes)
137
+ }
138
+
139
+ // Add new scope row
140
+ function addRow(): void {
141
+ scopeRows.value.push({ resource: '', action: 'read' })
142
+ }
143
+
144
+ // Remove scope row
145
+ function removeRow(index: number): void {
146
+ scopeRows.value.splice(index, 1)
147
+ emitChanges()
148
+ }
149
+
150
+ // Update resource
151
+ function updateResource(index: number, value: string): void {
152
+ const row = scopeRows.value[index]
153
+ if (row) {
154
+ row.resource = value
155
+ if (value && row.action) {
156
+ emitChanges()
157
+ }
158
+ }
159
+ }
160
+
161
+ // Update action
162
+ function updateAction(index: number, value: string): void {
163
+ const row = scopeRows.value[index]
164
+ if (row) {
165
+ row.action = value
166
+ if (row.resource && value) {
167
+ emitChanges()
168
+ }
169
+ }
170
+ }
171
+
172
+ // Check if row is complete
173
+ function isRowComplete(row: ScopeRow): boolean {
174
+ return !!row.resource && !!row.action
175
+ }
176
+
177
+ // Load scope definition from API
178
+ async function loadScopeDefinition(): Promise<void> {
179
+ loading.value = true
180
+ try {
181
+ if (!api) {
182
+ // No API adapter, use defaults
183
+ scopeDefinition.value = {
184
+ resources: [...config.value.resources],
185
+ actions: [...config.value.actions],
186
+ }
187
+ return
188
+ }
189
+
190
+ const data = await api.request('GET', config.value.endpoint)
191
+ scopeDefinition.value = {
192
+ resources: data.resources || [...config.value.resources],
193
+ actions: data.actions || [...config.value.actions],
194
+ }
195
+ } catch (error) {
196
+ console.error('[ScopeEditor] Failed to load scope definition:', error)
197
+ scopeDefinition.value = {
198
+ resources: [...config.value.resources],
199
+ actions: [...config.value.actions],
200
+ }
201
+ } finally {
202
+ loading.value = false
203
+ }
204
+ }
205
+
206
+ // Watch for external changes
207
+ watch(() => props.modelValue, (newVal: string[] | undefined) => {
208
+ // Compare to avoid loops
209
+ const currentScopes = scopeRows.value
210
+ .map(row => buildScope(row.resource, row.action))
211
+ .filter(s => s)
212
+ .sort()
213
+ .join(',')
214
+ const newScopes = (newVal || []).sort().join(',')
215
+
216
+ if (currentScopes !== newScopes) {
217
+ initFromValue()
218
+ }
219
+ }, { deep: true })
220
+
221
+ onMounted(async () => {
222
+ await loadScopeDefinition()
223
+ initFromValue()
224
+ })
225
+ </script>
226
+
227
+ <template>
228
+ <div class="scope-editor">
229
+ <div v-if="scopeRows.length === 0" class="scope-empty">
230
+ <span class="text-surface-400">No scopes defined</span>
231
+ </div>
232
+
233
+ <div v-for="(row, index) in scopeRows" :key="index" class="scope-row">
234
+ <span class="scope-prefix">{{ scopePrefix }}.</span>
235
+ <AutoComplete
236
+ v-model="row.resource"
237
+ :suggestions="resourceSuggestions"
238
+ @complete="searchResources"
239
+ @change="updateResource(index, row.resource)"
240
+ @item-select="(e) => updateResource(index, e.value)"
241
+ :disabled="disabled || loading"
242
+ placeholder="resource"
243
+ class="scope-resource"
244
+ :dropdown="true"
245
+ :minLength="0"
246
+ />
247
+ <span class="scope-separator">:</span>
248
+ <Select
249
+ v-model="row.action"
250
+ :options="allActions"
251
+ @change="updateAction(index, row.action)"
252
+ :disabled="disabled || loading"
253
+ placeholder="action"
254
+ class="scope-action"
255
+ />
256
+ <span v-if="isRowComplete(row)" class="scope-valid">✓</span>
257
+ <span v-else class="scope-incomplete">...</span>
258
+ <Button
259
+ icon="pi pi-trash"
260
+ severity="danger"
261
+ text
262
+ rounded
263
+ size="small"
264
+ :disabled="disabled"
265
+ @click="removeRow(index)"
266
+ class="scope-remove"
267
+ />
268
+ </div>
269
+
270
+ <div class="scope-add">
271
+ <Button
272
+ label="Add Scope"
273
+ icon="pi pi-plus"
274
+ severity="secondary"
275
+ text
276
+ size="small"
277
+ :disabled="disabled"
278
+ @click="addRow"
279
+ />
280
+ </div>
281
+ </div>
282
+ </template>
283
+
284
+ <style scoped>
285
+ /*
286
+ * Only keep styles here that REQUIRE scoping (:deep, dynamic binding, component-specific overrides).
287
+ * Generic/reusable styles belong in src/styles/ partials (see _forms.scss, _cards.scss, etc.).
288
+ */
289
+ .scope-editor {
290
+ border: 1px solid var(--p-surface-200);
291
+ border-radius: 0.5rem;
292
+ padding: 0.75rem;
293
+ background: var(--p-surface-50);
294
+ }
295
+
296
+ .scope-empty {
297
+ padding: 1rem;
298
+ text-align: center;
299
+ font-size: 0.875rem;
300
+ }
301
+
302
+ .scope-row {
303
+ display: flex;
304
+ align-items: center;
305
+ gap: 0.25rem;
306
+ margin-bottom: 0.5rem;
307
+ }
308
+
309
+ .scope-prefix {
310
+ font-family: monospace;
311
+ font-size: 0.875rem;
312
+ color: var(--p-surface-500);
313
+ flex-shrink: 0;
314
+ }
315
+
316
+ .scope-separator {
317
+ font-family: monospace;
318
+ font-size: 0.875rem;
319
+ color: var(--p-surface-500);
320
+ flex-shrink: 0;
321
+ }
322
+
323
+ .scope-resource {
324
+ flex: 1 1 auto !important;
325
+ min-width: 200px !important;
326
+ }
327
+
328
+ .scope-action {
329
+ width: 120px !important;
330
+ min-width: 120px !important;
331
+ flex: 0 0 120px !important;
332
+ }
333
+
334
+ .scope-valid {
335
+ color: var(--p-green-500);
336
+ font-weight: bold;
337
+ width: 20px;
338
+ text-align: center;
339
+ }
340
+
341
+ .scope-incomplete {
342
+ color: var(--p-surface-400);
343
+ width: 20px;
344
+ text-align: center;
345
+ }
346
+
347
+ .scope-remove {
348
+ flex-shrink: 0;
349
+ }
350
+
351
+ .scope-add {
352
+ margin-top: 0.5rem;
353
+ padding-top: 0.5rem;
354
+ border-top: 1px solid var(--p-surface-200);
355
+ }
356
+
357
+ :deep(.scope-resource .p-autocomplete) {
358
+ width: 100% !important;
359
+ }
360
+
361
+ :deep(.scope-resource .p-autocomplete-input) {
362
+ width: 100%;
363
+ font-family: monospace;
364
+ font-size: 0.875rem;
365
+ }
366
+
367
+ :deep(.scope-action .p-select) {
368
+ width: 120px !important;
369
+ font-family: monospace;
370
+ font-size: 0.875rem;
371
+ }
372
+ </style>
@@ -0,0 +1,183 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * VanillaJsonEditor - JSON editor using vanilla-jsoneditor
4
+ *
5
+ * Provides tree/text/table modes like jsoneditoronline.org
6
+ *
7
+ * Usage:
8
+ * <VanillaJsonEditor v-model="jsonData" mode="tree" />
9
+ */
10
+
11
+ import { ref, watch, onMounted, onUnmounted, nextTick } from 'vue'
12
+ import { JSONEditor, type Content, type ContentErrors, type OnChangeStatus, type Mode } from 'vanilla-jsoneditor'
13
+
14
+ type JsonValue = Record<string, unknown> | unknown[] | string | null
15
+
16
+ interface Props {
17
+ modelValue?: JsonValue
18
+ mode?: Mode
19
+ height?: string
20
+ readOnly?: boolean
21
+ mainMenuBar?: boolean
22
+ navigationBar?: boolean
23
+ statusBar?: boolean
24
+ }
25
+
26
+ const props = withDefaults(defineProps<Props>(), {
27
+ modelValue: () => ({}),
28
+ mode: 'tree' as Mode,
29
+ height: '400px',
30
+ readOnly: false,
31
+ mainMenuBar: true,
32
+ navigationBar: true,
33
+ statusBar: true
34
+ })
35
+
36
+ const emit = defineEmits<{
37
+ 'update:modelValue': [value: JsonValue]
38
+ 'change': [value: JsonValue]
39
+ 'error': [errors: ContentErrors]
40
+ }>()
41
+
42
+ const containerRef = ref<HTMLElement | null>(null)
43
+ let editor: JSONEditor | null = null
44
+ // Flag to prevent onChange from firing during programmatic updates (instance-specific)
45
+ const updatingFromProp = ref<boolean>(false)
46
+
47
+ // Parse value to ensure it's an object/array
48
+ function parseValue(val: JsonValue): Record<string, unknown> | unknown[] {
49
+ if (val === null || val === undefined) {
50
+ return {}
51
+ }
52
+ if (typeof val === 'string') {
53
+ try {
54
+ return JSON.parse(val)
55
+ } catch {
56
+ return {}
57
+ }
58
+ }
59
+ return val as Record<string, unknown> | unknown[]
60
+ }
61
+
62
+ // Initialize editor
63
+ onMounted(() => {
64
+ if (!containerRef.value) return
65
+
66
+ const content: Content = {
67
+ json: parseValue(props.modelValue)
68
+ }
69
+
70
+ editor = new JSONEditor({
71
+ target: containerRef.value,
72
+ props: {
73
+ content,
74
+ mode: props.mode,
75
+ readOnly: props.readOnly,
76
+ mainMenuBar: props.mainMenuBar,
77
+ navigationBar: props.navigationBar,
78
+ statusBar: props.statusBar,
79
+ onChange: (updatedContent: Content, _previousContent: Content, { contentErrors }: OnChangeStatus) => {
80
+ // Skip if this change came from a programmatic prop update
81
+ if (updatingFromProp.value) return
82
+
83
+ if (contentErrors) {
84
+ emit('error', contentErrors)
85
+ return
86
+ }
87
+
88
+ // Extract JSON from content
89
+ let newValue: JsonValue | undefined
90
+ if ('json' in updatedContent && updatedContent.json !== undefined) {
91
+ newValue = updatedContent.json as JsonValue
92
+ } else if ('text' in updatedContent && updatedContent.text !== undefined) {
93
+ try {
94
+ newValue = JSON.parse(updatedContent.text)
95
+ } catch {
96
+ // Invalid JSON in text mode, don't emit
97
+ return
98
+ }
99
+ }
100
+
101
+ if (newValue !== undefined) {
102
+ emit('update:modelValue', newValue)
103
+ emit('change', newValue)
104
+ }
105
+ }
106
+ }
107
+ })
108
+ })
109
+
110
+ // Watch for external changes
111
+ watch(() => props.modelValue, (newVal: JsonValue | undefined) => {
112
+ if (!editor) return
113
+
114
+ const currentContent = editor.get()
115
+ const newParsed = parseValue(newVal ?? null)
116
+
117
+ // Only update if different (avoid loops)
118
+ const currentJson = 'json' in currentContent && currentContent.json !== undefined
119
+ ? currentContent.json
120
+ : ('text' in currentContent && currentContent.text ? JSON.parse(currentContent.text) : null)
121
+
122
+ if (JSON.stringify(currentJson) !== JSON.stringify(newParsed)) {
123
+ // Set flag to prevent onChange from emitting during this update
124
+ updatingFromProp.value = true
125
+ editor.set({ json: newParsed })
126
+ // Reset flag after the update is processed
127
+ nextTick(() => {
128
+ updatingFromProp.value = false
129
+ })
130
+ }
131
+ }, { deep: true })
132
+
133
+ // Watch mode changes
134
+ watch(() => props.mode, (newMode: Mode | undefined) => {
135
+ if (editor && newMode) {
136
+ editor.updateProps({ mode: newMode })
137
+ }
138
+ })
139
+
140
+ // Watch readOnly changes
141
+ watch(() => props.readOnly, (newReadOnly: boolean) => {
142
+ if (editor) {
143
+ editor.updateProps({ readOnly: newReadOnly })
144
+ }
145
+ })
146
+
147
+ // Cleanup
148
+ onUnmounted(() => {
149
+ if (editor) {
150
+ editor.destroy()
151
+ editor = null
152
+ }
153
+ })
154
+ </script>
155
+
156
+ <template>
157
+ <div class="vanilla-json-editor" :style="{ height }">
158
+ <div ref="containerRef" class="editor-container"></div>
159
+ </div>
160
+ </template>
161
+
162
+ <style scoped>
163
+ /*
164
+ * Only keep styles here that REQUIRE scoping (:deep, dynamic binding, component-specific overrides).
165
+ * Generic/reusable styles belong in src/styles/ partials (see _forms.scss, _cards.scss, etc.).
166
+ */
167
+ .vanilla-json-editor {
168
+ width: 100%;
169
+ border: 1px solid var(--p-surface-300);
170
+ border-radius: 0.375rem;
171
+ overflow: hidden;
172
+ }
173
+
174
+ .editor-container {
175
+ height: 100%;
176
+ }
177
+
178
+ /* Override some vanilla-jsoneditor styles for dark mode compatibility */
179
+ :deep(.jse-main) {
180
+ --jse-theme-color: var(--p-primary-color, #10b981);
181
+ --jse-theme-color-highlight: var(--p-primary-100, #d1fae5);
182
+ }
183
+ </style>
@@ -0,0 +1,83 @@
1
+ /**
2
+ * qdadm - Components exports
3
+ */
4
+
5
+ // Layout
6
+ export { default as AppLayout } from './layout/AppLayout.vue'
7
+ export { default as BaseLayout } from './layout/BaseLayout.vue'
8
+ export { default as PageLayout } from './layout/PageLayout.vue'
9
+ export { default as PageHeader } from './layout/PageHeader.vue'
10
+ export { default as Breadcrumb } from './layout/Breadcrumb.vue'
11
+ export { default as PageNav } from './layout/PageNav.vue'
12
+ export { default as Zone } from './layout/Zone.vue'
13
+ export { default as SidebarBox } from './layout/SidebarBox.vue'
14
+
15
+ // Default zone components
16
+ export { default as DefaultHeader } from './layout/defaults/DefaultHeader.vue'
17
+ export { default as DefaultMenu } from './layout/defaults/DefaultMenu.vue'
18
+ export { default as DefaultFooter } from './layout/defaults/DefaultFooter.vue'
19
+ export { default as DefaultUserInfo } from './layout/defaults/DefaultUserInfo.vue'
20
+ export { default as DefaultBreadcrumb } from './layout/defaults/DefaultBreadcrumb.vue'
21
+
22
+ // Item (shared between edit and show)
23
+ export { default as FieldGroups } from './item/FieldGroups.vue'
24
+
25
+ // Edit (form pages)
26
+ export { default as FormPage } from './edit/FormPage.vue'
27
+ export { default as FormField } from './edit/FormField.vue'
28
+ export { default as FormInput } from './edit/FormInput.vue'
29
+ export { default as FormActions } from './edit/FormActions.vue'
30
+ export { default as FormTabs } from './edit/FormTabs.vue' // Deprecated: use FieldGroups with layout="tabs"
31
+ export { default as FormTab } from './edit/FormTab.vue' // Deprecated: use FieldGroups with layout="tabs"
32
+ export { default as LookupField } from './edit/LookupField.vue'
33
+ export { default as LookupPickerDialog } from './edit/LookupPickerDialog.vue'
34
+
35
+ // Show (read-only detail pages)
36
+ export { default as ShowPage } from './show/ShowPage.vue'
37
+ export { default as ShowField } from './show/ShowField.vue'
38
+ export { default as ShowDisplay } from './show/ShowDisplay.vue'
39
+
40
+ // Lists
41
+ export { default as ListPage } from './lists/ListPage.vue'
42
+ export { default as ActionButtons } from './lists/ActionButtons.vue'
43
+ export { default as ActionColumn } from './lists/ActionColumn.vue'
44
+ export { default as FilterBar } from './lists/FilterBar.vue'
45
+
46
+ // Editors (vanilla-jsoneditor free)
47
+ export { default as KeyValueEditor } from './editors/KeyValueEditor.vue'
48
+ export { default as LanguageEditor } from './editors/LanguageEditor.vue'
49
+ export { default as ScopeEditor } from './editors/ScopeEditor.vue'
50
+ export { default as PermissionEditor } from './editors/PermissionEditor.vue'
51
+ export { default as JsonEditorFoldable } from './editors/JsonEditorFoldable.vue'
52
+ export { default as JsonViewer } from './editors/JsonViewer.vue'
53
+
54
+ // NOTE: VanillaJsonEditor and JsonStructuredField require vanilla-jsoneditor
55
+ // Import from '@quazardous/qdadm/editors' instead:
56
+ // import { VanillaJsonEditor, JsonStructuredField } from '@quazardous/qdadm/editors'
57
+
58
+ // Dialogs
59
+ export { default as SimpleDialog } from './dialogs/SimpleDialog.vue'
60
+ export { default as MultiStepDialog } from './dialogs/MultiStepDialog.vue'
61
+ export { default as BulkStatusDialog } from './dialogs/BulkStatusDialog.vue'
62
+ export { default as UnsavedChangesDialog } from './dialogs/UnsavedChangesDialog.vue'
63
+
64
+ // Display
65
+ export { default as CardsGrid } from './display/CardsGrid.vue'
66
+ export { default as RichCardsGrid } from './display/RichCardsGrid.vue'
67
+ export { default as CopyableId } from './display/CopyableId.vue'
68
+ export { default as EmptyState } from './display/EmptyState.vue'
69
+ export { default as IntensityBar } from './display/IntensityBar.vue'
70
+ export { default as BoolCell } from './BoolCell.vue'
71
+ export { default as SeverityTag } from './SeverityTag.vue'
72
+ export { default as InfoBanner } from './InfoBanner.vue'
73
+ export { default as BannerZone } from './display/BannerZone.vue'
74
+
75
+ // Pages
76
+ export { default as LoginPage } from './pages/LoginPage.vue'
77
+ export { default as NotFoundPage } from './pages/NotFoundPage.vue'
78
+
79
+ // Host integration helper — render in a host App.vue when using
80
+ // `Kernel({ existingApp })` so qdadm's DOM extras (Toast,
81
+ // ToastListener, DebugBar) appear without the Kernel-managed
82
+ // WrappedRoot.
83
+ export { default as QdadmRoot } from './QdadmRoot.vue'