@nvent-addon/app 0.5.15 → 1.0.0-alpha.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.
Files changed (95) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +3 -2
  3. package/dist/runtime/app/components/DashboardCard.d.vue.ts +1 -1
  4. package/dist/runtime/app/components/DashboardCard.vue.d.ts +1 -1
  5. package/dist/runtime/app/composables/useWorkers.d.ts +57 -0
  6. package/dist/runtime/app/composables/useWorkers.js +42 -0
  7. package/dist/runtime/app/pages/dashboard.vue +1 -654
  8. package/dist/runtime/app/pages/index.vue +25 -41
  9. package/dist/runtime/app/pages/workers.vue +458 -0
  10. package/dist/runtime/server/api/_workers/index.get.d.ts +8 -0
  11. package/dist/runtime/server/api/_workers/index.get.js +14 -0
  12. package/package.json +12 -11
  13. package/dist/runtime/app/components/ComponentRouter.d.vue.ts +0 -46
  14. package/dist/runtime/app/components/ComponentRouter.vue +0 -26
  15. package/dist/runtime/app/components/ComponentRouter.vue.d.ts +0 -46
  16. package/dist/runtime/app/components/ComponentShell.d.vue.ts +0 -23
  17. package/dist/runtime/app/components/ComponentShell.vue +0 -97
  18. package/dist/runtime/app/components/ComponentShell.vue.d.ts +0 -23
  19. package/dist/runtime/app/components/ConfirmDialog.d.vue.ts +0 -33
  20. package/dist/runtime/app/components/ConfirmDialog.vue +0 -120
  21. package/dist/runtime/app/components/ConfirmDialog.vue.d.ts +0 -33
  22. package/dist/runtime/app/composables/useComponentRouter.d.ts +0 -46
  23. package/dist/runtime/app/composables/useComponentRouter.js +0 -248
  24. package/dist/runtime/app/pages/flows/[name].vue +0 -750
  25. package/dist/runtime/app/pages/flows/index.d.vue.ts +0 -3
  26. package/dist/runtime/app/pages/flows/index.vue +0 -381
  27. package/dist/runtime/app/pages/flows/index.vue.d.ts +0 -3
  28. package/dist/runtime/app/pages/queues/index.d.vue.ts +0 -3
  29. package/dist/runtime/app/pages/queues/index.vue +0 -236
  30. package/dist/runtime/app/pages/queues/index.vue.d.ts +0 -3
  31. package/dist/runtime/app/pages/queues/job.d.vue.ts +0 -3
  32. package/dist/runtime/app/pages/queues/job.vue +0 -261
  33. package/dist/runtime/app/pages/queues/job.vue.d.ts +0 -3
  34. package/dist/runtime/app/pages/queues/jobs.d.vue.ts +0 -3
  35. package/dist/runtime/app/pages/queues/jobs.vue +0 -595
  36. package/dist/runtime/app/pages/queues/jobs.vue.d.ts +0 -3
  37. package/dist/runtime/app/pages/settings/scheduler.d.vue.ts +0 -3
  38. package/dist/runtime/app/pages/settings/scheduler.vue +0 -310
  39. package/dist/runtime/app/pages/settings/scheduler.vue.d.ts +0 -3
  40. package/dist/runtime/app/pages/triggers/[name]/edit.d.vue.ts +0 -3
  41. package/dist/runtime/app/pages/triggers/[name]/edit.vue +0 -429
  42. package/dist/runtime/app/pages/triggers/[name]/edit.vue.d.ts +0 -3
  43. package/dist/runtime/app/pages/triggers/[name].d.vue.ts +0 -3
  44. package/dist/runtime/app/pages/triggers/[name].vue +0 -870
  45. package/dist/runtime/app/pages/triggers/[name].vue.d.ts +0 -3
  46. package/dist/runtime/app/pages/triggers/index.d.vue.ts +0 -3
  47. package/dist/runtime/app/pages/triggers/index.vue +0 -525
  48. package/dist/runtime/app/pages/triggers/index.vue.d.ts +0 -3
  49. package/dist/runtime/app/pages/triggers/new.d.vue.ts +0 -3
  50. package/dist/runtime/app/pages/triggers/new.vue +0 -610
  51. package/dist/runtime/app/pages/triggers/new.vue.d.ts +0 -3
  52. package/dist/runtime/server/api/_flows/[name]/clear-history.delete.d.ts +0 -10
  53. package/dist/runtime/server/api/_flows/[name]/clear-history.delete.js +0 -49
  54. package/dist/runtime/server/api/_flows/[name]/runs/[runId]/cancel.post.d.ts +0 -2
  55. package/dist/runtime/server/api/_flows/[name]/runs/[runId]/cancel.post.js +0 -21
  56. package/dist/runtime/server/api/_flows/[name]/runs/[runId]/restart.post.d.ts +0 -2
  57. package/dist/runtime/server/api/_flows/[name]/runs/[runId]/restart.post.js +0 -21
  58. package/dist/runtime/server/api/_flows/[name]/runs.get.d.ts +0 -17
  59. package/dist/runtime/server/api/_flows/[name]/runs.get.js +0 -64
  60. package/dist/runtime/server/api/_flows/[name]/start.post.d.ts +0 -2
  61. package/dist/runtime/server/api/_flows/[name]/start.post.js +0 -9
  62. package/dist/runtime/server/api/_flows/index.get.d.ts +0 -7
  63. package/dist/runtime/server/api/_flows/index.get.js +0 -5
  64. package/dist/runtime/server/api/_flows/recent-runs.get.d.ts +0 -15
  65. package/dist/runtime/server/api/_flows/recent-runs.get.js +0 -67
  66. package/dist/runtime/server/api/_flows/ws.d.ts +0 -80
  67. package/dist/runtime/server/api/_flows/ws.js +0 -309
  68. package/dist/runtime/server/api/_queues/[name]/job/[id].get.d.ts +0 -2
  69. package/dist/runtime/server/api/_queues/[name]/job/[id].get.js +0 -14
  70. package/dist/runtime/server/api/_queues/[name]/job/index.get.d.ts +0 -2
  71. package/dist/runtime/server/api/_queues/[name]/job/index.get.js +0 -39
  72. package/dist/runtime/server/api/_queues/index.get.d.ts +0 -2
  73. package/dist/runtime/server/api/_queues/index.get.js +0 -106
  74. package/dist/runtime/server/api/_queues/ws.d.ts +0 -48
  75. package/dist/runtime/server/api/_queues/ws.js +0 -215
  76. package/dist/runtime/server/api/_scheduler/jobs.get.d.ts +0 -19
  77. package/dist/runtime/server/api/_scheduler/jobs.get.js +0 -36
  78. package/dist/runtime/server/api/_triggers/[name]/events.get.d.ts +0 -6
  79. package/dist/runtime/server/api/_triggers/[name]/events.get.js +0 -43
  80. package/dist/runtime/server/api/_triggers/[name]/index.get.d.ts +0 -6
  81. package/dist/runtime/server/api/_triggers/[name]/index.get.js +0 -76
  82. package/dist/runtime/server/api/_triggers/[name].delete.d.ts +0 -7
  83. package/dist/runtime/server/api/_triggers/[name].delete.js +0 -37
  84. package/dist/runtime/server/api/_triggers/[name].patch.d.ts +0 -7
  85. package/dist/runtime/server/api/_triggers/[name].patch.js +0 -117
  86. package/dist/runtime/server/api/_triggers/index.get.d.ts +0 -6
  87. package/dist/runtime/server/api/_triggers/index.get.js +0 -44
  88. package/dist/runtime/server/api/_triggers/index.post.d.ts +0 -7
  89. package/dist/runtime/server/api/_triggers/index.post.js +0 -124
  90. package/dist/runtime/server/api/_triggers/stats.get.d.ts +0 -6
  91. package/dist/runtime/server/api/_triggers/stats.get.js +0 -41
  92. package/dist/runtime/server/api/_triggers/ws.d.ts +0 -74
  93. package/dist/runtime/server/api/_triggers/ws.js +0 -315
  94. /package/dist/runtime/app/pages/{flows/[name].d.vue.ts → workers.d.vue.ts} +0 -0
  95. /package/dist/runtime/app/pages/{flows/[name].vue.d.ts → workers.vue.d.ts} +0 -0
@@ -1,750 +0,0 @@
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-3">
7
- <UButton
8
- icon="i-lucide-arrow-left"
9
- size="xs"
10
- color="neutral"
11
- variant="ghost"
12
- square
13
- @click="goBack"
14
- />
15
- <div>
16
- <h1 class="text-lg font-semibold flex items-center gap-2">
17
- <UIcon
18
- name="i-lucide-git-branch"
19
- class="w-5 h-5 text-blue-500"
20
- />
21
- <span>{{ selectedFlow }}</span>
22
- </h1>
23
- <div
24
- v-if="selectedFlowMeta"
25
- class="flex items-center gap-2 mt-1"
26
- >
27
- <UBadge
28
- v-if="selectedFlowMeta.hasAwait"
29
- label="await"
30
- color="secondary"
31
- variant="subtle"
32
- size="xs"
33
- />
34
- <UBadge
35
- v-if="selectedFlowMeta.stepCount"
36
- :label="`${selectedFlowMeta.stepCount} steps`"
37
- color="primary"
38
- variant="subtle"
39
- size="xs"
40
- />
41
- <UBadge
42
- v-if="selectedFlowMeta.levelCount"
43
- :label="`${selectedFlowMeta.levelCount} levels`"
44
- color="neutral"
45
- variant="subtle"
46
- size="xs"
47
- />
48
- </div>
49
- </div>
50
- </div>
51
- <div class="flex items-center gap-3">
52
- <!-- Flow actions can go here -->
53
- </div>
54
- </div>
55
- </div>
56
-
57
- <!-- Main Content -->
58
- <div class="flex-1 min-h-0 overflow-hidden">
59
- <div class="h-full flex gap-px bg-gray-200 dark:bg-gray-800">
60
- <!-- Runs List -->
61
- <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">
62
- <div class="px-4 py-3 min-h-[49px] border-b border-gray-200 dark:border-gray-800 flex items-center justify-between shrink-0">
63
- <h2 class="text-sm font-medium text-gray-900 dark:text-gray-100">
64
- Runs
65
- </h2>
66
- <div class="flex items-center gap-2">
67
- <UButton
68
- v-if="selectedFlow"
69
- icon="i-lucide-play"
70
- size="xs"
71
- color="primary"
72
- variant="soft"
73
- @click="openStartFlowModal"
74
- >
75
- Start
76
- </UButton>
77
- <div
78
- v-if="selectedFlow"
79
- class="flex items-center gap-2 text-xs text-gray-500"
80
- >
81
- <UIcon
82
- name="i-lucide-list"
83
- class="w-3.5 h-3.5"
84
- />
85
- <span>{{ totalRuns }} run{{ totalRuns === 1 ? "" : "s" }}</span>
86
- </div>
87
- <UDropdownMenu
88
- v-if="selectedFlow"
89
- :items="flowActionsItems"
90
- :ui="{ content: 'min-w-48' }"
91
- >
92
- <UButton
93
- icon="i-lucide-more-vertical"
94
- size="xs"
95
- color="neutral"
96
- variant="ghost"
97
- square
98
- />
99
- </UDropdownMenu>
100
- </div>
101
- </div>
102
- <div
103
- v-if="!selectedFlow"
104
- class="flex-1 overflow-y-auto min-h-0"
105
- >
106
- <div class="h-full flex items-center justify-center text-sm text-gray-400 px-4 text-center">
107
- Select a flow to view runs
108
- </div>
109
- </div>
110
- <div
111
- v-else-if="!runs || runs.length === 0"
112
- class="flex-1 overflow-y-auto min-h-0"
113
- >
114
- <div class="h-full flex items-center justify-center text-sm text-gray-400">
115
- <ClientOnly>
116
- <div class="text-center">
117
- <div v-if="loadingRuns">
118
- Loading runs...
119
- </div>
120
- <div v-else>
121
- No runs yet
122
- </div>
123
- </div>
124
- <template #fallback>
125
- <div class="text-center">
126
- No runs yet
127
- </div>
128
- </template>
129
- </ClientOnly>
130
- </div>
131
- </div>
132
- <div
133
- v-else
134
- class="flex-1 min-h-0 flex flex-col overflow-hidden"
135
- >
136
- <div class="flex-1 min-h-0 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800">
137
- <SelectableListItem
138
- v-for="r in runs"
139
- :key="r.id"
140
- :selected="selectedRunId === r.id"
141
- :icon="getRunStatusIcon(r.status)"
142
- :icon-class="getRunStatusIconClass(r.status)"
143
- :subtitle="truncateId(r.id)"
144
- :meta="formatTime(r.createdAt)"
145
- @click="selectRun(r.id)"
146
- >
147
- <template #title>
148
- Run {{ r.id?.substring(0, 8) }}
149
- </template>
150
- <template #badge>
151
- <FlowRunStatusBadge
152
- :is-running="r.status === 'running'"
153
- :is-completed="r.status === 'completed'"
154
- :is-failed="r.status === 'failed'"
155
- :is-canceled="r.status === 'canceled'"
156
- :is-stalled="r.status === 'stalled'"
157
- :is-awaiting="r.status === 'awaiting'"
158
- />
159
- </template>
160
- <template #meta>
161
- <span>{{ formatTime(r.createdAt) }}</span>
162
- <span
163
- v-if="r.stepCount > 0"
164
- class="flex items-center gap-1"
165
- >
166
- <UIcon
167
- name="i-lucide-list-checks"
168
- class="w-3 h-3"
169
- />
170
- {{ r.completedSteps }}/{{ r.stepCount }}
171
- </span>
172
- <span
173
- v-if="r.completedAt && r.startedAt"
174
- class="flex items-center gap-1"
175
- >
176
- <UIcon
177
- name="i-lucide-timer"
178
- class="w-3 h-3"
179
- />
180
- {{ formatDuration(r.startedAt, r.completedAt) }}
181
- </span>
182
- </template>
183
- </SelectableListItem>
184
-
185
- <!-- Loading indicator -->
186
- <div
187
- v-if="loadingRuns"
188
- class="px-4 py-3 text-center text-xs text-gray-400"
189
- >
190
- <UIcon
191
- name="i-lucide-loader-2"
192
- class="w-4 h-4 animate-spin inline-block"
193
- />
194
- <span class="ml-2">Loading runs...</span>
195
- </div>
196
- </div>
197
-
198
- <!-- Pagination Footer -->
199
- <div
200
- v-if="totalRuns > runsPerPage"
201
- class="border-t border-gray-200 dark:border-gray-800 px-4 py-3 flex items-center justify-center shrink-0"
202
- >
203
- <UPagination
204
- v-model:page="currentPage"
205
- :items-per-page="runsPerPage"
206
- :total="totalRuns"
207
- size="xs"
208
- />
209
- </div>
210
- </div>
211
- </div>
212
-
213
- <!-- Main Content Area with Tabs -->
214
- <div class="flex-1 min-w-0 bg-white dark:bg-gray-950 flex flex-col min-h-0 overflow-hidden">
215
- <div class="px-4 py-2.5 border-b border-gray-200 dark:border-gray-800 shrink-0">
216
- <div class="flex items-center justify-between">
217
- <UTabs
218
- v-model="mainTab"
219
- :items="mainTabs"
220
- size="xs"
221
- :ui="{
222
- root: 'gap-0',
223
- trigger: 'px-2 py-0.5'
224
- }"
225
- />
226
- <div class="flex items-center gap-2">
227
- <span
228
- v-if="selectedRunId"
229
- class="text-xs text-gray-500 flex items-center gap-2"
230
- >
231
- <span>Run: {{ selectedRunId.substring(0, 8) }}...</span>
232
- <div
233
- v-if="isReconnecting || isConnected && flowState.isRunning.value"
234
- class="flex items-center gap-1.5"
235
- >
236
- <div
237
- class="w-1.5 h-1.5 rounded-full"
238
- :class="isReconnecting ? 'bg-amber-500 animate-pulse' : 'bg-emerald-500 animate-pulse'"
239
- />
240
- <span>{{ isReconnecting ? "Reconnecting" : "Live" }}</span>
241
- </div>
242
- </span>
243
- <span
244
- v-else-if="selectedFlow"
245
- class="text-xs font-mono text-gray-500"
246
- >
247
- {{ selectedFlow }}
248
- </span>
249
- </div>
250
- </div>
251
- </div>
252
- <div class="flex-1 min-h-0">
253
- <!-- Diagram Tab -->
254
- <div
255
- v-if="mainTab === 'diagram'"
256
- class="h-full"
257
- >
258
- <div
259
- v-if="!selectedFlow"
260
- class="h-full flex items-center justify-center text-sm text-gray-400"
261
- >
262
- Select a flow to view diagram
263
- </div>
264
- <FlowDiagram
265
- v-else
266
- :flow="selectedFlowMeta"
267
- :show-controls="true"
268
- :show-background="true"
269
- :step-states="diagramStepStates"
270
- :flow-status="runSnapshot.status"
271
- height-class="h-full"
272
- @node-action="handleNodeAction"
273
- />
274
- </div>
275
-
276
- <!-- Timeline Tab -->
277
- <div
278
- v-else-if="mainTab === 'timeline'"
279
- class="h-full flex gap-px bg-gray-200 dark:bg-gray-800"
280
- >
281
- <!-- Left: Overview -->
282
- <div class="flex-1 min-w-0 max-w-[50%] bg-white dark:bg-gray-950 flex flex-col min-h-0 overflow-hidden">
283
- <div class="flex-1 overflow-y-auto min-h-0">
284
- <FlowRunOverview
285
- :run-status="runSnapshot.status"
286
- :started-at="runSnapshot.startedAt"
287
- :completed-at="runSnapshot.completedAt"
288
- :steps="enhancedStepList"
289
- :flow-name="selectedFlow || void 0"
290
- :run-id="selectedRunId || void 0"
291
- :trigger-name="flowState.state.value.meta?.triggerName"
292
- :trigger-type="flowState.state.value.meta?.triggerType"
293
- :flow-def="selectedFlowDef"
294
- :stall-timeout="runSnapshot.stallTimeout"
295
- @select-step="handleSelectStep"
296
- @cancel-flow="handleCancelFlow"
297
- @restart-flow="handleRestartFlow"
298
- />
299
- </div>
300
- </div>
301
-
302
- <!-- Right: Combined Logs & Events -->
303
- <div class="flex-1 min-w-0 max-w-[50%] bg-white dark:bg-gray-950 flex flex-col min-h-0 overflow-hidden">
304
- <FlowRunTimeline
305
- :events="timeline"
306
- :logs="filteredLogs"
307
- :is-live="isConnected"
308
- @export="exportTimelineJson"
309
- />
310
- </div>
311
- </div>
312
- </div>
313
- </div>
314
- </div>
315
- </div>
316
-
317
- <!-- Start Flow Modal -->
318
- <UModal v-model:open="startFlowModalOpen">
319
- <template #header>
320
- <div class="flex items-center justify-between">
321
- <div>
322
- <h3 class="text-lg font-semibold">
323
- Start Flow Run
324
- </h3>
325
- <p class="text-sm text-gray-500 mt-1">
326
- {{ selectedFlow }}
327
- </p>
328
- </div>
329
- </div>
330
- </template>
331
- <template #body>
332
- <div class="space-y-4">
333
- <div>
334
- <label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">
335
- Input Data (JSON)
336
- </label>
337
- <UTextarea
338
- v-model="flowInputJson"
339
- :rows="12"
340
- placeholder="{\n &quot;key&quot;: &quot;value&quot;\n}"
341
- class="w-full font-mono text-sm"
342
- />
343
- <p
344
- v-if="jsonError"
345
- class="text-xs text-red-500 mt-2"
346
- >
347
- {{ jsonError }}
348
- </p>
349
- </div>
350
- </div>
351
- </template>
352
- <template #footer>
353
- <div class="flex justify-end gap-2">
354
- <UButton
355
- color="neutral"
356
- variant="ghost"
357
- @click="startFlowModalOpen = false"
358
- >
359
- Cancel
360
- </UButton>
361
- <UButton
362
- color="primary"
363
- :loading="startingFlow"
364
- :disabled="!!jsonError"
365
- @click="startFlowRun"
366
- >
367
- Start Flow
368
- </UButton>
369
- </div>
370
- </template>
371
- </UModal>
372
-
373
- <!-- Confirm Dialog -->
374
- <ConfirmDialog
375
- v-model:open="confirmDialogOpen"
376
- :title="confirmDialogConfig.title"
377
- :description="confirmDialogConfig.description"
378
- :items="confirmDialogConfig.items"
379
- :warning="confirmDialogConfig.warning"
380
- :loading="clearingHistory"
381
- confirm-label="Clear History"
382
- confirm-color="error"
383
- icon="i-lucide-trash-2"
384
- icon-color="error"
385
- @confirm="confirmDialogConfig.onConfirm"
386
- />
387
- </div>
388
- </template>
389
-
390
- <script setup>
391
- import { ref, computed, watch } from "#imports";
392
- import FlowDiagram from "../../components/flow/Diagram.vue";
393
- import FlowRunOverview from "../../components/flow/RunOverview.vue";
394
- import FlowRunTimeline from "../../components/flow/RunTimeline.vue";
395
- import FlowRunStatusBadge from "../../components/flow/RunStatusBadge.vue";
396
- import ConfirmDialog from "../../components/ConfirmDialog.vue";
397
- import SelectableListItem from "../../components/SelectableListItem.vue";
398
- import { useRoute, useRouter } from "#app";
399
- import { useAnalyzedFlows } from "../../composables/useAnalyzedFlows";
400
- import { useFlowRuns } from "../../composables/useFlowRuns";
401
- import { useFlowRunTimeline } from "../../composables/useFlowRunTimeline";
402
- import { useFlowRunsUpdates } from "../../composables/useFlowRunsUpdates";
403
- import { useComponentRouter } from "../../composables/useComponentRouter";
404
- const componentRouter = useComponentRouter();
405
- const router = useRouter();
406
- const route = useRoute();
407
- const selectedFlow = computed(() => {
408
- const path = componentRouter.route.value?.path || "";
409
- const match = path.match(/\/flows\/([^/]+)/);
410
- return match && match[1] ? decodeURIComponent(match[1]) : null;
411
- });
412
- const goBack = () => {
413
- componentRouter.push("/flows");
414
- };
415
- const selectedRunId = ref("");
416
- const mainTab = ref("diagram");
417
- const mainTabs = computed(() => [
418
- { label: "Diagram", value: "diagram", icon: "i-lucide-git-branch" },
419
- {
420
- label: "Timeline",
421
- value: "timeline",
422
- icon: "i-lucide-activity",
423
- disabled: !selectedRunId.value
424
- }
425
- ]);
426
- watch(selectedRunId, (newRunId, oldRunId) => {
427
- if (newRunId && newRunId !== oldRunId) {
428
- mainTab.value = "timeline";
429
- } else if (!newRunId) {
430
- mainTab.value = "diagram";
431
- }
432
- });
433
- const flows = useAnalyzedFlows();
434
- const selectedFlowDef = computed(() => {
435
- if (!selectedFlow.value) return null;
436
- return flows.value.find((f) => f.id === selectedFlow.value);
437
- });
438
- const selectedFlowRef = computed(() => selectedFlow.value || "");
439
- const selectedRunIdRef = computed(() => selectedRunId.value || "");
440
- const runsPerPage = 20;
441
- const currentPage = computed({
442
- get: () => {
443
- const page = route.query.page;
444
- return page ? Number.parseInt(page, 10) : 1;
445
- },
446
- set: (value) => {
447
- router.replace({
448
- query: {
449
- ...route.query,
450
- page: value > 1 ? value.toString() : void 0
451
- }
452
- });
453
- }
454
- });
455
- const runsQueryOptions = computed(() => ({
456
- limit: runsPerPage,
457
- offset: (currentPage.value - 1) * runsPerPage
458
- }));
459
- const {
460
- runs: runsResponse,
461
- refresh: refreshRuns,
462
- status: runsStatus
463
- } = useFlowRuns(selectedFlowRef, runsQueryOptions);
464
- const runs = computed(() => runsResponse.value?.items || []);
465
- const totalRuns = computed(() => runsResponse.value?.total || 0);
466
- const loadingRuns = computed(() => runsStatus.value === "pending");
467
- const { flowState, isConnected, isReconnecting } = useFlowRunTimeline(selectedFlowRef, selectedRunIdRef);
468
- const { shouldRefreshRuns, resetRefreshFlag } = useFlowRunsUpdates(selectedFlowRef);
469
- watch(shouldRefreshRuns, async (shouldRefresh) => {
470
- if (shouldRefresh) {
471
- await refreshRuns();
472
- resetRefreshFlag();
473
- }
474
- });
475
- const startFlowModalOpen = ref(false);
476
- const flowInputJson = ref("{}");
477
- const jsonError = ref("");
478
- const startingFlow = ref(false);
479
- const clearingHistory = ref(false);
480
- const confirmDialogOpen = ref(false);
481
- const confirmDialogConfig = ref({
482
- title: "",
483
- description: "",
484
- items: [],
485
- warning: "",
486
- onConfirm: () => {
487
- }
488
- });
489
- const flowActionsItems = computed(() => [[
490
- {
491
- label: "Clear History",
492
- icon: "i-lucide-trash-2",
493
- disabled: clearingHistory.value,
494
- onSelect: () => confirmClearHistory()
495
- }
496
- ]]);
497
- watch(flowInputJson, (value) => {
498
- try {
499
- JSON.parse(value);
500
- jsonError.value = "";
501
- } catch (err) {
502
- jsonError.value = err instanceof Error ? err.message : "Invalid JSON";
503
- }
504
- });
505
- const formatTime = (timestamp) => {
506
- const date = new Date(timestamp);
507
- const now = /* @__PURE__ */ new Date();
508
- const diff = now.getTime() - date.getTime();
509
- const seconds = Math.floor(diff / 1e3);
510
- const minutes = Math.floor(seconds / 60);
511
- const hours = Math.floor(minutes / 60);
512
- const days = Math.floor(hours / 24);
513
- if (days > 0) return `${days}d ago`;
514
- if (hours > 0) return `${hours}h ago`;
515
- if (minutes > 0) return `${minutes}m ago`;
516
- if (seconds > 10) return `${seconds}s ago`;
517
- return "just now";
518
- };
519
- const formatDuration = (start, end) => {
520
- const startTime = new Date(start).getTime();
521
- const endTime = new Date(end).getTime();
522
- const diff = endTime - startTime;
523
- const seconds = Math.floor(diff / 1e3);
524
- const minutes = Math.floor(seconds / 60);
525
- const hours = Math.floor(minutes / 60);
526
- if (hours > 0) {
527
- const remainingMinutes = minutes % 60;
528
- return `${hours}h ${remainingMinutes}m`;
529
- }
530
- if (minutes > 0) {
531
- const remainingSeconds = seconds % 60;
532
- return `${minutes}m ${remainingSeconds}s`;
533
- }
534
- return `${seconds}s`;
535
- };
536
- const truncateId = (id) => {
537
- if (!id || id.length <= 16) return id;
538
- return `${id.substring(0, 8)}...${id.substring(id.length - 8)}`;
539
- };
540
- const getRunStatusIcon = (status) => {
541
- switch (status) {
542
- case "running":
543
- return "i-lucide-loader-2";
544
- case "completed":
545
- return "i-lucide-check-circle";
546
- case "failed":
547
- return "i-lucide-x-circle";
548
- case "canceled":
549
- return "i-lucide-ban";
550
- case "stalled":
551
- return "i-lucide-alert-triangle";
552
- case "awaiting":
553
- return "i-lucide-pause-circle";
554
- default:
555
- return "i-lucide-circle";
556
- }
557
- };
558
- const getRunStatusIconClass = (status) => {
559
- switch (status) {
560
- case "running":
561
- return "text-blue-500 animate-spin";
562
- case "completed":
563
- return "text-emerald-500";
564
- case "failed":
565
- return "text-red-500";
566
- case "canceled":
567
- return "text-gray-500";
568
- case "stalled":
569
- return "text-amber-500";
570
- case "awaiting":
571
- return "text-purple-500";
572
- default:
573
- return "text-gray-400";
574
- }
575
- };
576
- const runSnapshot = computed(() => {
577
- const state = flowState.state.value;
578
- const flowMeta = selectedFlowMeta.value;
579
- return {
580
- status: state.status,
581
- startedAt: state.startedAt,
582
- completedAt: state.completedAt,
583
- logsCount: state.logs.length,
584
- lastLogLevel: state.logs.length > 0 ? state.logs[state.logs.length - 1]?.level : void 0,
585
- // Use stallTimeout from event data if available, otherwise fall back to static flow definition
586
- stallTimeout: state.meta?.stallTimeout || flowMeta?.analyzed?.stallTimeout
587
- };
588
- });
589
- const selectedStepKey = ref(null);
590
- const timeline = computed(() => {
591
- const events = flowState.events.value;
592
- if (!selectedStepKey.value) return events;
593
- return events.filter((e) => e.stepName === selectedStepKey.value);
594
- });
595
- const filteredLogs = computed(() => {
596
- const logs = flowState.state.value.logs;
597
- if (!selectedStepKey.value) return logs;
598
- return logs.filter((log) => log.stepName === selectedStepKey.value);
599
- });
600
- const selectedFlowMeta = computed(() => {
601
- const id = selectedFlow.value;
602
- if (!id) return null;
603
- return (flows.value || []).find((f) => f?.id === id) || null;
604
- });
605
- const enhancedStepList = computed(() => {
606
- const steps = flowState.stepList.value;
607
- const flowMeta = selectedFlowMeta.value;
608
- if (!flowMeta?.analyzed?.steps) return steps;
609
- const stepTimeoutMap = /* @__PURE__ */ new Map();
610
- for (const [stepName, analyzedStep] of Object.entries(flowMeta.analyzed.steps)) {
611
- if (analyzedStep.stepTimeout !== void 0) {
612
- stepTimeoutMap.set(stepName, analyzedStep.stepTimeout);
613
- }
614
- }
615
- return steps.map((step) => {
616
- const baseStepName = step.key.includes(":await-") ? step.key.split(":await-")[0] : step.key;
617
- const staticTimeout = stepTimeoutMap.get(baseStepName);
618
- if (staticTimeout !== void 0) {
619
- return { ...step, stepTimeout: staticTimeout };
620
- }
621
- return step;
622
- });
623
- });
624
- const handleSelectStep = (stepKey) => {
625
- selectedStepKey.value = stepKey;
626
- };
627
- const handleCancelFlow = async () => {
628
- if (!selectedFlow.value || !selectedRunId.value) return;
629
- try {
630
- await $fetch(`/api/_flows/${selectedFlow.value}/runs/${selectedRunId.value}/cancel`, {
631
- method: "POST"
632
- });
633
- } catch (error) {
634
- console.error("Failed to cancel flow:", error);
635
- }
636
- };
637
- const handleRestartFlow = async () => {
638
- if (!selectedFlow.value || !selectedRunId.value) return;
639
- try {
640
- const result = await $fetch(`/api/_flows/${selectedFlow.value}/runs/${selectedRunId.value}/restart`, {
641
- method: "POST"
642
- });
643
- if (result?.newRunId) {
644
- selectedRunId.value = result.newRunId;
645
- mainTab.value = "timeline";
646
- }
647
- await refreshRuns();
648
- } catch (error) {
649
- console.error("Failed to restart flow:", error);
650
- }
651
- };
652
- const diagramStepStates = computed(() => {
653
- if (!selectedRunId.value) return void 0;
654
- return flowState.state.value.steps;
655
- });
656
- const selectRun = (runId) => {
657
- selectedRunId.value = runId;
658
- mainTab.value = "timeline";
659
- };
660
- const exportTimelineJson = () => {
661
- const blob = new Blob([JSON.stringify(flowState.events.value, null, 2)], { type: "application/json" });
662
- const url = URL.createObjectURL(blob);
663
- const a = document.createElement("a");
664
- a.href = url;
665
- a.download = `flow-${selectedFlow.value}-${selectedRunId.value}-events-${(/* @__PURE__ */ new Date()).toISOString()}.json`;
666
- document.body.appendChild(a);
667
- a.click();
668
- a.remove();
669
- URL.revokeObjectURL(url);
670
- };
671
- const handleNodeAction = async (payload) => {
672
- const _stepName = payload.id.split(":")[1];
673
- if (!selectedRunId.value) {
674
- alert("Please select a flow run first to view logs or details.");
675
- return;
676
- }
677
- mainTab.value = "timeline";
678
- };
679
- const openStartFlowModal = () => {
680
- flowInputJson.value = "{}";
681
- jsonError.value = "";
682
- startFlowModalOpen.value = true;
683
- };
684
- const startFlowRun = async () => {
685
- if (!selectedFlow.value || jsonError.value) return;
686
- try {
687
- startingFlow.value = true;
688
- const input = JSON.parse(flowInputJson.value);
689
- const result = await $fetch(`/api/_flows/${encodeURIComponent(selectedFlow.value)}/start`, {
690
- method: "POST",
691
- body: input
692
- });
693
- startFlowModalOpen.value = false;
694
- flowInputJson.value = "{}";
695
- if (result?.flowId) {
696
- selectedRunId.value = result.flowId;
697
- mainTab.value = "timeline";
698
- }
699
- await refreshRuns();
700
- } catch (err) {
701
- console.error("Failed to start flow:", err);
702
- jsonError.value = err instanceof Error ? err.message : "Failed to start flow";
703
- } finally {
704
- startingFlow.value = false;
705
- }
706
- };
707
- const confirmClearHistory = () => {
708
- if (!selectedFlow.value) return;
709
- confirmDialogConfig.value = {
710
- title: "Clear Flow History",
711
- description: `Are you sure you want to clear all history for "${selectedFlow.value}"?`,
712
- items: [
713
- "All flow run events",
714
- "All flow run logs",
715
- "The runs index"
716
- ],
717
- warning: "This action cannot be undone.",
718
- onConfirm: () => {
719
- clearFlowHistory();
720
- }
721
- };
722
- confirmDialogOpen.value = true;
723
- };
724
- const clearFlowHistory = async () => {
725
- if (!selectedFlow.value) return;
726
- try {
727
- clearingHistory.value = true;
728
- await $fetch(`/api/_flows/${encodeURIComponent(selectedFlow.value)}/clear-history`, {
729
- method: "DELETE"
730
- });
731
- confirmDialogOpen.value = false;
732
- selectedRunId.value = "";
733
- mainTab.value = "diagram";
734
- await refreshRuns();
735
- } catch (err) {
736
- console.error("Failed to clear history:", err);
737
- confirmDialogConfig.value = {
738
- title: "Error Clearing History",
739
- description: `Failed to clear history: ${err instanceof Error ? err.message : "Unknown error"}`,
740
- items: [],
741
- warning: "",
742
- onConfirm: () => {
743
- confirmDialogOpen.value = false;
744
- }
745
- };
746
- } finally {
747
- clearingHistory.value = false;
748
- }
749
- };
750
- </script>