nvent 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +389 -0
  3. package/dist/module.d.mts +193 -0
  4. package/dist/module.json +9 -0
  5. package/dist/module.mjs +974 -0
  6. package/dist/runtime/app/components/ConfirmDialog.d.vue.ts +33 -0
  7. package/dist/runtime/app/components/ConfirmDialog.vue +121 -0
  8. package/dist/runtime/app/components/ConfirmDialog.vue.d.ts +33 -0
  9. package/dist/runtime/app/components/FlowDiagram.d.vue.ts +64 -0
  10. package/dist/runtime/app/components/FlowDiagram.vue +338 -0
  11. package/dist/runtime/app/components/FlowDiagram.vue.d.ts +64 -0
  12. package/dist/runtime/app/components/FlowNodeCard.d.vue.ts +29 -0
  13. package/dist/runtime/app/components/FlowNodeCard.vue +156 -0
  14. package/dist/runtime/app/components/FlowNodeCard.vue.d.ts +29 -0
  15. package/dist/runtime/app/components/FlowRunOverview.d.vue.ts +9 -0
  16. package/dist/runtime/app/components/FlowRunOverview.vue +291 -0
  17. package/dist/runtime/app/components/FlowRunOverview.vue.d.ts +9 -0
  18. package/dist/runtime/app/components/FlowRunStatusBadge.d.vue.ts +14 -0
  19. package/dist/runtime/app/components/FlowRunStatusBadge.vue +60 -0
  20. package/dist/runtime/app/components/FlowRunStatusBadge.vue.d.ts +14 -0
  21. package/dist/runtime/app/components/FlowRunTimeline.d.vue.ts +12 -0
  22. package/dist/runtime/app/components/FlowRunTimeline.vue +127 -0
  23. package/dist/runtime/app/components/FlowRunTimeline.vue.d.ts +12 -0
  24. package/dist/runtime/app/components/FlowScheduleDialog.d.vue.ts +16 -0
  25. package/dist/runtime/app/components/FlowScheduleDialog.vue +226 -0
  26. package/dist/runtime/app/components/FlowScheduleDialog.vue.d.ts +16 -0
  27. package/dist/runtime/app/components/FlowSchedulesList.d.vue.ts +12 -0
  28. package/dist/runtime/app/components/FlowSchedulesList.vue +99 -0
  29. package/dist/runtime/app/components/FlowSchedulesList.vue.d.ts +12 -0
  30. package/dist/runtime/app/components/JobScheduling.d.vue.ts +6 -0
  31. package/dist/runtime/app/components/JobScheduling.vue +203 -0
  32. package/dist/runtime/app/components/JobScheduling.vue.d.ts +6 -0
  33. package/dist/runtime/app/components/ListItem.d.vue.ts +23 -0
  34. package/dist/runtime/app/components/ListItem.vue +70 -0
  35. package/dist/runtime/app/components/ListItem.vue.d.ts +23 -0
  36. package/dist/runtime/app/components/QueueConfigDetails.d.vue.ts +45 -0
  37. package/dist/runtime/app/components/QueueConfigDetails.vue +412 -0
  38. package/dist/runtime/app/components/QueueConfigDetails.vue.d.ts +45 -0
  39. package/dist/runtime/app/components/StatCounter.d.vue.ts +9 -0
  40. package/dist/runtime/app/components/StatCounter.vue +25 -0
  41. package/dist/runtime/app/components/StatCounter.vue.d.ts +9 -0
  42. package/dist/runtime/app/components/TimelineList.d.vue.ts +7 -0
  43. package/dist/runtime/app/components/TimelineList.vue +210 -0
  44. package/dist/runtime/app/components/TimelineList.vue.d.ts +7 -0
  45. package/dist/runtime/app/components/nhealth/component-router.d.vue.ts +46 -0
  46. package/dist/runtime/app/components/nhealth/component-router.vue +26 -0
  47. package/dist/runtime/app/components/nhealth/component-router.vue.d.ts +46 -0
  48. package/dist/runtime/app/components/nhealth/component-shell.d.vue.ts +24 -0
  49. package/dist/runtime/app/components/nhealth/component-shell.vue +89 -0
  50. package/dist/runtime/app/components/nhealth/component-shell.vue.d.ts +24 -0
  51. package/dist/runtime/app/composables/useAnalyzedFlows.d.ts +14 -0
  52. package/dist/runtime/app/composables/useAnalyzedFlows.js +7 -0
  53. package/dist/runtime/app/composables/useComponentRouter.d.ts +38 -0
  54. package/dist/runtime/app/composables/useComponentRouter.js +240 -0
  55. package/dist/runtime/app/composables/useFlowRunTimeline.d.ts +15 -0
  56. package/dist/runtime/app/composables/useFlowRunTimeline.js +66 -0
  57. package/dist/runtime/app/composables/useFlowRuns.d.ts +11 -0
  58. package/dist/runtime/app/composables/useFlowRuns.js +31 -0
  59. package/dist/runtime/app/composables/useFlowRunsInfinite.d.ts +24 -0
  60. package/dist/runtime/app/composables/useFlowRunsInfinite.js +123 -0
  61. package/dist/runtime/app/composables/useFlowRunsPolling.d.ts +8 -0
  62. package/dist/runtime/app/composables/useFlowRunsPolling.js +26 -0
  63. package/dist/runtime/app/composables/useFlowState.d.ts +125 -0
  64. package/dist/runtime/app/composables/useFlowState.js +211 -0
  65. package/dist/runtime/app/composables/useFlowWebSocket.d.ts +27 -0
  66. package/dist/runtime/app/composables/useFlowWebSocket.js +205 -0
  67. package/dist/runtime/app/composables/useFlowsNavigation.d.ts +10 -0
  68. package/dist/runtime/app/composables/useFlowsNavigation.js +57 -0
  69. package/dist/runtime/app/composables/useQueueJobs.d.ts +20 -0
  70. package/dist/runtime/app/composables/useQueueJobs.js +20 -0
  71. package/dist/runtime/app/composables/useQueueUpdates.d.ts +26 -0
  72. package/dist/runtime/app/composables/useQueueUpdates.js +122 -0
  73. package/dist/runtime/app/composables/useQueues.d.ts +43 -0
  74. package/dist/runtime/app/composables/useQueues.js +26 -0
  75. package/dist/runtime/app/composables/useQueuesLive.d.ts +19 -0
  76. package/dist/runtime/app/composables/useQueuesLive.js +143 -0
  77. package/dist/runtime/app/pages/flows/index.d.vue.ts +3 -0
  78. package/dist/runtime/app/pages/flows/index.vue +645 -0
  79. package/dist/runtime/app/pages/flows/index.vue.d.ts +3 -0
  80. package/dist/runtime/app/pages/index.d.vue.ts +3 -0
  81. package/dist/runtime/app/pages/index.vue +34 -0
  82. package/dist/runtime/app/pages/index.vue.d.ts +3 -0
  83. package/dist/runtime/app/pages/queues/index.d.vue.ts +3 -0
  84. package/dist/runtime/app/pages/queues/index.vue +229 -0
  85. package/dist/runtime/app/pages/queues/index.vue.d.ts +3 -0
  86. package/dist/runtime/app/pages/queues/job.d.vue.ts +3 -0
  87. package/dist/runtime/app/pages/queues/job.vue +262 -0
  88. package/dist/runtime/app/pages/queues/job.vue.d.ts +3 -0
  89. package/dist/runtime/app/pages/queues/jobs.d.vue.ts +3 -0
  90. package/dist/runtime/app/pages/queues/jobs.vue +291 -0
  91. package/dist/runtime/app/pages/queues/jobs.vue.d.ts +3 -0
  92. package/dist/runtime/app/plugins/vueflow.client.d.ts +6 -0
  93. package/dist/runtime/app/plugins/vueflow.client.js +15 -0
  94. package/dist/runtime/constants.d.ts +11 -0
  95. package/dist/runtime/constants.js +11 -0
  96. package/dist/runtime/python/get_config.py +64 -0
  97. package/dist/runtime/schema.d.ts +37 -0
  98. package/dist/runtime/schema.js +20 -0
  99. package/dist/runtime/server/api/_flows/[name]/clear-history.delete.d.ts +10 -0
  100. package/dist/runtime/server/api/_flows/[name]/clear-history.delete.js +44 -0
  101. package/dist/runtime/server/api/_flows/[name]/runs.get.d.ts +7 -0
  102. package/dist/runtime/server/api/_flows/[name]/runs.get.js +53 -0
  103. package/dist/runtime/server/api/_flows/[name]/schedule.post.d.ts +2 -0
  104. package/dist/runtime/server/api/_flows/[name]/schedule.post.js +57 -0
  105. package/dist/runtime/server/api/_flows/[name]/schedules/[id].delete.d.ts +2 -0
  106. package/dist/runtime/server/api/_flows/[name]/schedules/[id].delete.js +42 -0
  107. package/dist/runtime/server/api/_flows/[name]/schedules.get.d.ts +2 -0
  108. package/dist/runtime/server/api/_flows/[name]/schedules.get.js +48 -0
  109. package/dist/runtime/server/api/_flows/[name]/start.post.d.ts +2 -0
  110. package/dist/runtime/server/api/_flows/[name]/start.post.js +9 -0
  111. package/dist/runtime/server/api/_flows/index.get.d.ts +6 -0
  112. package/dist/runtime/server/api/_flows/index.get.js +5 -0
  113. package/dist/runtime/server/api/_flows/ws.d.ts +60 -0
  114. package/dist/runtime/server/api/_flows/ws.js +183 -0
  115. package/dist/runtime/server/api/_queues/[name]/job/[id].get.d.ts +2 -0
  116. package/dist/runtime/server/api/_queues/[name]/job/[id].get.js +9 -0
  117. package/dist/runtime/server/api/_queues/[name]/job/index.get.d.ts +2 -0
  118. package/dist/runtime/server/api/_queues/[name]/job/index.get.js +18 -0
  119. package/dist/runtime/server/api/_queues/index.get.d.ts +2 -0
  120. package/dist/runtime/server/api/_queues/index.get.js +63 -0
  121. package/dist/runtime/server/api/_queues/ws.d.ts +48 -0
  122. package/dist/runtime/server/api/_queues/ws.js +200 -0
  123. package/dist/runtime/server/events/adapters/fileAdapter.d.ts +2 -0
  124. package/dist/runtime/server/events/adapters/fileAdapter.js +382 -0
  125. package/dist/runtime/server/events/adapters/memoryAdapter.d.ts +2 -0
  126. package/dist/runtime/server/events/adapters/memoryAdapter.js +171 -0
  127. package/dist/runtime/server/events/adapters/redis/redisAdapter.d.ts +2 -0
  128. package/dist/runtime/server/events/adapters/redis/redisAdapter.js +348 -0
  129. package/dist/runtime/server/events/adapters/redis/redisPubSubGateway.d.ts +29 -0
  130. package/dist/runtime/server/events/adapters/redis/redisPubSubGateway.js +82 -0
  131. package/dist/runtime/server/events/eventBus.d.ts +20 -0
  132. package/dist/runtime/server/events/eventBus.js +35 -0
  133. package/dist/runtime/server/events/eventStoreFactory.d.ts +19 -0
  134. package/dist/runtime/server/events/eventStoreFactory.js +44 -0
  135. package/dist/runtime/server/events/streamNames.d.ts +17 -0
  136. package/dist/runtime/server/events/streamNames.js +17 -0
  137. package/dist/runtime/server/events/types.d.ts +63 -0
  138. package/dist/runtime/server/events/types.js +0 -0
  139. package/dist/runtime/server/events/wiring/flowWiring.d.ts +33 -0
  140. package/dist/runtime/server/events/wiring/flowWiring.js +406 -0
  141. package/dist/runtime/server/events/wiring/registry.d.ts +10 -0
  142. package/dist/runtime/server/events/wiring/registry.js +24 -0
  143. package/dist/runtime/server/plugins/00.event-store.d.ts +13 -0
  144. package/dist/runtime/server/plugins/00.event-store.js +16 -0
  145. package/dist/runtime/server/plugins/00.ws-lifecycle.d.ts +5 -0
  146. package/dist/runtime/server/plugins/00.ws-lifecycle.js +66 -0
  147. package/dist/runtime/server/plugins/flow-management.d.ts +13 -0
  148. package/dist/runtime/server/plugins/flow-management.js +65 -0
  149. package/dist/runtime/server/plugins/queue-management.d.ts +2 -0
  150. package/dist/runtime/server/plugins/queue-management.js +27 -0
  151. package/dist/runtime/server/plugins/state-cleanup.d.ts +11 -0
  152. package/dist/runtime/server/plugins/state-cleanup.js +93 -0
  153. package/dist/runtime/server/plugins/worker-management.d.ts +2 -0
  154. package/dist/runtime/server/plugins/worker-management.js +33 -0
  155. package/dist/runtime/server/queue/adapters/bullmq.d.ts +17 -0
  156. package/dist/runtime/server/queue/adapters/bullmq.js +164 -0
  157. package/dist/runtime/server/queue/queueFactory.d.ts +3 -0
  158. package/dist/runtime/server/queue/queueFactory.js +10 -0
  159. package/dist/runtime/server/queue/types.d.ts +47 -0
  160. package/dist/runtime/server/queue/types.js +0 -0
  161. package/dist/runtime/server/state/adapters/redis.d.ts +2 -0
  162. package/dist/runtime/server/state/adapters/redis.js +42 -0
  163. package/dist/runtime/server/state/stateFactory.d.ts +3 -0
  164. package/dist/runtime/server/state/stateFactory.js +17 -0
  165. package/dist/runtime/server/state/types.d.ts +23 -0
  166. package/dist/runtime/server/state/types.js +0 -0
  167. package/dist/runtime/server/tsconfig.json +3 -0
  168. package/dist/runtime/server/utils/defineQueueConfig.d.ts +154 -0
  169. package/dist/runtime/server/utils/defineQueueConfig.js +2 -0
  170. package/dist/runtime/server/utils/defineQueueWorker.d.ts +10 -0
  171. package/dist/runtime/server/utils/defineQueueWorker.js +17 -0
  172. package/dist/runtime/server/utils/useEventManager.d.ts +15 -0
  173. package/dist/runtime/server/utils/useEventManager.js +26 -0
  174. package/dist/runtime/server/utils/useEventStore.d.ts +20 -0
  175. package/dist/runtime/server/utils/useEventStore.js +119 -0
  176. package/dist/runtime/server/utils/useFlowEngine.d.ts +9 -0
  177. package/dist/runtime/server/utils/useFlowEngine.js +44 -0
  178. package/dist/runtime/server/utils/useLogs.d.ts +41 -0
  179. package/dist/runtime/server/utils/useLogs.js +74 -0
  180. package/dist/runtime/server/utils/useQueue.d.ts +31 -0
  181. package/dist/runtime/server/utils/useQueue.js +24 -0
  182. package/dist/runtime/server/utils/useServerLogger.d.ts +42 -0
  183. package/dist/runtime/server/utils/useServerLogger.js +54 -0
  184. package/dist/runtime/server/utils/wsPeerManager.d.ts +34 -0
  185. package/dist/runtime/server/utils/wsPeerManager.js +23 -0
  186. package/dist/runtime/server/worker/adapter.d.ts +4 -0
  187. package/dist/runtime/server/worker/adapter.js +65 -0
  188. package/dist/runtime/server/worker/runner/node.d.ts +27 -0
  189. package/dist/runtime/server/worker/runner/node.js +196 -0
  190. package/dist/runtime/types.d.ts +132 -0
  191. package/dist/types.d.mts +3 -0
  192. package/package.json +75 -0
@@ -0,0 +1,645 @@
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
+ Flows
9
+ </h1>
10
+ </div>
11
+ <div class="flex items-center gap-3">
12
+ <USelectMenu
13
+ v-model="selectedFlow"
14
+ :items="(flows || []).map((f) => f.id)"
15
+ placeholder="Select a flow..."
16
+ class="w-64"
17
+ >
18
+ <template #leading>
19
+ <UIcon
20
+ v-if="selectedFlow"
21
+ name="i-lucide-git-branch"
22
+ class="w-4 h-4 text-gray-500"
23
+ />
24
+ </template>
25
+ </USelectMenu>
26
+ </div>
27
+ </div>
28
+ </div>
29
+
30
+ <!-- Main Content -->
31
+ <div class="flex-1 min-h-0 overflow-hidden">
32
+ <div class="h-full flex gap-px bg-gray-200 dark:bg-gray-800">
33
+ <!-- Runs List -->
34
+ <div class="w-1/3 bg-white dark:bg-gray-950 flex flex-col min-h-0">
35
+ <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">
36
+ <h2 class="text-sm font-medium text-gray-900 dark:text-gray-100">
37
+ Runs
38
+ </h2>
39
+ <div class="flex items-center gap-2">
40
+ <UButton
41
+ v-if="selectedFlow"
42
+ icon="i-lucide-play"
43
+ size="xs"
44
+ color="primary"
45
+ variant="soft"
46
+ @click="openStartFlowModal"
47
+ >
48
+ Start
49
+ </UButton>
50
+ <div
51
+ v-if="selectedFlow"
52
+ class="flex items-center gap-2 text-xs text-gray-500"
53
+ >
54
+ <UIcon
55
+ name="i-lucide-list"
56
+ class="w-3.5 h-3.5"
57
+ />
58
+ <span>{{ totalRuns }} run{{ totalRuns === 1 ? "" : "s" }}</span>
59
+ </div>
60
+ <UDropdownMenu
61
+ v-if="selectedFlow"
62
+ :items="flowActionsItems"
63
+ :ui="{ content: 'min-w-48' }"
64
+ >
65
+ <UButton
66
+ icon="i-lucide-more-vertical"
67
+ size="xs"
68
+ color="neutral"
69
+ variant="ghost"
70
+ square
71
+ />
72
+ </UDropdownMenu>
73
+ </div>
74
+ </div>
75
+ <div
76
+ v-if="!selectedFlow"
77
+ class="flex-1 overflow-y-auto min-h-0"
78
+ >
79
+ <div class="h-full flex items-center justify-center text-sm text-gray-400 px-4 text-center">
80
+ Select a flow to view runs
81
+ </div>
82
+ </div>
83
+ <div
84
+ v-else-if="!runs || runs.length === 0"
85
+ class="flex-1 overflow-y-auto min-h-0"
86
+ >
87
+ <div class="h-full flex items-center justify-center text-sm text-gray-400">
88
+ <div class="text-center">
89
+ <div v-if="loadingRuns">
90
+ Loading runs...
91
+ </div>
92
+ <div v-else>
93
+ No runs yet
94
+ </div>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ <div
99
+ v-else
100
+ ref="runsScrollContainer"
101
+ class="flex-1 overflow-y-auto min-h-0 divide-y divide-gray-100 dark:divide-gray-800"
102
+ @scroll="handleRunsScroll"
103
+ >
104
+ <div
105
+ v-for="r in runs"
106
+ :key="r.id"
107
+ class="group"
108
+ >
109
+ <div
110
+ class="px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-900/50 transition-colors cursor-pointer"
111
+ :class="{ 'bg-gray-50 dark:bg-gray-900': selectedRunId === r.id }"
112
+ @click="selectRun(r.id)"
113
+ >
114
+ <div class="flex items-start justify-between gap-3">
115
+ <div class="flex-1 min-w-0">
116
+ <div class="text-xs font-mono text-gray-900 dark:text-gray-100 truncate">
117
+ {{ r.id?.substring(0, 8) }}...{{ r.id?.substring(r.id?.length - 4) }}
118
+ </div>
119
+ <div class="flex items-center gap-3 mt-1.5">
120
+ <div class="text-xs text-gray-500">
121
+ {{ formatTime(r.createdAt) }}
122
+ </div>
123
+ <!-- Step progress -->
124
+ <div
125
+ v-if="r.stepCount > 0"
126
+ class="text-xs text-gray-500 flex items-center gap-1"
127
+ >
128
+ <UIcon
129
+ name="i-lucide-list-checks"
130
+ class="w-3 h-3"
131
+ />
132
+ <span>{{ r.completedSteps }}/{{ r.stepCount }}</span>
133
+ </div>
134
+ <!-- Duration (if completed) -->
135
+ <div
136
+ v-if="r.completedAt && r.startedAt"
137
+ class="text-xs text-gray-500 flex items-center gap-1"
138
+ >
139
+ <UIcon
140
+ name="i-lucide-timer"
141
+ class="w-3 h-3"
142
+ />
143
+ <span>{{ formatDuration(r.startedAt, r.completedAt) }}</span>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ <!-- Status badge -->
148
+ <FlowRunStatusBadge
149
+ :is-running="r.status === 'running'"
150
+ :is-completed="r.status === 'completed'"
151
+ :is-failed="r.status === 'failed'"
152
+ />
153
+ </div>
154
+ </div>
155
+ </div>
156
+
157
+ <!-- Loading indicator for infinite scroll -->
158
+ <div
159
+ v-if="loadingRuns"
160
+ class="px-4 py-3 text-center text-xs text-gray-400"
161
+ >
162
+ <UIcon
163
+ name="i-lucide-loader-2"
164
+ class="w-4 h-4 animate-spin inline-block"
165
+ />
166
+ <span class="ml-2">Loading more runs...</span>
167
+ </div>
168
+
169
+ <!-- End of list indicator -->
170
+ <div
171
+ v-else-if="!hasMoreRuns && runs.length > 0"
172
+ class="px-4 py-3 text-center text-xs text-gray-400"
173
+ >
174
+ All runs loaded
175
+ </div>
176
+ </div>
177
+
178
+ <!-- Schedules Section -->
179
+ <div
180
+ v-if="selectedFlow"
181
+ class="border-t border-gray-200 dark:border-gray-800 shrink-0"
182
+ >
183
+ <UAccordion
184
+ :items="scheduleAccordionItems"
185
+ :ui="{
186
+ root: 'w-full',
187
+ trigger: 'px-4 py-2 bg-gray-50 dark:bg-gray-900/50',
188
+ content: 'max-h-48 overflow-y-auto'
189
+ }"
190
+ >
191
+ <template #item>
192
+ <FlowSchedulesList
193
+ v-if="selectedFlow"
194
+ ref="schedulesListRef"
195
+ :flow-name="selectedFlow"
196
+ class="px-4 py-3"
197
+ @updated="handleSchedulesUpdated"
198
+ />
199
+ </template>
200
+ </UAccordion>
201
+ </div>
202
+ </div>
203
+
204
+ <!-- Main Content Area with Tabs -->
205
+ <div class="flex-1 bg-white dark:bg-gray-950 flex flex-col min-h-0">
206
+ <div class="px-4 py-2.5 border-b border-gray-200 dark:border-gray-800 shrink-0">
207
+ <div class="flex items-center justify-between">
208
+ <UTabs
209
+ v-model="mainTab"
210
+ :items="mainTabs"
211
+ size="xs"
212
+ :ui="{
213
+ root: 'gap-0',
214
+ trigger: 'px-2 py-0.5'
215
+ }"
216
+ />
217
+ <div class="flex items-center gap-2">
218
+ <span
219
+ v-if="selectedRunId"
220
+ class="text-xs text-gray-500 flex items-center gap-2"
221
+ >
222
+ <span>Run: {{ selectedRunId.substring(0, 8) }}...</span>
223
+ <div
224
+ v-if="isReconnecting || isConnected && flowState.isRunning.value"
225
+ class="flex items-center gap-1.5"
226
+ >
227
+ <div
228
+ class="w-1.5 h-1.5 rounded-full"
229
+ :class="isReconnecting ? 'bg-amber-500 animate-pulse' : 'bg-emerald-500 animate-pulse'"
230
+ />
231
+ <span>{{ isReconnecting ? "Reconnecting" : "Live" }}</span>
232
+ </div>
233
+ </span>
234
+ <span
235
+ v-else-if="selectedFlow"
236
+ class="text-xs font-mono text-gray-500"
237
+ >
238
+ {{ selectedFlow }}
239
+ </span>
240
+ </div>
241
+ </div>
242
+ </div>
243
+ <div class="flex-1 min-h-0">
244
+ <!-- Diagram Tab -->
245
+ <div
246
+ v-if="mainTab === 'diagram'"
247
+ class="h-full"
248
+ >
249
+ <div
250
+ v-if="!selectedFlow"
251
+ class="h-full flex items-center justify-center text-sm text-gray-400"
252
+ >
253
+ Select a flow to view diagram
254
+ </div>
255
+ <FlowDiagram
256
+ v-else
257
+ :flow="selectedFlowMeta"
258
+ :show-controls="true"
259
+ :show-background="true"
260
+ :step-states="diagramStepStates"
261
+ height-class="h-full"
262
+ @node-action="handleNodeAction"
263
+ />
264
+ </div>
265
+
266
+ <!-- Timeline Tab -->
267
+ <div
268
+ v-else-if="mainTab === 'timeline'"
269
+ class="h-full flex gap-px bg-gray-200 dark:bg-gray-800"
270
+ >
271
+ <!-- Left: Overview -->
272
+ <div class="w-1/2 bg-white dark:bg-gray-950 flex flex-col min-h-0">
273
+ <div class="flex-1 overflow-y-auto min-h-0">
274
+ <FlowRunOverview
275
+ :run-status="runSnapshot.status"
276
+ :started-at="runSnapshot.startedAt"
277
+ :completed-at="runSnapshot.completedAt"
278
+ :steps="flowState.stepList.value"
279
+ />
280
+ </div>
281
+ </div>
282
+
283
+ <!-- Right: Combined Logs & Events -->
284
+ <div class="w-1/2 bg-white dark:bg-gray-950 flex flex-col min-h-0">
285
+ <FlowRunTimeline
286
+ :events="timeline"
287
+ :logs="flowState.state.value.logs"
288
+ :is-live="isConnected"
289
+ @export="exportTimelineJson"
290
+ />
291
+ </div>
292
+ </div>
293
+ </div>
294
+ </div>
295
+ </div>
296
+ </div>
297
+
298
+ <!-- Start Flow Modal -->
299
+ <UModal v-model:open="startFlowModalOpen">
300
+ <template #header>
301
+ <div class="flex items-center justify-between">
302
+ <div>
303
+ <h3 class="text-lg font-semibold">
304
+ Start Flow Run
305
+ </h3>
306
+ <p class="text-sm text-gray-500 mt-1">
307
+ {{ selectedFlow }}
308
+ </p>
309
+ </div>
310
+ </div>
311
+ </template>
312
+ <template #body>
313
+ <div class="space-y-4">
314
+ <div>
315
+ <label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">
316
+ Input Data (JSON)
317
+ </label>
318
+ <UTextarea
319
+ v-model="flowInputJson"
320
+ :rows="12"
321
+ placeholder="{\n &quot;key&quot;: &quot;value&quot;\n}"
322
+ class="w-full font-mono text-sm"
323
+ />
324
+ <p
325
+ v-if="jsonError"
326
+ class="text-xs text-red-500 mt-2"
327
+ >
328
+ {{ jsonError }}
329
+ </p>
330
+ </div>
331
+ </div>
332
+ </template>
333
+ <template #footer>
334
+ <div class="flex justify-end gap-2">
335
+ <UButton
336
+ color="neutral"
337
+ variant="ghost"
338
+ @click="startFlowModalOpen = false"
339
+ >
340
+ Cancel
341
+ </UButton>
342
+ <UButton
343
+ color="primary"
344
+ :loading="startingFlow"
345
+ :disabled="!!jsonError"
346
+ @click="startFlowRun"
347
+ >
348
+ Start Flow
349
+ </UButton>
350
+ </div>
351
+ </template>
352
+ </UModal>
353
+
354
+ <!-- Schedule Flow Modal -->
355
+ <FlowScheduleDialog
356
+ v-if="selectedFlow"
357
+ v-model="scheduleModalOpen"
358
+ :flow-name="selectedFlow"
359
+ @scheduled="handleFlowScheduled"
360
+ />
361
+
362
+ <!-- Confirm Dialog -->
363
+ <ConfirmDialog
364
+ v-model:open="confirmDialogOpen"
365
+ :title="confirmDialogConfig.title"
366
+ :description="confirmDialogConfig.description"
367
+ :items="confirmDialogConfig.items"
368
+ :warning="confirmDialogConfig.warning"
369
+ :loading="clearingHistory"
370
+ confirm-label="Clear History"
371
+ confirm-color="error"
372
+ icon="i-lucide-trash-2"
373
+ icon-color="error"
374
+ @confirm="confirmDialogConfig.onConfirm"
375
+ />
376
+ </div>
377
+ </template>
378
+
379
+ <script setup>
380
+ import { ref, computed, watch } from "#imports";
381
+ import FlowDiagram from "../../components/FlowDiagram.vue";
382
+ import FlowRunOverview from "../../components/FlowRunOverview.vue";
383
+ import FlowRunTimeline from "../../components/FlowRunTimeline.vue";
384
+ import FlowRunStatusBadge from "../../components/FlowRunStatusBadge.vue";
385
+ import FlowSchedulesList from "../../components/FlowSchedulesList.vue";
386
+ import FlowScheduleDialog from "../../components/FlowScheduleDialog.vue";
387
+ import ConfirmDialog from "../../components/ConfirmDialog.vue";
388
+ import {
389
+ USelectMenu,
390
+ UIcon,
391
+ UButton,
392
+ UModal,
393
+ UTextarea,
394
+ UTabs,
395
+ UDropdownMenu,
396
+ UAccordion
397
+ } from "#components";
398
+ import { useAnalyzedFlows } from "../../composables/useAnalyzedFlows";
399
+ import { useFlowsNavigation } from "../../composables/useFlowsNavigation";
400
+ import { useFlowRunsInfinite } from "../../composables/useFlowRunsInfinite";
401
+ import { useFlowRunTimeline } from "../../composables/useFlowRunTimeline";
402
+ import { useFlowRunsPolling } from "../../composables/useFlowRunsPolling";
403
+ const { selectedFlow, selectedRunId } = useFlowsNavigation();
404
+ const mainTab = ref("diagram");
405
+ const mainTabs = computed(() => [
406
+ { label: "Diagram", value: "diagram", icon: "i-lucide-git-branch" },
407
+ {
408
+ label: "Timeline",
409
+ value: "timeline",
410
+ icon: "i-lucide-activity",
411
+ disabled: !selectedRunId.value
412
+ }
413
+ ]);
414
+ watch(selectedRunId, (newRunId, oldRunId) => {
415
+ if (newRunId && newRunId !== oldRunId) {
416
+ mainTab.value = "timeline";
417
+ } else if (!newRunId) {
418
+ mainTab.value = "diagram";
419
+ }
420
+ });
421
+ const flows = useAnalyzedFlows();
422
+ const {
423
+ items: runs,
424
+ total: totalRuns,
425
+ loading: loadingRuns,
426
+ hasMore: hasMoreRuns,
427
+ loadMore: loadMoreRuns,
428
+ refresh: refreshRuns,
429
+ checkForNewRuns
430
+ } = useFlowRunsInfinite(selectedFlow);
431
+ const { flowState, isConnected, isReconnecting } = useFlowRunTimeline(selectedFlow, selectedRunId);
432
+ const shouldPoll = computed(() => !!selectedFlow.value);
433
+ useFlowRunsPolling(checkForNewRuns, shouldPoll);
434
+ const startFlowModalOpen = ref(false);
435
+ const scheduleModalOpen = ref(false);
436
+ const flowInputJson = ref("{}");
437
+ const jsonError = ref("");
438
+ const startingFlow = ref(false);
439
+ const schedulesListRef = ref();
440
+ const clearingHistory = ref(false);
441
+ const scheduleAccordionItems = [
442
+ {
443
+ label: "Schedules",
444
+ slot: "item",
445
+ defaultOpen: false
446
+ }
447
+ ];
448
+ const confirmDialogOpen = ref(false);
449
+ const confirmDialogConfig = ref({
450
+ title: "",
451
+ description: "",
452
+ items: [],
453
+ warning: "",
454
+ onConfirm: () => {
455
+ }
456
+ });
457
+ const flowActionsItems = computed(() => [[
458
+ {
459
+ label: "Schedule Flow",
460
+ icon: "i-lucide-clock",
461
+ onSelect: () => openScheduleModal()
462
+ },
463
+ {
464
+ label: "Clear History",
465
+ icon: "i-lucide-trash-2",
466
+ disabled: clearingHistory.value,
467
+ onSelect: () => confirmClearHistory()
468
+ }
469
+ ]]);
470
+ watch(flowInputJson, (value) => {
471
+ try {
472
+ JSON.parse(value);
473
+ jsonError.value = "";
474
+ } catch (err) {
475
+ jsonError.value = err instanceof Error ? err.message : "Invalid JSON";
476
+ }
477
+ });
478
+ const runsScrollContainer = ref(null);
479
+ const handleRunsScroll = (event) => {
480
+ if (!hasMoreRuns.value || loadingRuns.value) return;
481
+ const container = event.target;
482
+ const scrollTop = container.scrollTop;
483
+ const scrollHeight = container.scrollHeight;
484
+ const clientHeight = container.clientHeight;
485
+ if (scrollTop + clientHeight >= scrollHeight - 200) {
486
+ loadMoreRuns();
487
+ }
488
+ };
489
+ const formatTime = (timestamp) => {
490
+ const date = new Date(timestamp);
491
+ const now = /* @__PURE__ */ new Date();
492
+ const diff = now.getTime() - date.getTime();
493
+ const seconds = Math.floor(diff / 1e3);
494
+ const minutes = Math.floor(seconds / 60);
495
+ const hours = Math.floor(minutes / 60);
496
+ const days = Math.floor(hours / 24);
497
+ if (days > 0) return `${days}d ago`;
498
+ if (hours > 0) return `${hours}h ago`;
499
+ if (minutes > 0) return `${minutes}m ago`;
500
+ if (seconds > 10) return `${seconds}s ago`;
501
+ return "just now";
502
+ };
503
+ const formatDuration = (start, end) => {
504
+ const startTime = new Date(start).getTime();
505
+ const endTime = new Date(end).getTime();
506
+ const diff = endTime - startTime;
507
+ const seconds = Math.floor(diff / 1e3);
508
+ const minutes = Math.floor(seconds / 60);
509
+ const hours = Math.floor(minutes / 60);
510
+ if (hours > 0) {
511
+ const remainingMinutes = minutes % 60;
512
+ return `${hours}h ${remainingMinutes}m`;
513
+ }
514
+ if (minutes > 0) {
515
+ const remainingSeconds = seconds % 60;
516
+ return `${minutes}m ${remainingSeconds}s`;
517
+ }
518
+ return `${seconds}s`;
519
+ };
520
+ const runSnapshot = computed(() => {
521
+ const state = flowState.state.value;
522
+ return {
523
+ status: state.status,
524
+ startedAt: state.startedAt,
525
+ completedAt: state.completedAt,
526
+ logsCount: state.logs.length,
527
+ lastLogLevel: state.logs.length > 0 ? state.logs[state.logs.length - 1]?.level : void 0
528
+ };
529
+ });
530
+ const timeline = computed(() => flowState.events.value);
531
+ const selectedFlowMeta = computed(() => {
532
+ const id = selectedFlow.value;
533
+ if (!id) return null;
534
+ return (flows.value || []).find((f) => f?.id === id) || null;
535
+ });
536
+ const diagramStepStates = computed(() => {
537
+ if (!selectedRunId.value) return void 0;
538
+ return flowState.state.value.steps;
539
+ });
540
+ const selectRun = (runId) => {
541
+ selectedRunId.value = runId;
542
+ mainTab.value = "timeline";
543
+ };
544
+ const exportTimelineJson = () => {
545
+ const blob = new Blob([JSON.stringify(flowState.events.value, null, 2)], { type: "application/json" });
546
+ const url = URL.createObjectURL(blob);
547
+ const a = document.createElement("a");
548
+ a.href = url;
549
+ a.download = `flow-${selectedFlow.value}-${selectedRunId.value}-events-${(/* @__PURE__ */ new Date()).toISOString()}.json`;
550
+ document.body.appendChild(a);
551
+ a.click();
552
+ a.remove();
553
+ URL.revokeObjectURL(url);
554
+ };
555
+ const handleNodeAction = async (payload) => {
556
+ const _stepName = payload.id.split(":")[1];
557
+ if (!selectedRunId.value) {
558
+ console.log("[flows/index] No run selected, showing alert");
559
+ alert("Please select a flow run first to view logs or details.");
560
+ return;
561
+ }
562
+ mainTab.value = "timeline";
563
+ };
564
+ const openStartFlowModal = () => {
565
+ flowInputJson.value = "{}";
566
+ jsonError.value = "";
567
+ startFlowModalOpen.value = true;
568
+ };
569
+ const openScheduleModal = () => {
570
+ scheduleModalOpen.value = true;
571
+ };
572
+ const handleFlowScheduled = () => {
573
+ schedulesListRef.value?.loadSchedules();
574
+ };
575
+ const handleSchedulesUpdated = () => {
576
+ schedulesListRef.value?.loadSchedules();
577
+ };
578
+ const startFlowRun = async () => {
579
+ if (!selectedFlow.value || jsonError.value) return;
580
+ try {
581
+ startingFlow.value = true;
582
+ const input = JSON.parse(flowInputJson.value);
583
+ const result = await $fetch(`/api/_flows/${encodeURIComponent(selectedFlow.value)}/start`, {
584
+ method: "POST",
585
+ body: input
586
+ });
587
+ startFlowModalOpen.value = false;
588
+ flowInputJson.value = "{}";
589
+ if (result?.flowId) {
590
+ selectedRunId.value = result.flowId;
591
+ mainTab.value = "timeline";
592
+ }
593
+ await refreshRuns();
594
+ } catch (err) {
595
+ console.error("Failed to start flow:", err);
596
+ jsonError.value = err instanceof Error ? err.message : "Failed to start flow";
597
+ } finally {
598
+ startingFlow.value = false;
599
+ }
600
+ };
601
+ const confirmClearHistory = () => {
602
+ if (!selectedFlow.value) return;
603
+ confirmDialogConfig.value = {
604
+ title: "Clear Flow History",
605
+ description: `Are you sure you want to clear all history for "${selectedFlow.value}"?`,
606
+ items: [
607
+ "All flow run events",
608
+ "All flow run logs",
609
+ "The runs index"
610
+ ],
611
+ warning: "This action cannot be undone.",
612
+ onConfirm: () => {
613
+ clearFlowHistory();
614
+ }
615
+ };
616
+ confirmDialogOpen.value = true;
617
+ };
618
+ const clearFlowHistory = async () => {
619
+ if (!selectedFlow.value) return;
620
+ try {
621
+ clearingHistory.value = true;
622
+ await $fetch(`/api/_flows/${encodeURIComponent(selectedFlow.value)}/clear-history`, {
623
+ method: "DELETE"
624
+ });
625
+ confirmDialogOpen.value = false;
626
+ selectedRunId.value = "";
627
+ mainTab.value = "diagram";
628
+ await refreshRuns();
629
+ console.log(`Successfully cleared history for "${selectedFlow.value}"`);
630
+ } catch (err) {
631
+ console.error("Failed to clear history:", err);
632
+ confirmDialogConfig.value = {
633
+ title: "Error Clearing History",
634
+ description: `Failed to clear history: ${err instanceof Error ? err.message : "Unknown error"}`,
635
+ items: [],
636
+ warning: "",
637
+ onConfirm: () => {
638
+ confirmDialogOpen.value = false;
639
+ }
640
+ };
641
+ } finally {
642
+ clearingHistory.value = false;
643
+ }
644
+ };
645
+ </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,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;