@nvent-addon/app 0.4.5 → 0.5.1
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/dist/module.d.mts +19 -1
- package/dist/module.mjs +20 -8
- package/dist/runtime/app/components/{nhealth/component-router.d.vue.ts → ComponentRouter.d.vue.ts} +1 -5
- package/dist/runtime/app/components/{nhealth/component-router.vue.d.ts → ComponentRouter.vue.d.ts} +1 -5
- package/dist/runtime/app/components/{nhealth/component-shell.d.vue.ts → ComponentShell.d.vue.ts} +4 -9
- package/dist/runtime/app/components/ComponentShell.vue +87 -0
- package/dist/runtime/app/components/{nhealth/component-shell.vue.d.ts → ComponentShell.vue.d.ts} +4 -9
- package/dist/runtime/app/components/ConfirmDialog.d.vue.ts +1 -6
- package/dist/runtime/app/components/ConfirmDialog.vue.d.ts +1 -6
- package/dist/runtime/app/components/ListItem.d.vue.ts +3 -6
- package/dist/runtime/app/components/ListItem.vue.d.ts +3 -6
- package/dist/runtime/app/components/LiveIndicator.d.vue.ts +7 -0
- package/dist/runtime/app/components/LiveIndicator.vue +30 -0
- package/dist/runtime/app/components/LiveIndicator.vue.d.ts +7 -0
- package/dist/runtime/app/components/{QueueConfigDetails.d.vue.ts → QueueConfiguration.d.vue.ts} +1 -10
- package/dist/runtime/app/components/QueueConfiguration.vue +387 -0
- package/dist/runtime/app/components/{QueueConfigDetails.vue.d.ts → QueueConfiguration.vue.d.ts} +1 -10
- package/dist/runtime/app/components/StatCard.d.vue.ts +9 -0
- package/dist/runtime/app/components/StatCard.vue +57 -0
- package/dist/runtime/app/components/StatCard.vue.d.ts +9 -0
- package/dist/runtime/app/components/TimelineList.vue +67 -0
- package/dist/runtime/app/components/flow/AwaitNode.d.vue.ts +18 -0
- package/dist/runtime/app/components/flow/AwaitNode.vue +91 -0
- package/dist/runtime/app/components/flow/AwaitNode.vue.d.ts +18 -0
- package/dist/runtime/app/components/{FlowDiagram.d.vue.ts → flow/Diagram.d.vue.ts} +12 -1
- package/dist/runtime/app/components/{FlowDiagram.vue → flow/Diagram.vue} +92 -11
- package/dist/runtime/app/components/{FlowDiagram.vue.d.ts → flow/Diagram.vue.d.ts} +12 -1
- package/dist/runtime/app/components/{FlowRunOverview.d.vue.ts → flow/RunOverview.d.vue.ts} +3 -0
- package/dist/runtime/app/components/{FlowRunOverview.vue → flow/RunOverview.vue} +94 -8
- package/dist/runtime/app/components/{FlowRunOverview.vue.d.ts → flow/RunOverview.vue.d.ts} +3 -0
- package/dist/runtime/app/components/{FlowRunStatusBadge.d.vue.ts → flow/RunStatusBadge.d.vue.ts} +2 -8
- package/dist/runtime/app/components/{FlowRunStatusBadge.vue → flow/RunStatusBadge.vue} +8 -1
- package/dist/runtime/app/components/{FlowRunStatusBadge.vue.d.ts → flow/RunStatusBadge.vue.d.ts} +2 -8
- package/dist/runtime/app/components/{FlowRunTimeline.vue → flow/RunTimeline.vue} +1 -1
- package/dist/runtime/app/components/{FlowStepSelector.d.vue.ts → flow/StepSelector.d.vue.ts} +1 -0
- package/dist/runtime/app/components/flow/StepSelector.vue +553 -0
- package/dist/runtime/app/components/{FlowStepSelector.vue.d.ts → flow/StepSelector.vue.d.ts} +1 -0
- package/dist/runtime/app/components/trigger/BasicInfoCard.d.vue.ts +33 -0
- package/dist/runtime/app/components/trigger/BasicInfoCard.vue +168 -0
- package/dist/runtime/app/components/trigger/BasicInfoCard.vue.d.ts +33 -0
- package/dist/runtime/app/components/{FlowSchedulesList.d.vue.ts → trigger/DangerZone.d.vue.ts} +4 -6
- package/dist/runtime/app/components/trigger/DangerZone.vue +46 -0
- package/dist/runtime/app/components/{FlowSchedulesList.vue.d.ts → trigger/DangerZone.vue.d.ts} +4 -6
- package/dist/runtime/app/components/trigger/EditHeader.d.vue.ts +15 -0
- package/dist/runtime/app/components/trigger/EditHeader.vue +55 -0
- package/dist/runtime/app/components/trigger/EditHeader.vue.d.ts +15 -0
- package/dist/runtime/app/components/trigger/EventConfig.d.vue.ts +24 -0
- package/dist/runtime/app/components/trigger/EventConfig.vue +68 -0
- package/dist/runtime/app/components/trigger/EventConfig.vue.d.ts +24 -0
- package/dist/runtime/app/components/trigger/FlowSubscriptions.d.vue.ts +14 -0
- package/dist/runtime/app/components/trigger/FlowSubscriptions.vue +128 -0
- package/dist/runtime/app/components/trigger/FlowSubscriptions.vue.d.ts +14 -0
- package/dist/runtime/app/components/trigger/ScheduleConfig.d.vue.ts +27 -0
- package/dist/runtime/app/components/trigger/ScheduleConfig.vue +375 -0
- package/dist/runtime/app/components/trigger/ScheduleConfig.vue.d.ts +27 -0
- package/dist/runtime/app/components/{FlowScheduleDialog.d.vue.ts → trigger/StatusConfig.d.vue.ts} +6 -6
- package/dist/runtime/app/components/trigger/StatusConfig.vue +78 -0
- package/dist/runtime/app/components/{FlowScheduleDialog.vue.d.ts → trigger/StatusConfig.vue.d.ts} +6 -6
- package/dist/runtime/app/components/trigger/WebhookConfig.d.vue.ts +30 -0
- package/dist/runtime/app/components/trigger/WebhookConfig.vue +97 -0
- package/dist/runtime/app/components/trigger/WebhookConfig.vue.d.ts +30 -0
- package/dist/runtime/app/composables/useAnalyzedFlows.d.ts +5 -0
- package/dist/runtime/app/composables/useAnalyzedFlows.js +15 -1
- package/dist/runtime/app/composables/useComponentRouter.d.ts +8 -0
- package/dist/runtime/app/composables/useComponentRouter.js +10 -2
- package/dist/runtime/app/composables/useFlowRunsInfinite.d.ts +1 -1
- package/dist/runtime/app/composables/useFlowState.js +65 -0
- package/dist/runtime/app/composables/useFlowWebSocket.d.ts +11 -2
- package/dist/runtime/app/composables/useFlowWebSocket.js +181 -65
- package/dist/runtime/app/composables/useQueueJobs.d.ts +12 -1
- package/dist/runtime/app/composables/useQueueJobs.js +13 -7
- package/dist/runtime/app/composables/useTrigger.d.ts +137 -0
- package/dist/runtime/app/composables/useTrigger.js +116 -0
- package/dist/runtime/app/composables/useTriggerWebSocket.d.ts +35 -0
- package/dist/runtime/app/composables/useTriggerWebSocket.js +333 -0
- package/dist/runtime/app/pages/dashboard.d.vue.ts +3 -0
- package/dist/runtime/app/pages/dashboard.vue +738 -0
- package/dist/runtime/app/pages/dashboard.vue.d.ts +3 -0
- package/dist/runtime/app/pages/flows/[name].d.vue.ts +3 -0
- package/dist/runtime/app/pages/flows/[name].vue +680 -0
- package/dist/runtime/app/pages/flows/[name].vue.d.ts +3 -0
- package/dist/runtime/app/pages/flows/index.vue +321 -620
- package/dist/runtime/app/pages/index.vue +39 -9
- package/dist/runtime/app/pages/queues/index.vue +202 -194
- package/dist/runtime/app/pages/queues/jobs.vue +534 -207
- package/dist/runtime/app/pages/settings/scheduler.d.vue.ts +3 -0
- package/dist/runtime/app/pages/settings/scheduler.vue +310 -0
- package/dist/runtime/app/pages/settings/scheduler.vue.d.ts +3 -0
- package/dist/runtime/app/pages/triggers/[name]/edit.d.vue.ts +3 -0
- package/dist/runtime/app/pages/triggers/[name]/edit.vue +429 -0
- package/dist/runtime/app/pages/triggers/[name]/edit.vue.d.ts +3 -0
- package/dist/runtime/app/pages/triggers/[name].d.vue.ts +3 -0
- package/dist/runtime/app/pages/triggers/[name].vue +898 -0
- package/dist/runtime/app/pages/triggers/[name].vue.d.ts +3 -0
- package/dist/runtime/app/pages/triggers/index.d.vue.ts +3 -0
- package/dist/runtime/app/pages/triggers/index.vue +528 -0
- package/dist/runtime/app/pages/triggers/index.vue.d.ts +3 -0
- package/dist/runtime/app/pages/triggers/new.d.vue.ts +3 -0
- package/dist/runtime/app/pages/triggers/new.vue +610 -0
- package/dist/runtime/app/pages/triggers/new.vue.d.ts +3 -0
- package/dist/runtime/server/api/_flows/[name]/clear-history.delete.d.ts +10 -0
- package/dist/runtime/server/api/_flows/[name]/clear-history.delete.js +49 -0
- package/dist/runtime/server/api/_flows/[name]/runs/[runId]/cancel.post.d.ts +2 -0
- package/dist/runtime/server/api/_flows/[name]/runs/[runId]/cancel.post.js +21 -0
- package/dist/runtime/server/api/_flows/[name]/runs.get.d.ts +17 -0
- package/dist/runtime/server/api/_flows/[name]/runs.get.js +64 -0
- package/dist/runtime/server/api/_flows/[name]/start.post.d.ts +2 -0
- package/dist/runtime/server/api/_flows/[name]/start.post.js +9 -0
- package/dist/runtime/server/api/_flows/index.get.d.ts +7 -0
- package/dist/runtime/server/api/_flows/index.get.js +5 -0
- package/dist/runtime/server/api/_flows/recent-runs.get.d.ts +15 -0
- package/dist/runtime/server/api/_flows/recent-runs.get.js +67 -0
- package/dist/runtime/server/api/_flows/ws.d.ts +80 -0
- package/dist/runtime/server/api/_flows/ws.js +309 -0
- package/dist/runtime/server/api/_queues/[name]/job/[id].get.d.ts +2 -0
- package/dist/runtime/server/api/_queues/[name]/job/[id].get.js +14 -0
- package/dist/runtime/server/api/_queues/[name]/job/index.get.d.ts +2 -0
- package/dist/runtime/server/api/_queues/[name]/job/index.get.js +39 -0
- package/dist/runtime/server/api/_queues/index.get.d.ts +2 -0
- package/dist/runtime/server/api/_queues/index.get.js +106 -0
- package/dist/runtime/server/api/_queues/ws.d.ts +48 -0
- package/dist/runtime/server/api/_queues/ws.js +215 -0
- package/dist/runtime/server/api/_scheduler/jobs.get.d.ts +19 -0
- package/dist/runtime/server/api/_scheduler/jobs.get.js +36 -0
- package/dist/runtime/server/api/_triggers/[name]/events.get.d.ts +6 -0
- package/dist/runtime/server/api/_triggers/[name]/events.get.js +43 -0
- package/dist/runtime/server/api/_triggers/[name]/index.get.d.ts +6 -0
- package/dist/runtime/server/api/_triggers/[name]/index.get.js +76 -0
- package/dist/runtime/server/api/_triggers/[name].delete.d.ts +7 -0
- package/dist/runtime/server/api/_triggers/[name].delete.js +37 -0
- package/dist/runtime/server/api/_triggers/[name].patch.d.ts +7 -0
- package/dist/runtime/server/api/_triggers/[name].patch.js +117 -0
- package/dist/runtime/server/api/_triggers/index.get.d.ts +6 -0
- package/dist/runtime/server/api/_triggers/index.get.js +44 -0
- package/dist/runtime/server/api/_triggers/index.post.d.ts +7 -0
- package/dist/runtime/server/api/_triggers/index.post.js +124 -0
- package/dist/runtime/server/api/_triggers/stats.get.d.ts +6 -0
- package/dist/runtime/server/api/_triggers/stats.get.js +41 -0
- package/dist/runtime/server/api/_triggers/ws.d.ts +74 -0
- package/dist/runtime/server/api/_triggers/ws.js +315 -0
- package/dist/runtime/server/tsconfig.json +7 -0
- package/package.json +8 -8
- package/dist/runtime/app/components/FlowScheduleDialog.vue +0 -226
- package/dist/runtime/app/components/FlowSchedulesList.vue +0 -99
- package/dist/runtime/app/components/FlowStepSelector.vue +0 -238
- package/dist/runtime/app/components/QueueConfigDetails.vue +0 -412
- package/dist/runtime/app/components/nhealth/component-shell.vue +0 -89
- /package/dist/runtime/app/components/{nhealth/component-router.vue → ComponentRouter.vue} +0 -0
- /package/dist/runtime/app/components/{FlowNodeCard.d.vue.ts → flow/NodeCard.d.vue.ts} +0 -0
- /package/dist/runtime/app/components/{FlowNodeCard.vue → flow/NodeCard.vue} +0 -0
- /package/dist/runtime/app/components/{FlowNodeCard.vue.d.ts → flow/NodeCard.vue.d.ts} +0 -0
- /package/dist/runtime/app/components/{FlowRunTimeline.d.vue.ts → flow/RunTimeline.d.vue.ts} +0 -0
- /package/dist/runtime/app/components/{FlowRunTimeline.vue.d.ts → flow/RunTimeline.vue.d.ts} +0 -0
|
@@ -27,12 +27,6 @@
|
|
|
27
27
|
>
|
|
28
28
|
<div class="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
|
|
29
29
|
<span>Live</span>
|
|
30
|
-
<span
|
|
31
|
-
v-if="isAutoRefreshing"
|
|
32
|
-
class="text-[10px] text-gray-400"
|
|
33
|
-
>
|
|
34
|
-
(updating...)
|
|
35
|
-
</span>
|
|
36
30
|
</div>
|
|
37
31
|
<div
|
|
38
32
|
v-else-if="isReconnecting"
|
|
@@ -41,128 +35,383 @@
|
|
|
41
35
|
<div class="w-2 h-2 rounded-full bg-amber-500 animate-pulse" />
|
|
42
36
|
<span>Reconnecting...</span>
|
|
43
37
|
</div>
|
|
44
|
-
<UBadge
|
|
45
|
-
:label="counts?.active.toString() || '0'"
|
|
46
|
-
color="warning"
|
|
47
|
-
variant="subtle"
|
|
48
|
-
>
|
|
49
|
-
<template #leading>
|
|
50
|
-
Active
|
|
51
|
-
</template>
|
|
52
|
-
</UBadge>
|
|
53
|
-
<UBadge
|
|
54
|
-
:label="counts?.waiting.toString() || '0'"
|
|
55
|
-
color="info"
|
|
56
|
-
variant="subtle"
|
|
57
|
-
>
|
|
58
|
-
<template #leading>
|
|
59
|
-
Waiting
|
|
60
|
-
</template>
|
|
61
|
-
</UBadge>
|
|
62
|
-
<UBadge
|
|
63
|
-
:label="counts?.completed.toString() || '0'"
|
|
64
|
-
color="success"
|
|
65
|
-
variant="subtle"
|
|
66
|
-
>
|
|
67
|
-
<template #leading>
|
|
68
|
-
Completed
|
|
69
|
-
</template>
|
|
70
|
-
</UBadge>
|
|
71
|
-
<UBadge
|
|
72
|
-
:label="counts?.failed.toString() || '0'"
|
|
73
|
-
color="error"
|
|
74
|
-
variant="subtle"
|
|
75
|
-
>
|
|
76
|
-
<template #leading>
|
|
77
|
-
Failed
|
|
78
|
-
</template>
|
|
79
|
-
</UBadge>
|
|
80
|
-
<USelectMenu
|
|
81
|
-
v-model="selectedStateOption"
|
|
82
|
-
:items="stateOptions"
|
|
83
|
-
placeholder="All States"
|
|
84
|
-
size="xs"
|
|
85
|
-
class="w-32"
|
|
86
|
-
/>
|
|
87
38
|
<UButton
|
|
88
|
-
icon="i-lucide-
|
|
39
|
+
icon="i-lucide-settings"
|
|
89
40
|
size="xs"
|
|
90
41
|
color="neutral"
|
|
91
42
|
variant="ghost"
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
</UButton>
|
|
43
|
+
square
|
|
44
|
+
title="View configuration"
|
|
45
|
+
@click="showConfig = !showConfig"
|
|
46
|
+
/>
|
|
97
47
|
</div>
|
|
98
48
|
</div>
|
|
99
49
|
</div>
|
|
100
50
|
|
|
101
51
|
<!-- Main Content -->
|
|
102
|
-
<div class="flex-1 min-h-0 overflow-hidden
|
|
103
|
-
<div
|
|
104
|
-
|
|
105
|
-
class="
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
:ui="{
|
|
121
|
-
base: 'table-fixed border-separate border-spacing-0',
|
|
122
|
-
thead: '[&>tr]:bg-elevated/50 [&>tr]:after:content-none',
|
|
123
|
-
tbody: '[&>tr]:last:[&>td]:border-b-0',
|
|
124
|
-
th: 'py-2 first:rounded-l-lg last:rounded-r-lg border-y border-default first:border-l last:border-r',
|
|
125
|
-
td: 'border-b border-default',
|
|
126
|
-
separator: 'h-0'
|
|
127
|
-
}"
|
|
128
|
-
/>
|
|
129
|
-
</div>
|
|
52
|
+
<div class="flex-1 min-h-0 overflow-hidden">
|
|
53
|
+
<div class="h-full flex gap-px bg-gray-200 dark:bg-gray-800">
|
|
54
|
+
<!-- Left: Jobs List -->
|
|
55
|
+
<div class="w-1/3 min-w-0 flex-shrink-0 bg-white dark:bg-gray-950 flex flex-col min-h-0 overflow-hidden">
|
|
56
|
+
<div class="px-4 py-3 border-b border-gray-200 dark:border-gray-800 flex items-center justify-between shrink-0">
|
|
57
|
+
<h2 class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
58
|
+
Jobs
|
|
59
|
+
</h2>
|
|
60
|
+
<div class="flex items-center gap-2">
|
|
61
|
+
<USelectMenu
|
|
62
|
+
v-model="selectedStateOption"
|
|
63
|
+
:items="stateOptions"
|
|
64
|
+
placeholder="All States"
|
|
65
|
+
size="xs"
|
|
66
|
+
class="w-32"
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
130
70
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
71
|
+
<div
|
|
72
|
+
v-if="!data || !data.jobs || data.jobs.length === 0"
|
|
73
|
+
class="flex-1 flex items-center justify-center"
|
|
74
|
+
>
|
|
75
|
+
<div class="text-center">
|
|
76
|
+
<UIcon
|
|
77
|
+
name="i-lucide-inbox"
|
|
78
|
+
class="w-8 h-8 mx-auto mb-2 text-gray-300 dark:text-gray-700"
|
|
79
|
+
/>
|
|
80
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
81
|
+
No jobs found
|
|
82
|
+
</p>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div
|
|
87
|
+
v-else
|
|
88
|
+
class="flex-1 min-h-0 overflow-y-auto"
|
|
89
|
+
>
|
|
90
|
+
<div class="divide-y divide-gray-100 dark:divide-gray-800">
|
|
91
|
+
<div
|
|
92
|
+
v-for="job in paginatedJobs"
|
|
93
|
+
:key="job.id"
|
|
94
|
+
class="px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-900/50 cursor-pointer transition-colors"
|
|
95
|
+
:class="{
|
|
96
|
+
'bg-blue-50 dark:bg-blue-950/30 border-l-2 border-l-blue-500': selectedJobId === job.id
|
|
97
|
+
}"
|
|
98
|
+
@click="selectJob(job.id)"
|
|
99
|
+
>
|
|
100
|
+
<div class="flex items-start gap-3">
|
|
101
|
+
<div class="flex-shrink-0 mt-0.5">
|
|
102
|
+
<UIcon
|
|
103
|
+
:name="getJobIcon(job.state)"
|
|
104
|
+
class="w-5 h-5"
|
|
105
|
+
:class="getJobIconColor(job.state)"
|
|
106
|
+
/>
|
|
107
|
+
</div>
|
|
108
|
+
<div class="flex-1 min-w-0">
|
|
109
|
+
<div class="flex items-center justify-between gap-2 mb-1">
|
|
110
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate">
|
|
111
|
+
{{ job.name }}
|
|
112
|
+
</h3>
|
|
113
|
+
<UBadge
|
|
114
|
+
:label="job.state || 'unknown'"
|
|
115
|
+
:color="getStateBadgeColor(job.state)"
|
|
116
|
+
variant="subtle"
|
|
117
|
+
size="xs"
|
|
118
|
+
class="capitalize flex-shrink-0"
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 font-mono truncate mb-1">
|
|
122
|
+
{{ truncateId(job.id) }}
|
|
123
|
+
</p>
|
|
124
|
+
<div class="flex items-center gap-3 text-xs text-gray-500 dark:text-gray-400">
|
|
125
|
+
<span v-if="job.timestamp">
|
|
126
|
+
{{ formatTime(job.timestamp) }}
|
|
127
|
+
</span>
|
|
128
|
+
<span v-if="job.finishedOn && job.processedOn">
|
|
129
|
+
• {{ formatDuration(job.processedOn, job.finishedOn) }}
|
|
130
|
+
</span>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
140
136
|
</div>
|
|
141
|
-
|
|
137
|
+
|
|
138
|
+
<!-- Pagination Footer -->
|
|
139
|
+
<div
|
|
140
|
+
v-if="data && data.total > jobsPerPage"
|
|
141
|
+
class="border-t border-gray-200 dark:border-gray-800 px-4 py-3 flex items-center justify-center shrink-0"
|
|
142
|
+
>
|
|
142
143
|
<UPagination
|
|
143
|
-
:
|
|
144
|
-
:items-per-page="
|
|
145
|
-
:total="data.
|
|
146
|
-
|
|
144
|
+
v-model:page="currentPage"
|
|
145
|
+
:items-per-page="jobsPerPage"
|
|
146
|
+
:total="data.total"
|
|
147
|
+
size="xs"
|
|
147
148
|
/>
|
|
148
149
|
</div>
|
|
149
150
|
</div>
|
|
150
|
-
|
|
151
|
+
|
|
152
|
+
<!-- Right: Overview or Job Details -->
|
|
153
|
+
<div class="flex-1 min-w-0 bg-white dark:bg-gray-950 flex flex-col min-h-0 overflow-hidden">
|
|
154
|
+
<div class="px-4 py-2.5 border-b border-gray-200 dark:border-gray-800 shrink-0">
|
|
155
|
+
<div class="flex items-center justify-between">
|
|
156
|
+
<UTabs
|
|
157
|
+
v-model="activeTab"
|
|
158
|
+
:items="tabItems"
|
|
159
|
+
size="xs"
|
|
160
|
+
:ui="{
|
|
161
|
+
root: 'gap-0',
|
|
162
|
+
trigger: 'px-2 py-0.5'
|
|
163
|
+
}"
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<div class="flex-1 min-h-0 overflow-y-auto">
|
|
169
|
+
<!-- Overview Tab -->
|
|
170
|
+
<div
|
|
171
|
+
v-if="activeTab === 'overview'"
|
|
172
|
+
class="p-6 space-y-6"
|
|
173
|
+
>
|
|
174
|
+
<!-- Stats Cards -->
|
|
175
|
+
<div>
|
|
176
|
+
<h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
177
|
+
Queue Statistics
|
|
178
|
+
</h3>
|
|
179
|
+
<div class="grid grid-cols-2 gap-4">
|
|
180
|
+
<StatCard
|
|
181
|
+
icon="i-lucide-clock"
|
|
182
|
+
:count="counts?.waiting || 0"
|
|
183
|
+
label="Waiting"
|
|
184
|
+
variant="blue"
|
|
185
|
+
/>
|
|
186
|
+
<StatCard
|
|
187
|
+
icon="i-lucide-loader-2"
|
|
188
|
+
:count="counts?.active || 0"
|
|
189
|
+
label="Active"
|
|
190
|
+
variant="amber"
|
|
191
|
+
/>
|
|
192
|
+
<StatCard
|
|
193
|
+
icon="i-lucide-check-circle"
|
|
194
|
+
:count="counts?.completed || 0"
|
|
195
|
+
label="Completed"
|
|
196
|
+
variant="emerald"
|
|
197
|
+
/>
|
|
198
|
+
<StatCard
|
|
199
|
+
icon="i-lucide-x-circle"
|
|
200
|
+
:count="counts?.failed || 0"
|
|
201
|
+
label="Failed"
|
|
202
|
+
variant="red"
|
|
203
|
+
/>
|
|
204
|
+
<StatCard
|
|
205
|
+
icon="i-lucide-timer"
|
|
206
|
+
:count="counts?.delayed || 0"
|
|
207
|
+
label="Delayed"
|
|
208
|
+
variant="purple"
|
|
209
|
+
/>
|
|
210
|
+
<StatCard
|
|
211
|
+
icon="i-lucide-pause-circle"
|
|
212
|
+
:count="counts?.paused || 0"
|
|
213
|
+
label="Paused"
|
|
214
|
+
variant="gray"
|
|
215
|
+
/>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
<!-- Queue Configuration -->
|
|
220
|
+
<div>
|
|
221
|
+
<h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
222
|
+
Queue Configuration
|
|
223
|
+
</h3>
|
|
224
|
+
<div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4 space-y-3">
|
|
225
|
+
<div class="flex items-center justify-between py-2 border-b border-gray-200 dark:border-gray-800">
|
|
226
|
+
<span class="text-sm text-gray-600 dark:text-gray-400">Queue Name</span>
|
|
227
|
+
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 font-mono">{{ queueName }}</span>
|
|
228
|
+
</div>
|
|
229
|
+
<div class="flex items-center justify-between py-2 border-b border-gray-200 dark:border-gray-800">
|
|
230
|
+
<span class="text-sm text-gray-600 dark:text-gray-400">Total Jobs</span>
|
|
231
|
+
<span class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ data?.jobs?.length || 0 }}</span>
|
|
232
|
+
</div>
|
|
233
|
+
<div class="flex items-center justify-between py-2">
|
|
234
|
+
<span class="text-sm text-gray-600 dark:text-gray-400">Connection Status</span>
|
|
235
|
+
<UBadge
|
|
236
|
+
:label="isConnected ? 'Connected' : isReconnecting ? 'Reconnecting' : 'Disconnected'"
|
|
237
|
+
:color="isConnected ? 'success' : isReconnecting ? 'warning' : 'error'"
|
|
238
|
+
variant="subtle"
|
|
239
|
+
size="xs"
|
|
240
|
+
/>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
|
|
246
|
+
<!-- Job Details Tab -->
|
|
247
|
+
<div
|
|
248
|
+
v-else-if="activeTab === 'details' && selectedJob"
|
|
249
|
+
class="p-6 space-y-6"
|
|
250
|
+
>
|
|
251
|
+
<!-- Job Info -->
|
|
252
|
+
<div>
|
|
253
|
+
<h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center gap-2">
|
|
254
|
+
<UIcon
|
|
255
|
+
:name="getJobIcon(selectedJob.state)"
|
|
256
|
+
class="w-5 h-5"
|
|
257
|
+
:class="getJobIconColor(selectedJob.state)"
|
|
258
|
+
/>
|
|
259
|
+
<span>Job Information</span>
|
|
260
|
+
</h3>
|
|
261
|
+
<div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4 space-y-3">
|
|
262
|
+
<div class="flex items-center justify-between py-2 border-b border-gray-200 dark:border-gray-800">
|
|
263
|
+
<span class="text-sm text-gray-600 dark:text-gray-400">ID</span>
|
|
264
|
+
<span class="text-xs font-mono text-gray-900 dark:text-gray-100">{{ selectedJob.id }}</span>
|
|
265
|
+
</div>
|
|
266
|
+
<div class="flex items-center justify-between py-2 border-b border-gray-200 dark:border-gray-800">
|
|
267
|
+
<span class="text-sm text-gray-600 dark:text-gray-400">Name</span>
|
|
268
|
+
<span class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ selectedJob.name }}</span>
|
|
269
|
+
</div>
|
|
270
|
+
<div class="flex items-center justify-between py-2">
|
|
271
|
+
<span class="text-sm text-gray-600 dark:text-gray-400">State</span>
|
|
272
|
+
<UBadge
|
|
273
|
+
:label="selectedJob.state || 'unknown'"
|
|
274
|
+
:color="getStateBadgeColor(selectedJob.state)"
|
|
275
|
+
variant="subtle"
|
|
276
|
+
size="xs"
|
|
277
|
+
class="capitalize"
|
|
278
|
+
/>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
<!-- Timing -->
|
|
284
|
+
<div>
|
|
285
|
+
<h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
286
|
+
Timing
|
|
287
|
+
</h3>
|
|
288
|
+
<div class="space-y-4">
|
|
289
|
+
<div class="grid grid-cols-1 gap-3">
|
|
290
|
+
<div class="flex items-center justify-between py-2 bg-gray-50 dark:bg-gray-900/50 rounded-lg px-4">
|
|
291
|
+
<span class="text-sm text-gray-600 dark:text-gray-400">Created</span>
|
|
292
|
+
<span class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ formatDate(selectedJob.timestamp) }}</span>
|
|
293
|
+
</div>
|
|
294
|
+
<div
|
|
295
|
+
v-if="selectedJob.processedOn"
|
|
296
|
+
class="flex items-center justify-between py-2 bg-gray-50 dark:bg-gray-900/50 rounded-lg px-4"
|
|
297
|
+
>
|
|
298
|
+
<span class="text-sm text-gray-600 dark:text-gray-400">Processed</span>
|
|
299
|
+
<span class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ formatDate(selectedJob.processedOn) }}</span>
|
|
300
|
+
</div>
|
|
301
|
+
<div
|
|
302
|
+
v-if="selectedJob.finishedOn"
|
|
303
|
+
class="flex items-center justify-between py-2 bg-gray-50 dark:bg-gray-900/50 rounded-lg px-4"
|
|
304
|
+
>
|
|
305
|
+
<span class="text-sm text-gray-600 dark:text-gray-400">Finished</span>
|
|
306
|
+
<span class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ formatDate(selectedJob.finishedOn) }}</span>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
<!-- Duration Cards -->
|
|
311
|
+
<div
|
|
312
|
+
v-if="selectedJobWaitDuration || selectedJobExecutionDuration"
|
|
313
|
+
class="grid grid-cols-2 gap-3 pt-2"
|
|
314
|
+
>
|
|
315
|
+
<div
|
|
316
|
+
v-if="selectedJobWaitDuration"
|
|
317
|
+
class="bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-800 rounded-lg p-4 text-center"
|
|
318
|
+
>
|
|
319
|
+
<p class="text-xs text-blue-600 dark:text-blue-400 mb-1">
|
|
320
|
+
Wait Time
|
|
321
|
+
</p>
|
|
322
|
+
<p class="text-lg font-bold text-blue-700 dark:text-blue-300">
|
|
323
|
+
{{ selectedJobWaitDuration }}
|
|
324
|
+
</p>
|
|
325
|
+
</div>
|
|
326
|
+
<div
|
|
327
|
+
v-if="selectedJobExecutionDuration"
|
|
328
|
+
class="rounded-lg p-4 text-center border"
|
|
329
|
+
:class="selectedJob.state === 'active' ? 'bg-amber-50 dark:bg-amber-950/30 border-amber-200 dark:border-amber-800' : 'bg-emerald-50 dark:bg-emerald-950/30 border-emerald-200 dark:border-emerald-800'"
|
|
330
|
+
>
|
|
331
|
+
<p
|
|
332
|
+
class="text-xs mb-1"
|
|
333
|
+
:class="selectedJob.state === 'active' ? 'text-amber-600 dark:text-amber-400' : 'text-emerald-600 dark:text-emerald-400'"
|
|
334
|
+
>
|
|
335
|
+
{{ selectedJob.state === "active" ? "Running" : "Execution" }}
|
|
336
|
+
</p>
|
|
337
|
+
<p
|
|
338
|
+
class="text-lg font-bold"
|
|
339
|
+
:class="selectedJob.state === 'active' ? 'text-amber-700 dark:text-amber-300' : 'text-emerald-700 dark:text-emerald-300'"
|
|
340
|
+
>
|
|
341
|
+
{{ selectedJobExecutionDuration }}
|
|
342
|
+
</p>
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
</div>
|
|
347
|
+
|
|
348
|
+
<!-- Job Data -->
|
|
349
|
+
<div>
|
|
350
|
+
<h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
351
|
+
Job Data
|
|
352
|
+
</h3>
|
|
353
|
+
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 overflow-x-auto">
|
|
354
|
+
<pre class="text-xs font-mono">{{ JSON.stringify(selectedJob.data, null, 2) }}</pre>
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
|
|
358
|
+
<!-- Return Value -->
|
|
359
|
+
<div v-if="selectedJob.returnvalue">
|
|
360
|
+
<h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
361
|
+
Return Value
|
|
362
|
+
</h3>
|
|
363
|
+
<div class="bg-emerald-50 dark:bg-emerald-950/30 border border-emerald-200 dark:border-emerald-800 rounded-lg p-4 overflow-x-auto">
|
|
364
|
+
<pre class="text-xs font-mono text-emerald-900 dark:text-emerald-100">{{ JSON.stringify(selectedJob.returnvalue, null, 2) }}</pre>
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
|
|
368
|
+
<!-- Error -->
|
|
369
|
+
<div v-if="selectedJob.failedReason">
|
|
370
|
+
<h3 class="text-sm font-semibold text-red-600 dark:text-red-400 mb-4">
|
|
371
|
+
Error
|
|
372
|
+
</h3>
|
|
373
|
+
<div class="bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800 rounded-lg p-4">
|
|
374
|
+
<p class="text-sm text-red-700 dark:text-red-300 whitespace-pre-wrap">
|
|
375
|
+
{{ selectedJob.failedReason }}
|
|
376
|
+
</p>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
</div>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
</div>
|
|
151
383
|
</div>
|
|
384
|
+
|
|
385
|
+
<!-- Configuration Slideover -->
|
|
386
|
+
<USlideover
|
|
387
|
+
v-model:open="showConfig"
|
|
388
|
+
title="Queue Configuration"
|
|
389
|
+
>
|
|
390
|
+
<template #body>
|
|
391
|
+
<QueueConfiguration
|
|
392
|
+
:queue-name="queueName"
|
|
393
|
+
:queue-config="queueInfo?.config?.queue"
|
|
394
|
+
:worker-config="queueInfo?.config?.worker"
|
|
395
|
+
/>
|
|
396
|
+
</template>
|
|
397
|
+
</USlideover>
|
|
152
398
|
</div>
|
|
153
399
|
</template>
|
|
154
400
|
|
|
155
401
|
<script setup>
|
|
156
|
-
import { ref, computed, watch
|
|
157
|
-
import {
|
|
158
|
-
import { UTable, UButton, UBadge, UPagination, USelectMenu } from "#components";
|
|
402
|
+
import { ref, computed, watch } from "#imports";
|
|
403
|
+
import { UButton, UBadge, UPagination, USelectMenu, UIcon, UTabs, USlideover } from "#components";
|
|
159
404
|
import { useQueueJobs } from "../../composables/useQueueJobs";
|
|
160
405
|
import { useQueueUpdates } from "../../composables/useQueueUpdates";
|
|
406
|
+
import { useQueues } from "../../composables/useQueues";
|
|
161
407
|
import { useComponentRouter } from "../../composables/useComponentRouter";
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
408
|
+
import { useRoute, useRouter } from "#app";
|
|
409
|
+
import StatCard from "../../components/StatCard.vue";
|
|
410
|
+
import QueueConfiguration from "../../components/QueueConfiguration.vue";
|
|
411
|
+
const componentRouter = useComponentRouter();
|
|
412
|
+
const router = useRouter();
|
|
413
|
+
const route = useRoute();
|
|
414
|
+
const queueName = computed(() => componentRouter.route.value?.params?.name || "");
|
|
166
415
|
const stateOptions = [
|
|
167
416
|
{ label: "All States", value: null },
|
|
168
417
|
{ label: "Waiting", value: "waiting" },
|
|
@@ -172,120 +421,198 @@ const stateOptions = [
|
|
|
172
421
|
{ label: "Delayed", value: "delayed" },
|
|
173
422
|
{ label: "Paused", value: "paused" }
|
|
174
423
|
];
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
424
|
+
const selectedState = computed({
|
|
425
|
+
get: () => route.query.state || null,
|
|
426
|
+
set: (value) => {
|
|
427
|
+
router.push({
|
|
428
|
+
query: {
|
|
429
|
+
...route.query,
|
|
430
|
+
state: value || void 0,
|
|
431
|
+
page: void 0
|
|
432
|
+
// Reset page when filter changes
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
const currentPage = computed({
|
|
438
|
+
get: () => {
|
|
439
|
+
const page = route.query.page;
|
|
440
|
+
return page ? Number.parseInt(page, 10) : 1;
|
|
441
|
+
},
|
|
442
|
+
set: (value) => {
|
|
443
|
+
router.push({
|
|
444
|
+
query: {
|
|
445
|
+
...route.query,
|
|
446
|
+
page: value > 1 ? value.toString() : void 0
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
const selectedStateOption = computed({
|
|
452
|
+
get: () => stateOptions.find((opt) => opt.value === selectedState.value) || stateOptions[0],
|
|
453
|
+
set: (option) => {
|
|
454
|
+
selectedState.value = option.value;
|
|
183
455
|
}
|
|
184
456
|
});
|
|
457
|
+
const jobsPerPage = 20;
|
|
458
|
+
const jobQueryOptions = computed(() => ({
|
|
459
|
+
state: selectedState.value,
|
|
460
|
+
limit: jobsPerPage,
|
|
461
|
+
offset: (currentPage.value - 1) * jobsPerPage
|
|
462
|
+
}));
|
|
463
|
+
const { data, refresh } = useQueueJobs(queueName, jobQueryOptions);
|
|
464
|
+
const { counts: liveCounts, isConnected, isReconnecting, shouldRefreshJobs, resetRefreshFlag } = useQueueUpdates(queueName);
|
|
465
|
+
const { queues } = useQueues();
|
|
466
|
+
const queueInfo = computed(() => {
|
|
467
|
+
return queues.value?.find((q) => q.name === queueName.value);
|
|
468
|
+
});
|
|
469
|
+
const counts = computed(() => liveCounts.value || queueInfo.value?.counts || null);
|
|
185
470
|
watch(shouldRefreshJobs, async (shouldRefresh) => {
|
|
186
471
|
if (shouldRefresh) {
|
|
187
|
-
isAutoRefreshing.value = true;
|
|
188
472
|
await refresh();
|
|
189
473
|
resetRefreshFlag();
|
|
190
|
-
setTimeout(() => {
|
|
191
|
-
isAutoRefreshing.value = false;
|
|
192
|
-
}, 500);
|
|
193
474
|
}
|
|
194
475
|
});
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
476
|
+
watch(() => jobQueryOptions.value, () => {
|
|
477
|
+
refresh();
|
|
478
|
+
}, { deep: true });
|
|
479
|
+
const paginatedJobs = computed(() => {
|
|
480
|
+
return data.value?.jobs || [];
|
|
481
|
+
});
|
|
482
|
+
const selectedJobId = ref(null);
|
|
483
|
+
const selectedJob = computed(() => {
|
|
484
|
+
if (!selectedJobId.value || !data.value?.jobs) return null;
|
|
485
|
+
return data.value.jobs.find((job) => job.id === selectedJobId.value);
|
|
486
|
+
});
|
|
487
|
+
const showConfig = ref(false);
|
|
488
|
+
const activeTab = ref("overview");
|
|
489
|
+
const tabItems = computed(() => [
|
|
490
|
+
{ label: "Overview", value: "overview", icon: "i-lucide-bar-chart-3" },
|
|
491
|
+
{
|
|
492
|
+
label: "Job Details",
|
|
493
|
+
value: "details",
|
|
494
|
+
icon: "i-lucide-file-text",
|
|
495
|
+
disabled: !selectedJobId.value
|
|
496
|
+
}
|
|
497
|
+
]);
|
|
498
|
+
watch(selectedJobId, (newId) => {
|
|
499
|
+
if (newId) {
|
|
500
|
+
activeTab.value = "details";
|
|
501
|
+
showConfig.value = false;
|
|
502
|
+
} else {
|
|
503
|
+
activeTab.value = "overview";
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
watch(showConfig, (show) => {
|
|
507
|
+
if (show) {
|
|
508
|
+
selectedJobId.value = null;
|
|
509
|
+
}
|
|
199
510
|
});
|
|
200
|
-
const
|
|
201
|
-
|
|
511
|
+
const selectJob = (jobId) => {
|
|
512
|
+
selectedJobId.value = jobId;
|
|
202
513
|
};
|
|
203
514
|
const back = () => {
|
|
204
|
-
|
|
515
|
+
componentRouter.push("/queues");
|
|
205
516
|
};
|
|
206
|
-
const
|
|
207
|
-
|
|
517
|
+
const getJobIcon = (state) => {
|
|
518
|
+
switch (state) {
|
|
519
|
+
case "waiting":
|
|
520
|
+
return "i-lucide-clock";
|
|
521
|
+
case "active":
|
|
522
|
+
return "i-lucide-loader-2";
|
|
523
|
+
case "completed":
|
|
524
|
+
return "i-lucide-check-circle";
|
|
525
|
+
case "failed":
|
|
526
|
+
return "i-lucide-x-circle";
|
|
527
|
+
case "delayed":
|
|
528
|
+
return "i-lucide-timer";
|
|
529
|
+
case "paused":
|
|
530
|
+
return "i-lucide-pause-circle";
|
|
531
|
+
default:
|
|
532
|
+
return "i-lucide-circle";
|
|
533
|
+
}
|
|
208
534
|
};
|
|
209
|
-
const
|
|
210
|
-
{
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
return h("div", {
|
|
226
|
-
class: "font-medium cursor-pointer hover:underline",
|
|
227
|
-
onClick: () => selectJob(row.original.id)
|
|
228
|
-
}, row.original.name);
|
|
229
|
-
}
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
accessorKey: "state",
|
|
233
|
-
header: "State",
|
|
234
|
-
cell: ({ row }) => {
|
|
235
|
-
const state = row.original.state;
|
|
236
|
-
const colorMap = {
|
|
237
|
-
waiting: "info",
|
|
238
|
-
active: "warning",
|
|
239
|
-
completed: "success",
|
|
240
|
-
failed: "error",
|
|
241
|
-
delayed: "secondary",
|
|
242
|
-
paused: "warning"
|
|
243
|
-
};
|
|
244
|
-
return h(UBadgeComponent, {
|
|
245
|
-
label: state || "unknown",
|
|
246
|
-
color: colorMap[state || ""] || "neutral",
|
|
247
|
-
variant: "subtle",
|
|
248
|
-
class: "capitalize"
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
accessorKey: "timestamp",
|
|
254
|
-
header: "Created",
|
|
255
|
-
cell: ({ row }) => {
|
|
256
|
-
const timestamp = row.original.timestamp;
|
|
257
|
-
if (!timestamp) return h("div", { class: "text-gray-400 text-xs" }, "-");
|
|
258
|
-
const date = new Date(timestamp);
|
|
259
|
-
return h(
|
|
260
|
-
"div",
|
|
261
|
-
{ class: "text-xs" },
|
|
262
|
-
date.toLocaleString("de-DE", {
|
|
263
|
-
timeZone: "Europe/Berlin",
|
|
264
|
-
day: "2-digit",
|
|
265
|
-
month: "2-digit",
|
|
266
|
-
year: "numeric",
|
|
267
|
-
hour: "2-digit",
|
|
268
|
-
minute: "2-digit",
|
|
269
|
-
second: "2-digit"
|
|
270
|
-
})
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
},
|
|
274
|
-
{
|
|
275
|
-
id: "actions",
|
|
276
|
-
header: "",
|
|
277
|
-
cell: ({ row }) => {
|
|
278
|
-
return h(UButtonComponent, {
|
|
279
|
-
icon: "i-lucide-arrow-right",
|
|
280
|
-
size: "xs",
|
|
281
|
-
color: "neutral",
|
|
282
|
-
variant: "ghost",
|
|
283
|
-
square: true,
|
|
284
|
-
onClick: () => {
|
|
285
|
-
selectJob(row.original.id);
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
}
|
|
535
|
+
const getJobIconColor = (state) => {
|
|
536
|
+
switch (state) {
|
|
537
|
+
case "waiting":
|
|
538
|
+
return "text-blue-500";
|
|
539
|
+
case "active":
|
|
540
|
+
return "text-amber-500 animate-spin";
|
|
541
|
+
case "completed":
|
|
542
|
+
return "text-emerald-500";
|
|
543
|
+
case "failed":
|
|
544
|
+
return "text-red-500";
|
|
545
|
+
case "delayed":
|
|
546
|
+
return "text-purple-500";
|
|
547
|
+
case "paused":
|
|
548
|
+
return "text-gray-500";
|
|
549
|
+
default:
|
|
550
|
+
return "text-gray-400";
|
|
289
551
|
}
|
|
290
|
-
|
|
552
|
+
};
|
|
553
|
+
const getStateBadgeColor = (state) => {
|
|
554
|
+
switch (state) {
|
|
555
|
+
case "waiting":
|
|
556
|
+
return "info";
|
|
557
|
+
case "active":
|
|
558
|
+
return "warning";
|
|
559
|
+
case "completed":
|
|
560
|
+
return "success";
|
|
561
|
+
case "failed":
|
|
562
|
+
return "error";
|
|
563
|
+
case "delayed":
|
|
564
|
+
return "secondary";
|
|
565
|
+
case "paused":
|
|
566
|
+
return "warning";
|
|
567
|
+
default:
|
|
568
|
+
return "neutral";
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
const truncateId = (id) => {
|
|
572
|
+
if (id.length <= 16) return id;
|
|
573
|
+
return `${id.substring(0, 8)}...${id.substring(id.length - 8)}`;
|
|
574
|
+
};
|
|
575
|
+
const formatTime = (timestamp) => {
|
|
576
|
+
if (!timestamp) return "-";
|
|
577
|
+
const date = new Date(timestamp);
|
|
578
|
+
const now = /* @__PURE__ */ new Date();
|
|
579
|
+
const diff = now.getTime() - date.getTime();
|
|
580
|
+
const seconds = Math.floor(diff / 1e3);
|
|
581
|
+
const minutes = Math.floor(seconds / 60);
|
|
582
|
+
const hours = Math.floor(minutes / 60);
|
|
583
|
+
const days = Math.floor(hours / 24);
|
|
584
|
+
if (days > 0) return `${days}d ago`;
|
|
585
|
+
if (hours > 0) return `${hours}h ago`;
|
|
586
|
+
if (minutes > 0) return `${minutes}m ago`;
|
|
587
|
+
if (seconds > 10) return `${seconds}s ago`;
|
|
588
|
+
return "just now";
|
|
589
|
+
};
|
|
590
|
+
const formatDuration = (start, end) => {
|
|
591
|
+
const diff = end - start;
|
|
592
|
+
if (diff < 1e3) return `${diff}ms`;
|
|
593
|
+
if (diff < 6e4) return `${(diff / 1e3).toFixed(2)}s`;
|
|
594
|
+
if (diff < 36e5) return `${(diff / 6e4).toFixed(2)}m`;
|
|
595
|
+
return `${(diff / 36e5).toFixed(2)}h`;
|
|
596
|
+
};
|
|
597
|
+
const formatDate = (timestamp) => {
|
|
598
|
+
if (!timestamp) return "-";
|
|
599
|
+
return new Date(timestamp).toLocaleString("de-DE", {
|
|
600
|
+
timeZone: "Europe/Berlin",
|
|
601
|
+
day: "2-digit",
|
|
602
|
+
month: "2-digit",
|
|
603
|
+
year: "numeric",
|
|
604
|
+
hour: "2-digit",
|
|
605
|
+
minute: "2-digit",
|
|
606
|
+
second: "2-digit"
|
|
607
|
+
});
|
|
608
|
+
};
|
|
609
|
+
const selectedJobWaitDuration = computed(() => {
|
|
610
|
+
if (!selectedJob.value?.timestamp || !selectedJob.value?.processedOn) return null;
|
|
611
|
+
return formatDuration(selectedJob.value.timestamp, selectedJob.value.processedOn);
|
|
612
|
+
});
|
|
613
|
+
const selectedJobExecutionDuration = computed(() => {
|
|
614
|
+
if (!selectedJob.value?.processedOn) return null;
|
|
615
|
+
const endTime = selectedJob.value.finishedOn || Date.now();
|
|
616
|
+
return formatDuration(selectedJob.value.processedOn, endTime);
|
|
617
|
+
});
|
|
291
618
|
</script>
|