@nvent-addon/app 0.5.15 → 1.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +3 -2
  3. package/dist/runtime/app/components/DashboardCard.d.vue.ts +1 -1
  4. package/dist/runtime/app/components/DashboardCard.vue.d.ts +1 -1
  5. package/dist/runtime/app/composables/useWorkers.d.ts +57 -0
  6. package/dist/runtime/app/composables/useWorkers.js +42 -0
  7. package/dist/runtime/app/pages/dashboard.vue +1 -654
  8. package/dist/runtime/app/pages/index.vue +25 -41
  9. package/dist/runtime/app/pages/workers.vue +458 -0
  10. package/dist/runtime/server/api/_workers/index.get.d.ts +8 -0
  11. package/dist/runtime/server/api/_workers/index.get.js +14 -0
  12. package/package.json +13 -12
  13. package/dist/runtime/app/components/ComponentRouter.d.vue.ts +0 -46
  14. package/dist/runtime/app/components/ComponentRouter.vue +0 -26
  15. package/dist/runtime/app/components/ComponentRouter.vue.d.ts +0 -46
  16. package/dist/runtime/app/components/ComponentShell.d.vue.ts +0 -23
  17. package/dist/runtime/app/components/ComponentShell.vue +0 -97
  18. package/dist/runtime/app/components/ComponentShell.vue.d.ts +0 -23
  19. package/dist/runtime/app/components/ConfirmDialog.d.vue.ts +0 -33
  20. package/dist/runtime/app/components/ConfirmDialog.vue +0 -120
  21. package/dist/runtime/app/components/ConfirmDialog.vue.d.ts +0 -33
  22. package/dist/runtime/app/composables/useComponentRouter.d.ts +0 -46
  23. package/dist/runtime/app/composables/useComponentRouter.js +0 -248
  24. package/dist/runtime/app/pages/flows/[name].vue +0 -750
  25. package/dist/runtime/app/pages/flows/index.d.vue.ts +0 -3
  26. package/dist/runtime/app/pages/flows/index.vue +0 -381
  27. package/dist/runtime/app/pages/flows/index.vue.d.ts +0 -3
  28. package/dist/runtime/app/pages/queues/index.d.vue.ts +0 -3
  29. package/dist/runtime/app/pages/queues/index.vue +0 -236
  30. package/dist/runtime/app/pages/queues/index.vue.d.ts +0 -3
  31. package/dist/runtime/app/pages/queues/job.d.vue.ts +0 -3
  32. package/dist/runtime/app/pages/queues/job.vue +0 -261
  33. package/dist/runtime/app/pages/queues/job.vue.d.ts +0 -3
  34. package/dist/runtime/app/pages/queues/jobs.d.vue.ts +0 -3
  35. package/dist/runtime/app/pages/queues/jobs.vue +0 -595
  36. package/dist/runtime/app/pages/queues/jobs.vue.d.ts +0 -3
  37. package/dist/runtime/app/pages/settings/scheduler.d.vue.ts +0 -3
  38. package/dist/runtime/app/pages/settings/scheduler.vue +0 -310
  39. package/dist/runtime/app/pages/settings/scheduler.vue.d.ts +0 -3
  40. package/dist/runtime/app/pages/triggers/[name]/edit.d.vue.ts +0 -3
  41. package/dist/runtime/app/pages/triggers/[name]/edit.vue +0 -429
  42. package/dist/runtime/app/pages/triggers/[name]/edit.vue.d.ts +0 -3
  43. package/dist/runtime/app/pages/triggers/[name].d.vue.ts +0 -3
  44. package/dist/runtime/app/pages/triggers/[name].vue +0 -870
  45. package/dist/runtime/app/pages/triggers/[name].vue.d.ts +0 -3
  46. package/dist/runtime/app/pages/triggers/index.d.vue.ts +0 -3
  47. package/dist/runtime/app/pages/triggers/index.vue +0 -525
  48. package/dist/runtime/app/pages/triggers/index.vue.d.ts +0 -3
  49. package/dist/runtime/app/pages/triggers/new.d.vue.ts +0 -3
  50. package/dist/runtime/app/pages/triggers/new.vue +0 -610
  51. package/dist/runtime/app/pages/triggers/new.vue.d.ts +0 -3
  52. package/dist/runtime/server/api/_flows/[name]/clear-history.delete.d.ts +0 -10
  53. package/dist/runtime/server/api/_flows/[name]/clear-history.delete.js +0 -49
  54. package/dist/runtime/server/api/_flows/[name]/runs/[runId]/cancel.post.d.ts +0 -2
  55. package/dist/runtime/server/api/_flows/[name]/runs/[runId]/cancel.post.js +0 -21
  56. package/dist/runtime/server/api/_flows/[name]/runs/[runId]/restart.post.d.ts +0 -2
  57. package/dist/runtime/server/api/_flows/[name]/runs/[runId]/restart.post.js +0 -21
  58. package/dist/runtime/server/api/_flows/[name]/runs.get.d.ts +0 -17
  59. package/dist/runtime/server/api/_flows/[name]/runs.get.js +0 -64
  60. package/dist/runtime/server/api/_flows/[name]/start.post.d.ts +0 -2
  61. package/dist/runtime/server/api/_flows/[name]/start.post.js +0 -9
  62. package/dist/runtime/server/api/_flows/index.get.d.ts +0 -7
  63. package/dist/runtime/server/api/_flows/index.get.js +0 -5
  64. package/dist/runtime/server/api/_flows/recent-runs.get.d.ts +0 -15
  65. package/dist/runtime/server/api/_flows/recent-runs.get.js +0 -67
  66. package/dist/runtime/server/api/_flows/ws.d.ts +0 -80
  67. package/dist/runtime/server/api/_flows/ws.js +0 -309
  68. package/dist/runtime/server/api/_queues/[name]/job/[id].get.d.ts +0 -2
  69. package/dist/runtime/server/api/_queues/[name]/job/[id].get.js +0 -14
  70. package/dist/runtime/server/api/_queues/[name]/job/index.get.d.ts +0 -2
  71. package/dist/runtime/server/api/_queues/[name]/job/index.get.js +0 -39
  72. package/dist/runtime/server/api/_queues/index.get.d.ts +0 -2
  73. package/dist/runtime/server/api/_queues/index.get.js +0 -106
  74. package/dist/runtime/server/api/_queues/ws.d.ts +0 -48
  75. package/dist/runtime/server/api/_queues/ws.js +0 -215
  76. package/dist/runtime/server/api/_scheduler/jobs.get.d.ts +0 -19
  77. package/dist/runtime/server/api/_scheduler/jobs.get.js +0 -36
  78. package/dist/runtime/server/api/_triggers/[name]/events.get.d.ts +0 -6
  79. package/dist/runtime/server/api/_triggers/[name]/events.get.js +0 -43
  80. package/dist/runtime/server/api/_triggers/[name]/index.get.d.ts +0 -6
  81. package/dist/runtime/server/api/_triggers/[name]/index.get.js +0 -76
  82. package/dist/runtime/server/api/_triggers/[name].delete.d.ts +0 -7
  83. package/dist/runtime/server/api/_triggers/[name].delete.js +0 -37
  84. package/dist/runtime/server/api/_triggers/[name].patch.d.ts +0 -7
  85. package/dist/runtime/server/api/_triggers/[name].patch.js +0 -117
  86. package/dist/runtime/server/api/_triggers/index.get.d.ts +0 -6
  87. package/dist/runtime/server/api/_triggers/index.get.js +0 -44
  88. package/dist/runtime/server/api/_triggers/index.post.d.ts +0 -7
  89. package/dist/runtime/server/api/_triggers/index.post.js +0 -124
  90. package/dist/runtime/server/api/_triggers/stats.get.d.ts +0 -6
  91. package/dist/runtime/server/api/_triggers/stats.get.js +0 -41
  92. package/dist/runtime/server/api/_triggers/ws.d.ts +0 -74
  93. package/dist/runtime/server/api/_triggers/ws.js +0 -315
  94. /package/dist/runtime/app/pages/{flows/[name].d.vue.ts → workers.d.vue.ts} +0 -0
  95. /package/dist/runtime/app/pages/{flows/[name].vue.d.ts → workers.vue.d.ts} +0 -0
@@ -1,64 +1,48 @@
1
1
  <template>
2
- <NventComponentRouter
2
+ <NUtilsComponentRouter
3
3
  v-slot="{ component }"
4
4
  :routes="routes"
5
5
  base="p"
6
6
  mode="query"
7
7
  >
8
- <NventComponentShell
8
+ <NUtilsComponentShell
9
9
  orientation="horizontal"
10
10
  :items="navItems"
11
11
  >
12
+ <template #leading>
13
+ <div class="px-4 py-3 border-gray-200 dark:border-gray-800 flex items-center gap-2.5">
14
+ <div class="flex items-center justify-center w-7 h-7 rounded-lg bg-primary-100 dark:bg-primary-900/40 shrink-0">
15
+ <UIcon name="i-lucide-scan-line" class="w-4 h-4 text-primary-600 dark:text-primary-400" />
16
+ </div>
17
+ <span class="text-sm font-semibold tracking-tight">NVENT</span>
18
+ </div>
19
+ </template>
20
+
21
+ <template #trailing>
22
+ <div class="mt-auto px-3 py-3 border-t border-gray-200 dark:border-gray-800">
23
+ <!-- TODO -->
24
+ </div>
25
+ </template>
26
+
12
27
  <component :is="component" />
13
- </NventComponentShell>
14
- </NventComponentRouter>
28
+ </NUtilsComponentShell>
29
+ </NUtilsComponentRouter>
30
+
31
+ <!-- Confirm Modal from nutils -->
32
+ <NUtilsConfirmModal />
15
33
  </template>
16
34
 
17
35
  <script setup>
18
36
  import Dashboard from "./dashboard.vue";
19
- import Queue from "./queues/index.vue";
20
- import QueueJobs from "./queues/jobs.vue";
21
- import QueueJob from "./queues/job.vue";
22
- import Flows from "./flows/index.vue";
23
- import FlowDetail from "./flows/[name].vue";
24
- import Triggers from "./triggers/index.vue";
25
- import TriggerDetail from "./triggers/[name].vue";
26
- import TriggerNew from "./triggers/new.vue";
27
- import TriggerEdit from "./triggers/[name]/edit.vue";
28
- import SettingsScheduler from "./settings/scheduler.vue";
37
+ import Workers from "./workers.vue";
29
38
  const navItems = [
30
39
  [
31
40
  { label: "Dashboard", icon: "i-lucide-layout-dashboard", path: "/" },
32
- { label: "Queues", icon: "i-lucide-app-window", path: "/queues" },
33
- { label: "Flows", icon: "i-lucide-git-branch", path: "/flows" },
34
- { label: "Triggers", icon: "i-lucide-zap", path: "/triggers" }
35
- ],
36
- [
37
- {
38
- label: "Settings",
39
- icon: "i-lucide-settings",
40
- children: [
41
- {
42
- label: "Scheduler",
43
- description: "Monitor scheduled jobs and their execution",
44
- icon: "i-lucide-clock",
45
- path: "/settings/scheduler"
46
- }
47
- ]
48
- }
41
+ { label: "Workers", icon: "i-lucide-server", path: "/workers" }
49
42
  ]
50
43
  ];
51
44
  const routes = {
52
45
  "/": Dashboard,
53
- "/queues": Queue,
54
- "/queues/:name/jobs": QueueJobs,
55
- "/queues/:name/jobs/:id": QueueJob,
56
- "/flows": Flows,
57
- "/flows/:name": FlowDetail,
58
- "/triggers": Triggers,
59
- "/triggers/new": TriggerNew,
60
- "/triggers/:name/edit": TriggerEdit,
61
- "/triggers/:name": TriggerDetail,
62
- "/settings/scheduler": SettingsScheduler
46
+ "/workers": Workers
63
47
  };
64
48
  </script>
@@ -0,0 +1,458 @@
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
+ <h1 class="text-lg font-semibold">
7
+ Workers
8
+ </h1>
9
+ <div class="flex items-center gap-3">
10
+ <div
11
+ v-if="status === 'pending'"
12
+ class="flex items-center gap-1.5 text-xs text-amber-600 dark:text-amber-400"
13
+ >
14
+ <div class="w-2 h-2 rounded-full bg-amber-400 animate-pulse" />
15
+ <span>Connecting...</span>
16
+ </div>
17
+ <div
18
+ v-else-if="engineOnline"
19
+ class="flex items-center gap-1.5 text-xs text-emerald-600 dark:text-emerald-400"
20
+ >
21
+ <div class="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
22
+ <span>Engine online</span>
23
+ </div>
24
+ <div
25
+ v-else
26
+ class="flex items-center gap-1.5 text-xs text-red-500 dark:text-red-400"
27
+ >
28
+ <div class="w-2 h-2 rounded-full bg-red-500" />
29
+ <span>Engine offline</span>
30
+ </div>
31
+ <UButton
32
+ icon="i-lucide-refresh-cw"
33
+ size="xs"
34
+ color="neutral"
35
+ variant="ghost"
36
+ :loading="status === 'pending'"
37
+ @click="refresh"
38
+ />
39
+ </div>
40
+ </div>
41
+ </div>
42
+
43
+ <!-- Content -->
44
+ <div class="flex-1 min-h-0 overflow-y-auto">
45
+ <div class="max-w-5xl mx-auto p-6 space-y-6">
46
+ <!-- Stat cards -->
47
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
48
+ <NventStatCard
49
+ icon="i-lucide-server"
50
+ :count="namedWorkers.length"
51
+ label="Workers"
52
+ variant="blue"
53
+ />
54
+ <NventStatCard
55
+ icon="i-lucide-zap"
56
+ :count="totalActiveInvocations"
57
+ label="Active invocations"
58
+ variant="amber"
59
+ />
60
+ <NventStatCard
61
+ icon="i-lucide-cpu"
62
+ :count="totalFunctions"
63
+ label="Functions"
64
+ variant="purple"
65
+ />
66
+ <NventStatCard
67
+ icon="i-lucide-shield-check"
68
+ :count="`${healthyComponentCount}/${totalComponentCount}`"
69
+ label="Components healthy"
70
+ variant="emerald"
71
+ />
72
+ </div>
73
+
74
+ <!-- Engine health (collapsible) -->
75
+ <div
76
+ v-if="data?.health"
77
+ class="bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 overflow-hidden"
78
+ >
79
+ <button
80
+ type="button"
81
+ class="w-full flex items-center gap-2 px-4 py-3 text-left hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors"
82
+ @click="showHealth = !showHealth"
83
+ >
84
+ <UIcon
85
+ name="i-lucide-heart-pulse"
86
+ class="w-4 h-4 text-emerald-500 shrink-0"
87
+ />
88
+ <h2 class="text-sm font-semibold flex-1">
89
+ Engine Health
90
+ </h2>
91
+ <UBadge
92
+ :color="data.health.status === 'healthy' ? 'success' : 'error'"
93
+ variant="subtle"
94
+ size="xs"
95
+ >
96
+ {{ data.health.status }}
97
+ </UBadge>
98
+ <span class="text-xs text-gray-400 mx-2 font-mono">v{{ data.health.version }}</span>
99
+ <span class="text-xs text-gray-400 mr-1">updated {{ formatRelativeMs(data.health.timestamp) }}</span>
100
+ <UIcon
101
+ :name="showHealth ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'"
102
+ class="w-3.5 h-3.5 text-gray-400 shrink-0"
103
+ />
104
+ </button>
105
+ <div
106
+ v-if="showHealth"
107
+ class="border-t border-gray-100 dark:border-gray-800 px-4 pb-4"
108
+ >
109
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-3 mt-3">
110
+ <div
111
+ v-for="(component, name) in data.health.components"
112
+ :key="name"
113
+ class="rounded-md border border-gray-100 dark:border-gray-800 p-2.5"
114
+ >
115
+ <div class="flex items-center gap-1.5 mb-1.5">
116
+ <div
117
+ class="w-1.5 h-1.5 rounded-full shrink-0"
118
+ :class="component.status === 'healthy' ? 'bg-emerald-500' : 'bg-red-500'"
119
+ />
120
+ <span class="text-xs font-semibold capitalize">{{ name }}</span>
121
+ </div>
122
+ <div class="space-y-0.5">
123
+ <div
124
+ v-for="(val, key) in component.details"
125
+ :key="key"
126
+ class="flex items-center justify-between gap-2 text-xs"
127
+ >
128
+ <span class="text-gray-500 dark:text-gray-400 truncate">{{ String(key).replace(/_/g, " ") }}</span>
129
+ <span class="font-mono font-medium text-gray-700 dark:text-gray-200 shrink-0">{{ val }}</span>
130
+ </div>
131
+ </div>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ </div>
136
+
137
+ <!-- Workers list -->
138
+ <div>
139
+ <div class="flex items-center justify-between mb-3">
140
+ <h2 class="text-sm font-semibold">
141
+ Registered Workers
142
+ <span class="text-gray-400 font-normal ml-1">({{ namedWorkers.length }})</span>
143
+ </h2>
144
+ <UButton
145
+ v-if="anonymousWorkers.length"
146
+ size="xs"
147
+ variant="ghost"
148
+ color="neutral"
149
+ @click="showAnonymous = !showAnonymous"
150
+ >
151
+ {{ showAnonymous ? "Hide" : "Show" }} {{ anonymousWorkers.length }} anonymous
152
+ </UButton>
153
+ </div>
154
+
155
+ <!-- Loading skeleton -->
156
+ <div
157
+ v-if="status === 'pending' && !data?.workers?.length"
158
+ class="space-y-2"
159
+ >
160
+ <div
161
+ v-for="n in 3"
162
+ :key="n"
163
+ class="bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 p-4 animate-pulse"
164
+ >
165
+ <div class="flex items-center gap-3">
166
+ <div class="w-8 h-8 rounded-full bg-gray-200 dark:bg-gray-700" />
167
+ <div class="flex-1 space-y-1.5">
168
+ <div class="h-3 bg-gray-200 dark:bg-gray-700 rounded w-1/3" />
169
+ <div class="h-2.5 bg-gray-100 dark:bg-gray-800 rounded w-1/2" />
170
+ </div>
171
+ </div>
172
+ </div>
173
+ </div>
174
+
175
+ <!-- Empty state -->
176
+ <div
177
+ v-else-if="!namedWorkers.length && status !== 'pending'"
178
+ class="bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 p-8 text-center text-gray-500"
179
+ >
180
+ <UIcon
181
+ name="i-lucide-server-off"
182
+ class="w-10 h-10 mx-auto mb-3 opacity-40"
183
+ />
184
+ <p class="text-sm">
185
+ No workers registered
186
+ </p>
187
+ </div>
188
+
189
+ <!-- Worker cards -->
190
+ <div
191
+ v-else
192
+ class="space-y-2"
193
+ >
194
+ <div
195
+ v-for="worker in namedWorkers"
196
+ :key="worker.id"
197
+ class="bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 overflow-hidden"
198
+ >
199
+ <!-- Card header row -->
200
+ <button
201
+ type="button"
202
+ class="w-full flex items-center gap-3 px-4 py-3 text-left hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors"
203
+ @click="toggleExpand(worker.id)"
204
+ >
205
+ <div
206
+ class="w-2 h-2 rounded-full shrink-0"
207
+ :class="statusDot(worker.status)"
208
+ />
209
+ <UIcon
210
+ :name="runtimeIcon(worker.runtime)"
211
+ class="w-4 h-4 shrink-0"
212
+ :class="runtimeIconClass(worker.runtime)"
213
+ />
214
+ <span class="text-sm font-semibold text-gray-900 dark:text-gray-100 truncate flex-1 min-w-0">
215
+ {{ worker.name }}
216
+ </span>
217
+ <!-- Active invocations pulse -->
218
+ <span
219
+ v-if="worker.active_invocations > 0"
220
+ class="flex items-center gap-1 text-xs text-amber-600 dark:text-amber-400 shrink-0"
221
+ >
222
+ <div class="w-1.5 h-1.5 rounded-full bg-amber-500 animate-pulse" />
223
+ {{ worker.active_invocations }} active
224
+ </span>
225
+ <span class="text-xs text-gray-400 shrink-0 hidden sm:block">
226
+ {{ worker.function_count }} fn{{ worker.function_count !== 1 ? "s" : "" }}
227
+ </span>
228
+ <span
229
+ v-if="worker.version"
230
+ class="text-xs text-gray-400 font-mono shrink-0 hidden md:block"
231
+ >v{{ worker.version }}</span>
232
+ <span class="text-xs text-gray-400 shrink-0">{{ formatRelativeMs(worker.connected_at_ms) }}</span>
233
+ <UIcon
234
+ :name="expandedId === worker.id ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'"
235
+ class="w-3.5 h-3.5 text-gray-400 shrink-0"
236
+ />
237
+ </button>
238
+
239
+ <!-- Expanded detail -->
240
+ <div
241
+ v-if="expandedId === worker.id"
242
+ class="border-t border-gray-100 dark:border-gray-800 px-4 pb-4"
243
+ >
244
+ <!-- System info -->
245
+ <div class="flex flex-wrap gap-x-6 gap-y-1 mt-3 text-xs">
246
+ <span v-if="worker.os">
247
+ <span class="font-medium text-gray-600 dark:text-gray-300">OS </span>
248
+ <span class="text-gray-500 dark:text-gray-400">{{ worker.os }}</span>
249
+ </span>
250
+ <span v-if="worker.pid != null">
251
+ <span class="font-medium text-gray-600 dark:text-gray-300">PID </span>
252
+ <span class="text-gray-500 dark:text-gray-400 font-mono">{{ worker.pid }}</span>
253
+ </span>
254
+ <span v-if="worker.ip_address">
255
+ <span class="font-medium text-gray-600 dark:text-gray-300">IP </span>
256
+ <span class="text-gray-500 dark:text-gray-400 font-mono">{{ worker.ip_address }}</span>
257
+ </span>
258
+ <span class="text-gray-300 dark:text-gray-600 font-mono">{{ worker.id }}</span>
259
+ </div>
260
+
261
+ <!-- Resource metrics -->
262
+ <div
263
+ v-if="worker.latest_metrics"
264
+ class="mt-4 grid grid-cols-2 md:grid-cols-4 gap-3"
265
+ >
266
+ <!-- CPU -->
267
+ <div class="rounded-md bg-gray-50 dark:bg-gray-800/50 p-2.5">
268
+ <div class="flex items-center justify-between mb-1.5">
269
+ <span class="text-xs font-medium text-gray-500 dark:text-gray-400">CPU</span>
270
+ <span class="text-xs font-mono font-semibold text-gray-800 dark:text-gray-200">{{ worker.latest_metrics.cpu_percent.toFixed(1) }}%</span>
271
+ </div>
272
+ <div class="h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
273
+ <div
274
+ class="h-full rounded-full transition-all duration-500"
275
+ :class="worker.latest_metrics.cpu_percent > 80 ? 'bg-red-500' : worker.latest_metrics.cpu_percent > 50 ? 'bg-amber-500' : 'bg-emerald-500'"
276
+ :style="{ width: `${Math.min(worker.latest_metrics.cpu_percent, 100)}%` }"
277
+ />
278
+ </div>
279
+ </div>
280
+ <!-- Heap -->
281
+ <div class="rounded-md bg-gray-50 dark:bg-gray-800/50 p-2.5">
282
+ <div class="flex items-center justify-between mb-1.5">
283
+ <span class="text-xs font-medium text-gray-500 dark:text-gray-400">Heap</span>
284
+ <span class="text-xs font-mono font-semibold text-gray-800 dark:text-gray-200">{{ formatBytes(worker.latest_metrics.memory_heap_used) }} / {{ formatBytes(worker.latest_metrics.memory_heap_total) }}</span>
285
+ </div>
286
+ <div class="h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
287
+ <div
288
+ class="h-full bg-blue-500 rounded-full transition-all duration-500"
289
+ :style="{ width: `${Math.min(worker.latest_metrics.memory_heap_used / worker.latest_metrics.memory_heap_total * 100, 100)}%` }"
290
+ />
291
+ </div>
292
+ </div>
293
+ <!-- RSS -->
294
+ <div class="rounded-md bg-gray-50 dark:bg-gray-800/50 p-2.5">
295
+ <span class="text-xs font-medium text-gray-500 dark:text-gray-400">RSS</span>
296
+ <p class="text-xs font-mono font-semibold text-gray-800 dark:text-gray-200 mt-0.5">{{ formatBytes(worker.latest_metrics.memory_rss) }}</p>
297
+ </div>
298
+ <!-- Event loop lag -->
299
+ <div class="rounded-md bg-gray-50 dark:bg-gray-800/50 p-2.5">
300
+ <span class="text-xs font-medium text-gray-500 dark:text-gray-400">Event loop lag</span>
301
+ <p
302
+ class="text-xs font-mono font-semibold mt-0.5"
303
+ :class="worker.latest_metrics.event_loop_lag_ms > 100 ? 'text-red-500' : worker.latest_metrics.event_loop_lag_ms > 20 ? 'text-amber-500' : 'text-gray-800 dark:text-gray-200'"
304
+ >
305
+ {{ worker.latest_metrics.event_loop_lag_ms.toFixed(2) }} ms
306
+ </p>
307
+ </div>
308
+ </div>
309
+
310
+ <!-- Functions list -->
311
+ <div
312
+ v-if="worker.functions.length"
313
+ class="mt-4"
314
+ >
315
+ <p class="text-xs font-semibold text-gray-500 dark:text-gray-400 mb-2">
316
+ Functions ({{ worker.functions.length }})
317
+ </p>
318
+ <div class="space-y-1">
319
+ <div
320
+ v-for="fn in worker.functions"
321
+ :key="fn"
322
+ class="flex items-center gap-2 text-xs"
323
+ >
324
+ <UBadge
325
+ v-if="parseFn(fn).trigger"
326
+ :color="triggerColor(parseFn(fn).trigger)"
327
+ variant="subtle"
328
+ size="xs"
329
+ class="shrink-0 font-mono"
330
+ >
331
+ {{ parseFn(fn).trigger }}
332
+ </UBadge>
333
+ <span
334
+ v-else
335
+ class="w-[42px] shrink-0"
336
+ />
337
+ <span class="text-gray-600 dark:text-gray-300 font-mono truncate flex-1">{{ parseFn(fn).path }}</span>
338
+ <span
339
+ v-if="parseFn(fn).config"
340
+ class="text-gray-400 dark:text-gray-500 font-mono shrink-0 truncate max-w-[220px]"
341
+ >{{ parseFn(fn).config }}</span>
342
+ </div>
343
+ </div>
344
+ </div>
345
+ <p
346
+ v-else
347
+ class="mt-3 text-xs text-gray-400"
348
+ >
349
+ No functions registered
350
+ </p>
351
+ </div>
352
+ </div>
353
+
354
+ <!-- Anonymous connections -->
355
+ <template v-if="showAnonymous && anonymousWorkers.length">
356
+ <div class="flex items-center gap-2 mt-4 mb-1">
357
+ <div class="flex-1 h-px bg-gray-200 dark:bg-gray-700" />
358
+ <span class="text-xs text-gray-400 shrink-0">{{ anonymousWorkers.length }} anonymous connection(s)</span>
359
+ <div class="flex-1 h-px bg-gray-200 dark:bg-gray-700" />
360
+ </div>
361
+ <div
362
+ v-for="worker in anonymousWorkers"
363
+ :key="worker.id"
364
+ class="bg-gray-50 dark:bg-gray-900/50 rounded-lg border border-dashed border-gray-200 dark:border-gray-800 px-4 py-2.5 flex items-center gap-3 opacity-60"
365
+ >
366
+ <div
367
+ class="w-2 h-2 rounded-full shrink-0"
368
+ :class="statusDot(worker.status)"
369
+ />
370
+ <span class="text-xs text-gray-500 dark:text-gray-400 font-mono flex-1 truncate">{{ worker.id }}</span>
371
+ <span class="text-xs text-gray-400">{{ formatRelativeMs(worker.connected_at_ms) }}</span>
372
+ </div>
373
+ </template>
374
+ </div>
375
+ </div>
376
+
377
+ <!-- Error notice -->
378
+ <UAlert
379
+ v-if="data?.healthError || data?.workersError"
380
+ icon="i-lucide-triangle-alert"
381
+ color="error"
382
+ variant="subtle"
383
+ title="Engine unreachable"
384
+ :description="data?.healthError || data?.workersError || void 0"
385
+ />
386
+ </div>
387
+ </div>
388
+ </div>
389
+ </template>
390
+
391
+ <script setup>
392
+ import { computed, ref } from "#imports";
393
+ import { useWorkers } from "../composables/useWorkers";
394
+ const { data, refresh, status, engineOnline } = useWorkers(5e3);
395
+ const showHealth = ref(true);
396
+ const showAnonymous = ref(false);
397
+ const expandedId = ref(null);
398
+ function toggleExpand(id) {
399
+ expandedId.value = expandedId.value === id ? null : id;
400
+ }
401
+ const namedWorkers = computed(
402
+ () => (data.value?.workers ?? []).filter((w) => w.name !== null)
403
+ );
404
+ const anonymousWorkers = computed(
405
+ () => (data.value?.workers ?? []).filter((w) => w.name === null)
406
+ );
407
+ const totalActiveInvocations = computed(
408
+ () => (data.value?.workers ?? []).reduce((s, w) => s + (w.active_invocations ?? 0), 0)
409
+ );
410
+ const totalFunctions = computed(
411
+ () => namedWorkers.value.reduce((s, w) => s + (w.function_count ?? 0), 0)
412
+ );
413
+ const totalComponentCount = computed(
414
+ () => Object.keys(data.value?.health?.components ?? {}).length
415
+ );
416
+ const healthyComponentCount = computed(
417
+ () => Object.values(data.value?.health?.components ?? {}).filter((c) => c.status === "healthy").length
418
+ );
419
+ function statusDot(status2) {
420
+ if (status2 === "connected" || status2 === "idle") return "bg-emerald-500";
421
+ if (status2 === "disconnected") return "bg-red-500";
422
+ return "bg-gray-400";
423
+ }
424
+ function runtimeIcon(runtime) {
425
+ if (runtime === "node") return "i-devicon-nodejs";
426
+ if (runtime === "python") return "i-devicon-python";
427
+ return "i-lucide-cpu";
428
+ }
429
+ function runtimeIconClass(runtime) {
430
+ if (runtime === "node") return "text-green-500";
431
+ if (runtime === "python") return "text-blue-400";
432
+ return "text-gray-400";
433
+ }
434
+ function parseFn(id) {
435
+ const m = id.match(/^(.+?)::trigger::(\w+)\((.+)\)$/);
436
+ if (m) return { path: m[1], trigger: m[2], config: m[3] };
437
+ return { path: id, trigger: null, config: null };
438
+ }
439
+ function triggerColor(trigger) {
440
+ if (trigger === "http") return "info";
441
+ if (trigger === "queue") return "success";
442
+ if (trigger === "cron") return "warning";
443
+ return "neutral";
444
+ }
445
+ function formatBytes(bytes) {
446
+ if (bytes < 1024) return `${bytes} B`;
447
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
448
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
449
+ }
450
+ function formatRelativeMs(ms) {
451
+ const ts = typeof ms === "number" ? ms : new Date(ms).getTime();
452
+ const diff = Math.round((Date.now() - ts) / 1e3);
453
+ if (diff < 5) return "just now";
454
+ if (diff < 60) return `${diff}s ago`;
455
+ if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
456
+ return `${Math.floor(diff / 3600)}h ago`;
457
+ }
458
+ </script>
@@ -0,0 +1,8 @@
1
+ /**
2
+ * GET /api/_workers
3
+ *
4
+ * Combines the iii engine health check with the worker list so the
5
+ * production Workers page can show both in a single request.
6
+ */
7
+ declare const _default: any;
8
+ export default _default;
@@ -0,0 +1,14 @@
1
+ import { defineEventHandler, useIii } from "#imports";
2
+ export default defineEventHandler(async () => {
3
+ const iii = useIii();
4
+ const [health, workers] = await Promise.allSettled([
5
+ iii.trigger({ function_id: "engine::health::check", payload: {} }),
6
+ iii.trigger({ function_id: "engine::workers::list", payload: {} })
7
+ ]);
8
+ return {
9
+ health: health.status === "fulfilled" ? health.value : null,
10
+ healthError: health.status === "rejected" ? String(health.reason) : null,
11
+ workers: workers.status === "fulfilled" ? workers.value?.workers ?? [] : [],
12
+ workersError: workers.status === "rejected" ? String(workers.reason) : null
13
+ };
14
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nvent-addon/app",
3
- "version": "0.5.15",
3
+ "version": "1.0.0-alpha.10",
4
4
  "description": "nvent app module for Nuxt.js",
5
5
  "repository": "DevJoghurt/nvent",
6
6
  "license": "MIT",
@@ -23,26 +23,27 @@
23
23
  "prepack": "pnpm build"
24
24
  },
25
25
  "dependencies": {
26
- "@iconify-json/devicon": "^1.2.52",
26
+ "@iconify-json/devicon": "^1.2.62",
27
27
  "@iconify-json/heroicons": "1.2.3",
28
- "@iconify-json/lucide": "^1.2.80",
29
- "@nuxt/kit": "4.2.2",
30
- "@nuxt/ui": "4.2.1",
28
+ "@iconify-json/lucide": "^1.2.105",
29
+ "@nhealth/nutils": "^0.0.3",
30
+ "@nuxt/kit": "4.4.4",
31
+ "@nuxt/ui": "4.7.1",
31
32
  "@vue-flow/background": "^1.3.2",
32
33
  "@vue-flow/controls": "^1.1.3",
33
- "@vue-flow/core": "^1.48.0",
34
+ "@vue-flow/core": "^1.48.2",
34
35
  "@vue-flow/minimap": "^1.5.4",
35
- "defu": "^6.1.4",
36
+ "defu": "^6.1.7",
36
37
  "json-editor-vue": "^0.18.1",
37
- "nuxt": "4.2.2",
38
+ "nuxt": "4.4.4",
38
39
  "pathe": "^2.0.3",
39
- "zod": "^4.1.13"
40
+ "zod": "^4.4.3"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@nuxt/module-builder": "^1.0.2",
43
- "@nuxt/schema": "4.2.2",
44
- "@types/node": "^25.0.0",
44
+ "@nuxt/schema": "4.4.4",
45
+ "@types/node": "^25.6.0",
45
46
  "typescript": "latest",
46
- "vitest": "^4.0.15"
47
+ "vitest": "^4.1.5"
47
48
  }
48
49
  }
@@ -1,46 +0,0 @@
1
- type ComponentRouteRecord = {
2
- path: string;
3
- component: any;
4
- name?: string;
5
- };
6
- type ComponentRouterMode = 'query' | 'hash' | 'memory';
7
- type __VLS_Props = {
8
- routes: ComponentRouteRecord[] | Record<string, any>;
9
- base?: string;
10
- mode?: ComponentRouterMode;
11
- initial?: string;
12
- debug?: boolean;
13
- };
14
- declare var __VLS_1: {
15
- component: any;
16
- route: any;
17
- push: any;
18
- };
19
- type __VLS_Slots = {} & {
20
- default?: (props: typeof __VLS_1) => any;
21
- };
22
- declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
23
- push: (path: string) => Promise<void>;
24
- replace: (path: string) => Promise<void>;
25
- route: import("vue").ShallowRef<{
26
- path: string;
27
- params: Record<string, string>;
28
- query: Record<string, any>;
29
- } | undefined, {
30
- path: string;
31
- params: Record<string, string>;
32
- query: Record<string, any>;
33
- } | undefined>;
34
- }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
35
- mode: ComponentRouterMode;
36
- base: string;
37
- debug: boolean;
38
- }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
39
- declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
40
- declare const _default: typeof __VLS_export;
41
- export default _default;
42
- type __VLS_WithSlots<T, S> = T & {
43
- new (): {
44
- $slots: S;
45
- };
46
- };