qdadm 0.13.0 → 0.14.2
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/README.md +26 -117
- package/package.json +1 -2
- package/src/components/SeverityTag.vue +71 -0
- package/src/components/editors/ScopeEditor.vue +26 -17
- package/src/components/index.js +1 -0
- package/src/components/layout/AppLayout.vue +29 -1
- package/src/components/layout/PageHeader.vue +71 -5
- package/src/components/layout/PageLayout.vue +4 -3
- package/src/composables/index.js +1 -0
- package/src/composables/useBareForm.js +76 -4
- package/src/composables/useBreadcrumb.js +23 -14
- package/src/composables/useForm.js +48 -2
- package/src/composables/useListPageBuilder.js +51 -74
- package/src/composables/useManager.js +20 -0
- package/src/entity/EntityManager.js +391 -9
- package/src/entity/storage/ApiStorage.js +5 -0
- package/src/entity/storage/LocalStorage.js +5 -0
- package/src/kernel/Kernel.js +25 -8
- package/src/plugin.js +3 -1
- package/src/styles/main.css +44 -0
- package/src/styles/theme/index.css +1 -1
- package/CHANGELOG.md +0 -270
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ref, computed, provide, onUnmounted } from 'vue'
|
|
1
|
+
import { ref, computed, provide, inject, onUnmounted } from 'vue'
|
|
2
2
|
import { useRoute, useRouter } from 'vue-router'
|
|
3
3
|
import { useToast } from 'primevue/usetoast'
|
|
4
4
|
import { useDirtyState } from './useDirtyState'
|
|
@@ -20,6 +20,9 @@ import { registerGuardDialog, unregisterGuardDialog } from './useGuardStore'
|
|
|
20
20
|
* } = useBareForm({
|
|
21
21
|
* getState: () => ({ form: form.value }),
|
|
22
22
|
* routePrefix: 'agents', // for cancel() navigation
|
|
23
|
+
* entityName: 'Agent', // for auto-title (optional, or use 'entity' for auto-lookup)
|
|
24
|
+
* labelField: 'name', // field to use as entity label (optional)
|
|
25
|
+
* entity: 'agents', // EntityManager name for auto metadata (optional)
|
|
23
26
|
* guard: true, // enable unsaved changes modal
|
|
24
27
|
* onGuardSave: () => save() // optional save callback for guard modal
|
|
25
28
|
* })
|
|
@@ -32,20 +35,25 @@ import { registerGuardDialog, unregisterGuardDialog } from './useGuardStore'
|
|
|
32
35
|
* - Common computed (isEdit, entityId)
|
|
33
36
|
* - Navigation helpers (cancel)
|
|
34
37
|
* - Access to router, route, toast
|
|
38
|
+
* - Auto page title via provide (for PageHeader)
|
|
35
39
|
*
|
|
36
40
|
* @param {Object} options
|
|
37
41
|
* @param {Function} options.getState - Function returning current form state for comparison
|
|
38
42
|
* @param {string} options.routePrefix - Route name for cancel navigation (default: '')
|
|
43
|
+
* @param {string} options.entityName - Display name for entity (default: from manager or derived from routePrefix)
|
|
44
|
+
* @param {string|Function} options.labelField - Field name or function to extract entity label (default: from manager or 'name')
|
|
45
|
+
* @param {string|Ref|Object} options.entity - EntityManager name (string) for auto metadata, OR entity data (Ref/Object) for breadcrumb
|
|
39
46
|
* @param {boolean} options.guard - Enable unsaved changes guard (default: true)
|
|
40
47
|
* @param {Function} options.onGuardSave - Callback for save button in guard modal
|
|
41
48
|
* @param {Function} options.getId - Custom function to extract entity ID from route (optional)
|
|
42
|
-
* @param {Ref|Object} options.entity - Entity data for dynamic breadcrumb labels (optional)
|
|
43
49
|
* @param {Function} options.breadcrumbLabel - Callback (entity) => string for custom breadcrumb label (optional)
|
|
44
50
|
*/
|
|
45
51
|
export function useBareForm(options = {}) {
|
|
46
52
|
const {
|
|
47
53
|
getState,
|
|
48
54
|
routePrefix = '',
|
|
55
|
+
entityName = null,
|
|
56
|
+
labelField = null,
|
|
49
57
|
guard = true,
|
|
50
58
|
onGuardSave = null,
|
|
51
59
|
getId = null,
|
|
@@ -57,6 +65,19 @@ export function useBareForm(options = {}) {
|
|
|
57
65
|
throw new Error('useBareForm requires a getState function')
|
|
58
66
|
}
|
|
59
67
|
|
|
68
|
+
// Try to get EntityManager metadata if entity is a string
|
|
69
|
+
let manager = null
|
|
70
|
+
if (typeof entity === 'string') {
|
|
71
|
+
const orchestrator = inject('qdadmOrchestrator', null)
|
|
72
|
+
if (orchestrator) {
|
|
73
|
+
try {
|
|
74
|
+
manager = orchestrator.get(entity)
|
|
75
|
+
} catch {
|
|
76
|
+
// Manager not found, continue without it
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
60
81
|
// Router, route, toast - common dependencies
|
|
61
82
|
const router = useRouter()
|
|
62
83
|
const route = useRoute()
|
|
@@ -88,8 +109,53 @@ export function useBareForm(options = {}) {
|
|
|
88
109
|
provide('isFieldDirty', isFieldDirty)
|
|
89
110
|
provide('dirtyFields', dirtyFields)
|
|
90
111
|
|
|
112
|
+
// Resolve entityName: explicit > manager > derived from routePrefix
|
|
113
|
+
const derivedEntityName = routePrefix
|
|
114
|
+
? routePrefix.charAt(0).toUpperCase() + routePrefix.slice(1).replace(/s$/, '')
|
|
115
|
+
: null
|
|
116
|
+
const effectiveEntityName = entityName || manager?.label || derivedEntityName
|
|
117
|
+
|
|
118
|
+
// Resolve labelField: explicit > manager > default 'name'
|
|
119
|
+
const effectiveLabelField = labelField || manager?.labelField || 'name'
|
|
120
|
+
|
|
121
|
+
// Provide entity context for child components (e.g., SeverityTag auto-discovery)
|
|
122
|
+
if (routePrefix) {
|
|
123
|
+
const entityNameForProvider = routePrefix.endsWith('s') ? routePrefix : routePrefix + 's'
|
|
124
|
+
provide('mainEntity', entityNameForProvider)
|
|
125
|
+
} else if (typeof entity === 'string') {
|
|
126
|
+
provide('mainEntity', entity)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Auto page title parts for PageHeader
|
|
130
|
+
const getEntityLabel = () => {
|
|
131
|
+
const state = getState()
|
|
132
|
+
const formData = state.form || state
|
|
133
|
+
if (!formData) return null
|
|
134
|
+
// Use manager.getEntityLabel if available, otherwise use effectiveLabelField
|
|
135
|
+
if (manager) {
|
|
136
|
+
return manager.getEntityLabel(formData)
|
|
137
|
+
}
|
|
138
|
+
if (typeof effectiveLabelField === 'function') {
|
|
139
|
+
return effectiveLabelField(formData)
|
|
140
|
+
}
|
|
141
|
+
return formData[effectiveLabelField] || null
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const pageTitleParts = computed(() => ({
|
|
145
|
+
action: isEdit.value ? 'Edit' : 'Create',
|
|
146
|
+
entityName: effectiveEntityName,
|
|
147
|
+
entityLabel: isEdit.value ? getEntityLabel() : null
|
|
148
|
+
}))
|
|
149
|
+
|
|
150
|
+
// Provide title parts for automatic PageHeader consumption
|
|
151
|
+
if (effectiveEntityName) {
|
|
152
|
+
provide('qdadmPageTitleParts', pageTitleParts)
|
|
153
|
+
}
|
|
154
|
+
|
|
91
155
|
// Breadcrumb (auto-generated from route path, with optional entity for dynamic labels)
|
|
92
|
-
|
|
156
|
+
// Only pass entity to breadcrumb if it's actual entity data (not a string manager name)
|
|
157
|
+
const breadcrumbEntity = typeof entity === 'string' ? null : entity
|
|
158
|
+
const { breadcrumbItems } = useBreadcrumb({ entity: breadcrumbEntity, getEntityLabel: breadcrumbLabel })
|
|
93
159
|
|
|
94
160
|
// Unsaved changes guard
|
|
95
161
|
let guardDialog = null
|
|
@@ -117,6 +183,9 @@ export function useBareForm(options = {}) {
|
|
|
117
183
|
route,
|
|
118
184
|
toast,
|
|
119
185
|
|
|
186
|
+
// Manager (if resolved from entity string)
|
|
187
|
+
manager,
|
|
188
|
+
|
|
120
189
|
// State
|
|
121
190
|
loading,
|
|
122
191
|
saving,
|
|
@@ -138,6 +207,9 @@ export function useBareForm(options = {}) {
|
|
|
138
207
|
breadcrumb: breadcrumbItems,
|
|
139
208
|
|
|
140
209
|
// Guard dialog (for UnsavedChangesDialog component)
|
|
141
|
-
guardDialog
|
|
210
|
+
guardDialog,
|
|
211
|
+
|
|
212
|
+
// Title helpers
|
|
213
|
+
pageTitleParts
|
|
142
214
|
}
|
|
143
215
|
}
|
|
@@ -131,8 +131,16 @@ export function useBreadcrumb(options = {}) {
|
|
|
131
131
|
return items
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
// Action segments that should be excluded from breadcrumb
|
|
135
|
+
const actionSegments = ['edit', 'create', 'show', 'view', 'new', 'delete']
|
|
136
|
+
|
|
134
137
|
/**
|
|
135
138
|
* Build breadcrumb automatically from route path
|
|
139
|
+
*
|
|
140
|
+
* Breadcrumb shows navigable parents only:
|
|
141
|
+
* - Excludes action segments (edit, create, show, etc.)
|
|
142
|
+
* - Excludes IDs
|
|
143
|
+
* - All items have links (to navigate back to parent)
|
|
136
144
|
*/
|
|
137
145
|
function buildFromPath(entity, getEntityLabel) {
|
|
138
146
|
const items = []
|
|
@@ -147,19 +155,17 @@ export function useBreadcrumb(options = {}) {
|
|
|
147
155
|
const segment = segments[i]
|
|
148
156
|
currentPath += `/${segment}`
|
|
149
157
|
|
|
150
|
-
//
|
|
158
|
+
// Skip action segments (edit, create, show, etc.)
|
|
159
|
+
if (actionSegments.includes(segment.toLowerCase())) {
|
|
160
|
+
continue
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Skip IDs: numeric, UUID, ULID, or any alphanumeric string > 10 chars
|
|
151
164
|
const isId = /^\d+$/.test(segment) || // numeric
|
|
152
165
|
segment.match(/^[0-9a-f-]{36}$/i) || // UUID
|
|
153
166
|
segment.match(/^[0-7][0-9a-hjkmnp-tv-z]{25}$/i) || // ULID
|
|
154
|
-
(segment.match(/^[a-z0-9]+$/i) && segment.length > 10) // Generated ID
|
|
167
|
+
(segment.match(/^[a-z0-9]+$/i) && segment.length > 10) // Generated ID
|
|
155
168
|
if (isId) {
|
|
156
|
-
// If we have entity data, show its label instead of the ID
|
|
157
|
-
if (entity && getEntityLabel) {
|
|
158
|
-
const entityLabel = getEntityLabel(entity)
|
|
159
|
-
if (entityLabel) {
|
|
160
|
-
items.push({ label: entityLabel, to: null, icon: null })
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
169
|
continue
|
|
164
170
|
}
|
|
165
171
|
|
|
@@ -179,14 +185,17 @@ export function useBreadcrumb(options = {}) {
|
|
|
179
185
|
icon: i === 0 ? iconMap[segment] : null
|
|
180
186
|
}
|
|
181
187
|
|
|
182
|
-
// Last item has no link
|
|
183
|
-
if (i === segments.length - 1) {
|
|
184
|
-
item.to = null
|
|
185
|
-
}
|
|
186
|
-
|
|
187
188
|
items.push(item)
|
|
188
189
|
}
|
|
189
190
|
|
|
191
|
+
// Remove last item if it matches current route (we only show parents)
|
|
192
|
+
if (items.length > 1) {
|
|
193
|
+
const lastItem = items[items.length - 1]
|
|
194
|
+
if (lastItem.to?.name === route.name) {
|
|
195
|
+
items.pop()
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
190
199
|
return items
|
|
191
200
|
}
|
|
192
201
|
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* })
|
|
27
27
|
* ```
|
|
28
28
|
*/
|
|
29
|
-
import { ref, watch, onMounted, inject } from 'vue'
|
|
29
|
+
import { ref, computed, watch, onMounted, inject, provide } from 'vue'
|
|
30
30
|
import { useBareForm } from './useBareForm'
|
|
31
31
|
import { deepClone } from '../utils/transformers'
|
|
32
32
|
|
|
@@ -83,6 +83,8 @@ export function useForm(options = {}) {
|
|
|
83
83
|
checkDirty,
|
|
84
84
|
// Helpers
|
|
85
85
|
cancel,
|
|
86
|
+
// Breadcrumb
|
|
87
|
+
breadcrumb,
|
|
86
88
|
// Guard dialog for unsaved changes
|
|
87
89
|
guardDialog
|
|
88
90
|
} = useBareForm({
|
|
@@ -224,6 +226,42 @@ export function useForm(options = {}) {
|
|
|
224
226
|
load()
|
|
225
227
|
})
|
|
226
228
|
|
|
229
|
+
// ============ COMPUTED TITLE ============
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Entity display label (e.g., "David Berlioz" for an agent)
|
|
233
|
+
* Uses manager.getEntityLabel() with labelField config
|
|
234
|
+
*/
|
|
235
|
+
const entityLabel = computed(() => {
|
|
236
|
+
return manager.getEntityLabel(form.value)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Auto-generated page title
|
|
241
|
+
* - Edit mode: "Edit Agent: David Berlioz"
|
|
242
|
+
* - Create mode: "Create Agent"
|
|
243
|
+
*/
|
|
244
|
+
const pageTitle = computed(() => {
|
|
245
|
+
if (isEdit.value) {
|
|
246
|
+
const label = entityLabel.value
|
|
247
|
+
return label ? `Edit ${entityName}: ${label}` : `Edit ${entityName}`
|
|
248
|
+
}
|
|
249
|
+
return `Create ${entityName}`
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Structured page title for decorated rendering
|
|
254
|
+
* Returns { action, entityName, entityLabel } for custom styling
|
|
255
|
+
*/
|
|
256
|
+
const pageTitleParts = computed(() => ({
|
|
257
|
+
action: isEdit.value ? 'Edit' : 'Create',
|
|
258
|
+
entityName,
|
|
259
|
+
entityLabel: isEdit.value ? entityLabel.value : null
|
|
260
|
+
}))
|
|
261
|
+
|
|
262
|
+
// Provide title parts for automatic PageHeader consumption
|
|
263
|
+
provide('qdadmPageTitleParts', pageTitleParts)
|
|
264
|
+
|
|
227
265
|
return {
|
|
228
266
|
// Manager access
|
|
229
267
|
manager,
|
|
@@ -248,7 +286,15 @@ export function useForm(options = {}) {
|
|
|
248
286
|
checkDirty,
|
|
249
287
|
isFieldDirty,
|
|
250
288
|
|
|
289
|
+
// Breadcrumb (auto-generated from route)
|
|
290
|
+
breadcrumb,
|
|
291
|
+
|
|
251
292
|
// Guard dialog (for UnsavedChangesDialog - pass to PageLayout)
|
|
252
|
-
guardDialog
|
|
293
|
+
guardDialog,
|
|
294
|
+
|
|
295
|
+
// Title helpers
|
|
296
|
+
entityLabel,
|
|
297
|
+
pageTitle,
|
|
298
|
+
pageTitleParts
|
|
253
299
|
}
|
|
254
300
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ref, computed, watch, onMounted, inject } from 'vue'
|
|
1
|
+
import { ref, computed, watch, onMounted, inject, provide } from 'vue'
|
|
2
2
|
import { useRouter, useRoute } from 'vue-router'
|
|
3
3
|
import { useToast } from 'primevue/usetoast'
|
|
4
4
|
import { useConfirm } from 'primevue/useconfirm'
|
|
@@ -155,6 +155,9 @@ export function useListPageBuilder(config = {}) {
|
|
|
155
155
|
}
|
|
156
156
|
const manager = orchestrator.get(entity)
|
|
157
157
|
|
|
158
|
+
// Provide entity context for child components (e.g., SeverityTag auto-discovery)
|
|
159
|
+
provide('mainEntity', entity)
|
|
160
|
+
|
|
158
161
|
// Read config from manager with option overrides
|
|
159
162
|
const entityName = config.entityName ?? manager.label
|
|
160
163
|
const entityNamePlural = config.entityNamePlural ?? manager.labelPlural
|
|
@@ -663,71 +666,35 @@ export function useListPageBuilder(config = {}) {
|
|
|
663
666
|
searchConfig.value = { ...searchConfig.value, ...searchCfg }
|
|
664
667
|
}
|
|
665
668
|
|
|
666
|
-
// ============
|
|
667
|
-
//
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
return manager?.localFilterThreshold ?? 100
|
|
671
|
-
})
|
|
669
|
+
// ============ CACHE MODE ============
|
|
670
|
+
// EntityManager handles caching and filtering automatically via query()
|
|
671
|
+
// This computed applies any custom local_filter callbacks on top
|
|
672
|
+
const fromCache = ref(false)
|
|
672
673
|
|
|
673
|
-
const effectiveFilterMode = computed(() => {
|
|
674
|
-
if (filterMode !== 'auto') return filterMode
|
|
675
|
-
// Switch to local if we have few items
|
|
676
|
-
return items.value.length > 0 && items.value.length < resolvedThreshold.value ? 'local' : 'manager'
|
|
677
|
-
})
|
|
678
|
-
|
|
679
|
-
// Computed for local filtering
|
|
680
|
-
// Only applied when effectiveFilterMode === 'local'
|
|
681
|
-
// In manager mode, items are already filtered by the manager
|
|
682
674
|
const filteredItems = computed(() => {
|
|
683
|
-
const isLocalMode = effectiveFilterMode.value === 'local'
|
|
684
|
-
|
|
685
|
-
// In manager mode, return items as-is (manager already filtered)
|
|
686
|
-
if (!isLocalMode) {
|
|
687
|
-
return items.value
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// Local mode: apply all filtering client-side
|
|
691
675
|
let result = [...items.value]
|
|
692
676
|
|
|
693
|
-
// Apply
|
|
694
|
-
// local_search: false = manager only, skip in local mode
|
|
695
|
-
if (searchQuery.value && searchConfig.value.local_search !== false) {
|
|
696
|
-
const query = searchQuery.value.toLowerCase()
|
|
697
|
-
// Default: search on configured fields
|
|
698
|
-
const localSearch = searchConfig.value.local_search || (
|
|
699
|
-
searchConfig.value.fields?.length
|
|
700
|
-
? (item, q) => searchConfig.value.fields.some(field =>
|
|
701
|
-
String(item[field] || '').toLowerCase().includes(q)
|
|
702
|
-
)
|
|
703
|
-
: null
|
|
704
|
-
)
|
|
705
|
-
if (localSearch) {
|
|
706
|
-
result = result.filter(item => localSearch(item, query))
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
// Apply filters
|
|
677
|
+
// Apply custom local_filter callbacks (UI-specific post-filters)
|
|
711
678
|
for (const [name, value] of Object.entries(filterValues.value)) {
|
|
712
679
|
if (value === null || value === undefined || value === '') continue
|
|
713
680
|
const filterDef = filtersMap.value.get(name)
|
|
714
681
|
|
|
715
|
-
//
|
|
716
|
-
if (filterDef?.local_filter
|
|
682
|
+
// Only apply if there's a custom local_filter callback
|
|
683
|
+
if (typeof filterDef?.local_filter !== 'function') continue
|
|
717
684
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
result = result.filter(item =>
|
|
685
|
+
result = result.filter(item => filterDef.local_filter(item, value))
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Apply custom local_search callback
|
|
689
|
+
if (searchQuery.value && typeof searchConfig.value.local_search === 'function') {
|
|
690
|
+
const query = searchQuery.value.toLowerCase()
|
|
691
|
+
result = result.filter(item => searchConfig.value.local_search(item, query))
|
|
725
692
|
}
|
|
726
693
|
|
|
727
694
|
return result
|
|
728
695
|
})
|
|
729
696
|
|
|
730
|
-
// Items to display
|
|
697
|
+
// Items to display
|
|
731
698
|
const displayItems = computed(() => filteredItems.value)
|
|
732
699
|
|
|
733
700
|
// ============ LOADING ============
|
|
@@ -736,36 +703,40 @@ export function useListPageBuilder(config = {}) {
|
|
|
736
703
|
async function loadItems(extraParams = {}, { force = false } = {}) {
|
|
737
704
|
if (!manager) return
|
|
738
705
|
|
|
739
|
-
//
|
|
740
|
-
if (
|
|
741
|
-
|
|
706
|
+
// If forced, invalidate the manager's cache first
|
|
707
|
+
if (force && manager.invalidateCache) {
|
|
708
|
+
manager.invalidateCache()
|
|
742
709
|
}
|
|
743
710
|
|
|
744
711
|
loading.value = true
|
|
745
712
|
try {
|
|
713
|
+
// Build query params
|
|
746
714
|
let params = { ...extraParams }
|
|
747
715
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
}
|
|
716
|
+
// Pagination and sorting
|
|
717
|
+
params.page = page.value
|
|
718
|
+
params.page_size = pageSize.value
|
|
719
|
+
if (sortField.value) {
|
|
720
|
+
params.sort_by = sortField.value
|
|
721
|
+
params.sort_order = sortOrder.value === 1 ? 'asc' : 'desc'
|
|
755
722
|
}
|
|
756
723
|
|
|
757
|
-
// Add search param
|
|
758
|
-
if (searchQuery.value && searchConfig.value.
|
|
724
|
+
// Add search param (skip if custom local_search callback)
|
|
725
|
+
if (searchQuery.value && typeof searchConfig.value.local_search !== 'function') {
|
|
759
726
|
params.search = searchQuery.value
|
|
760
727
|
}
|
|
761
728
|
|
|
762
|
-
// Add filter values
|
|
729
|
+
// Add filter values (skip filters with custom local_filter callback)
|
|
730
|
+
const filters = {}
|
|
763
731
|
for (const [name, value] of Object.entries(filterValues.value)) {
|
|
764
732
|
if (value === null || value === undefined || value === '') continue
|
|
765
733
|
const filterDef = filtersMap.value.get(name)
|
|
766
|
-
// Skip
|
|
767
|
-
if (filterDef?.local_filter) continue
|
|
768
|
-
|
|
734
|
+
// Skip filters with custom local_filter - they're applied in filteredItems
|
|
735
|
+
if (typeof filterDef?.local_filter === 'function') continue
|
|
736
|
+
filters[name] = value
|
|
737
|
+
}
|
|
738
|
+
if (Object.keys(filters).length > 0) {
|
|
739
|
+
params.filters = filters
|
|
769
740
|
}
|
|
770
741
|
|
|
771
742
|
// Hook: modify params before request
|
|
@@ -773,7 +744,13 @@ export function useListPageBuilder(config = {}) {
|
|
|
773
744
|
params = onBeforeLoad(params) || params
|
|
774
745
|
}
|
|
775
746
|
|
|
776
|
-
|
|
747
|
+
// Use manager.query() for automatic cache handling
|
|
748
|
+
const response = manager.query
|
|
749
|
+
? await manager.query(params)
|
|
750
|
+
: await manager.list(params)
|
|
751
|
+
|
|
752
|
+
// Track if response came from cache
|
|
753
|
+
fromCache.value = response.fromCache || false
|
|
777
754
|
|
|
778
755
|
// Hook: transform response
|
|
779
756
|
let processedData
|
|
@@ -812,13 +789,13 @@ export function useListPageBuilder(config = {}) {
|
|
|
812
789
|
pageSize.value = event.rows
|
|
813
790
|
// Save page size preference to cookie
|
|
814
791
|
setCookie(COOKIE_NAME, event.rows, COOKIE_EXPIRY_DAYS)
|
|
815
|
-
|
|
792
|
+
loadItems()
|
|
816
793
|
}
|
|
817
794
|
|
|
818
795
|
function onSort(event) {
|
|
819
796
|
sortField.value = event.sortField
|
|
820
797
|
sortOrder.value = event.sortOrder
|
|
821
|
-
|
|
798
|
+
loadItems()
|
|
822
799
|
}
|
|
823
800
|
|
|
824
801
|
// ============ NAVIGATION ============
|
|
@@ -827,11 +804,11 @@ export function useListPageBuilder(config = {}) {
|
|
|
827
804
|
}
|
|
828
805
|
|
|
829
806
|
function goToEdit(item) {
|
|
830
|
-
router.push({ name: `${routePrefix}-edit`, params: {
|
|
807
|
+
router.push({ name: `${routePrefix}-edit`, params: { id: item[resolvedDataKey] } })
|
|
831
808
|
}
|
|
832
809
|
|
|
833
810
|
function goToShow(item) {
|
|
834
|
-
router.push({ name: `${routePrefix}-show`, params: {
|
|
811
|
+
router.push({ name: `${routePrefix}-show`, params: { id: item[resolvedDataKey] } })
|
|
835
812
|
}
|
|
836
813
|
|
|
837
814
|
// ============ DELETE ============
|
|
@@ -1126,7 +1103,7 @@ export function useListPageBuilder(config = {}) {
|
|
|
1126
1103
|
filters,
|
|
1127
1104
|
filterValues,
|
|
1128
1105
|
filteredItems,
|
|
1129
|
-
|
|
1106
|
+
fromCache, // true if last query used cache
|
|
1130
1107
|
addFilter,
|
|
1131
1108
|
removeFilter,
|
|
1132
1109
|
setFilterValue,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useManager - Access EntityManager by name
|
|
3
|
+
*
|
|
4
|
+
* @param {string} entityName - Entity name (e.g., 'jobs', 'stories')
|
|
5
|
+
* @returns {EntityManager|null} - The entity manager or null if not found
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const jobs = useManager('jobs')
|
|
9
|
+
* const severity = jobs.getSeverity('status', 'completed')
|
|
10
|
+
*/
|
|
11
|
+
import { inject } from 'vue'
|
|
12
|
+
|
|
13
|
+
export function useManager(entityName) {
|
|
14
|
+
const orchestrator = inject('qdadmOrchestrator')
|
|
15
|
+
if (!orchestrator) {
|
|
16
|
+
console.warn('[qdadm] useManager: orchestrator not available')
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
return orchestrator.get(entityName)
|
|
20
|
+
}
|