qdadm 1.13.0 → 1.19.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/package.json +8 -4
- package/src/chain/ActiveStack.ts +79 -98
- package/src/chain/StackHydrator.ts +3 -2
- package/src/chain/index.ts +7 -1
- package/src/components/QdadmRoot.vue +52 -0
- package/src/components/edit/FormActions.vue +9 -6
- package/src/components/edit/LookupPickerDialog.vue +6 -3
- package/src/components/index.ts +6 -0
- package/src/composables/useEntityItemFormPage.ts +1 -0
- package/src/composables/useEntityItemShowPage.ts +1 -0
- package/src/composables/useFieldManager.ts +100 -3
- package/src/composables/useListPage.ts +50 -59
- package/src/composables/useListPage.utils.ts +101 -0
- package/src/composables/useNavigation.ts +26 -3
- package/src/composables/useOptionsLookup.ts +5 -1
- package/src/gen/generateManagers.test.js +27 -0
- package/src/gen/generateManagers.ts +12 -0
- package/src/hooks/HookRegistry.ts +14 -435
- 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 +37 -0
- package/src/kernel/EventRouter.ts +17 -300
- package/src/kernel/Kernel.i18n.ts +29 -0
- package/src/kernel/Kernel.modules.ts +6 -0
- package/src/kernel/Kernel.registries.ts +10 -2
- package/src/kernel/Kernel.routing.ts +43 -1
- package/src/kernel/Kernel.ts +43 -0
- package/src/kernel/Kernel.types.ts +52 -1
- package/src/kernel/Kernel.vue.ts +121 -15
- 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 +125 -834
- package/src/kernel/KernelContext.types.ts +173 -0
- package/src/kernel/KernelContext.zones.ts +54 -0
- package/src/kernel/SSEBridge.ts +7 -362
- package/src/kernel/SignalBus.ts +24 -148
- package/src/modules/debug/AuthCollector.ts +48 -1
- package/src/modules/debug/Collector.ts +16 -302
- package/src/modules/debug/DebugBridge.ts +10 -171
- package/src/modules/debug/DebugModule.ts +35 -5
- package/src/modules/debug/EntitiesCollector.ts +97 -1
- package/src/modules/debug/ErrorCollector.ts +2 -77
- package/src/modules/debug/I18nCollector.ts +9 -0
- package/src/modules/debug/LocalStorageAdapter.ts +3 -147
- package/src/modules/debug/RouterCollector.ts +101 -1
- package/src/modules/debug/SignalCollector.ts +2 -150
- package/src/modules/debug/ToastCollector.ts +2 -91
- package/src/modules/debug/ZonesCollector.ts +93 -1
- package/src/modules/debug/components/DebugBar.vue +19 -775
- package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
- package/src/modules/debug/components/index.ts +4 -3
- package/src/modules/debug/components/panels/AuthPanel.vue +1 -1
- package/src/modules/debug/components/panels/EntitiesPanel.vue +5 -5
- package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
- package/src/modules/debug/components/panels/RouterPanel.vue +1 -1
- package/src/modules/debug/components/panels/index.ts +10 -4
- package/src/modules/debug/index.ts +15 -0
- package/src/modules/debug/styles.scss +22 -18
- package/src/utils/index.ts +0 -3
- package/src/vite/qdadmDebugPlugin.ts +401 -0
- package/src/vite-env.d.ts +16 -0
- package/src/modules/debug/components/ObjectTree.vue +0 -123
- package/src/modules/debug/components/panels/EntriesPanel.vue +0 -100
- package/src/modules/debug/components/panels/SignalsPanel.vue +0 -188
- package/src/modules/debug/components/panels/ToastsPanel.vue +0 -45
- package/src/utils/debugInjector.ts +0 -306
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* ObjectTree - VSCode-style collapsible object explorer
|
|
4
|
-
*
|
|
5
|
-
* Renders objects/arrays as a tree with collapsible nodes.
|
|
6
|
-
* Primitive values are shown inline, objects/arrays are expandable.
|
|
7
|
-
*/
|
|
8
|
-
import { ref, computed } from 'vue'
|
|
9
|
-
|
|
10
|
-
type DataValue = Record<string, unknown> | unknown[] | string | number | boolean | null | undefined
|
|
11
|
-
|
|
12
|
-
const props = withDefaults(defineProps<{
|
|
13
|
-
data?: DataValue
|
|
14
|
-
depth?: number
|
|
15
|
-
maxDepth?: number
|
|
16
|
-
name?: string | null
|
|
17
|
-
defaultExpanded?: boolean
|
|
18
|
-
}>(), {
|
|
19
|
-
data: null,
|
|
20
|
-
depth: 0,
|
|
21
|
-
maxDepth: 10,
|
|
22
|
-
name: null,
|
|
23
|
-
defaultExpanded: false
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
const expanded = ref<boolean>(props.defaultExpanded && props.depth < 2)
|
|
27
|
-
|
|
28
|
-
const isObject = computed<boolean>(() => props.data !== null && typeof props.data === 'object')
|
|
29
|
-
const isArray = computed<boolean>(() => Array.isArray(props.data))
|
|
30
|
-
const isEmpty = computed<boolean>(() => {
|
|
31
|
-
if (!isObject.value) return false
|
|
32
|
-
return isArray.value ? (props.data as unknown[]).length === 0 : Object.keys(props.data as object).length === 0
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
const keys = computed<string[]>(() => {
|
|
36
|
-
if (!isObject.value) return []
|
|
37
|
-
return Object.keys(props.data as object).slice(0, 100)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
const preview = computed<string>(() => {
|
|
41
|
-
if (!isObject.value) return ''
|
|
42
|
-
if (isArray.value) {
|
|
43
|
-
return `Array(${(props.data as unknown[]).length})`
|
|
44
|
-
}
|
|
45
|
-
const dataObj = props.data as Record<string, unknown>
|
|
46
|
-
const keyCount = Object.keys(dataObj).length
|
|
47
|
-
const firstKeys = Object.keys(dataObj).slice(0, 3).join(', ')
|
|
48
|
-
return keyCount <= 3 ? `{${firstKeys}}` : `{${firstKeys}, ...}`
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
const valueClass = computed<string>(() => {
|
|
52
|
-
if (props.data === null) return 'obj-null'
|
|
53
|
-
if (props.data === undefined) return 'obj-undefined'
|
|
54
|
-
switch (typeof props.data) {
|
|
55
|
-
case 'string': return 'obj-string'
|
|
56
|
-
case 'number': return 'obj-number'
|
|
57
|
-
case 'boolean': return 'obj-boolean'
|
|
58
|
-
case 'function': return 'obj-function'
|
|
59
|
-
default: return ''
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
function formatValue(val: DataValue): string {
|
|
64
|
-
if (val === null) return 'null'
|
|
65
|
-
if (val === undefined) return 'undefined'
|
|
66
|
-
if (typeof val === 'string') {
|
|
67
|
-
const truncated = val.length > 100 ? val.slice(0, 100) + '...' : val
|
|
68
|
-
return `"${truncated}"`
|
|
69
|
-
}
|
|
70
|
-
if (typeof val === 'function') return 'ƒ()'
|
|
71
|
-
return String(val)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function toggleExpand(): void {
|
|
75
|
-
if (isObject.value && !isEmpty.value) {
|
|
76
|
-
expanded.value = !expanded.value
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
</script>
|
|
80
|
-
|
|
81
|
-
<template>
|
|
82
|
-
<div class="obj-node" :style="{ paddingLeft: depth > 0 ? '12px' : '0' }">
|
|
83
|
-
<!-- Expandable object/array -->
|
|
84
|
-
<template v-if="isObject && !isEmpty">
|
|
85
|
-
<div class="obj-line" @click="toggleExpand">
|
|
86
|
-
<span class="obj-toggle" :class="{ 'obj-expanded': expanded }">▶</span>
|
|
87
|
-
<span v-if="name !== null" class="obj-key">{{ name }}:</span>
|
|
88
|
-
<span class="obj-preview">{{ preview }}</span>
|
|
89
|
-
</div>
|
|
90
|
-
<div v-if="expanded && depth < maxDepth" class="obj-children">
|
|
91
|
-
<ObjectTree
|
|
92
|
-
v-for="key in keys"
|
|
93
|
-
:key="key"
|
|
94
|
-
:data="data && typeof data === 'object' ? (data as any)[key] : undefined"
|
|
95
|
-
:name="key"
|
|
96
|
-
:depth="depth + 1"
|
|
97
|
-
:maxDepth="maxDepth"
|
|
98
|
-
/>
|
|
99
|
-
<div v-if="data && typeof data === 'object' && Object.keys(data).length > 100" class="obj-truncated">
|
|
100
|
-
... {{ Object.keys(data as object).length - 100 }} more
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
<div v-else-if="expanded" class="obj-max-depth">[Max depth]</div>
|
|
104
|
-
</template>
|
|
105
|
-
|
|
106
|
-
<!-- Empty object/array -->
|
|
107
|
-
<template v-else-if="isObject && isEmpty">
|
|
108
|
-
<div class="obj-line">
|
|
109
|
-
<span v-if="name !== null" class="obj-key">{{ name }}:</span>
|
|
110
|
-
<span class="obj-empty">{{ isArray ? '[]' : '{}' }}</span>
|
|
111
|
-
</div>
|
|
112
|
-
</template>
|
|
113
|
-
|
|
114
|
-
<!-- Primitive value -->
|
|
115
|
-
<template v-else>
|
|
116
|
-
<div class="obj-line">
|
|
117
|
-
<span v-if="name !== null" class="obj-key">{{ name }}:</span>
|
|
118
|
-
<span :class="valueClass">{{ formatValue(data) }}</span>
|
|
119
|
-
</div>
|
|
120
|
-
</template>
|
|
121
|
-
</div>
|
|
122
|
-
</template>
|
|
123
|
-
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* EntriesPanel - Default entries display (horizontal/vertical)
|
|
4
|
-
*/
|
|
5
|
-
import { ref, type Ref } from 'vue'
|
|
6
|
-
import ObjectTree from '../ObjectTree.vue'
|
|
7
|
-
|
|
8
|
-
interface DebugEntry {
|
|
9
|
-
timestamp: number
|
|
10
|
-
_isNew?: boolean
|
|
11
|
-
name?: string
|
|
12
|
-
message?: string
|
|
13
|
-
[key: string]: unknown
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
type EntriesPanelMode = 'horizontal' | 'vertical'
|
|
17
|
-
|
|
18
|
-
interface Props {
|
|
19
|
-
entries: DebugEntry[]
|
|
20
|
-
mode?: EntriesPanelMode
|
|
21
|
-
maxEntries?: number
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
withDefaults(defineProps<Props>(), {
|
|
25
|
-
mode: 'vertical',
|
|
26
|
-
maxEntries: 10
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
const copiedIdx: Ref<number | null> = ref(null)
|
|
30
|
-
|
|
31
|
-
function formatTime(ts: number): string {
|
|
32
|
-
return new Date(ts).toLocaleTimeString('en-US', {
|
|
33
|
-
hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'
|
|
34
|
-
})
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function getEntryData(entry: DebugEntry): Record<string, unknown> {
|
|
38
|
-
const { timestamp: _, _isNew: __, ...rest } = entry
|
|
39
|
-
void _
|
|
40
|
-
void __
|
|
41
|
-
return rest
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function copyEntry(entry: DebugEntry, idx: number): Promise<void> {
|
|
45
|
-
try {
|
|
46
|
-
const data = getEntryData(entry)
|
|
47
|
-
await navigator.clipboard.writeText(JSON.stringify(data, null, 2))
|
|
48
|
-
copiedIdx.value = idx
|
|
49
|
-
setTimeout(() => { copiedIdx.value = null }, 1500)
|
|
50
|
-
} catch (e) {
|
|
51
|
-
console.error('Failed to copy:', e)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
</script>
|
|
55
|
-
|
|
56
|
-
<template>
|
|
57
|
-
<!-- Horizontal layout -->
|
|
58
|
-
<div v-if="mode === 'horizontal'" class="entries-h">
|
|
59
|
-
<div
|
|
60
|
-
v-for="(entry, idx) in entries.slice().reverse().slice(0, maxEntries)"
|
|
61
|
-
:key="idx"
|
|
62
|
-
class="entry-h"
|
|
63
|
-
:class="{ 'entry-new': entry._isNew }"
|
|
64
|
-
>
|
|
65
|
-
<div class="entry-meta">
|
|
66
|
-
<span v-if="entry._isNew" class="entry-new-dot" title="New (unseen)" />
|
|
67
|
-
<span class="entry-time">{{ formatTime(entry.timestamp) }}</span>
|
|
68
|
-
<span v-if="entry.name" class="entry-name">{{ entry.name }}</span>
|
|
69
|
-
</div>
|
|
70
|
-
<ObjectTree :data="getEntryData(entry)" :maxDepth="3" />
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
|
|
74
|
-
<!-- Vertical layout -->
|
|
75
|
-
<div v-else class="entries-v">
|
|
76
|
-
<div
|
|
77
|
-
v-for="(entry, idx) in entries.slice().reverse()"
|
|
78
|
-
:key="idx"
|
|
79
|
-
class="entry-v"
|
|
80
|
-
:class="{ 'entry-new': entry._isNew }"
|
|
81
|
-
>
|
|
82
|
-
<div class="entry-header">
|
|
83
|
-
<span v-if="entry._isNew" class="entry-new-dot" title="New (unseen)" />
|
|
84
|
-
<span class="entry-time">{{ formatTime(entry.timestamp) }}</span>
|
|
85
|
-
<span v-if="entry.name" class="entry-name">{{ entry.name }}</span>
|
|
86
|
-
<span v-if="entry.message" class="entry-message">{{ entry.message }}</span>
|
|
87
|
-
<button
|
|
88
|
-
class="entry-copy"
|
|
89
|
-
:class="{ 'entry-copied': copiedIdx === idx }"
|
|
90
|
-
@click="copyEntry(entry, idx)"
|
|
91
|
-
:title="copiedIdx === idx ? 'Copied!' : 'Copy to clipboard'"
|
|
92
|
-
>
|
|
93
|
-
<i :class="['pi', copiedIdx === idx ? 'pi-check' : 'pi-copy']" />
|
|
94
|
-
</button>
|
|
95
|
-
</div>
|
|
96
|
-
<ObjectTree :data="getEntryData(entry)" :maxDepth="6" />
|
|
97
|
-
</div>
|
|
98
|
-
</div>
|
|
99
|
-
</template>
|
|
100
|
-
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SignalsPanel - Debug panel for signals with pattern filter
|
|
4
|
-
*
|
|
5
|
-
* Supports QuarKernel wildcard patterns:
|
|
6
|
-
* - auth:** → all auth signals (auth:login, auth:impersonate:start)
|
|
7
|
-
* - cache:** → all cache signals
|
|
8
|
-
* - *:created → all creation signals
|
|
9
|
-
* - ** → all signals (default)
|
|
10
|
-
*/
|
|
11
|
-
import { ref, computed, watch } from 'vue'
|
|
12
|
-
import ObjectTree from '../ObjectTree.vue'
|
|
13
|
-
|
|
14
|
-
interface SignalEntry {
|
|
15
|
-
name: string
|
|
16
|
-
timestamp: number
|
|
17
|
-
data?: Record<string, unknown>
|
|
18
|
-
_isNew?: boolean
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface SignalCollector {
|
|
22
|
-
// Collector interface - minimal typing for now
|
|
23
|
-
[key: string]: unknown
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface FilterPreset {
|
|
27
|
-
label: string
|
|
28
|
-
pattern: string
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const props = defineProps<{
|
|
32
|
-
collector: SignalCollector
|
|
33
|
-
entries: SignalEntry[]
|
|
34
|
-
}>()
|
|
35
|
-
|
|
36
|
-
// Filter state - persisted in localStorage
|
|
37
|
-
const STORAGE_KEY = 'qdadm-signals-filter'
|
|
38
|
-
const STORAGE_KEY_MAX = 'qdadm-signals-max'
|
|
39
|
-
const filterPattern = ref<string>(localStorage.getItem(STORAGE_KEY) || '')
|
|
40
|
-
const maxSignals = ref<number>(parseInt(localStorage.getItem(STORAGE_KEY_MAX) || '50') || 50)
|
|
41
|
-
|
|
42
|
-
watch(filterPattern, (val) => {
|
|
43
|
-
localStorage.setItem(STORAGE_KEY, val)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
watch(maxSignals, (val) => {
|
|
47
|
-
localStorage.setItem(STORAGE_KEY_MAX, String(val))
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
// Convert wildcard pattern to regex
|
|
51
|
-
function wildcardToRegex(pattern: string): RegExp | null {
|
|
52
|
-
if (!pattern || pattern === '**') return null // No filter
|
|
53
|
-
|
|
54
|
-
// Escape regex special chars except * and :
|
|
55
|
-
const regex = pattern
|
|
56
|
-
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
57
|
-
// ** matches anything (including colons)
|
|
58
|
-
.replace(/\*\*/g, '.*')
|
|
59
|
-
// * matches anything except colon
|
|
60
|
-
.replace(/\*/g, '[^:]*')
|
|
61
|
-
|
|
62
|
-
return new RegExp(`^${regex}$`)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const filterRegex = computed<RegExp | null>(() => wildcardToRegex(filterPattern.value.trim()))
|
|
66
|
-
|
|
67
|
-
// Apply filter, max limit, and reverse for top-down (newest first)
|
|
68
|
-
const filteredEntries = computed<SignalEntry[]>(() => {
|
|
69
|
-
let result: SignalEntry[] = props.entries
|
|
70
|
-
if (filterRegex.value) {
|
|
71
|
-
result = result.filter((e: SignalEntry) => filterRegex.value!.test(e.name))
|
|
72
|
-
}
|
|
73
|
-
// Apply max limit (slice from end to keep newest)
|
|
74
|
-
if (maxSignals.value > 0 && result.length > maxSignals.value) {
|
|
75
|
-
result = result.slice(-maxSignals.value)
|
|
76
|
-
}
|
|
77
|
-
// Reverse for top-down display (newest first)
|
|
78
|
-
return [...result].reverse()
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
const filterStats = computed<string>(() => {
|
|
82
|
-
const total = props.entries.length
|
|
83
|
-
const shown = filteredEntries.value.length
|
|
84
|
-
return total !== shown ? `${shown}/${total}` : `${total}`
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
// Preset filters
|
|
88
|
-
const presets: FilterPreset[] = [
|
|
89
|
-
{ label: 'All', pattern: '' },
|
|
90
|
-
{ label: 'data', pattern: 'entity:data-invalidate' },
|
|
91
|
-
{ label: 'datalayer', pattern: 'entity:datalayer-invalidate' },
|
|
92
|
-
{ label: 'auth', pattern: 'auth:**' },
|
|
93
|
-
{ label: 'entity', pattern: 'entity:**' },
|
|
94
|
-
{ label: 'toast', pattern: 'toast:**' }
|
|
95
|
-
]
|
|
96
|
-
|
|
97
|
-
function applyPreset(pattern: string): void {
|
|
98
|
-
filterPattern.value = pattern
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function formatTime(ts: number): string {
|
|
102
|
-
const d = new Date(ts)
|
|
103
|
-
return d.toLocaleTimeString('en-US', { hour12: false }) + '.' + String(d.getMilliseconds()).padStart(3, '0')
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Extract domain from signal name (first segment)
|
|
107
|
-
function getDomain(name: string): string {
|
|
108
|
-
return name.split(':')[0] ?? ''
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const domainColors: Record<string, string> = {
|
|
112
|
-
auth: '#10b981',
|
|
113
|
-
cache: '#f59e0b',
|
|
114
|
-
entity: '#3b82f6',
|
|
115
|
-
toast: '#8b5cf6',
|
|
116
|
-
route: '#06b6d4',
|
|
117
|
-
error: '#ef4444'
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function getDomainColor(name: string): string {
|
|
121
|
-
const domain = getDomain(name)
|
|
122
|
-
return domainColors[domain] || '#6b7280'
|
|
123
|
-
}
|
|
124
|
-
</script>
|
|
125
|
-
|
|
126
|
-
<template>
|
|
127
|
-
<div class="signals-panel">
|
|
128
|
-
<!-- Filter bar -->
|
|
129
|
-
<div class="signals-filter">
|
|
130
|
-
<div class="signals-filter-input">
|
|
131
|
-
<i class="pi pi-filter" />
|
|
132
|
-
<input
|
|
133
|
-
v-model="filterPattern"
|
|
134
|
-
type="text"
|
|
135
|
-
placeholder="auth:** cache:** *:created"
|
|
136
|
-
class="filter-input"
|
|
137
|
-
/>
|
|
138
|
-
<span class="signals-count">{{ filterStats }}</span>
|
|
139
|
-
<span class="signals-max-label">max</span>
|
|
140
|
-
<input
|
|
141
|
-
v-model.number="maxSignals"
|
|
142
|
-
type="number"
|
|
143
|
-
min="10"
|
|
144
|
-
max="500"
|
|
145
|
-
class="max-input"
|
|
146
|
-
/>
|
|
147
|
-
</div>
|
|
148
|
-
<div class="signals-presets">
|
|
149
|
-
<button
|
|
150
|
-
v-for="p in presets"
|
|
151
|
-
:key="p.pattern"
|
|
152
|
-
class="debug-toolbar-btn"
|
|
153
|
-
:class="{ 'debug-toolbar-btn--active': filterPattern === p.pattern }"
|
|
154
|
-
@click="applyPreset(p.pattern)"
|
|
155
|
-
>
|
|
156
|
-
{{ p.label }}
|
|
157
|
-
</button>
|
|
158
|
-
</div>
|
|
159
|
-
</div>
|
|
160
|
-
|
|
161
|
-
<!-- Entries list -->
|
|
162
|
-
<div class="signals-list">
|
|
163
|
-
<div v-if="filteredEntries.length === 0" class="signals-empty">
|
|
164
|
-
<i class="pi pi-inbox" />
|
|
165
|
-
<span>{{ entries.length === 0 ? 'No signals' : 'No matching signals' }}</span>
|
|
166
|
-
</div>
|
|
167
|
-
|
|
168
|
-
<div
|
|
169
|
-
v-for="(entry, idx) in filteredEntries"
|
|
170
|
-
:key="idx"
|
|
171
|
-
class="signal-entry"
|
|
172
|
-
:class="{ 'signal-new': entry._isNew }"
|
|
173
|
-
>
|
|
174
|
-
<div class="signal-header">
|
|
175
|
-
<span v-if="entry._isNew" class="signal-new-dot" title="New (unseen)" />
|
|
176
|
-
<span class="signal-time">{{ formatTime(entry.timestamp) }}</span>
|
|
177
|
-
<span class="signal-name" :style="{ color: getDomainColor(entry.name) }">
|
|
178
|
-
{{ entry.name }}
|
|
179
|
-
</span>
|
|
180
|
-
</div>
|
|
181
|
-
<div v-if="entry.data && Object.keys(entry.data).length > 0" class="signal-data">
|
|
182
|
-
<ObjectTree :data="entry.data" :expanded="false" />
|
|
183
|
-
</div>
|
|
184
|
-
</div>
|
|
185
|
-
</div>
|
|
186
|
-
</div>
|
|
187
|
-
</template>
|
|
188
|
-
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* ToastsPanel - Toasts collector display (vertical mode)
|
|
4
|
-
*/
|
|
5
|
-
interface ToastEntry {
|
|
6
|
-
timestamp: number
|
|
7
|
-
severity: string
|
|
8
|
-
summary?: string
|
|
9
|
-
detail?: string
|
|
10
|
-
emitter?: string
|
|
11
|
-
_isNew?: boolean
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface Props {
|
|
15
|
-
entries: ToastEntry[]
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
defineProps<Props>()
|
|
19
|
-
|
|
20
|
-
function formatTime(ts: number): string {
|
|
21
|
-
return new Date(ts).toLocaleTimeString('en-US', {
|
|
22
|
-
hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'
|
|
23
|
-
})
|
|
24
|
-
}
|
|
25
|
-
</script>
|
|
26
|
-
|
|
27
|
-
<template>
|
|
28
|
-
<div class="toasts-panel">
|
|
29
|
-
<div
|
|
30
|
-
v-for="(entry, idx) in entries.slice().reverse()"
|
|
31
|
-
:key="idx"
|
|
32
|
-
class="toast-entry"
|
|
33
|
-
:class="[`toast-${entry.severity}`, { 'entry-new': entry._isNew }]"
|
|
34
|
-
>
|
|
35
|
-
<div class="toast-header">
|
|
36
|
-
<span class="toast-time">{{ formatTime(entry.timestamp) }}</span>
|
|
37
|
-
<span class="toast-severity" :class="`toast-severity-${entry.severity}`">{{ entry.severity }}</span>
|
|
38
|
-
<span v-if="entry.summary" class="toast-summary">{{ entry.summary }}</span>
|
|
39
|
-
<span v-if="entry.emitter" class="toast-emitter">{{ entry.emitter }}</span>
|
|
40
|
-
</div>
|
|
41
|
-
<div v-if="entry.detail" class="toast-detail">{{ entry.detail }}</div>
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
|
-
</template>
|
|
45
|
-
|