@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
@@ -0,0 +1,898 @@
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="getTriggerIcon(trigger?.type || 'event')"
19
+ class="w-5 h-5"
20
+ :class="getTriggerIconColor(trigger?.type || 'event')"
21
+ />
22
+ <span>{{ trigger?.displayName || trigger?.name }}</span>
23
+ </h1>
24
+ <div class="flex items-center gap-2 mt-1">
25
+ <UBadge
26
+ v-if="trigger"
27
+ :label="trigger.type"
28
+ :color="getTriggerTypeColor(trigger.type)"
29
+ variant="subtle"
30
+ size="xs"
31
+ />
32
+ <UBadge
33
+ v-if="trigger"
34
+ :label="trigger.scope"
35
+ color="neutral"
36
+ variant="subtle"
37
+ size="xs"
38
+ />
39
+ <UBadge
40
+ v-if="trigger"
41
+ :label="trigger.status"
42
+ :color="trigger.status === 'active' ? 'success' : trigger.status === 'inactive' ? 'warning' : 'neutral'"
43
+ variant="subtle"
44
+ size="xs"
45
+ />
46
+ </div>
47
+ </div>
48
+ </div>
49
+ <div class="flex items-center gap-3">
50
+ <UButton
51
+ icon="i-lucide-pencil"
52
+ color="neutral"
53
+ variant="outline"
54
+ size="sm"
55
+ @click="goToEdit"
56
+ >
57
+ Edit
58
+ </UButton>
59
+ <div
60
+ v-if="isConnected"
61
+ class="flex items-center gap-1.5 text-xs text-emerald-600 dark:text-emerald-400"
62
+ >
63
+ <div class="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
64
+ <span>Live</span>
65
+ </div>
66
+ <div
67
+ v-else-if="isReconnecting"
68
+ class="flex items-center gap-1.5 text-xs text-amber-600 dark:text-amber-400"
69
+ >
70
+ <div class="w-2 h-2 rounded-full bg-amber-500 animate-pulse" />
71
+ <span>Reconnecting...</span>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+
77
+ <!-- Main Content -->
78
+ <div class="flex-1 min-h-0 overflow-hidden">
79
+ <div
80
+ v-if="status === 'pending' && !trigger"
81
+ class="h-full flex items-center justify-center"
82
+ >
83
+ <div class="text-center">
84
+ <UIcon
85
+ name="i-lucide-loader-2"
86
+ class="w-8 h-8 animate-spin mx-auto mb-2 text-gray-400"
87
+ />
88
+ <p class="text-sm text-gray-500 dark:text-gray-400">
89
+ Loading trigger...
90
+ </p>
91
+ </div>
92
+ </div>
93
+
94
+ <div
95
+ v-else-if="!trigger"
96
+ class="h-full flex items-center justify-center"
97
+ >
98
+ <div class="text-center">
99
+ <UIcon
100
+ name="i-lucide-alert-circle"
101
+ class="w-8 h-8 mx-auto mb-2 text-gray-300 dark:text-gray-700"
102
+ />
103
+ <p class="text-sm text-gray-500 dark:text-gray-400">
104
+ Trigger not found
105
+ </p>
106
+ </div>
107
+ </div>
108
+
109
+ <div
110
+ v-else
111
+ class="h-full flex gap-px bg-gray-200 dark:bg-gray-800"
112
+ >
113
+ <!-- Left: Events List -->
114
+ <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">
115
+ <div class="px-4 py-3 border-b border-gray-200 dark:border-gray-800 flex items-center justify-between shrink-0">
116
+ <h2 class="text-sm font-medium text-gray-900 dark:text-gray-100">
117
+ Events
118
+ </h2>
119
+ <div class="flex items-center gap-2">
120
+ <USelectMenu
121
+ v-model="eventTypeFilter"
122
+ :items="eventTypeFilterOptions"
123
+ value-key="value"
124
+ placeholder="All Events"
125
+ size="xs"
126
+ class="w-40"
127
+ >
128
+ <template #label>
129
+ <div class="flex items-center gap-2">
130
+ <UIcon
131
+ :name="getFilterIcon(eventTypeFilter)"
132
+ class="w-4 h-4"
133
+ :class="getFilterIconColor(eventTypeFilter)"
134
+ />
135
+ <span class="text-xs">{{ getFilterLabel(eventTypeFilter) }}</span>
136
+ </div>
137
+ </template>
138
+ <template #option="{ option }">
139
+ <div class="flex items-center gap-2">
140
+ <UIcon
141
+ :name="getFilterIcon(option.value)"
142
+ class="w-4 h-4"
143
+ :class="getFilterIconColor(option.value)"
144
+ />
145
+ <span>{{ option.label }}</span>
146
+ </div>
147
+ </template>
148
+ </USelectMenu>
149
+ </div>
150
+ </div>
151
+
152
+ <div
153
+ v-if="eventsStatus === 'pending' && !events"
154
+ class="flex-1 flex items-center justify-center"
155
+ >
156
+ <div class="text-center">
157
+ <UIcon
158
+ name="i-lucide-loader-2"
159
+ class="w-8 h-8 animate-spin mx-auto mb-2 text-gray-400"
160
+ />
161
+ <p class="text-sm text-gray-500 dark:text-gray-400">
162
+ Loading events...
163
+ </p>
164
+ </div>
165
+ </div>
166
+
167
+ <div
168
+ v-else-if="!events || events.events.length === 0"
169
+ class="flex-1 flex items-center justify-center"
170
+ >
171
+ <div class="text-center">
172
+ <UIcon
173
+ name="i-lucide-inbox"
174
+ class="w-8 h-8 mx-auto mb-2 text-gray-300 dark:text-gray-700"
175
+ />
176
+ <p class="text-sm text-gray-500 dark:text-gray-400">
177
+ No events yet
178
+ </p>
179
+ </div>
180
+ </div>
181
+
182
+ <div
183
+ v-else
184
+ class="flex-1 min-h-0 overflow-y-auto"
185
+ >
186
+ <div class="divide-y divide-gray-100 dark:divide-gray-800">
187
+ <div
188
+ v-for="(event, idx) in paginatedEvents"
189
+ :key="idx"
190
+ class="px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-900/50 cursor-pointer transition-colors"
191
+ :class="{
192
+ 'bg-blue-50 dark:bg-blue-950/30 border-l-2 border-l-blue-500': selectedEvent && selectedEvent.type === event.type && (selectedEvent.ts || selectedEvent.timestamp) === (event.ts || event.timestamp)
193
+ }"
194
+ @click="selectEvent(event)"
195
+ >
196
+ <div class="flex items-start gap-3">
197
+ <div class="flex-shrink-0 mt-0.5">
198
+ <UIcon
199
+ :name="getEventIcon(event.type)"
200
+ class="w-5 h-5"
201
+ :class="getEventIconColor(event.type)"
202
+ />
203
+ </div>
204
+ <div class="flex-1 min-w-0">
205
+ <div class="flex items-center justify-between gap-2 mb-1">
206
+ <h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate">
207
+ {{ event.type }}
208
+ </h3>
209
+ <UBadge
210
+ :label="event.type.split('.')[1] || 'event'"
211
+ :color="getEventBadgeColor(event.type)"
212
+ variant="subtle"
213
+ size="xs"
214
+ class="capitalize flex-shrink-0"
215
+ />
216
+ </div>
217
+ <p class="text-xs text-gray-500 dark:text-gray-400 font-mono truncate mb-1">
218
+ {{ formatDate(event.ts || event.timestamp) }}
219
+ </p>
220
+ <div class="flex items-center gap-3 text-xs text-gray-500 dark:text-gray-400">
221
+ <span>
222
+ {{ formatTime(event.ts || event.timestamp) }}
223
+ </span>
224
+ </div>
225
+ </div>
226
+ </div>
227
+ </div>
228
+ </div>
229
+ </div>
230
+
231
+ <!-- Pagination Footer -->
232
+ <div
233
+ v-if="events && (events.total || events.count) > eventsPerPage"
234
+ class="border-t border-gray-200 dark:border-gray-800 px-4 py-3 flex items-center justify-center shrink-0"
235
+ >
236
+ <UPagination
237
+ v-model:page="currentPage"
238
+ :items-per-page="eventsPerPage"
239
+ :total="events.total || events.count"
240
+ size="xs"
241
+ />
242
+ </div>
243
+ </div>
244
+
245
+ <!-- Right: Overview or Event Details -->
246
+ <div class="flex-1 min-w-0 bg-white dark:bg-gray-950 flex flex-col min-h-0 overflow-hidden">
247
+ <div class="px-4 py-2.5 border-b border-gray-200 dark:border-gray-800 shrink-0">
248
+ <div class="flex items-center justify-between">
249
+ <UTabs
250
+ v-model="activeTab"
251
+ :items="tabItems"
252
+ size="xs"
253
+ :ui="{
254
+ root: 'gap-0',
255
+ trigger: 'px-2 py-0.5'
256
+ }"
257
+ />
258
+ </div>
259
+ </div>
260
+
261
+ <div class="flex-1 min-h-0 overflow-y-auto">
262
+ <!-- Overview Tab -->
263
+ <div
264
+ v-if="activeTab === 'overview'"
265
+ class="p-6 space-y-6"
266
+ >
267
+ <!-- Stats Cards -->
268
+ <div>
269
+ <h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-4">
270
+ Trigger Statistics
271
+ </h3>
272
+ <div class="grid grid-cols-2 gap-4">
273
+ <StatCard
274
+ icon="i-lucide-zap"
275
+ :count="trigger.stats.totalFires"
276
+ label="Total Fires"
277
+ variant="gray"
278
+ />
279
+ <StatCard
280
+ icon="i-lucide-git-branch"
281
+ :count="trigger.stats.activeSubscribers"
282
+ label="Active Subscribers"
283
+ variant="purple"
284
+ />
285
+ <StatCard
286
+ v-if="trigger.stats.lastFiredAt"
287
+ icon="i-lucide-clock"
288
+ :count="formatTime(new Date(trigger.stats.lastFiredAt).getTime())"
289
+ label="Last Fired"
290
+ variant="blue"
291
+ />
292
+ <StatCard
293
+ icon="i-lucide-users"
294
+ :count="trigger.subscriptionCount"
295
+ label="Subscriptions"
296
+ variant="emerald"
297
+ />
298
+ </div>
299
+ </div>
300
+
301
+ <!-- Description -->
302
+ <div
303
+ v-if="trigger.description"
304
+ class="bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-800 rounded-lg p-4"
305
+ >
306
+ <div class="flex items-start gap-2">
307
+ <UIcon
308
+ name="i-lucide-info"
309
+ class="w-4 h-4 text-blue-600 dark:text-blue-400 mt-0.5 shrink-0"
310
+ />
311
+ <p class="text-sm text-blue-900 dark:text-blue-100">
312
+ {{ trigger.description }}
313
+ </p>
314
+ </div>
315
+ </div>
316
+
317
+ <!-- Configuration -->
318
+ <div>
319
+ <h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-4">
320
+ Configuration
321
+ </h3>
322
+ <div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4 space-y-3">
323
+ <div class="flex items-center justify-between py-2 border-b border-gray-200 dark:border-gray-800">
324
+ <span class="text-sm text-gray-600 dark:text-gray-400">Name</span>
325
+ <span class="text-sm font-medium font-mono text-gray-900 dark:text-gray-100">{{ trigger.name }}</span>
326
+ </div>
327
+ <div class="flex items-center justify-between py-2 border-b border-gray-200 dark:border-gray-800">
328
+ <span class="text-sm text-gray-600 dark:text-gray-400">Type</span>
329
+ <UBadge
330
+ :label="trigger.type"
331
+ :color="getTriggerTypeColor(trigger.type)"
332
+ variant="subtle"
333
+ size="xs"
334
+ />
335
+ </div>
336
+ <div class="flex items-center justify-between py-2 border-b border-gray-200 dark:border-gray-800">
337
+ <span class="text-sm text-gray-600 dark:text-gray-400">Scope</span>
338
+ <span class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ trigger.scope }}</span>
339
+ </div>
340
+ <div
341
+ v-if="trigger.source"
342
+ class="flex items-center justify-between py-2 border-b border-gray-200 dark:border-gray-800"
343
+ >
344
+ <span class="text-sm text-gray-600 dark:text-gray-400">Source</span>
345
+ <span class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ trigger.source }}</span>
346
+ </div>
347
+ <div
348
+ v-if="trigger.registeredAt"
349
+ class="flex items-center justify-between py-2 border-b border-gray-200 dark:border-gray-800"
350
+ >
351
+ <span class="text-sm text-gray-600 dark:text-gray-400">Registered</span>
352
+ <span class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ formatDate(trigger.registeredAt) }}</span>
353
+ </div>
354
+ <div
355
+ v-if="trigger.stats.lastFiredAt"
356
+ class="flex items-center justify-between py-2 border-b border-gray-200 dark:border-gray-800"
357
+ >
358
+ <span class="text-sm text-gray-600 dark:text-gray-400">Last Fired</span>
359
+ <span class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ formatTime(trigger.stats.lastFiredAt) }}</span>
360
+ </div>
361
+ <div
362
+ v-if="trigger.lastActivityAt"
363
+ class="flex items-center justify-between py-2"
364
+ >
365
+ <span class="text-sm text-gray-600 dark:text-gray-400">Last Modified</span>
366
+ <span class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ formatTime(trigger.lastActivityAt) }}</span>
367
+ </div>
368
+ </div>
369
+ </div>
370
+
371
+ <!-- Webhook Config -->
372
+ <div v-if="trigger.webhook">
373
+ <h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-3">
374
+ Webhook Configuration
375
+ </h3>
376
+ <div class="space-y-3">
377
+ <div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-3">
378
+ <div class="flex items-center justify-between mb-2">
379
+ <span class="text-xs text-gray-500 dark:text-gray-400">Webhook URL</span>
380
+ <UButton
381
+ v-if="trigger.webhook.fullUrl"
382
+ icon="i-lucide-copy"
383
+ size="xs"
384
+ color="neutral"
385
+ variant="ghost"
386
+ @click="copyToClipboard(trigger.webhook.fullUrl)"
387
+ />
388
+ </div>
389
+ <code class="text-xs font-mono text-blue-600 dark:text-blue-400 break-all">
390
+ {{ trigger.webhook.fullUrl || trigger.webhook.path }}
391
+ </code>
392
+ </div>
393
+ <div class="flex items-center justify-between py-2 border-b border-gray-100 dark:border-gray-800">
394
+ <span class="text-sm text-gray-500 dark:text-gray-400">Method</span>
395
+ <UBadge
396
+ :label="trigger.webhook.method || 'POST'"
397
+ color="neutral"
398
+ variant="subtle"
399
+ size="xs"
400
+ />
401
+ </div>
402
+ </div>
403
+ </div>
404
+
405
+ <!-- Schedule Config -->
406
+ <div v-if="trigger.schedule">
407
+ <h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-3">
408
+ Schedule Configuration
409
+ </h3>
410
+ <div class="space-y-2 text-sm">
411
+ <div
412
+ v-if="trigger.schedule.cron"
413
+ class="flex items-center justify-between py-2 border-b border-gray-100 dark:border-gray-800"
414
+ >
415
+ <span class="text-gray-500 dark:text-gray-400">Cron Expression</span>
416
+ <span class="font-mono text-gray-900 dark:text-gray-100">{{ trigger.schedule.cron }}</span>
417
+ </div>
418
+ <div
419
+ v-if="trigger.schedule.interval"
420
+ class="flex items-center justify-between py-2 border-b border-gray-100 dark:border-gray-800"
421
+ >
422
+ <span class="text-gray-500 dark:text-gray-400">Interval</span>
423
+ <span class="text-gray-900 dark:text-gray-100">{{ formatInterval(trigger.schedule.interval) }}</span>
424
+ </div>
425
+ <div
426
+ v-if="trigger.schedule.timezone"
427
+ class="flex items-center justify-between py-2"
428
+ >
429
+ <span class="text-gray-500 dark:text-gray-400">Timezone</span>
430
+ <span class="text-gray-900 dark:text-gray-100">{{ trigger.schedule.timezone }}</span>
431
+ </div>
432
+ </div>
433
+ </div>
434
+
435
+ <!-- Subscribed Flows -->
436
+ <div>
437
+ <h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-4">
438
+ Subscribed Flows ({{ trigger.subscriptionCount }})
439
+ </h3>
440
+ <div
441
+ v-if="trigger.subscriptions.length === 0"
442
+ class="text-center py-8"
443
+ >
444
+ <UIcon
445
+ name="i-lucide-git-branch-plus"
446
+ class="w-8 h-8 mx-auto mb-2 text-gray-300 dark:text-gray-700"
447
+ />
448
+ <p class="text-sm text-gray-500 dark:text-gray-400">
449
+ No subscriptions
450
+ </p>
451
+ </div>
452
+ <div
453
+ v-else
454
+ class="space-y-2"
455
+ >
456
+ <div
457
+ v-for="sub in trigger.subscriptions"
458
+ :key="`${sub.flowName}-${sub.triggerName}`"
459
+ class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-900/50 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-900 transition-colors cursor-pointer"
460
+ @click="goToFlow(sub.flowName)"
461
+ >
462
+ <div class="flex items-center gap-2 min-w-0">
463
+ <UIcon
464
+ name="i-lucide-git-branch"
465
+ class="w-4 h-4 text-blue-500 shrink-0"
466
+ />
467
+ <span class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate">{{ sub.flowName }}</span>
468
+ </div>
469
+ <div class="flex items-center gap-2">
470
+ <UBadge
471
+ :label="sub.mode"
472
+ :color="sub.mode === 'auto' ? 'success' : 'neutral'"
473
+ variant="subtle"
474
+ size="xs"
475
+ />
476
+ <UIcon
477
+ name="i-lucide-arrow-right"
478
+ class="w-4 h-4 text-gray-400"
479
+ />
480
+ </div>
481
+ </div>
482
+ </div>
483
+ </div>
484
+ </div>
485
+
486
+ <!-- Event Details Tab -->
487
+ <div
488
+ v-else-if="activeTab === 'details' && selectedEvent"
489
+ class="p-6 space-y-6"
490
+ >
491
+ <!-- Event Info -->
492
+ <div>
493
+ <h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center gap-2">
494
+ <UIcon
495
+ :name="getEventIcon(selectedEvent.type)"
496
+ class="w-5 h-5"
497
+ :class="getEventIconColor(selectedEvent.type)"
498
+ />
499
+ <span>Event Information</span>
500
+ </h3>
501
+ <div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-4 space-y-3">
502
+ <div class="flex items-center justify-between py-2 border-b border-gray-200 dark:border-gray-800">
503
+ <span class="text-sm text-gray-600 dark:text-gray-400">Type</span>
504
+ <UBadge
505
+ :label="selectedEvent.type"
506
+ :color="getEventBadgeColor(selectedEvent.type)"
507
+ variant="subtle"
508
+ size="xs"
509
+ />
510
+ </div>
511
+ <div class="flex items-center justify-between py-2">
512
+ <span class="text-sm text-gray-600 dark:text-gray-400">Timestamp</span>
513
+ <span class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ formatDate(selectedEvent.ts || selectedEvent.timestamp) }}</span>
514
+ </div>
515
+ </div>
516
+ </div>
517
+
518
+ <!-- Event Data -->
519
+ <div>
520
+ <h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-4">
521
+ Event Data
522
+ </h3>
523
+ <div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 overflow-x-auto">
524
+ <pre class="text-xs font-mono">{{ JSON.stringify(selectedEvent.data, null, 2) }}</pre>
525
+ </div>
526
+ </div>
527
+
528
+ <!-- Raw Event -->
529
+ <div>
530
+ <h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-4">
531
+ Raw Event
532
+ </h3>
533
+ <div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 overflow-x-auto">
534
+ <pre class="text-xs font-mono">{{ JSON.stringify(selectedEvent, null, 2) }}</pre>
535
+ </div>
536
+ </div>
537
+ </div>
538
+ </div>
539
+ </div>
540
+ </div>
541
+ </div>
542
+ </div>
543
+ </template>
544
+
545
+ <script setup>
546
+ import { ref, computed, onUnmounted, watch, onMounted } from "#imports";
547
+ import { UButton, UIcon, UBadge, USelectMenu, UTabs } from "#components";
548
+ import { useTrigger, useTriggerEvents } from "../../composables/useTrigger";
549
+ import { useComponentRouter } from "../../composables/useComponentRouter";
550
+ import { useTriggerWebSocket } from "../../composables/useTriggerWebSocket";
551
+ import { useRoute, useRouter } from "#app";
552
+ import StatCard from "../../components/StatCard.vue";
553
+ const componentRouter = useComponentRouter();
554
+ const router = useRouter();
555
+ const route = useRoute();
556
+ const triggerName = computed(() => {
557
+ const name = componentRouter.route.value?.params?.name;
558
+ return name || null;
559
+ });
560
+ const { trigger, status } = useTrigger(triggerName);
561
+ const eventTypeFilter = computed({
562
+ get: () => route.query.type || "all",
563
+ set: (value) => {
564
+ router.push({
565
+ query: {
566
+ ...route.query,
567
+ type: value === "all" ? void 0 : value,
568
+ page: void 0
569
+ // Reset page when filter changes
570
+ }
571
+ });
572
+ }
573
+ });
574
+ const currentPage = computed({
575
+ get: () => {
576
+ const page = route.query.page;
577
+ return page ? Number.parseInt(page, 10) : 1;
578
+ },
579
+ set: (value) => {
580
+ router.push({
581
+ query: {
582
+ ...route.query,
583
+ page: value > 1 ? value.toString() : void 0
584
+ }
585
+ });
586
+ }
587
+ });
588
+ const eventTypeFilterOptions = [
589
+ { label: "All Events", value: "all" },
590
+ { label: "Fired", value: "trigger.fired" },
591
+ { label: "Registered", value: "trigger.registered" },
592
+ { label: "Updated", value: "trigger.updated" }
593
+ ];
594
+ const eventsPerPage = 20;
595
+ const eventQueryOptions = computed(() => {
596
+ const options = {
597
+ limit: eventsPerPage,
598
+ offset: (currentPage.value - 1) * eventsPerPage
599
+ };
600
+ if (eventTypeFilter.value !== "all") {
601
+ options.types = [eventTypeFilter.value];
602
+ }
603
+ return options;
604
+ });
605
+ const { events: fetchedEvents, refresh: refreshEvents, status: eventsStatus } = useTriggerEvents(triggerName, eventQueryOptions);
606
+ const liveEvents = ref([]);
607
+ const { connected: isConnected, reconnecting: isReconnecting, subscribe, unsubscribe, subscribeStats, unsubscribeStats } = useTriggerWebSocket();
608
+ const pendingStatsUpdate = ref(null);
609
+ watch(trigger, (newTrigger) => {
610
+ if (newTrigger && pendingStatsUpdate.value) {
611
+ const metadata = pendingStatsUpdate.value.metadata;
612
+ if (metadata) {
613
+ trigger.value = {
614
+ ...newTrigger,
615
+ stats: {
616
+ totalFires: metadata.stats?.totalFires || metadata.totalFires || metadata["stats.totalFires"] || newTrigger.stats.totalFires || 0,
617
+ totalFlowsStarted: metadata.stats?.totalFlowsStarted || metadata.totalFlowsStarted || metadata["stats.totalFlowsStarted"] || newTrigger.stats.totalFlowsStarted || 0,
618
+ activeSubscribers: metadata.stats?.activeSubscribers || metadata.activeSubscribers || metadata["stats.activeSubscribers"] || newTrigger.stats.activeSubscribers || 0,
619
+ lastFiredAt: metadata.stats?.lastFiredAt || metadata.lastFiredAt || metadata["stats.lastFiredAt"] || newTrigger.stats.lastFiredAt
620
+ },
621
+ lastActivityAt: metadata.lastActivityAt || newTrigger.lastActivityAt
622
+ };
623
+ pendingStatsUpdate.value = null;
624
+ }
625
+ }
626
+ });
627
+ function setupSubscriptions(newName) {
628
+ subscribe({
629
+ triggerName: newName,
630
+ onEvent: (event) => {
631
+ liveEvents.value = [event, ...liveEvents.value].slice(0, 50);
632
+ },
633
+ onHistory: (events2) => {
634
+ liveEvents.value = events2.slice(0, 50);
635
+ }
636
+ });
637
+ subscribeStats({
638
+ onInitial: (data) => {
639
+ if (data.id !== newName) {
640
+ return;
641
+ }
642
+ const metadata = data?.metadata;
643
+ if (!metadata) {
644
+ return;
645
+ }
646
+ if (!trigger.value) {
647
+ pendingStatsUpdate.value = data;
648
+ return;
649
+ }
650
+ trigger.value = {
651
+ ...trigger.value,
652
+ stats: {
653
+ totalFires: metadata.stats?.totalFires || metadata.totalFires || metadata["stats.totalFires"] || trigger.value.stats.totalFires || 0,
654
+ totalFlowsStarted: metadata.stats?.totalFlowsStarted || metadata.totalFlowsStarted || metadata["stats.totalFlowsStarted"] || trigger.value.stats.totalFlowsStarted || 0,
655
+ activeSubscribers: metadata.stats?.activeSubscribers || metadata.activeSubscribers || metadata["stats.activeSubscribers"] || trigger.value.stats.activeSubscribers || 0,
656
+ lastFiredAt: metadata.stats?.lastFiredAt || metadata.lastFiredAt || metadata["stats.lastFiredAt"] || trigger.value.stats.lastFiredAt
657
+ },
658
+ lastActivityAt: metadata.lastActivityAt || trigger.value.lastActivityAt
659
+ };
660
+ },
661
+ onUpdate: (data) => {
662
+ if (data.id !== newName) {
663
+ return;
664
+ }
665
+ if (!trigger.value) {
666
+ pendingStatsUpdate.value = data;
667
+ return;
668
+ }
669
+ const metadata = data?.metadata;
670
+ if (!metadata) {
671
+ return;
672
+ }
673
+ trigger.value = {
674
+ ...trigger.value,
675
+ stats: {
676
+ totalFires: metadata.stats?.totalFires || metadata.totalFires || metadata["stats.totalFires"] || trigger.value.stats.totalFires || 0,
677
+ totalFlowsStarted: metadata.stats?.totalFlowsStarted || metadata.totalFlowsStarted || metadata["stats.totalFlowsStarted"] || trigger.value.stats.totalFlowsStarted || 0,
678
+ activeSubscribers: metadata.stats?.activeSubscribers || metadata.activeSubscribers || metadata["stats.activeSubscribers"] || trigger.value.stats.activeSubscribers || 0,
679
+ lastFiredAt: metadata.stats?.lastFiredAt || metadata.lastFiredAt || metadata["stats.lastFiredAt"] || trigger.value.stats.lastFiredAt
680
+ },
681
+ lastActivityAt: metadata.lastActivityAt || trigger.value.lastActivityAt
682
+ };
683
+ }
684
+ });
685
+ }
686
+ onMounted(() => {
687
+ if (import.meta.server) return;
688
+ if (triggerName.value) {
689
+ setupSubscriptions(triggerName.value);
690
+ }
691
+ });
692
+ onUnmounted(() => {
693
+ unsubscribe();
694
+ unsubscribeStats();
695
+ });
696
+ const events = computed(() => {
697
+ return fetchedEvents.value;
698
+ });
699
+ const paginatedEvents = computed(() => {
700
+ if (!events.value) return [];
701
+ if (currentPage.value === 1) {
702
+ const filteredLiveEvents = liveEvents.value.filter((event) => {
703
+ if (eventTypeFilter.value === "all") return true;
704
+ return event.type === eventTypeFilter.value;
705
+ });
706
+ const allEvents = [...filteredLiveEvents, ...events.value.events || []];
707
+ const seen = /* @__PURE__ */ new Set();
708
+ const unique = allEvents.filter((event) => {
709
+ const key = event.id || `${event.type}-${event.ts || event.timestamp}`;
710
+ if (seen.has(key)) return false;
711
+ seen.add(key);
712
+ return true;
713
+ });
714
+ unique.sort((a, b) => {
715
+ const aTime = a.ts || a.timestamp || 0;
716
+ const bTime = b.ts || b.timestamp || 0;
717
+ return bTime - aTime;
718
+ });
719
+ return unique.slice(0, eventsPerPage);
720
+ }
721
+ return events.value.events || [];
722
+ });
723
+ watch(() => eventQueryOptions.value, () => {
724
+ refreshEvents();
725
+ }, { deep: true });
726
+ const activeTab = ref("overview");
727
+ const tabItems = computed(() => [
728
+ { label: "Overview", value: "overview", icon: "i-lucide-bar-chart-3" },
729
+ {
730
+ label: "Event Details",
731
+ value: "details",
732
+ icon: "i-lucide-file-text",
733
+ disabled: !selectedEvent.value
734
+ }
735
+ ]);
736
+ const selectedEvent = ref(null);
737
+ const selectEvent = (event) => {
738
+ selectedEvent.value = event;
739
+ activeTab.value = "details";
740
+ };
741
+ watch(selectedEvent, (newEvent) => {
742
+ if (newEvent) {
743
+ activeTab.value = "details";
744
+ } else {
745
+ activeTab.value = "overview";
746
+ }
747
+ });
748
+ const goBack = () => {
749
+ componentRouter.push("/triggers");
750
+ };
751
+ const goToEdit = () => {
752
+ if (triggerName.value) {
753
+ componentRouter.push(`/triggers/${encodeURIComponent(triggerName.value)}/edit`);
754
+ }
755
+ };
756
+ const goToFlow = (flowName) => {
757
+ componentRouter.push(`/flows?flow=${encodeURIComponent(flowName)}`);
758
+ };
759
+ const getTriggerIcon = (type) => {
760
+ switch (type) {
761
+ case "event":
762
+ return "i-lucide-radio";
763
+ case "webhook":
764
+ return "i-lucide-webhook";
765
+ case "schedule":
766
+ return "i-lucide-clock";
767
+ case "manual":
768
+ return "i-lucide-hand";
769
+ default:
770
+ return "i-lucide-zap";
771
+ }
772
+ };
773
+ const getTriggerIconColor = (type) => {
774
+ switch (type) {
775
+ case "event":
776
+ return "text-blue-500";
777
+ case "webhook":
778
+ return "text-purple-500";
779
+ case "schedule":
780
+ return "text-emerald-500";
781
+ case "manual":
782
+ return "text-amber-500";
783
+ default:
784
+ return "text-gray-500";
785
+ }
786
+ };
787
+ const getTriggerTypeColor = (type) => {
788
+ switch (type) {
789
+ case "event":
790
+ return "primary";
791
+ case "webhook":
792
+ return "success";
793
+ case "schedule":
794
+ return "warning";
795
+ case "manual":
796
+ return "neutral";
797
+ default:
798
+ return "neutral";
799
+ }
800
+ };
801
+ const getEventIcon = (type) => {
802
+ if (type.includes("fired")) return "i-lucide-zap";
803
+ if (type.includes("registered")) return "i-lucide-plus-circle";
804
+ if (type.includes("updated")) return "i-lucide-pencil";
805
+ if (type.includes("subscription")) return "i-lucide-link";
806
+ return "i-lucide-circle-dot";
807
+ };
808
+ const getEventIconColor = (type) => {
809
+ if (type.includes("fired")) return "text-emerald-500";
810
+ if (type.includes("registered")) return "text-blue-500";
811
+ if (type.includes("updated")) return "text-amber-500";
812
+ if (type.includes("subscription")) return "text-purple-500";
813
+ return "text-gray-500";
814
+ };
815
+ const getEventBadgeColor = (type) => {
816
+ if (type.includes("fired")) return "success";
817
+ if (type.includes("registered")) return "primary";
818
+ if (type.includes("updated")) return "warning";
819
+ if (type.includes("subscription")) return "secondary";
820
+ return "neutral";
821
+ };
822
+ const formatTime = (timestamp) => {
823
+ const date = new Date(timestamp);
824
+ const now = /* @__PURE__ */ new Date();
825
+ const diff = now.getTime() - date.getTime();
826
+ const seconds = Math.floor(diff / 1e3);
827
+ const minutes = Math.floor(seconds / 60);
828
+ const hours = Math.floor(minutes / 60);
829
+ const days = Math.floor(hours / 24);
830
+ if (days > 0) return `${days}d ago`;
831
+ if (hours > 0) return `${hours}h ago`;
832
+ if (minutes > 0) return `${minutes}m ago`;
833
+ if (seconds > 10) return `${seconds}s ago`;
834
+ return "just now";
835
+ };
836
+ const formatDate = (timestamp) => {
837
+ return new Date(timestamp).toLocaleString();
838
+ };
839
+ const getFilterIcon = (value) => {
840
+ switch (value) {
841
+ case "trigger.fired":
842
+ return "i-lucide-zap";
843
+ case "trigger.registered":
844
+ return "i-lucide-plus-circle";
845
+ case "trigger.updated":
846
+ return "i-lucide-pencil";
847
+ default:
848
+ return "i-lucide-filter";
849
+ }
850
+ };
851
+ const getFilterIconColor = (value) => {
852
+ switch (value) {
853
+ case "trigger.fired":
854
+ return "text-emerald-500";
855
+ case "trigger.registered":
856
+ return "text-blue-500";
857
+ case "trigger.updated":
858
+ return "text-amber-500";
859
+ default:
860
+ return "text-gray-500";
861
+ }
862
+ };
863
+ const getFilterLabel = (value) => {
864
+ const option = eventTypeFilterOptions.find((o) => o.value === value);
865
+ return option?.label || "All Events";
866
+ };
867
+ const copyToClipboard = async (text) => {
868
+ try {
869
+ await navigator.clipboard.writeText(text);
870
+ } catch (err) {
871
+ console.error("Failed to copy:", err);
872
+ }
873
+ };
874
+ const formatInterval = (seconds) => {
875
+ const units = [
876
+ { value: 86400, label: "day", pluralLabel: "days" },
877
+ { value: 3600, label: "hour", pluralLabel: "hours" },
878
+ { value: 60, label: "minute", pluralLabel: "minutes" },
879
+ { value: 1, label: "second", pluralLabel: "seconds" }
880
+ ];
881
+ for (const unit of units) {
882
+ if (seconds >= unit.value && seconds % unit.value === 0) {
883
+ const count = seconds / unit.value;
884
+ const label = count === 1 ? unit.label : unit.pluralLabel;
885
+ return `Every ${count} ${label}`;
886
+ }
887
+ }
888
+ if (seconds >= 60) {
889
+ const minutes = Math.floor(seconds / 60);
890
+ const remainingSeconds = seconds % 60;
891
+ if (remainingSeconds === 0) {
892
+ return `Every ${minutes} ${minutes === 1 ? "minute" : "minutes"}`;
893
+ }
894
+ return `Every ${minutes}m ${remainingSeconds}s`;
895
+ }
896
+ return `Every ${seconds} ${seconds === 1 ? "second" : "seconds"}`;
897
+ };
898
+ </script>