qdadm 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +270 -0
  2. package/LICENSE +21 -0
  3. package/README.md +166 -0
  4. package/package.json +48 -0
  5. package/src/assets/logo.svg +6 -0
  6. package/src/components/BoolCell.vue +28 -0
  7. package/src/components/dialogs/BulkStatusDialog.vue +43 -0
  8. package/src/components/dialogs/MultiStepDialog.vue +321 -0
  9. package/src/components/dialogs/SimpleDialog.vue +108 -0
  10. package/src/components/dialogs/UnsavedChangesDialog.vue +87 -0
  11. package/src/components/display/CardsGrid.vue +155 -0
  12. package/src/components/display/CopyableId.vue +92 -0
  13. package/src/components/display/EmptyState.vue +114 -0
  14. package/src/components/display/IntensityBar.vue +171 -0
  15. package/src/components/display/RichCardsGrid.vue +220 -0
  16. package/src/components/editors/JsonEditorFoldable.vue +467 -0
  17. package/src/components/editors/JsonStructuredField.vue +218 -0
  18. package/src/components/editors/JsonViewer.vue +91 -0
  19. package/src/components/editors/KeyValueEditor.vue +314 -0
  20. package/src/components/editors/LanguageEditor.vue +245 -0
  21. package/src/components/editors/ScopeEditor.vue +341 -0
  22. package/src/components/editors/VanillaJsonEditor.vue +185 -0
  23. package/src/components/forms/FormActions.vue +104 -0
  24. package/src/components/forms/FormField.vue +64 -0
  25. package/src/components/forms/FormTab.vue +217 -0
  26. package/src/components/forms/FormTabs.vue +108 -0
  27. package/src/components/index.js +44 -0
  28. package/src/components/layout/AppLayout.vue +430 -0
  29. package/src/components/layout/Breadcrumb.vue +106 -0
  30. package/src/components/layout/PageHeader.vue +75 -0
  31. package/src/components/layout/PageLayout.vue +93 -0
  32. package/src/components/lists/ActionButtons.vue +41 -0
  33. package/src/components/lists/ActionColumn.vue +37 -0
  34. package/src/components/lists/FilterBar.vue +53 -0
  35. package/src/components/lists/ListPage.vue +319 -0
  36. package/src/composables/index.js +19 -0
  37. package/src/composables/useApp.js +43 -0
  38. package/src/composables/useAuth.js +49 -0
  39. package/src/composables/useBareForm.js +143 -0
  40. package/src/composables/useBreadcrumb.js +221 -0
  41. package/src/composables/useDirtyState.js +103 -0
  42. package/src/composables/useEntityTitle.js +121 -0
  43. package/src/composables/useForm.js +254 -0
  44. package/src/composables/useGuardStore.js +37 -0
  45. package/src/composables/useJsonSyntax.js +101 -0
  46. package/src/composables/useListPageBuilder.js +1176 -0
  47. package/src/composables/useNavigation.js +89 -0
  48. package/src/composables/usePageBuilder.js +334 -0
  49. package/src/composables/useStatus.js +146 -0
  50. package/src/composables/useSubEditor.js +165 -0
  51. package/src/composables/useTabSync.js +110 -0
  52. package/src/composables/useUnsavedChangesGuard.js +122 -0
  53. package/src/entity/EntityManager.js +540 -0
  54. package/src/entity/index.js +11 -0
  55. package/src/entity/storage/ApiStorage.js +146 -0
  56. package/src/entity/storage/LocalStorage.js +220 -0
  57. package/src/entity/storage/MemoryStorage.js +201 -0
  58. package/src/entity/storage/index.js +10 -0
  59. package/src/index.js +29 -0
  60. package/src/kernel/Kernel.js +234 -0
  61. package/src/kernel/index.js +7 -0
  62. package/src/module/index.js +16 -0
  63. package/src/module/moduleRegistry.js +222 -0
  64. package/src/orchestrator/Orchestrator.js +141 -0
  65. package/src/orchestrator/index.js +8 -0
  66. package/src/orchestrator/useOrchestrator.js +61 -0
  67. package/src/plugin.js +142 -0
  68. package/src/styles/_alerts.css +48 -0
  69. package/src/styles/_code.css +33 -0
  70. package/src/styles/_dialogs.css +17 -0
  71. package/src/styles/_markdown.css +82 -0
  72. package/src/styles/_show-pages.css +84 -0
  73. package/src/styles/index.css +16 -0
  74. package/src/styles/main.css +845 -0
  75. package/src/styles/theme/components.css +286 -0
  76. package/src/styles/theme/index.css +10 -0
  77. package/src/styles/theme/tokens.css +125 -0
  78. package/src/styles/theme/utilities.css +172 -0
  79. package/src/utils/debugInjector.js +261 -0
  80. package/src/utils/formatters.js +165 -0
  81. package/src/utils/index.js +35 -0
  82. package/src/utils/transformers.js +105 -0
@@ -0,0 +1,261 @@
1
+ /* eslint-disable no-console */
2
+ /**
3
+ * Debug Injector - Bypasses MCP/DevTools limitations
4
+ *
5
+ * Usage in browser console:
6
+ * window.__debug.listFilters()
7
+ * window.__debug.setFilter('queue_status', 'pending')
8
+ * window.__debug.getVueApp()
9
+ * window.__debug.triggerEvent('filterChange', { name: 'queue_status', value: 'pending' })
10
+ */
11
+
12
+ class DebugInjector {
13
+ constructor() {
14
+ this.vueApp = null
15
+ this.components = new Map()
16
+ }
17
+
18
+ /**
19
+ * Initialize and expose to window
20
+ */
21
+ init() {
22
+ window.__debug = this
23
+ console.log('🔧 Debug Injector initialized. Use window.__debug to access.')
24
+ this.findVueApp()
25
+ return this
26
+ }
27
+
28
+ /**
29
+ * Find Vue app instance
30
+ */
31
+ findVueApp() {
32
+ const appEl = document.querySelector('#app')
33
+ if (appEl && appEl.__vue_app__) {
34
+ this.vueApp = appEl.__vue_app__
35
+ console.log('✅ Vue app found:', this.vueApp)
36
+ return this.vueApp
37
+ }
38
+ console.warn('⚠️ Vue app not found on #app')
39
+ return null
40
+ }
41
+
42
+ /**
43
+ * Get Vue component instance from DOM element
44
+ */
45
+ getComponentFromElement(selector) {
46
+ const el = typeof selector === 'string' ? document.querySelector(selector) : selector
47
+ if (!el) return null
48
+
49
+ // Vue 3 stores component on __vueParentComponent
50
+ let vnode = el.__vueParentComponent
51
+ while (vnode && !vnode.proxy) {
52
+ vnode = vnode.parent
53
+ }
54
+ return vnode?.proxy || null
55
+ }
56
+
57
+ /**
58
+ * Find all PrimeVue Select components
59
+ */
60
+ findSelects() {
61
+ const selects = document.querySelectorAll('[data-pc-name="select"]')
62
+ const results = []
63
+ selects.forEach((el, i) => {
64
+ const component = this.getComponentFromElement(el)
65
+ results.push({
66
+ index: i,
67
+ element: el,
68
+ component,
69
+ value: component?.modelValue,
70
+ placeholder: el.querySelector('[data-pc-section="label"]')?.textContent
71
+ })
72
+ })
73
+ console.table(results.map(r => ({
74
+ index: r.index,
75
+ placeholder: r.placeholder,
76
+ value: r.value
77
+ })))
78
+ return results
79
+ }
80
+
81
+ /**
82
+ * Simulate selecting a value in a PrimeVue Select
83
+ */
84
+ selectValue(selectIndex, optionText) {
85
+ const selects = this.findSelects()
86
+ const select = selects[selectIndex]
87
+ if (!select) {
88
+ console.error(`Select ${selectIndex} not found`)
89
+ return false
90
+ }
91
+
92
+ // Click to open dropdown
93
+ select.element.click()
94
+
95
+ // Wait for dropdown to open and find option
96
+ setTimeout(() => {
97
+ const options = document.querySelectorAll('[data-pc-section="option"]')
98
+ for (const opt of options) {
99
+ if (opt.textContent.includes(optionText)) {
100
+ console.log(`🎯 Clicking option: ${opt.textContent}`)
101
+ opt.click()
102
+ return true
103
+ }
104
+ }
105
+ console.error(`Option "${optionText}" not found`)
106
+ // Close dropdown
107
+ document.body.click()
108
+ }, 100)
109
+
110
+ return true
111
+ }
112
+
113
+ /**
114
+ * Direct Vue reactivity manipulation - find ListPage component
115
+ */
116
+ findListPage() {
117
+ // Find the main content area
118
+ const main = document.querySelector('main')
119
+ if (!main) return null
120
+
121
+ let component = this.getComponentFromElement(main)
122
+
123
+ // Walk up to find ListPage
124
+ while (component) {
125
+ if (component.$options?.name === 'ListPage' || component.localFilterValues) {
126
+ console.log('✅ Found ListPage component:', component)
127
+ return component
128
+ }
129
+ component = component.$parent
130
+ }
131
+
132
+ // Alternative: search in Vue app's component tree
133
+ console.warn('ListPage not found via DOM, searching component tree...')
134
+ return null
135
+ }
136
+
137
+ /**
138
+ * Get current filter values from ListPage
139
+ */
140
+ getFilterValues() {
141
+ const listPage = this.findListPage()
142
+ if (listPage) {
143
+ console.log('Filter values:', listPage.localFilterValues)
144
+ return listPage.localFilterValues
145
+ }
146
+ return null
147
+ }
148
+
149
+ /**
150
+ * Directly set a filter value (bypasses UI)
151
+ */
152
+ setFilterDirect(filterName, value) {
153
+ const listPage = this.findListPage()
154
+ if (!listPage) {
155
+ console.error('ListPage not found')
156
+ return false
157
+ }
158
+
159
+ console.log(`Setting ${filterName} = ${value}`)
160
+ listPage.localFilterValues[filterName] = value
161
+
162
+ // Trigger the change handler
163
+ if (listPage.onFilterChange) {
164
+ listPage.onFilterChange(filterName)
165
+ }
166
+
167
+ return true
168
+ }
169
+
170
+ /**
171
+ * Dispatch a custom event that Vue components can listen to
172
+ */
173
+ dispatchFilterChange(filterName, value) {
174
+ const event = new CustomEvent('debug:filterChange', {
175
+ detail: { filterName, value },
176
+ bubbles: true
177
+ })
178
+ document.dispatchEvent(event)
179
+ console.log(`📤 Dispatched debug:filterChange for ${filterName}=${value}`)
180
+ }
181
+
182
+ /**
183
+ * Log all Vue component instances in the tree
184
+ */
185
+ logComponentTree(root = null, depth = 0) {
186
+ if (!root) {
187
+ root = this.findVueApp()?._instance
188
+ if (!root) return
189
+ }
190
+
191
+ const indent = ' '.repeat(depth)
192
+ const name = root.type?.name || root.type?.__name || 'Anonymous'
193
+ console.log(`${indent}${name}`)
194
+
195
+ if (root.subTree?.children) {
196
+ for (const child of root.subTree.children) {
197
+ if (child?.component) {
198
+ this.logComponentTree(child.component, depth + 1)
199
+ }
200
+ }
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Execute code in Vue component context
206
+ */
207
+ execInComponent(selector, code) {
208
+ const component = this.getComponentFromElement(selector)
209
+ if (!component) {
210
+ console.error('Component not found')
211
+ return null
212
+ }
213
+
214
+ try {
215
+ const fn = new Function('component', `with(component) { return ${code} }`)
216
+ return fn(component)
217
+ } catch (e) {
218
+ console.error('Exec failed:', e)
219
+ return null
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Monitor all events on an element
225
+ */
226
+ monitorEvents(selector) {
227
+ const el = document.querySelector(selector)
228
+ if (!el) return
229
+
230
+ const events = ['click', 'change', 'input', 'focus', 'blur', 'mousedown', 'mouseup']
231
+ events.forEach(evt => {
232
+ el.addEventListener(evt, (e) => {
233
+ console.log(`📡 ${evt}:`, e.target, e)
234
+ }, true)
235
+ })
236
+ console.log(`Monitoring events on ${selector}`)
237
+ }
238
+
239
+ /**
240
+ * Quick help
241
+ */
242
+ help() {
243
+ console.log(`
244
+ 🔧 Debug Injector Commands:
245
+ __debug.findSelects() - List all Select components
246
+ __debug.selectValue(0, 'text') - Select option by text in Select #0
247
+ __debug.findListPage() - Find ListPage component
248
+ __debug.getFilterValues() - Get current filter values
249
+ __debug.setFilterDirect('name', 'value') - Directly set filter
250
+ __debug.monitorEvents('selector') - Monitor all events
251
+ __debug.logComponentTree() - Log Vue component tree
252
+ __debug.help() - Show this help
253
+ `)
254
+ }
255
+ }
256
+
257
+ // Auto-initialize in development
258
+ const debugInjector = new DebugInjector()
259
+
260
+ export default debugInjector
261
+ export { DebugInjector }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Centralized formatting utilities
3
+ *
4
+ * Usage:
5
+ * import { formatDate, formatDateTime, formatDuration } from '@/dashboard/utils/formatters'
6
+ *
7
+ * {{ formatDate(item.created_at) }}
8
+ * {{ formatDateTime(item.updated_at) }}
9
+ */
10
+
11
+ // =============================================================================
12
+ // DATE/TIME FORMATTING
13
+ // =============================================================================
14
+
15
+ /**
16
+ * Format a date string to localized date+time
17
+ * Default format: toLocaleString() (e.g., "20/12/2025, 14:30:00")
18
+ *
19
+ * @param {string|Date} dateStr - ISO date string or Date object
20
+ * @param {object} options - Intl.DateTimeFormat options
21
+ * @returns {string} Formatted date string or '-' if empty
22
+ */
23
+ export function formatDate(dateStr, options = {}) {
24
+ if (!dateStr) return '-'
25
+ const date = new Date(dateStr)
26
+ if (isNaN(date.getTime())) return '-'
27
+
28
+ if (Object.keys(options).length === 0) {
29
+ return date.toLocaleString()
30
+ }
31
+ return date.toLocaleString(undefined, options)
32
+ }
33
+
34
+ /**
35
+ * Format to full datetime with year (20/12/2025 14:30)
36
+ */
37
+ export function formatDateTime(dateStr) {
38
+ return formatDate(dateStr, {
39
+ year: 'numeric',
40
+ month: '2-digit',
41
+ day: '2-digit',
42
+ hour: '2-digit',
43
+ minute: '2-digit'
44
+ })
45
+ }
46
+
47
+ /**
48
+ * Format to short datetime without year (20/12 14:30)
49
+ */
50
+ export function formatDateTimeShort(dateStr) {
51
+ return formatDate(dateStr, {
52
+ month: '2-digit',
53
+ day: '2-digit',
54
+ hour: '2-digit',
55
+ minute: '2-digit'
56
+ })
57
+ }
58
+
59
+ /**
60
+ * Format to date only (20/12/2025)
61
+ */
62
+ export function formatDateOnly(dateStr) {
63
+ if (!dateStr) return '-'
64
+ const date = new Date(dateStr)
65
+ if (isNaN(date.getTime())) return '-'
66
+ return date.toLocaleDateString(undefined, {
67
+ year: 'numeric',
68
+ month: '2-digit',
69
+ day: '2-digit'
70
+ })
71
+ }
72
+
73
+ /**
74
+ * Format to time only (14:30:00)
75
+ */
76
+ export function formatTimeOnly(dateStr) {
77
+ if (!dateStr) return '-'
78
+ const date = new Date(dateStr)
79
+ if (isNaN(date.getTime())) return '-'
80
+ return date.toLocaleTimeString()
81
+ }
82
+
83
+ /**
84
+ * Format with "Never" as default (for last_used fields)
85
+ */
86
+ export function formatDateOrNever(dateStr) {
87
+ if (!dateStr) return 'Never'
88
+ return formatDate(dateStr)
89
+ }
90
+
91
+ // =============================================================================
92
+ // DURATION FORMATTING
93
+ // =============================================================================
94
+
95
+ /**
96
+ * Format a duration in milliseconds to human-readable string
97
+ *
98
+ * @param {number} ms - Duration in milliseconds
99
+ * @returns {string} Human-readable duration (e.g., "2h 30m 15s" or "45.2s")
100
+ */
101
+ export function formatDuration(ms) {
102
+ if (ms === null || ms === undefined) return '-'
103
+ if (ms < 1000) return `${ms}ms`
104
+
105
+ const seconds = Math.floor(ms / 1000)
106
+ const minutes = Math.floor(seconds / 60)
107
+ const hours = Math.floor(minutes / 60)
108
+
109
+ if (hours > 0) {
110
+ const remainingMinutes = minutes % 60
111
+ const remainingSeconds = seconds % 60
112
+ return `${hours}h ${remainingMinutes}m ${remainingSeconds}s`
113
+ }
114
+ if (minutes > 0) {
115
+ const remainingSeconds = seconds % 60
116
+ return `${minutes}m ${remainingSeconds}s`
117
+ }
118
+ // Show decimal seconds for short durations
119
+ return `${(ms / 1000).toFixed(1)}s`
120
+ }
121
+
122
+ /**
123
+ * Format duration from start/end timestamps
124
+ */
125
+ export function formatDurationBetween(startDate, endDate) {
126
+ if (!startDate || !endDate) return '-'
127
+ const start = new Date(startDate)
128
+ const end = new Date(endDate)
129
+ if (isNaN(start.getTime()) || isNaN(end.getTime())) return '-'
130
+ return formatDuration(end - start)
131
+ }
132
+
133
+ // =============================================================================
134
+ // NUMBER FORMATTING
135
+ // =============================================================================
136
+
137
+ /**
138
+ * Format a number with locale-specific separators
139
+ */
140
+ export function formatNumber(value, options = {}) {
141
+ if (value === null || value === undefined) return '-'
142
+ return value.toLocaleString(undefined, options)
143
+ }
144
+
145
+ /**
146
+ * Format bytes to human-readable size
147
+ */
148
+ export function formatBytes(bytes) {
149
+ if (bytes === null || bytes === undefined) return '-'
150
+ if (bytes === 0) return '0 B'
151
+
152
+ const units = ['B', 'KB', 'MB', 'GB', 'TB']
153
+ const k = 1024
154
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
155
+
156
+ return `${(bytes / Math.pow(k, i)).toFixed(1)} ${units[i]}`
157
+ }
158
+
159
+ /**
160
+ * Format a percentage
161
+ */
162
+ export function formatPercent(value, decimals = 1) {
163
+ if (value === null || value === undefined) return '-'
164
+ return `${(value * 100).toFixed(decimals)}%`
165
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * qdadm - Utils exports
3
+ */
4
+
5
+ // Formatters
6
+ export {
7
+ formatDate,
8
+ formatDateTime,
9
+ formatDateTimeShort,
10
+ formatDateOnly,
11
+ formatTimeOnly,
12
+ formatDateOrNever,
13
+ formatDuration,
14
+ formatDurationBetween,
15
+ formatNumber,
16
+ formatBytes,
17
+ formatPercent
18
+ } from './formatters'
19
+
20
+ // Transformers
21
+ export {
22
+ toKeyValueArray,
23
+ toKeyValueObject,
24
+ toLanguageCodes,
25
+ toLanguageObjects,
26
+ toDateObject,
27
+ toIsoDate,
28
+ toIsoDateTime,
29
+ deepClone,
30
+ getSparseUpdate,
31
+ isEqual
32
+ } from './transformers'
33
+
34
+ // Debug Injector
35
+ export { default as debugInjector, DebugInjector } from './debugInjector'
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Data transformation utilities for forms
3
+ *
4
+ * Provides consistent conversions between API data formats and form structures.
5
+ */
6
+
7
+ /**
8
+ * Convert object {key: value} to array [{key, value}] for KeyValueEditor
9
+ */
10
+ export function toKeyValueArray(obj) {
11
+ if (!obj || typeof obj !== 'object') return []
12
+ return Object.entries(obj).map(([key, value]) => ({ key, value }))
13
+ }
14
+
15
+ /**
16
+ * Convert array [{key, value}] to object {key: value} for API
17
+ */
18
+ export function toKeyValueObject(arr) {
19
+ if (!Array.isArray(arr)) return {}
20
+ return Object.fromEntries(
21
+ arr.filter(item => item.key && item.key.trim())
22
+ .map(item => [item.key.trim(), item.value])
23
+ )
24
+ }
25
+
26
+ /**
27
+ * Convert language objects [{code, fluency}] to array of codes ['en', 'fr']
28
+ */
29
+ export function toLanguageCodes(languages) {
30
+ if (!Array.isArray(languages)) return []
31
+ return languages.map(l => typeof l === 'string' ? l : l.code).filter(Boolean)
32
+ }
33
+
34
+ /**
35
+ * Convert array of codes to language objects [{code, fluency}]
36
+ */
37
+ export function toLanguageObjects(codes, defaultFluency = 1) {
38
+ if (!Array.isArray(codes)) return []
39
+ return codes.filter(Boolean).map(code => ({
40
+ code: typeof code === 'string' ? code : code.code,
41
+ fluency: typeof code === 'object' ? code.fluency : defaultFluency
42
+ }))
43
+ }
44
+
45
+ /**
46
+ * Convert ISO date string to Date object
47
+ */
48
+ export function toDateObject(dateStr) {
49
+ if (!dateStr) return null
50
+ if (dateStr instanceof Date) return dateStr
51
+ return new Date(dateStr)
52
+ }
53
+
54
+ /**
55
+ * Convert Date to ISO date string (YYYY-MM-DD)
56
+ */
57
+ export function toIsoDate(date) {
58
+ if (!date) return null
59
+ if (typeof date === 'string') return date.split('T')[0]
60
+ return date.toISOString().split('T')[0]
61
+ }
62
+
63
+ /**
64
+ * Convert Date to ISO datetime string
65
+ */
66
+ export function toIsoDateTime(date) {
67
+ if (!date) return null
68
+ if (typeof date === 'string') return date
69
+ return date.toISOString()
70
+ }
71
+
72
+ /**
73
+ * Deep clone an object (for snapshots)
74
+ */
75
+ export function deepClone(obj) {
76
+ if (obj === null || typeof obj !== 'object') return obj
77
+ if (obj instanceof Date) return new Date(obj)
78
+ if (Array.isArray(obj)) return obj.map(deepClone)
79
+ return Object.fromEntries(
80
+ Object.entries(obj).map(([k, v]) => [k, deepClone(v)])
81
+ )
82
+ }
83
+
84
+ /**
85
+ * Get sparse update object (only changed fields)
86
+ */
87
+ export function getSparseUpdate(original, modified, excludeFields = []) {
88
+ const patch = {}
89
+ for (const key in modified) {
90
+ if (excludeFields.includes(key)) continue
91
+ const origVal = JSON.stringify(original[key])
92
+ const modVal = JSON.stringify(modified[key])
93
+ if (origVal !== modVal) {
94
+ patch[key] = modified[key]
95
+ }
96
+ }
97
+ return patch
98
+ }
99
+
100
+ /**
101
+ * Check if two values are deeply equal
102
+ */
103
+ export function isEqual(a, b) {
104
+ return JSON.stringify(a) === JSON.stringify(b)
105
+ }