@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,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'
|