@quazardous/qdadm 2.13.0 → 2.14.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/package.json +1 -1
- package/src/components/InfoBanner.vue +3 -3
- package/src/components/base/QdButton.vue +20 -0
- package/src/components/base/QdMessage.vue +17 -0
- package/src/components/dialogs/MultiStepDialog.vue +5 -5
- package/src/components/dialogs/SimpleDialog.vue +4 -4
- package/src/components/dialogs/UnsavedChangesDialog.vue +2 -2
- package/src/components/display/CopyableId.vue +2 -2
- package/src/components/edit/FormActions.vue +4 -4
- package/src/components/edit/FormPage.vue +7 -7
- package/src/components/edit/LookupPickerDialog.vue +3 -3
- package/src/components/editors/JsonEditorFoldable.vue +8 -8
- package/src/components/editors/JsonStructuredField.vue +3 -3
- package/src/components/editors/KeyValueEditor.vue +3 -3
- package/src/components/editors/LanguageEditor.vue +5 -5
- package/src/components/editors/ScopeEditor.vue +3 -3
- package/src/components/index.ts +4 -0
- package/src/components/layout/AppLayout.vue +2 -2
- package/src/components/layout/PageLayout.vue +2 -2
- package/src/components/layout/defaults/DefaultUserInfo.vue +2 -2
- package/src/components/lists/ActionButtons.vue +2 -2
- package/src/components/lists/ListPage.vue +4 -4
- package/src/components/pages/LoginPage.vue +2 -2
- package/src/components/show/ShowPage.vue +6 -6
- package/src/generated/managers/testManager.js +45 -0
- package/src/security/pages/RoleList.vue +3 -3
- package/src/vite/qdadmDebugPlugin.ts +178 -2
package/package.json
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* Just some information.
|
|
21
21
|
* </InfoBanner>
|
|
22
22
|
*/
|
|
23
|
-
import
|
|
23
|
+
import QdMessage from './base/QdMessage.vue'
|
|
24
24
|
import { computed } from 'vue'
|
|
25
25
|
|
|
26
26
|
type Severity = 'info' | 'success' | 'warn' | 'error' | 'secondary' | 'contrast'
|
|
@@ -58,7 +58,7 @@ const iconClass = computed(() => {
|
|
|
58
58
|
</script>
|
|
59
59
|
|
|
60
60
|
<template>
|
|
61
|
-
<
|
|
61
|
+
<QdMessage
|
|
62
62
|
:severity="severity"
|
|
63
63
|
:closable="closable"
|
|
64
64
|
class="info-banner"
|
|
@@ -69,5 +69,5 @@ const iconClass = computed(() => {
|
|
|
69
69
|
<slot />
|
|
70
70
|
</span>
|
|
71
71
|
</div>
|
|
72
|
-
</
|
|
72
|
+
</QdMessage>
|
|
73
73
|
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* QdButton — the single indirection point over the widget library's Button
|
|
4
|
+
* (#1391 exit-readiness). Generic pass-through: every attr/prop/listener and
|
|
5
|
+
* every slot is forwarded verbatim, so swapping or shimming the underlying
|
|
6
|
+
* widget is a one-file change. Imports `primevue/button`, which the
|
|
7
|
+
* qdadmVitePlugin flavor alias (#1393) traverses.
|
|
8
|
+
*/
|
|
9
|
+
import Button from 'primevue/button'
|
|
10
|
+
|
|
11
|
+
defineOptions({ inheritAttrs: false })
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<Button v-bind="$attrs">
|
|
16
|
+
<template v-for="(_, name) in $slots" #[name]="slotProps">
|
|
17
|
+
<slot :name="name" v-bind="slotProps ?? {}" />
|
|
18
|
+
</template>
|
|
19
|
+
</Button>
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* QdMessage — the single indirection point over the widget library's Message
|
|
4
|
+
* (#1391 exit-readiness). Generic pass-through, same contract as QdButton.
|
|
5
|
+
*/
|
|
6
|
+
import Message from 'primevue/message'
|
|
7
|
+
|
|
8
|
+
defineOptions({ inheritAttrs: false })
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<Message v-bind="$attrs">
|
|
13
|
+
<template v-for="(_, name) in $slots" #[name]="slotProps">
|
|
14
|
+
<slot :name="name" v-bind="slotProps ?? {}" />
|
|
15
|
+
</template>
|
|
16
|
+
</Message>
|
|
17
|
+
</template>
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
*/
|
|
59
59
|
import { computed, watch, type PropType } from 'vue'
|
|
60
60
|
import Dialog from 'primevue/dialog'
|
|
61
|
-
import
|
|
61
|
+
import QdButton from '../base/QdButton.vue'
|
|
62
62
|
import Stepper from 'primevue/stepper'
|
|
63
63
|
import StepList from 'primevue/steplist'
|
|
64
64
|
import Step from 'primevue/step'
|
|
@@ -284,7 +284,7 @@ const slotActions = computed<SlotActions>(() => ({
|
|
|
284
284
|
<div class="step-actions">
|
|
285
285
|
<slot name="actions" v-bind="slotActions">
|
|
286
286
|
<!-- Back button -->
|
|
287
|
-
<
|
|
287
|
+
<QdButton
|
|
288
288
|
v-if="!isFirstStep || showBackOnFirst"
|
|
289
289
|
:label="backLabel"
|
|
290
290
|
icon="pi pi-arrow-left"
|
|
@@ -294,7 +294,7 @@ const slotActions = computed<SlotActions>(() => ({
|
|
|
294
294
|
/>
|
|
295
295
|
|
|
296
296
|
<!-- Cancel button -->
|
|
297
|
-
<
|
|
297
|
+
<QdButton
|
|
298
298
|
:label="cancelLabel"
|
|
299
299
|
severity="secondary"
|
|
300
300
|
@click="cancel"
|
|
@@ -302,7 +302,7 @@ const slotActions = computed<SlotActions>(() => ({
|
|
|
302
302
|
/>
|
|
303
303
|
|
|
304
304
|
<!-- Next button (not on last step) -->
|
|
305
|
-
<
|
|
305
|
+
<QdButton
|
|
306
306
|
v-if="!isLastStep"
|
|
307
307
|
:label="nextLabel"
|
|
308
308
|
icon="pi pi-arrow-right"
|
|
@@ -312,7 +312,7 @@ const slotActions = computed<SlotActions>(() => ({
|
|
|
312
312
|
/>
|
|
313
313
|
|
|
314
314
|
<!-- Submit button (only on last step) -->
|
|
315
|
-
<
|
|
315
|
+
<QdButton
|
|
316
316
|
v-if="isLastStep"
|
|
317
317
|
:label="submitLabel"
|
|
318
318
|
icon="pi pi-check"
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
* <SimpleDialog v-model:visible="showDialog" title="View Key" :showConfirm="false">
|
|
27
27
|
* <p>{{ key }}</p>
|
|
28
28
|
* <template #actions>
|
|
29
|
-
* <
|
|
29
|
+
* <QdButton label="Copy" icon="pi pi-copy" @click="copyKey" />
|
|
30
30
|
* </template>
|
|
31
31
|
* </SimpleDialog>
|
|
32
32
|
*/
|
|
33
33
|
import { computed } from 'vue'
|
|
34
34
|
import Dialog from 'primevue/dialog'
|
|
35
|
-
import
|
|
35
|
+
import QdButton from '../base/QdButton.vue'
|
|
36
36
|
|
|
37
37
|
interface Props {
|
|
38
38
|
visible?: boolean
|
|
@@ -101,14 +101,14 @@ const canClose = computed((): boolean => props.closable && !props.loading)
|
|
|
101
101
|
|
|
102
102
|
<template #footer v-if="showCancel || showConfirm || $slots.actions">
|
|
103
103
|
<slot name="actions" />
|
|
104
|
-
<
|
|
104
|
+
<QdButton
|
|
105
105
|
v-if="showCancel"
|
|
106
106
|
:label="cancelLabel"
|
|
107
107
|
severity="secondary"
|
|
108
108
|
@click="onCancel"
|
|
109
109
|
:disabled="loading"
|
|
110
110
|
/>
|
|
111
|
-
<
|
|
111
|
+
<QdButton
|
|
112
112
|
v-if="showConfirm"
|
|
113
113
|
:label="confirmLabel"
|
|
114
114
|
:icon="confirmIcon"
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Stay: cancels navigation
|
|
9
9
|
*/
|
|
10
10
|
import SimpleDialog from './SimpleDialog.vue'
|
|
11
|
-
import
|
|
11
|
+
import QdButton from '../base/QdButton.vue'
|
|
12
12
|
|
|
13
13
|
interface Props {
|
|
14
14
|
visible?: boolean
|
|
@@ -67,7 +67,7 @@ function onSaveAndLeave(): void {
|
|
|
67
67
|
</div>
|
|
68
68
|
|
|
69
69
|
<template #actions>
|
|
70
|
-
<
|
|
70
|
+
<QdButton
|
|
71
71
|
v-if="hasOnSave"
|
|
72
72
|
label="Save & Leave"
|
|
73
73
|
icon="pi pi-save"
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* <CopyableId :value="apiKey" label="API Key" />
|
|
8
8
|
*/
|
|
9
9
|
import { ref, inject } from 'vue'
|
|
10
|
-
import
|
|
10
|
+
import QdButton from '../base/QdButton.vue'
|
|
11
11
|
import type { Orchestrator } from '../../orchestrator/Orchestrator'
|
|
12
12
|
|
|
13
13
|
interface Props {
|
|
@@ -40,7 +40,7 @@ async function copyToClipboard(): Promise<void> {
|
|
|
40
40
|
<div class="form-field">
|
|
41
41
|
<label class="form-field-label">{{ label }}</label>
|
|
42
42
|
<div class="copyable-id">
|
|
43
|
-
<
|
|
43
|
+
<QdButton
|
|
44
44
|
type="button"
|
|
45
45
|
:icon="copied ? 'pi pi-check' : 'pi pi-copy'"
|
|
46
46
|
:severity="copied ? 'success' : 'secondary'"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - cancel: Cancel/close without saving
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import
|
|
16
|
+
import QdButton from '../base/QdButton.vue'
|
|
17
17
|
import { useI18n } from '../../i18n/useI18n'
|
|
18
18
|
|
|
19
19
|
const { t } = useI18n()
|
|
@@ -42,7 +42,7 @@ const emit = defineEmits<{
|
|
|
42
42
|
<template>
|
|
43
43
|
<div class="form-actions">
|
|
44
44
|
<div class="form-actions-left">
|
|
45
|
-
<
|
|
45
|
+
<QdButton
|
|
46
46
|
type="button"
|
|
47
47
|
:label="isEdit ? t('core.actions.update') : t('core.actions.create')"
|
|
48
48
|
:loading="saving"
|
|
@@ -51,7 +51,7 @@ const emit = defineEmits<{
|
|
|
51
51
|
@click="emit('save')"
|
|
52
52
|
v-tooltip.top="t('core.tooltips.saveAndContinue')"
|
|
53
53
|
/>
|
|
54
|
-
<
|
|
54
|
+
<QdButton
|
|
55
55
|
v-if="showSaveAndClose"
|
|
56
56
|
type="button"
|
|
57
57
|
:label="isEdit ? t('core.actions.updateAndClose') : t('core.actions.createAndClose')"
|
|
@@ -66,7 +66,7 @@ const emit = defineEmits<{
|
|
|
66
66
|
<i class="pi pi-circle-fill"></i>
|
|
67
67
|
</span>
|
|
68
68
|
</div>
|
|
69
|
-
<
|
|
69
|
+
<QdButton
|
|
70
70
|
type="button"
|
|
71
71
|
:label="t('core.actions.cancel')"
|
|
72
72
|
severity="secondary"
|
|
@@ -38,8 +38,8 @@ import FormActions from './FormActions.vue'
|
|
|
38
38
|
import UnsavedChangesDialog from '../dialogs/UnsavedChangesDialog.vue'
|
|
39
39
|
import CardShell from '../layout/CardShell.vue'
|
|
40
40
|
import { formatFetchError } from '../../utils/errors'
|
|
41
|
-
import
|
|
42
|
-
import
|
|
41
|
+
import QdButton from '../base/QdButton.vue'
|
|
42
|
+
import QdMessage from '../base/QdMessage.vue'
|
|
43
43
|
import type { ResolvedAction, ResolvedFieldConfig } from '../../composables/useEntityItemFormPage'
|
|
44
44
|
import type { GuardDialogState } from '../../composables/useUnsavedChangesGuard'
|
|
45
45
|
|
|
@@ -151,7 +151,7 @@ function onGuardStay(): void {
|
|
|
151
151
|
<template #actions>
|
|
152
152
|
<slot name="header-actions" />
|
|
153
153
|
<!-- Header actions from builder (excludes save/delete/cancel) -->
|
|
154
|
-
<
|
|
154
|
+
<QdButton
|
|
155
155
|
v-for="action in headerActions"
|
|
156
156
|
:key="action.name"
|
|
157
157
|
:label="action.label"
|
|
@@ -180,16 +180,16 @@ function onGuardStay(): void {
|
|
|
180
180
|
<!-- Error State (fetch error) -->
|
|
181
181
|
<template v-else-if="fetchError">
|
|
182
182
|
<slot name="error" :error="fetchError">
|
|
183
|
-
<
|
|
183
|
+
<QdMessage severity="error" :closable="false" class="form-error-message">
|
|
184
184
|
{{ fetchErrorMessage }}
|
|
185
|
-
</
|
|
185
|
+
</QdMessage>
|
|
186
186
|
</slot>
|
|
187
187
|
</template>
|
|
188
188
|
|
|
189
189
|
<!-- Form Content -->
|
|
190
190
|
<template v-else>
|
|
191
191
|
<!-- Validation Error Summary -->
|
|
192
|
-
<
|
|
192
|
+
<QdMessage
|
|
193
193
|
v-if="errorSummary && errorSummary.length > 0"
|
|
194
194
|
severity="warn"
|
|
195
195
|
:closable="false"
|
|
@@ -200,7 +200,7 @@ function onGuardStay(): void {
|
|
|
200
200
|
<strong>{{ error.label }}:</strong> {{ error.message }}
|
|
201
201
|
</li>
|
|
202
202
|
</ul>
|
|
203
|
-
</
|
|
203
|
+
</QdMessage>
|
|
204
204
|
|
|
205
205
|
<!-- Single content body; Card wrapper is conditional (#1193) -->
|
|
206
206
|
<CardShell :card="cardWrapper">
|
|
@@ -12,7 +12,7 @@ import Dialog from 'primevue/dialog'
|
|
|
12
12
|
import DataTable from 'primevue/datatable'
|
|
13
13
|
import Column from 'primevue/column'
|
|
14
14
|
import InputText from 'primevue/inputtext'
|
|
15
|
-
import
|
|
15
|
+
import QdButton from '../base/QdButton.vue'
|
|
16
16
|
import { useI18n } from '../../i18n/useI18n'
|
|
17
17
|
|
|
18
18
|
const { t } = useI18n()
|
|
@@ -208,12 +208,12 @@ const confirmLabel = computed(() => {
|
|
|
208
208
|
</DataTable>
|
|
209
209
|
|
|
210
210
|
<template #footer>
|
|
211
|
-
<
|
|
211
|
+
<QdButton
|
|
212
212
|
:label="t('core.actions.cancel')"
|
|
213
213
|
severity="secondary"
|
|
214
214
|
@click="onCancel"
|
|
215
215
|
/>
|
|
216
|
-
<
|
|
216
|
+
<QdButton
|
|
217
217
|
:label="confirmLabel"
|
|
218
218
|
icon="pi pi-check"
|
|
219
219
|
:disabled="confirmDisabled"
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { ref, computed, watch } from 'vue'
|
|
13
|
-
import
|
|
13
|
+
import QdButton from '../base/QdButton.vue'
|
|
14
14
|
import Textarea from 'primevue/textarea'
|
|
15
15
|
import InputText from 'primevue/inputtext'
|
|
16
|
-
import
|
|
16
|
+
import QdMessage from '../base/QdMessage.vue'
|
|
17
17
|
import { getJsonValueType, getJsonPreview } from '../../composables/useJsonSyntax'
|
|
18
18
|
|
|
19
19
|
interface Props {
|
|
@@ -165,7 +165,7 @@ function isMultiline(value: unknown): boolean {
|
|
|
165
165
|
<!-- Toolbar -->
|
|
166
166
|
<div class="editor-toolbar">
|
|
167
167
|
<div class="toolbar-left">
|
|
168
|
-
<
|
|
168
|
+
<QdButton
|
|
169
169
|
:icon="rawJsonMode ? 'pi pi-list' : 'pi pi-code'"
|
|
170
170
|
:label="rawJsonMode ? 'Structured' : 'Raw JSON'"
|
|
171
171
|
size="small"
|
|
@@ -175,7 +175,7 @@ function isMultiline(value: unknown): boolean {
|
|
|
175
175
|
/>
|
|
176
176
|
</div>
|
|
177
177
|
<div class="toolbar-right" v-if="!rawJsonMode">
|
|
178
|
-
<
|
|
178
|
+
<QdButton
|
|
179
179
|
icon="pi pi-plus"
|
|
180
180
|
label="Expand All"
|
|
181
181
|
size="small"
|
|
@@ -183,7 +183,7 @@ function isMultiline(value: unknown): boolean {
|
|
|
183
183
|
text
|
|
184
184
|
@click="expandAll"
|
|
185
185
|
/>
|
|
186
|
-
<
|
|
186
|
+
<QdButton
|
|
187
187
|
icon="pi pi-minus"
|
|
188
188
|
label="Collapse All"
|
|
189
189
|
size="small"
|
|
@@ -282,12 +282,12 @@ function isMultiline(value: unknown): boolean {
|
|
|
282
282
|
spellcheck="false"
|
|
283
283
|
></textarea>
|
|
284
284
|
<div v-if="parseError" class="raw-json-actions">
|
|
285
|
-
<
|
|
285
|
+
<QdMessage severity="error" :closable="false" class="parse-error">
|
|
286
286
|
{{ parseError }}
|
|
287
|
-
</
|
|
287
|
+
</QdMessage>
|
|
288
288
|
</div>
|
|
289
289
|
<div v-else class="raw-json-actions">
|
|
290
|
-
<
|
|
290
|
+
<QdButton
|
|
291
291
|
label="Apply Changes"
|
|
292
292
|
icon="pi pi-check"
|
|
293
293
|
size="small"
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
|
|
43
43
|
import { ref, computed, watch } from 'vue'
|
|
44
44
|
import SelectButton from 'primevue/selectbutton'
|
|
45
|
-
import
|
|
45
|
+
import QdMessage from '../base/QdMessage.vue'
|
|
46
46
|
import VanillaJsonEditor from './VanillaJsonEditor.vue'
|
|
47
47
|
import type { Mode } from 'vanilla-jsoneditor'
|
|
48
48
|
|
|
@@ -179,14 +179,14 @@ watch(() => props.mode, (newMode: ViewMode | null | undefined) => {
|
|
|
179
179
|
|
|
180
180
|
<!-- JSON View -->
|
|
181
181
|
<div v-if="viewMode === 'json'" class="json-view">
|
|
182
|
-
<
|
|
182
|
+
<QdMessage
|
|
183
183
|
v-if="guardInvalidJson && hasJsonError"
|
|
184
184
|
severity="error"
|
|
185
185
|
:closable="false"
|
|
186
186
|
class="json-error"
|
|
187
187
|
>
|
|
188
188
|
{{ invalidJsonMessage }}
|
|
189
|
-
</
|
|
189
|
+
</QdMessage>
|
|
190
190
|
<VanillaJsonEditor
|
|
191
191
|
:modelValue="effectiveJsonValue"
|
|
192
192
|
:mode="jsonMode"
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { ref, computed } from 'vue'
|
|
21
|
-
import
|
|
21
|
+
import QdButton from '../base/QdButton.vue'
|
|
22
22
|
import InputText from 'primevue/inputtext'
|
|
23
23
|
import AutoComplete from 'primevue/autocomplete'
|
|
24
24
|
import Slider from 'primevue/slider'
|
|
@@ -161,7 +161,7 @@ function updateItemValue(index: number, value: string | number): void {
|
|
|
161
161
|
/>
|
|
162
162
|
</template>
|
|
163
163
|
</div>
|
|
164
|
-
<
|
|
164
|
+
<QdButton
|
|
165
165
|
icon="pi pi-times"
|
|
166
166
|
severity="danger"
|
|
167
167
|
text
|
|
@@ -172,7 +172,7 @@ function updateItemValue(index: number, value: string | number): void {
|
|
|
172
172
|
</div>
|
|
173
173
|
</div>
|
|
174
174
|
<div class="kv-add">
|
|
175
|
-
<
|
|
175
|
+
<QdButton
|
|
176
176
|
icon="pi pi-plus"
|
|
177
177
|
@click="addItem"
|
|
178
178
|
:disabled="!newItem.key || (isTextMode && !newItem.value)"
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { ref, computed } from 'vue'
|
|
12
|
-
import
|
|
12
|
+
import QdButton from '../base/QdButton.vue'
|
|
13
13
|
import InputText from 'primevue/inputtext'
|
|
14
14
|
import Slider from 'primevue/slider'
|
|
15
15
|
import Checkbox from 'primevue/checkbox'
|
|
@@ -113,7 +113,7 @@ function updateFluency(index: number, fluency: number | number[]): void {
|
|
|
113
113
|
style="width: 100px"
|
|
114
114
|
/>
|
|
115
115
|
</div>
|
|
116
|
-
<
|
|
116
|
+
<QdButton
|
|
117
117
|
v-if="lang.primary"
|
|
118
118
|
icon="pi pi-star-fill"
|
|
119
119
|
severity="warning"
|
|
@@ -122,7 +122,7 @@ function updateFluency(index: number, fluency: number | number[]): void {
|
|
|
122
122
|
size="small"
|
|
123
123
|
v-tooltip.top="'Primary language'"
|
|
124
124
|
/>
|
|
125
|
-
<
|
|
125
|
+
<QdButton
|
|
126
126
|
v-else
|
|
127
127
|
icon="pi pi-star"
|
|
128
128
|
severity="secondary"
|
|
@@ -132,7 +132,7 @@ function updateFluency(index: number, fluency: number | number[]): void {
|
|
|
132
132
|
@click="setPrimaryLanguage(index)"
|
|
133
133
|
v-tooltip.top="'Set as primary'"
|
|
134
134
|
/>
|
|
135
|
-
<
|
|
135
|
+
<QdButton
|
|
136
136
|
icon="pi pi-times"
|
|
137
137
|
severity="danger"
|
|
138
138
|
text
|
|
@@ -143,7 +143,7 @@ function updateFluency(index: number, fluency: number | number[]): void {
|
|
|
143
143
|
</div>
|
|
144
144
|
</div>
|
|
145
145
|
<div class="lang-add">
|
|
146
|
-
<
|
|
146
|
+
<QdButton
|
|
147
147
|
icon="pi pi-plus"
|
|
148
148
|
@click="addLanguage"
|
|
149
149
|
:disabled="!newLanguage.code"
|
|
@@ -3,7 +3,7 @@ import { ref, computed, onMounted, watch, inject } from 'vue'
|
|
|
3
3
|
import type { HttpClient } from '../../entity/storage/ApiStorage'
|
|
4
4
|
import AutoComplete from 'primevue/autocomplete'
|
|
5
5
|
import Select from 'primevue/select'
|
|
6
|
-
import
|
|
6
|
+
import QdButton from '../base/QdButton.vue'
|
|
7
7
|
|
|
8
8
|
interface ScopeRow {
|
|
9
9
|
resource: string
|
|
@@ -266,7 +266,7 @@ onMounted(async () => {
|
|
|
266
266
|
/>
|
|
267
267
|
<span v-if="isRowComplete(row)" class="scope-valid">✓</span>
|
|
268
268
|
<span v-else class="scope-incomplete">...</span>
|
|
269
|
-
<
|
|
269
|
+
<QdButton
|
|
270
270
|
icon="pi pi-trash"
|
|
271
271
|
severity="danger"
|
|
272
272
|
text
|
|
@@ -279,7 +279,7 @@ onMounted(async () => {
|
|
|
279
279
|
</div>
|
|
280
280
|
|
|
281
281
|
<div class="scope-add">
|
|
282
|
-
<
|
|
282
|
+
<QdButton
|
|
283
283
|
label="Add Scope"
|
|
284
284
|
icon="pi pi-plus"
|
|
285
285
|
severity="secondary"
|
package/src/components/index.ts
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
// Layout
|
|
6
|
+
// Base widget wrappers — the single indirection points over the widget
|
|
7
|
+
// library (#1391 exit-readiness)
|
|
8
|
+
export { default as QdButton } from './base/QdButton.vue'
|
|
9
|
+
export { default as QdMessage } from './base/QdMessage.vue'
|
|
6
10
|
export { default as AppLayout } from './layout/AppLayout.vue'
|
|
7
11
|
export { default as BaseLayout } from './layout/BaseLayout.vue'
|
|
8
12
|
export { default as PageLayout } from './layout/PageLayout.vue'
|
|
@@ -21,7 +21,7 @@ import { useApp } from '../../composables/useApp'
|
|
|
21
21
|
import { useAuth } from '../../composables/useAuth'
|
|
22
22
|
import { useGuardDialog } from '../../composables/useGuardStore'
|
|
23
23
|
import { useNavContext, type BreadcrumbItem, type NavLinkItem } from '../../composables/useNavContext'
|
|
24
|
-
import
|
|
24
|
+
import QdButton from '../base/QdButton.vue'
|
|
25
25
|
import Breadcrumb from 'primevue/breadcrumb'
|
|
26
26
|
import UnsavedChangesDialog from '../dialogs/UnsavedChangesDialog.vue'
|
|
27
27
|
import SidebarBox from './SidebarBox.vue'
|
|
@@ -366,7 +366,7 @@ const showBreadcrumb = computed<boolean>(() => {
|
|
|
366
366
|
<main class="main-content" :class="{ 'main-content--sidebar-collapsed': sidebarCollapsed }">
|
|
367
367
|
<!-- Mobile header bar -->
|
|
368
368
|
<div class="mobile-header">
|
|
369
|
-
<
|
|
369
|
+
<QdButton
|
|
370
370
|
icon="pi pi-bars"
|
|
371
371
|
severity="secondary"
|
|
372
372
|
text
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import PageHeader from './PageHeader.vue'
|
|
19
19
|
import CardsGrid from '../display/CardsGrid.vue'
|
|
20
|
-
import
|
|
20
|
+
import QdButton from '../base/QdButton.vue'
|
|
21
21
|
|
|
22
22
|
interface TitleParts {
|
|
23
23
|
action?: string
|
|
@@ -89,7 +89,7 @@ function resolveLabel(label: string | (() => string)): string {
|
|
|
89
89
|
</template>
|
|
90
90
|
<template #actions>
|
|
91
91
|
<slot name="header-actions" ></slot>
|
|
92
|
-
<
|
|
92
|
+
<QdButton
|
|
93
93
|
v-for="action in headerActions"
|
|
94
94
|
:key="action.name"
|
|
95
95
|
:label="resolveLabel(action.label)"
|
|
@@ -11,7 +11,7 @@ import { computed, type ComputedRef } from 'vue'
|
|
|
11
11
|
import { useRouter } from 'vue-router'
|
|
12
12
|
import { prettifyRole } from '../../../utils/formatters'
|
|
13
13
|
import { useAuth } from '../../../composables/useAuth'
|
|
14
|
-
import
|
|
14
|
+
import QdButton from '../../base/QdButton.vue'
|
|
15
15
|
|
|
16
16
|
interface UserData {
|
|
17
17
|
username?: string
|
|
@@ -56,7 +56,7 @@ function handleLogout(): void {
|
|
|
56
56
|
<div class="user-name">{{ userDisplayName }}</div>
|
|
57
57
|
<div class="user-role">{{ userSubtitle }}</div>
|
|
58
58
|
</div>
|
|
59
|
-
<
|
|
59
|
+
<QdButton
|
|
60
60
|
icon="pi pi-sign-out"
|
|
61
61
|
severity="secondary"
|
|
62
62
|
text
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - handler: Click handler function
|
|
13
13
|
* - isDisabled: Boolean disabled state
|
|
14
14
|
*/
|
|
15
|
-
import
|
|
15
|
+
import QdButton from '../base/QdButton.vue'
|
|
16
16
|
|
|
17
17
|
interface Action {
|
|
18
18
|
name: string
|
|
@@ -32,7 +32,7 @@ defineProps<Props>()
|
|
|
32
32
|
|
|
33
33
|
<template>
|
|
34
34
|
<div class="table-actions">
|
|
35
|
-
<
|
|
35
|
+
<QdButton
|
|
36
36
|
v-for="action in actions"
|
|
37
37
|
:key="action.name"
|
|
38
38
|
:icon="action.icon"
|
|
@@ -21,7 +21,7 @@ import FilterBar from './FilterBar.vue'
|
|
|
21
21
|
import ActionButtons from './ActionButtons.vue'
|
|
22
22
|
import DataTable from 'primevue/datatable'
|
|
23
23
|
import Column from 'primevue/column'
|
|
24
|
-
import
|
|
24
|
+
import QdButton from '../base/QdButton.vue'
|
|
25
25
|
import Select from 'primevue/select'
|
|
26
26
|
import AutoComplete from 'primevue/autocomplete'
|
|
27
27
|
import SplitButton from 'primevue/splitbutton'
|
|
@@ -333,7 +333,7 @@ function getFilterClass(filter: FilterConfig): Record<string, boolean> {
|
|
|
333
333
|
<!-- Mobile: split button (primary action + dropdown) -->
|
|
334
334
|
<template v-if="isMobile && headerActions.length > 0 && primaryHeaderAction">
|
|
335
335
|
<!-- Single action: just a button -->
|
|
336
|
-
<
|
|
336
|
+
<QdButton
|
|
337
337
|
v-if="headerActions.length === 1"
|
|
338
338
|
:label="resolveLabel(primaryHeaderAction.label)"
|
|
339
339
|
:icon="primaryHeaderAction.icon"
|
|
@@ -355,7 +355,7 @@ function getFilterClass(filter: FilterConfig): Record<string, boolean> {
|
|
|
355
355
|
</template>
|
|
356
356
|
<!-- Desktop: individual buttons -->
|
|
357
357
|
<template v-else>
|
|
358
|
-
<
|
|
358
|
+
<QdButton
|
|
359
359
|
v-for="action in headerActions"
|
|
360
360
|
:key="action.name"
|
|
361
361
|
:label="resolveLabel(action.label)"
|
|
@@ -417,7 +417,7 @@ function getFilterClass(filter: FilterConfig): Record<string, boolean> {
|
|
|
417
417
|
/>
|
|
418
418
|
</template>
|
|
419
419
|
<slot name="filters" ></slot>
|
|
420
|
-
<
|
|
420
|
+
<QdButton
|
|
421
421
|
v-if="hasActiveFilters"
|
|
422
422
|
icon="pi pi-filter-slash"
|
|
423
423
|
severity="secondary"
|
|
@@ -22,7 +22,7 @@ import { useRouter } from 'vue-router'
|
|
|
22
22
|
import Card from 'primevue/card'
|
|
23
23
|
import InputText from 'primevue/inputtext'
|
|
24
24
|
import Password from 'primevue/password'
|
|
25
|
-
import
|
|
25
|
+
import QdButton from '../base/QdButton.vue'
|
|
26
26
|
import type { OrchestratorLike } from '../../entity/EntityManager.interface'
|
|
27
27
|
|
|
28
28
|
/**
|
|
@@ -245,7 +245,7 @@ async function handleLogin(): Promise<void> {
|
|
|
245
245
|
:disabled="loading"
|
|
246
246
|
/>
|
|
247
247
|
</div>
|
|
248
|
-
<
|
|
248
|
+
<QdButton
|
|
249
249
|
type="submit"
|
|
250
250
|
:label="submitLabel"
|
|
251
251
|
icon="pi pi-sign-in"
|
|
@@ -41,8 +41,8 @@ import { computed, type PropType } from 'vue'
|
|
|
41
41
|
import PageHeader from '../layout/PageHeader.vue'
|
|
42
42
|
import CardShell from '../layout/CardShell.vue'
|
|
43
43
|
import { formatFetchError } from '../../utils/errors'
|
|
44
|
-
import
|
|
45
|
-
import
|
|
44
|
+
import QdButton from '../base/QdButton.vue'
|
|
45
|
+
import QdMessage from '../base/QdMessage.vue'
|
|
46
46
|
import FieldGroups from '../item/FieldGroups.vue'
|
|
47
47
|
import ShowField from './ShowField.vue'
|
|
48
48
|
import type { ButtonSeverity } from '../../types'
|
|
@@ -187,7 +187,7 @@ type ShowFieldValue = string | number | boolean | Date | Record<string, unknown>
|
|
|
187
187
|
<template #actions>
|
|
188
188
|
<slot name="header-actions" />
|
|
189
189
|
<!-- Header actions from builder (e.g., edit) -->
|
|
190
|
-
<
|
|
190
|
+
<QdButton
|
|
191
191
|
v-for="action in headerActions"
|
|
192
192
|
:key="action.name"
|
|
193
193
|
:label="action.label"
|
|
@@ -215,9 +215,9 @@ type ShowFieldValue = string | number | boolean | Date | Record<string, unknown>
|
|
|
215
215
|
<!-- Error State (fetch error) -->
|
|
216
216
|
<template v-else-if="fetchError">
|
|
217
217
|
<slot name="error" :error="fetchError">
|
|
218
|
-
<
|
|
218
|
+
<QdMessage severity="error" :closable="false" class="show-error-message">
|
|
219
219
|
{{ fetchErrorMessage }}
|
|
220
|
-
</
|
|
220
|
+
</QdMessage>
|
|
221
221
|
</slot>
|
|
222
222
|
</template>
|
|
223
223
|
|
|
@@ -269,7 +269,7 @@ type ShowFieldValue = string | number | boolean | Date | Record<string, unknown>
|
|
|
269
269
|
<template v-if="showActions && footerActions.length > 0">
|
|
270
270
|
<slot name="footer">
|
|
271
271
|
<div class="show-actions">
|
|
272
|
-
<
|
|
272
|
+
<QdButton
|
|
273
273
|
v-for="action in footerActions"
|
|
274
274
|
:key="action.name"
|
|
275
275
|
:label="action.label"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TestManager - Auto-generated EntityManager
|
|
3
|
+
*
|
|
4
|
+
* Generated by qdadm/gen/generateManagers
|
|
5
|
+
* DO NOT EDIT MANUALLY - Changes will be overwritten
|
|
6
|
+
*
|
|
7
|
+
* Entity: test
|
|
8
|
+
* Endpoint: /test
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { EntityManager } from '@quazardous/qdadm'
|
|
12
|
+
import { ApiStorage } from '@quazardous/qdadm'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Schema definition for test
|
|
16
|
+
* @type {import('qdadm/gen').UnifiedEntitySchema}
|
|
17
|
+
*/
|
|
18
|
+
export const testSchema = {
|
|
19
|
+
name: "test",
|
|
20
|
+
endpoint: "/test",
|
|
21
|
+
fields: {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Storage options for test
|
|
26
|
+
*/
|
|
27
|
+
const storageOptions = {
|
|
28
|
+
endpoint: "/test"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* TestManager instance
|
|
33
|
+
*
|
|
34
|
+
* Provides CRUD operations for test entity.
|
|
35
|
+
*
|
|
36
|
+
* @type {EntityManager}
|
|
37
|
+
*/
|
|
38
|
+
export const testManager = new EntityManager({
|
|
39
|
+
...{
|
|
40
|
+
name: "test",
|
|
41
|
+
idField: "id",
|
|
42
|
+
fields: {}
|
|
43
|
+
},
|
|
44
|
+
storage: new ApiStorage(storageOptions)
|
|
45
|
+
})
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { useListPage, ListPage, useOrchestrator } from '../../index.js'
|
|
7
7
|
import Column from 'primevue/column'
|
|
8
|
-
import
|
|
8
|
+
import QdMessage from '../../components/base/QdMessage.vue'
|
|
9
9
|
import Chip from 'primevue/chip'
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -49,7 +49,7 @@ const storageCanPersist: boolean = manager?.rolesProvider?.canPersist ?? false
|
|
|
49
49
|
<ListPage v-bind="list.props.value" v-on="list.events">
|
|
50
50
|
<!-- Read-only Warning -->
|
|
51
51
|
<template #before-table>
|
|
52
|
-
<
|
|
52
|
+
<QdMessage v-if="!storageCanPersist" severity="info" :closable="false" class="mb-4">
|
|
53
53
|
<div class="flex align-items-center gap-2">
|
|
54
54
|
<i class="pi pi-info-circle text-xl"></i>
|
|
55
55
|
<span>
|
|
@@ -57,7 +57,7 @@ const storageCanPersist: boolean = manager?.rolesProvider?.canPersist ?? false
|
|
|
57
57
|
Use a PersistableRoleProvider for editing.
|
|
58
58
|
</span>
|
|
59
59
|
</div>
|
|
60
|
-
</
|
|
60
|
+
</QdMessage>
|
|
61
61
|
</template>
|
|
62
62
|
|
|
63
63
|
<template #columns>
|
|
@@ -34,9 +34,22 @@ interface PendingRequest {
|
|
|
34
34
|
timeout: ReturnType<typeof setTimeout>
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/** Typed page-side queries (#1398) — served by the injected client. */
|
|
38
|
+
export type DebugQueryType =
|
|
39
|
+
| 'describe'
|
|
40
|
+
| 'dump'
|
|
41
|
+
| 'call'
|
|
42
|
+
| 'sessionInfo'
|
|
43
|
+
| 'routes'
|
|
44
|
+
| 'entityState'
|
|
45
|
+
| 'entityCall'
|
|
46
|
+
| 'storageDump'
|
|
47
|
+
| 'bootlog'
|
|
48
|
+
| 'recentSignals'
|
|
49
|
+
|
|
37
50
|
interface DebugRequest {
|
|
38
51
|
id: string
|
|
39
|
-
type:
|
|
52
|
+
type: DebugQueryType
|
|
40
53
|
payload?: unknown
|
|
41
54
|
/** Target a specific session (server → client). */
|
|
42
55
|
sessionId?: string
|
|
@@ -67,6 +80,26 @@ interface SessionState {
|
|
|
67
80
|
meta: Record<string, unknown>
|
|
68
81
|
}
|
|
69
82
|
|
|
83
|
+
/** Broker surface exposed to sibling plugins via `plugin.api` (#1398). */
|
|
84
|
+
export interface QdadmDebugPluginApi {
|
|
85
|
+
/** Send a typed query to the page and await its reply. */
|
|
86
|
+
ask: (type: DebugQueryType, payload?: unknown, sessionId?: string) => Promise<unknown>
|
|
87
|
+
/** Resolve a session param ('latest' or id) to a live session, if any. */
|
|
88
|
+
pickSession: (
|
|
89
|
+
sessionParam: string | null
|
|
90
|
+
) => { id: string; lastSeenAt: number; meta: Record<string, unknown> } | null
|
|
91
|
+
listSessions: () => Array<{
|
|
92
|
+
id: string
|
|
93
|
+
firstSeenAt: number
|
|
94
|
+
lastSeenAt: number
|
|
95
|
+
ageMs: number
|
|
96
|
+
hasDescribe: boolean
|
|
97
|
+
hasSnapshot: boolean
|
|
98
|
+
meta: Record<string, unknown>
|
|
99
|
+
}>
|
|
100
|
+
prefix: string
|
|
101
|
+
}
|
|
102
|
+
|
|
70
103
|
export interface QdadmDebugPluginOptions {
|
|
71
104
|
/** URL prefix (default '/__qdadm') */
|
|
72
105
|
prefix?: string
|
|
@@ -203,9 +236,21 @@ export function qdadmDebugPlugin(options: QdadmDebugPluginOptions = {}): Plugin
|
|
|
203
236
|
return s
|
|
204
237
|
}
|
|
205
238
|
|
|
239
|
+
/** Inter-plugin API (#1398) — consumed by @quazardous/qdmcp. */
|
|
240
|
+
const api: QdadmDebugPluginApi = {
|
|
241
|
+
ask,
|
|
242
|
+
pickSession: (sessionParam) => {
|
|
243
|
+
const s = pickSession(sessionParam)
|
|
244
|
+
return s ? { id: s.id, lastSeenAt: s.lastSeenAt, meta: s.meta } : null
|
|
245
|
+
},
|
|
246
|
+
listSessions,
|
|
247
|
+
prefix,
|
|
248
|
+
}
|
|
249
|
+
|
|
206
250
|
return {
|
|
207
251
|
name: 'qdadm-debug',
|
|
208
252
|
apply: 'serve',
|
|
253
|
+
api,
|
|
209
254
|
|
|
210
255
|
transformIndexHtml: {
|
|
211
256
|
order: 'pre',
|
|
@@ -294,7 +339,10 @@ export function qdadmDebugPlugin(options: QdadmDebugPluginOptions = {}): Plugin
|
|
|
294
339
|
res.setHeader('x-qdadm-session', session.id)
|
|
295
340
|
return sendJson(res, 200, data)
|
|
296
341
|
}
|
|
297
|
-
|
|
342
|
+
// Unknown subpath: fall through — sibling plugins (e.g.
|
|
343
|
+
// @quazardous/qdadm-mcp) mount their own endpoints under the
|
|
344
|
+
// same prefix (#1398).
|
|
345
|
+
return next()
|
|
298
346
|
} catch (e) {
|
|
299
347
|
return sendJson(res, 500, { error: (e as Error).message })
|
|
300
348
|
}
|
|
@@ -333,6 +381,34 @@ function readBody(req: IncomingMessage): Promise<string> {
|
|
|
333
381
|
* stays fresh. Listens for ad-hoc requests targeted at this session.
|
|
334
382
|
*/
|
|
335
383
|
const HMR_CLIENT_SCRIPT = String.raw`
|
|
384
|
+
// ── Boot capture (#1398): this head module runs BEFORE the app's entry
|
|
385
|
+
// module, so console/window errors from the very first app statement are
|
|
386
|
+
// buffered — visible over MCP even when the app dies before the bridge
|
|
387
|
+
// exists (the class of failure a bridge-only design can never see).
|
|
388
|
+
const __bootAt = Date.now()
|
|
389
|
+
const __bootlog = { errors: [], warns: [], pageErrors: [], rejections: [] }
|
|
390
|
+
function __cap(arr, entry) { arr.push(entry); if (arr.length > 200) arr.shift() }
|
|
391
|
+
function __fmt(args) {
|
|
392
|
+
try {
|
|
393
|
+
return Array.from(args).map((a) => {
|
|
394
|
+
if (a instanceof Error) return a.stack || a.message
|
|
395
|
+
if (typeof a === 'object') { try { return JSON.stringify(a) } catch (e) { return String(a) } }
|
|
396
|
+
return String(a)
|
|
397
|
+
}).join(' ').slice(0, 2000)
|
|
398
|
+
} catch (e) { return '[unformattable]' }
|
|
399
|
+
}
|
|
400
|
+
const __origError = console.error.bind(console)
|
|
401
|
+
const __origWarn = console.warn.bind(console)
|
|
402
|
+
console.error = function () { __cap(__bootlog.errors, { at: Date.now(), msg: __fmt(arguments) }); __origError.apply(null, arguments) }
|
|
403
|
+
console.warn = function () { __cap(__bootlog.warns, { at: Date.now(), msg: __fmt(arguments) }); __origWarn.apply(null, arguments) }
|
|
404
|
+
window.addEventListener('error', (e) => {
|
|
405
|
+
__cap(__bootlog.pageErrors, { at: Date.now(), msg: String(e.message || e), source: e.filename ? e.filename + ':' + e.lineno : undefined })
|
|
406
|
+
})
|
|
407
|
+
window.addEventListener('unhandledrejection', (e) => {
|
|
408
|
+
let msg; try { msg = e.reason && (e.reason.stack || e.reason.message) || String(e.reason) } catch (err) { msg = '[unreadable reason]' }
|
|
409
|
+
__cap(__bootlog.rejections, { at: Date.now(), msg: String(msg).slice(0, 2000) })
|
|
410
|
+
})
|
|
411
|
+
|
|
336
412
|
const hot = import.meta.hot
|
|
337
413
|
if (!hot) {
|
|
338
414
|
console.warn('[qdadm-debug] import.meta.hot unavailable in injected script')
|
|
@@ -344,6 +420,95 @@ if (!hot) {
|
|
|
344
420
|
|
|
345
421
|
const w = /** @type {any} */ (window)
|
|
346
422
|
|
|
423
|
+
// Signal ring buffer — armed once __qdadm.signals is reachable.
|
|
424
|
+
const __signals = []
|
|
425
|
+
let __signalsArmed = false
|
|
426
|
+
function __armSignals() {
|
|
427
|
+
if (__signalsArmed) return
|
|
428
|
+
const q = w.__qdadm
|
|
429
|
+
if (!q || !q.signals || typeof q.signals.on !== 'function') return
|
|
430
|
+
try {
|
|
431
|
+
q.signals.on('**', (event) => { __cap(__signals, { at: Date.now(), name: event && event.name }) ; if (__signals.length > 100) __signals.shift() })
|
|
432
|
+
__signalsArmed = true
|
|
433
|
+
} catch (e) {}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function __sessionInfo() {
|
|
437
|
+
const q = w.__qdadm || {}
|
|
438
|
+
let app = {}
|
|
439
|
+
try { app = (q.kernel && q.kernel.options && q.kernel.options.app) || {} } catch (e) {}
|
|
440
|
+
let route = null
|
|
441
|
+
try { const r = q.router && q.router.currentRoute && q.router.currentRoute.value; route = r ? { name: r.name, fullPath: r.fullPath } : null } catch (e) {}
|
|
442
|
+
return {
|
|
443
|
+
sessionId, bootAt: __bootAt, now: Date.now(), url: location.href,
|
|
444
|
+
app: { name: app.name, version: typeof app.version === 'function' ? app.version() : app.version },
|
|
445
|
+
route, bridgeReady: !!bridge, signalsArmed: __signalsArmed,
|
|
446
|
+
bootlogCounts: { errors: __bootlog.errors.length, warns: __bootlog.warns.length, pageErrors: __bootlog.pageErrors.length, rejections: __bootlog.rejections.length },
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function __routes() {
|
|
451
|
+
const q = w.__qdadm
|
|
452
|
+
if (!q || !q.router) throw new Error('router not ready')
|
|
453
|
+
return q.router.getRoutes().map((r) => ({
|
|
454
|
+
name: r.name, path: r.path,
|
|
455
|
+
meta: { entity: r.meta && r.meta.entity, layout: r.meta && r.meta.layout, requiresAuth: r.meta && r.meta.requiresAuth, public: r.meta && r.meta.public },
|
|
456
|
+
}))
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function __manager(entity) {
|
|
460
|
+
const q = w.__qdadm
|
|
461
|
+
if (!q || !q.orchestrator) throw new Error('orchestrator not ready')
|
|
462
|
+
if (!q.orchestrator.isRegistered(entity)) throw new Error('entity not registered: ' + entity + ' (known: ' + q.orchestrator.getRegisteredNames().join(', ') + ')')
|
|
463
|
+
return q.orchestrator.get(entity)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function __entityState(payload) {
|
|
467
|
+
const q = w.__qdadm
|
|
468
|
+
if (!payload || !payload.entity) {
|
|
469
|
+
return { entities: q && q.orchestrator ? q.orchestrator.getRegisteredNames() : [] }
|
|
470
|
+
}
|
|
471
|
+
const m = __manager(payload.entity)
|
|
472
|
+
const safe = (fn) => { try { return fn() } catch (e) { return '[error: ' + (e && e.message) + ']' } }
|
|
473
|
+
const fields = {}
|
|
474
|
+
try {
|
|
475
|
+
const fs = m.fields || m._fields || {}
|
|
476
|
+
for (const k in fs) fields[k] = { type: fs[k] && fs[k].type, label: fs[k] && fs[k].label, required: !!(fs[k] && fs[k].required) }
|
|
477
|
+
} catch (e) {}
|
|
478
|
+
return {
|
|
479
|
+
entity: payload.entity,
|
|
480
|
+
idField: safe(() => m.idField), labelField: safe(() => m.labelField),
|
|
481
|
+
label: safe(() => m.label), labelPlural: safe(() => m.labelPlural),
|
|
482
|
+
can: { create: safe(() => m.canCreate()), read: safe(() => m.canRead()), update: safe(() => m.canUpdate()), delete: safe(() => m.canDelete()) },
|
|
483
|
+
fields,
|
|
484
|
+
storage: safe(() => { const st = m.storage || m._storage; return st ? { kind: st.constructor && st.constructor.name, storageKey: st._storageKey } : null }),
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async function __entityCall(payload) {
|
|
489
|
+
if (!payload || !payload.entity || !payload.op) throw new Error('entityCall requires { entity, op, ... }')
|
|
490
|
+
const m = __manager(payload.entity)
|
|
491
|
+
const op = payload.op
|
|
492
|
+
if (op === 'list') return await m.list(payload.params || {})
|
|
493
|
+
if (op === 'get') return await m.get(payload.id)
|
|
494
|
+
if (op === 'create') return await m.create(payload.data || {})
|
|
495
|
+
if (op === 'update') return await m.update(payload.id, payload.data || {})
|
|
496
|
+
if (op === 'delete') { await m.delete(payload.id); return { deleted: payload.id } }
|
|
497
|
+
throw new Error('unknown op "' + op + '" (list|get|create|update|delete)')
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function __storageDump(payload) {
|
|
501
|
+
if (!payload || !payload.entity) throw new Error('storageDump requires { entity }')
|
|
502
|
+
const m = __manager(payload.entity)
|
|
503
|
+
const st = m.storage || m._storage
|
|
504
|
+
const key = st && st._storageKey
|
|
505
|
+
if (!key) return { storageKey: null, note: 'storage exposes no localStorage key (' + (st && st.constructor && st.constructor.name) + ')' }
|
|
506
|
+
const raw = localStorage.getItem(key)
|
|
507
|
+
let parsed = null
|
|
508
|
+
try { parsed = raw ? JSON.parse(raw) : null } catch (e) { parsed = '[unparsable]' }
|
|
509
|
+
return { storageKey: key, count: Array.isArray(parsed) ? parsed.length : null, data: parsed }
|
|
510
|
+
}
|
|
511
|
+
|
|
347
512
|
function helloIfNeeded() {
|
|
348
513
|
try { hot.send('qdadm:debug:push', { type: 'hello', sessionId, meta }) } catch (e) {}
|
|
349
514
|
}
|
|
@@ -365,6 +530,16 @@ if (!hot) {
|
|
|
365
530
|
if (msg && msg.sessionId && msg.sessionId !== sessionId) return
|
|
366
531
|
const id = (msg && msg.id) || ''
|
|
367
532
|
try {
|
|
533
|
+
// Typed queries (#1398): bridge-independent — they must answer even
|
|
534
|
+
// when the app died before the bridge existed.
|
|
535
|
+
if (msg.type === 'bootlog') { hot.send('qdadm:debug:reply', { id, ok: true, data: __bootlog, sessionId }); return }
|
|
536
|
+
if (msg.type === 'sessionInfo') { hot.send('qdadm:debug:reply', { id, ok: true, data: __sessionInfo(), sessionId }); return }
|
|
537
|
+
if (msg.type === 'recentSignals') { __armSignals(); hot.send('qdadm:debug:reply', { id, ok: true, data: { armed: __signalsArmed, events: __signals }, sessionId }); return }
|
|
538
|
+
if (msg.type === 'routes') { hot.send('qdadm:debug:reply', { id, ok: true, data: __routes(), sessionId }); return }
|
|
539
|
+
if (msg.type === 'entityState') { hot.send('qdadm:debug:reply', { id, ok: true, data: __entityState(msg.payload), sessionId }); return }
|
|
540
|
+
if (msg.type === 'entityCall') { const data = await __entityCall(msg.payload); hot.send('qdadm:debug:reply', { id, ok: true, data, sessionId }); return }
|
|
541
|
+
if (msg.type === 'storageDump') { hot.send('qdadm:debug:reply', { id, ok: true, data: __storageDump(msg.payload), sessionId }); return }
|
|
542
|
+
|
|
368
543
|
const b = w.__qdadm && w.__qdadm.debug && w.__qdadm.debug.bridge
|
|
369
544
|
if (!b) throw new Error('debug bridge not ready')
|
|
370
545
|
let data
|
|
@@ -385,6 +560,7 @@ if (!hot) {
|
|
|
385
560
|
bridge = w.__qdadm && w.__qdadm.debug && w.__qdadm.debug.bridge
|
|
386
561
|
if (!bridge) return
|
|
387
562
|
clearInterval(attachTimer)
|
|
563
|
+
__armSignals()
|
|
388
564
|
helloIfNeeded()
|
|
389
565
|
pushDescribe()
|
|
390
566
|
pushSnapshot()
|