@nvent-addon/app 0.4.5
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 +6 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +42 -0
- package/dist/runtime/app/assets/vueflow.css +1 -0
- package/dist/runtime/app/components/ConfirmDialog.d.vue.ts +33 -0
- package/dist/runtime/app/components/ConfirmDialog.vue +121 -0
- package/dist/runtime/app/components/ConfirmDialog.vue.d.ts +33 -0
- package/dist/runtime/app/components/FlowDiagram.d.vue.ts +65 -0
- package/dist/runtime/app/components/FlowDiagram.vue +341 -0
- package/dist/runtime/app/components/FlowDiagram.vue.d.ts +65 -0
- package/dist/runtime/app/components/FlowNodeCard.d.vue.ts +29 -0
- package/dist/runtime/app/components/FlowNodeCard.vue +158 -0
- package/dist/runtime/app/components/FlowNodeCard.vue.d.ts +29 -0
- package/dist/runtime/app/components/FlowRunOverview.d.vue.ts +17 -0
- package/dist/runtime/app/components/FlowRunOverview.vue +188 -0
- package/dist/runtime/app/components/FlowRunOverview.vue.d.ts +17 -0
- package/dist/runtime/app/components/FlowRunStatusBadge.d.vue.ts +18 -0
- package/dist/runtime/app/components/FlowRunStatusBadge.vue +74 -0
- package/dist/runtime/app/components/FlowRunStatusBadge.vue.d.ts +18 -0
- package/dist/runtime/app/components/FlowRunTimeline.d.vue.ts +12 -0
- package/dist/runtime/app/components/FlowRunTimeline.vue +127 -0
- package/dist/runtime/app/components/FlowRunTimeline.vue.d.ts +12 -0
- package/dist/runtime/app/components/FlowScheduleDialog.d.vue.ts +16 -0
- package/dist/runtime/app/components/FlowScheduleDialog.vue +226 -0
- package/dist/runtime/app/components/FlowScheduleDialog.vue.d.ts +16 -0
- package/dist/runtime/app/components/FlowSchedulesList.d.vue.ts +12 -0
- package/dist/runtime/app/components/FlowSchedulesList.vue +99 -0
- package/dist/runtime/app/components/FlowSchedulesList.vue.d.ts +12 -0
- package/dist/runtime/app/components/FlowStepSelector.d.vue.ts +22 -0
- package/dist/runtime/app/components/FlowStepSelector.vue +238 -0
- package/dist/runtime/app/components/FlowStepSelector.vue.d.ts +22 -0
- package/dist/runtime/app/components/JobScheduling.d.vue.ts +6 -0
- package/dist/runtime/app/components/JobScheduling.vue +203 -0
- package/dist/runtime/app/components/JobScheduling.vue.d.ts +6 -0
- package/dist/runtime/app/components/ListItem.d.vue.ts +23 -0
- package/dist/runtime/app/components/ListItem.vue +70 -0
- package/dist/runtime/app/components/ListItem.vue.d.ts +23 -0
- package/dist/runtime/app/components/QueueConfigDetails.d.vue.ts +45 -0
- package/dist/runtime/app/components/QueueConfigDetails.vue +412 -0
- package/dist/runtime/app/components/QueueConfigDetails.vue.d.ts +45 -0
- package/dist/runtime/app/components/StatCounter.d.vue.ts +9 -0
- package/dist/runtime/app/components/StatCounter.vue +25 -0
- package/dist/runtime/app/components/StatCounter.vue.d.ts +9 -0
- package/dist/runtime/app/components/TimelineList.d.vue.ts +7 -0
- package/dist/runtime/app/components/TimelineList.vue +211 -0
- package/dist/runtime/app/components/TimelineList.vue.d.ts +7 -0
- package/dist/runtime/app/components/nhealth/component-router.d.vue.ts +46 -0
- package/dist/runtime/app/components/nhealth/component-router.vue +26 -0
- package/dist/runtime/app/components/nhealth/component-router.vue.d.ts +46 -0
- package/dist/runtime/app/components/nhealth/component-shell.d.vue.ts +24 -0
- package/dist/runtime/app/components/nhealth/component-shell.vue +89 -0
- package/dist/runtime/app/components/nhealth/component-shell.vue.d.ts +24 -0
- package/dist/runtime/app/composables/useAnalyzedFlows.d.ts +14 -0
- package/dist/runtime/app/composables/useAnalyzedFlows.js +8 -0
- package/dist/runtime/app/composables/useComponentRouter.d.ts +38 -0
- package/dist/runtime/app/composables/useComponentRouter.js +240 -0
- package/dist/runtime/app/composables/useFlowRunTimeline.d.ts +82 -0
- package/dist/runtime/app/composables/useFlowRunTimeline.js +67 -0
- package/dist/runtime/app/composables/useFlowRuns.d.ts +18 -0
- package/dist/runtime/app/composables/useFlowRuns.js +32 -0
- package/dist/runtime/app/composables/useFlowRunsInfinite.d.ts +24 -0
- package/dist/runtime/app/composables/useFlowRunsInfinite.js +123 -0
- package/dist/runtime/app/composables/useFlowRunsPolling.d.ts +9 -0
- package/dist/runtime/app/composables/useFlowRunsPolling.js +33 -0
- package/dist/runtime/app/composables/useFlowState.d.ts +127 -0
- package/dist/runtime/app/composables/useFlowState.js +225 -0
- package/dist/runtime/app/composables/useFlowWebSocket.d.ts +27 -0
- package/dist/runtime/app/composables/useFlowWebSocket.js +222 -0
- package/dist/runtime/app/composables/useFlowsNavigation.d.ts +10 -0
- package/dist/runtime/app/composables/useFlowsNavigation.js +58 -0
- package/dist/runtime/app/composables/useQueueJobs.d.ts +26 -0
- package/dist/runtime/app/composables/useQueueJobs.js +20 -0
- package/dist/runtime/app/composables/useQueueUpdates.d.ts +24 -0
- package/dist/runtime/app/composables/useQueueUpdates.js +54 -0
- package/dist/runtime/app/composables/useQueues.d.ts +45 -0
- package/dist/runtime/app/composables/useQueues.js +26 -0
- package/dist/runtime/app/composables/useQueuesLive.d.ts +16 -0
- package/dist/runtime/app/composables/useQueuesLive.js +62 -0
- package/dist/runtime/app/composables/useQueuesWebSocket.d.ts +17 -0
- package/dist/runtime/app/composables/useQueuesWebSocket.js +159 -0
- package/dist/runtime/app/pages/flows/index.d.vue.ts +3 -0
- package/dist/runtime/app/pages/flows/index.vue +683 -0
- package/dist/runtime/app/pages/flows/index.vue.d.ts +3 -0
- package/dist/runtime/app/pages/index.d.vue.ts +3 -0
- package/dist/runtime/app/pages/index.vue +34 -0
- package/dist/runtime/app/pages/index.vue.d.ts +3 -0
- package/dist/runtime/app/pages/queues/index.d.vue.ts +3 -0
- package/dist/runtime/app/pages/queues/index.vue +229 -0
- package/dist/runtime/app/pages/queues/index.vue.d.ts +3 -0
- package/dist/runtime/app/pages/queues/job.d.vue.ts +3 -0
- package/dist/runtime/app/pages/queues/job.vue +262 -0
- package/dist/runtime/app/pages/queues/job.vue.d.ts +3 -0
- package/dist/runtime/app/pages/queues/jobs.d.vue.ts +3 -0
- package/dist/runtime/app/pages/queues/jobs.vue +291 -0
- package/dist/runtime/app/pages/queues/jobs.vue.d.ts +3 -0
- package/dist/runtime/app/plugins/vueflow.client.d.ts +2 -0
- package/dist/runtime/app/plugins/vueflow.client.js +11 -0
- package/dist/types.d.mts +7 -0
- package/package.json +47 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<QueueNhealthComponentRouter
|
|
3
|
+
v-slot="{ component }"
|
|
4
|
+
:routes="routes"
|
|
5
|
+
base="p"
|
|
6
|
+
mode="query"
|
|
7
|
+
>
|
|
8
|
+
<QueueNhealthComponentShell
|
|
9
|
+
orientation="horizontal"
|
|
10
|
+
:items="navItems"
|
|
11
|
+
>
|
|
12
|
+
<component :is="component" />
|
|
13
|
+
</QueueNhealthComponentShell>
|
|
14
|
+
</QueueNhealthComponentRouter>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup>
|
|
18
|
+
import Queue from "./queues/index.vue";
|
|
19
|
+
import QueueJobs from "./queues/jobs.vue";
|
|
20
|
+
import QueueJob from "./queues/job.vue";
|
|
21
|
+
import QueueFlows from "./flows/index.vue";
|
|
22
|
+
const navItems = [
|
|
23
|
+
[
|
|
24
|
+
{ label: "Queues", icon: "i-lucide-app-window", path: "/" },
|
|
25
|
+
{ label: "Flows", icon: "i-lucide-git-branch", path: "/flows" }
|
|
26
|
+
]
|
|
27
|
+
];
|
|
28
|
+
const routes = {
|
|
29
|
+
"/": Queue,
|
|
30
|
+
"/queues/:name/jobs": QueueJobs,
|
|
31
|
+
"/queues/:name/jobs/:id": QueueJob,
|
|
32
|
+
"/flows": QueueFlows
|
|
33
|
+
};
|
|
34
|
+
</script>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="h-full flex flex-col overflow-hidden">
|
|
3
|
+
<!-- Header -->
|
|
4
|
+
<div class="border-b border-gray-200 dark:border-gray-800 px-6 py-3 shrink-0">
|
|
5
|
+
<div class="flex items-center justify-between">
|
|
6
|
+
<div class="flex items-center gap-4">
|
|
7
|
+
<h1 class="text-lg font-semibold">
|
|
8
|
+
Queues
|
|
9
|
+
</h1>
|
|
10
|
+
<div
|
|
11
|
+
v-if="isConnected"
|
|
12
|
+
class="flex items-center gap-1.5 text-xs text-emerald-600 dark:text-emerald-400"
|
|
13
|
+
>
|
|
14
|
+
<div class="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
|
|
15
|
+
<span>Live</span>
|
|
16
|
+
</div>
|
|
17
|
+
<div
|
|
18
|
+
v-else-if="isReconnecting"
|
|
19
|
+
class="flex items-center gap-1.5 text-xs text-amber-600 dark:text-amber-400"
|
|
20
|
+
>
|
|
21
|
+
<div class="w-2 h-2 rounded-full bg-amber-500 animate-pulse" />
|
|
22
|
+
<span>Reconnecting...</span>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="flex items-center gap-3">
|
|
26
|
+
<ClientOnly>
|
|
27
|
+
<UButton
|
|
28
|
+
icon="i-lucide-refresh-cw"
|
|
29
|
+
size="xs"
|
|
30
|
+
color="neutral"
|
|
31
|
+
variant="ghost"
|
|
32
|
+
:loading="status === 'pending'"
|
|
33
|
+
@click="refresh"
|
|
34
|
+
>
|
|
35
|
+
Refresh
|
|
36
|
+
</UButton>
|
|
37
|
+
</ClientOnly>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<!-- Main Content -->
|
|
43
|
+
<div class="flex-1 min-h-0 overflow-hidden flex flex-col">
|
|
44
|
+
<div
|
|
45
|
+
v-if="!queuesWithLive || queuesWithLive.length === 0"
|
|
46
|
+
class="flex-1 flex items-center justify-center text-sm text-gray-400"
|
|
47
|
+
>
|
|
48
|
+
No queues found
|
|
49
|
+
</div>
|
|
50
|
+
<template v-else>
|
|
51
|
+
<div class="flex-1 min-h-0 p-4 overflow-auto">
|
|
52
|
+
<UTable
|
|
53
|
+
ref="table"
|
|
54
|
+
v-model:pagination="pagination"
|
|
55
|
+
:data="queuesWithLive"
|
|
56
|
+
:columns="columns"
|
|
57
|
+
:loading="status === 'pending'"
|
|
58
|
+
:pagination-options="{
|
|
59
|
+
getPaginationRowModel: getPaginationRowModel()
|
|
60
|
+
}"
|
|
61
|
+
:ui="{
|
|
62
|
+
base: 'table-fixed border-separate border-spacing-0',
|
|
63
|
+
thead: '[&>tr]:bg-elevated/50 [&>tr]:after:content-none',
|
|
64
|
+
tbody: '[&>tr]:last:[&>td]:border-b-0',
|
|
65
|
+
th: 'py-2 first:rounded-l-lg last:rounded-r-lg border-y border-default first:border-l last:border-r',
|
|
66
|
+
td: 'border-b border-default',
|
|
67
|
+
separator: 'h-0'
|
|
68
|
+
}"
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div class="flex items-center justify-between gap-3 border-t border-default p-4 mt-4 shrink-0">
|
|
73
|
+
<div class="text-sm text-muted">
|
|
74
|
+
{{ queuesWithLive.length }} queue{{ queuesWithLive.length === 1 ? "" : "s" }}
|
|
75
|
+
</div>
|
|
76
|
+
<div class="flex items-center gap-1.5">
|
|
77
|
+
<UPagination
|
|
78
|
+
:default-page="(table?.tableApi?.getState().pagination.pageIndex || 0) + 1"
|
|
79
|
+
:items-per-page="table?.tableApi?.getState().pagination.pageSize"
|
|
80
|
+
:total="queuesWithLive.length"
|
|
81
|
+
@update:page="(p) => table?.tableApi?.setPageIndex(p - 1)"
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</template>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<!-- Config Details Slideover -->
|
|
89
|
+
<QueueConfigDetails
|
|
90
|
+
v-if="selectedQueueForConfig"
|
|
91
|
+
v-model:open="configDetailsOpen"
|
|
92
|
+
:queue-name="selectedQueueForConfig.name"
|
|
93
|
+
:queue-config="selectedQueueForConfig.config?.queue"
|
|
94
|
+
:worker-config="selectedQueueForConfig.config?.worker"
|
|
95
|
+
/>
|
|
96
|
+
</div>
|
|
97
|
+
</template>
|
|
98
|
+
|
|
99
|
+
<script setup>
|
|
100
|
+
import { ref, useTemplateRef, h } from "#imports";
|
|
101
|
+
import { getPaginationRowModel } from "@tanstack/table-core";
|
|
102
|
+
import { UTable, UButton, UPagination } from "#components";
|
|
103
|
+
import { useQueues } from "../../composables/useQueues";
|
|
104
|
+
import { useQueuesLive } from "../../composables/useQueuesLive";
|
|
105
|
+
import { useComponentRouter } from "../../composables/useComponentRouter";
|
|
106
|
+
import QueueConfigDetails from "../../components/QueueConfigDetails.vue";
|
|
107
|
+
const UBadgeComponent = resolveComponent("UBadge");
|
|
108
|
+
const UButtonComponent = resolveComponent("UButton");
|
|
109
|
+
const { queues, refresh, status } = useQueues();
|
|
110
|
+
const { queues: queuesWithLive, isConnected, isReconnecting } = useQueuesLive(queues);
|
|
111
|
+
const router = useComponentRouter();
|
|
112
|
+
const table = useTemplateRef("table");
|
|
113
|
+
const pagination = ref({
|
|
114
|
+
pageIndex: 0,
|
|
115
|
+
pageSize: 10
|
|
116
|
+
});
|
|
117
|
+
const configDetailsOpen = ref(false);
|
|
118
|
+
const selectedQueueForConfig = ref(null);
|
|
119
|
+
const openConfigDetails = (queue) => {
|
|
120
|
+
selectedQueueForConfig.value = queue;
|
|
121
|
+
configDetailsOpen.value = true;
|
|
122
|
+
};
|
|
123
|
+
const columns = [
|
|
124
|
+
{
|
|
125
|
+
accessorKey: "name",
|
|
126
|
+
header: "Queue Name",
|
|
127
|
+
cell: ({ row }) => {
|
|
128
|
+
return h("div", {
|
|
129
|
+
class: "font-medium text-highlighted cursor-pointer hover:underline",
|
|
130
|
+
onClick: () => router.push(`/queues/${row.original.name}/jobs`)
|
|
131
|
+
}, row.original.name);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
accessorKey: "counts.waiting",
|
|
136
|
+
header: "Waiting",
|
|
137
|
+
cell: ({ row }) => {
|
|
138
|
+
const count = row.original.counts.waiting;
|
|
139
|
+
return h("div", {
|
|
140
|
+
class: `flex items-center justify-center w-12 h-7 rounded text-sm font-medium ${count > 0 ? "bg-blue-50 dark:bg-blue-950/50 text-blue-600 dark:text-blue-400" : "bg-gray-50 dark:bg-gray-900/50 text-gray-400 dark:text-gray-600"}`
|
|
141
|
+
}, String(count));
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
accessorKey: "counts.active",
|
|
146
|
+
header: "Active",
|
|
147
|
+
cell: ({ row }) => {
|
|
148
|
+
const count = row.original.counts.active;
|
|
149
|
+
return h("div", {
|
|
150
|
+
class: `flex items-center justify-center w-12 h-7 rounded text-sm font-medium ${count > 0 ? "bg-amber-50 dark:bg-amber-950/50 text-amber-600 dark:text-amber-400" : "bg-gray-50 dark:bg-gray-900/50 text-gray-400 dark:text-gray-600"}`
|
|
151
|
+
}, String(count));
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
accessorKey: "counts.completed",
|
|
156
|
+
header: "Completed",
|
|
157
|
+
cell: ({ row }) => {
|
|
158
|
+
const count = row.original.counts.completed;
|
|
159
|
+
return h("div", {
|
|
160
|
+
class: `flex items-center justify-center w-12 h-7 rounded text-sm font-medium ${count > 0 ? "bg-emerald-50 dark:bg-emerald-950/50 text-emerald-600 dark:text-emerald-400" : "bg-gray-50 dark:bg-gray-900/50 text-gray-400 dark:text-gray-600"}`
|
|
161
|
+
}, String(count));
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
accessorKey: "counts.failed",
|
|
166
|
+
header: "Failed",
|
|
167
|
+
cell: ({ row }) => {
|
|
168
|
+
const count = row.original.counts.failed;
|
|
169
|
+
return h("div", {
|
|
170
|
+
class: `flex items-center justify-center w-12 h-7 rounded text-sm font-medium ${count > 0 ? "bg-red-50 dark:bg-red-950/50 text-red-600 dark:text-red-400" : "bg-gray-50 dark:bg-gray-900/50 text-gray-400 dark:text-gray-600"}`
|
|
171
|
+
}, String(count));
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
accessorKey: "counts.delayed",
|
|
176
|
+
header: "Delayed",
|
|
177
|
+
cell: ({ row }) => {
|
|
178
|
+
const count = row.original.counts.delayed;
|
|
179
|
+
return h("div", {
|
|
180
|
+
class: `flex items-center justify-center w-12 h-7 rounded text-sm font-medium ${count > 0 ? "bg-purple-50 dark:bg-purple-950/50 text-purple-600 dark:text-purple-400" : "bg-gray-50 dark:bg-gray-900/50 text-gray-400 dark:text-gray-600"}`
|
|
181
|
+
}, String(count));
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
accessorKey: "isPaused",
|
|
186
|
+
header: "Status",
|
|
187
|
+
cell: ({ row }) => {
|
|
188
|
+
return h(UBadgeComponent, {
|
|
189
|
+
label: row.original.isPaused ? "Paused" : "Running",
|
|
190
|
+
color: row.original.isPaused ? "warning" : "success",
|
|
191
|
+
variant: "subtle"
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: "config",
|
|
197
|
+
header: "Config",
|
|
198
|
+
cell: ({ row }) => {
|
|
199
|
+
return h(UButtonComponent, {
|
|
200
|
+
icon: "i-lucide-settings",
|
|
201
|
+
size: "xs",
|
|
202
|
+
color: "neutral",
|
|
203
|
+
variant: "ghost",
|
|
204
|
+
square: true,
|
|
205
|
+
title: "View configuration",
|
|
206
|
+
onClick: () => {
|
|
207
|
+
openConfigDetails(row.original);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: "actions",
|
|
214
|
+
header: "",
|
|
215
|
+
cell: ({ row }) => {
|
|
216
|
+
return h(UButtonComponent, {
|
|
217
|
+
icon: "i-lucide-arrow-right",
|
|
218
|
+
size: "xs",
|
|
219
|
+
color: "neutral",
|
|
220
|
+
variant: "ghost",
|
|
221
|
+
square: true,
|
|
222
|
+
onClick: () => {
|
|
223
|
+
router.push(`/queues/${row.original.name}/jobs`);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
];
|
|
229
|
+
</script>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="h-full flex flex-col overflow-hidden">
|
|
3
|
+
<!-- Header -->
|
|
4
|
+
<div class="border-b border-gray-200 dark:border-gray-800 px-6 py-3 shrink-0">
|
|
5
|
+
<div class="flex items-center justify-between">
|
|
6
|
+
<div class="flex items-center gap-4">
|
|
7
|
+
<UButton
|
|
8
|
+
icon="i-lucide-arrow-left"
|
|
9
|
+
size="xs"
|
|
10
|
+
color="neutral"
|
|
11
|
+
variant="ghost"
|
|
12
|
+
@click="back"
|
|
13
|
+
/>
|
|
14
|
+
<div>
|
|
15
|
+
<h1 class="text-lg font-semibold">
|
|
16
|
+
{{ job?.name || "Job Details" }}
|
|
17
|
+
</h1>
|
|
18
|
+
<p class="text-xs text-gray-500 font-mono">
|
|
19
|
+
{{ job?.id }}
|
|
20
|
+
</p>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="flex items-center gap-3">
|
|
24
|
+
<UBadge
|
|
25
|
+
v-if="job?.state"
|
|
26
|
+
:label="job.state"
|
|
27
|
+
:color="stateColor"
|
|
28
|
+
variant="subtle"
|
|
29
|
+
class="capitalize"
|
|
30
|
+
/>
|
|
31
|
+
<UButton
|
|
32
|
+
icon="i-lucide-refresh-cw"
|
|
33
|
+
size="xs"
|
|
34
|
+
color="neutral"
|
|
35
|
+
variant="ghost"
|
|
36
|
+
@click="() => refresh()"
|
|
37
|
+
>
|
|
38
|
+
Refresh
|
|
39
|
+
</UButton>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<!-- Main Content -->
|
|
45
|
+
<div class="flex-1 min-h-0 overflow-y-auto px-6 py-4">
|
|
46
|
+
<div
|
|
47
|
+
v-if="!job"
|
|
48
|
+
class="h-full flex items-center justify-center text-sm text-gray-400"
|
|
49
|
+
>
|
|
50
|
+
Job not found
|
|
51
|
+
</div>
|
|
52
|
+
<div
|
|
53
|
+
v-else
|
|
54
|
+
class="space-y-6"
|
|
55
|
+
>
|
|
56
|
+
<!-- Job Info Grid -->
|
|
57
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
58
|
+
<UCard>
|
|
59
|
+
<template #header>
|
|
60
|
+
<h2 class="text-sm font-semibold">
|
|
61
|
+
Job Info
|
|
62
|
+
</h2>
|
|
63
|
+
</template>
|
|
64
|
+
<div class="space-y-3">
|
|
65
|
+
<div>
|
|
66
|
+
<p class="text-xs text-gray-500">
|
|
67
|
+
ID
|
|
68
|
+
</p>
|
|
69
|
+
<p class="text-sm font-medium font-mono">
|
|
70
|
+
{{ job.id }}
|
|
71
|
+
</p>
|
|
72
|
+
</div>
|
|
73
|
+
<div>
|
|
74
|
+
<p class="text-xs text-gray-500">
|
|
75
|
+
Name
|
|
76
|
+
</p>
|
|
77
|
+
<p class="text-sm font-medium">
|
|
78
|
+
{{ job.name }}
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
<div>
|
|
82
|
+
<p class="text-xs text-gray-500">
|
|
83
|
+
State
|
|
84
|
+
</p>
|
|
85
|
+
<p class="text-sm font-medium capitalize">
|
|
86
|
+
{{ job.state || "unknown" }}
|
|
87
|
+
</p>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</UCard>
|
|
91
|
+
|
|
92
|
+
<UCard>
|
|
93
|
+
<template #header>
|
|
94
|
+
<h2 class="text-sm font-semibold">
|
|
95
|
+
Timing
|
|
96
|
+
</h2>
|
|
97
|
+
</template>
|
|
98
|
+
<div class="space-y-4">
|
|
99
|
+
<!-- Timestamps -->
|
|
100
|
+
<div class="grid grid-cols-1 gap-3">
|
|
101
|
+
<div class="flex items-center justify-between py-2 border-b border-gray-100 dark:border-gray-800">
|
|
102
|
+
<p class="text-xs text-gray-500">
|
|
103
|
+
Created
|
|
104
|
+
</p>
|
|
105
|
+
<p class="text-sm font-medium">
|
|
106
|
+
{{ formatDate(job.timestamp) }}
|
|
107
|
+
</p>
|
|
108
|
+
</div>
|
|
109
|
+
<div
|
|
110
|
+
v-if="job.processedOn"
|
|
111
|
+
class="flex items-center justify-between py-2 border-b border-gray-100 dark:border-gray-800"
|
|
112
|
+
>
|
|
113
|
+
<p class="text-xs text-gray-500">
|
|
114
|
+
Processed
|
|
115
|
+
</p>
|
|
116
|
+
<p class="text-sm font-medium">
|
|
117
|
+
{{ formatDate(job.processedOn) }}
|
|
118
|
+
</p>
|
|
119
|
+
</div>
|
|
120
|
+
<div
|
|
121
|
+
v-if="job.finishedOn"
|
|
122
|
+
class="flex items-center justify-between py-2"
|
|
123
|
+
>
|
|
124
|
+
<p class="text-xs text-gray-500">
|
|
125
|
+
Finished
|
|
126
|
+
</p>
|
|
127
|
+
<p class="text-sm font-medium">
|
|
128
|
+
{{ formatDate(job.finishedOn) }}
|
|
129
|
+
</p>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<!-- Durations -->
|
|
134
|
+
<div
|
|
135
|
+
v-if="waitDuration || executionDuration"
|
|
136
|
+
class="pt-2 mt-2 border-t border-gray-200 dark:border-gray-700"
|
|
137
|
+
>
|
|
138
|
+
<div class="grid grid-cols-2 gap-3">
|
|
139
|
+
<div
|
|
140
|
+
v-if="waitDuration"
|
|
141
|
+
class="text-center p-3 bg-gray-50 dark:bg-gray-800/50 rounded-lg"
|
|
142
|
+
>
|
|
143
|
+
<p class="text-xs text-gray-500 mb-1">
|
|
144
|
+
Wait Time
|
|
145
|
+
</p>
|
|
146
|
+
<p class="text-lg font-semibold text-blue-600 dark:text-blue-400">
|
|
147
|
+
{{ waitDuration }}
|
|
148
|
+
</p>
|
|
149
|
+
</div>
|
|
150
|
+
<div
|
|
151
|
+
v-if="executionDuration"
|
|
152
|
+
class="text-center p-3 bg-gray-50 dark:bg-gray-800/50 rounded-lg"
|
|
153
|
+
>
|
|
154
|
+
<p class="text-xs text-gray-500 mb-1">
|
|
155
|
+
{{ job.state === "active" ? "Running" : "Execution" }}
|
|
156
|
+
</p>
|
|
157
|
+
<p
|
|
158
|
+
class="text-lg font-semibold"
|
|
159
|
+
:class="job.state === 'active' ? 'text-amber-600 dark:text-amber-400' : 'text-emerald-600 dark:text-emerald-400'"
|
|
160
|
+
>
|
|
161
|
+
{{ executionDuration }}
|
|
162
|
+
</p>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</UCard>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<!-- Job Data -->
|
|
171
|
+
<UCard>
|
|
172
|
+
<template #header>
|
|
173
|
+
<h2 class="text-sm font-semibold">
|
|
174
|
+
Job Data
|
|
175
|
+
</h2>
|
|
176
|
+
</template>
|
|
177
|
+
<pre class="text-xs overflow-x-auto bg-gray-50 dark:bg-gray-900 p-4 rounded">{{ JSON.stringify(job.data, null, 2) }}</pre>
|
|
178
|
+
</UCard>
|
|
179
|
+
|
|
180
|
+
<!-- Return Value -->
|
|
181
|
+
<UCard v-if="job.returnvalue">
|
|
182
|
+
<template #header>
|
|
183
|
+
<h2 class="text-sm font-semibold">
|
|
184
|
+
Return Value
|
|
185
|
+
</h2>
|
|
186
|
+
</template>
|
|
187
|
+
<pre class="text-xs overflow-x-auto bg-gray-50 dark:bg-gray-900 p-4 rounded">{{ JSON.stringify(job.returnvalue, null, 2) }}</pre>
|
|
188
|
+
</UCard>
|
|
189
|
+
|
|
190
|
+
<!-- Error -->
|
|
191
|
+
<UCard v-if="job.failedReason">
|
|
192
|
+
<template #header>
|
|
193
|
+
<h2 class="text-sm font-semibold text-red-500">
|
|
194
|
+
Error
|
|
195
|
+
</h2>
|
|
196
|
+
</template>
|
|
197
|
+
<div class="text-sm text-red-600 dark:text-red-400">
|
|
198
|
+
{{ job.failedReason }}
|
|
199
|
+
</div>
|
|
200
|
+
</UCard>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
</template>
|
|
205
|
+
|
|
206
|
+
<script setup>
|
|
207
|
+
import { computed, useFetch } from "#imports";
|
|
208
|
+
import { UCard, UButton, UBadge } from "#components";
|
|
209
|
+
import { useComponentRouter } from "../../composables/useComponentRouter";
|
|
210
|
+
const router = useComponentRouter();
|
|
211
|
+
const queueName = computed(() => router.route.value?.params?.name || "");
|
|
212
|
+
const jobId = computed(() => router.route.value?.params?.id || "");
|
|
213
|
+
const { data: job, refresh } = await useFetch(
|
|
214
|
+
() => `/api/_queues/${encodeURIComponent(queueName.value)}/job/${encodeURIComponent(jobId.value)}`,
|
|
215
|
+
{
|
|
216
|
+
server: false
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
const stateColor = computed(() => {
|
|
220
|
+
const colorMap = {
|
|
221
|
+
waiting: "info",
|
|
222
|
+
active: "warning",
|
|
223
|
+
completed: "success",
|
|
224
|
+
failed: "error",
|
|
225
|
+
delayed: "secondary",
|
|
226
|
+
paused: "warning"
|
|
227
|
+
};
|
|
228
|
+
return colorMap[job.value?.state || ""] || "neutral";
|
|
229
|
+
});
|
|
230
|
+
const waitDuration = computed(() => {
|
|
231
|
+
if (!job.value?.timestamp || !job.value?.processedOn) return null;
|
|
232
|
+
const waitMs = job.value.processedOn - job.value.timestamp;
|
|
233
|
+
return formatDuration(waitMs);
|
|
234
|
+
});
|
|
235
|
+
const executionDuration = computed(() => {
|
|
236
|
+
if (!job.value?.processedOn) return null;
|
|
237
|
+
const endTime = job.value.finishedOn || Date.now();
|
|
238
|
+
const execMs = endTime - job.value.processedOn;
|
|
239
|
+
return formatDuration(execMs);
|
|
240
|
+
});
|
|
241
|
+
const back = () => {
|
|
242
|
+
router.push(`/queues/${queueName.value}/jobs`);
|
|
243
|
+
};
|
|
244
|
+
const formatDuration = (ms) => {
|
|
245
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
246
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(2)}s`;
|
|
247
|
+
if (ms < 36e5) return `${(ms / 6e4).toFixed(2)}m`;
|
|
248
|
+
return `${(ms / 36e5).toFixed(2)}h`;
|
|
249
|
+
};
|
|
250
|
+
const formatDate = (timestamp) => {
|
|
251
|
+
if (!timestamp) return "-";
|
|
252
|
+
return new Date(timestamp).toLocaleString("de-DE", {
|
|
253
|
+
timeZone: "Europe/Berlin",
|
|
254
|
+
day: "2-digit",
|
|
255
|
+
month: "2-digit",
|
|
256
|
+
year: "numeric",
|
|
257
|
+
hour: "2-digit",
|
|
258
|
+
minute: "2-digit",
|
|
259
|
+
second: "2-digit"
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
</script>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|