@nvent-addon/app 0.4.5 → 0.5.1

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