@nvent-addon/app 0.5.14 → 1.0.0-alpha.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.
- package/dist/module.json +1 -1
- package/dist/module.mjs +3 -2
- package/dist/runtime/app/components/DashboardCard.d.vue.ts +1 -1
- package/dist/runtime/app/components/DashboardCard.vue.d.ts +1 -1
- package/dist/runtime/app/composables/useWorkers.d.ts +57 -0
- package/dist/runtime/app/composables/useWorkers.js +42 -0
- package/dist/runtime/app/pages/dashboard.vue +1 -654
- package/dist/runtime/app/pages/index.vue +25 -41
- package/dist/runtime/app/pages/workers.vue +458 -0
- package/dist/runtime/server/api/_workers/index.get.d.ts +8 -0
- package/dist/runtime/server/api/_workers/index.get.js +14 -0
- package/package.json +12 -11
- package/dist/runtime/app/components/ComponentRouter.d.vue.ts +0 -46
- package/dist/runtime/app/components/ComponentRouter.vue +0 -26
- package/dist/runtime/app/components/ComponentRouter.vue.d.ts +0 -46
- package/dist/runtime/app/components/ComponentShell.d.vue.ts +0 -23
- package/dist/runtime/app/components/ComponentShell.vue +0 -97
- package/dist/runtime/app/components/ComponentShell.vue.d.ts +0 -23
- package/dist/runtime/app/components/ConfirmDialog.d.vue.ts +0 -33
- package/dist/runtime/app/components/ConfirmDialog.vue +0 -120
- package/dist/runtime/app/components/ConfirmDialog.vue.d.ts +0 -33
- package/dist/runtime/app/composables/useComponentRouter.d.ts +0 -46
- package/dist/runtime/app/composables/useComponentRouter.js +0 -248
- package/dist/runtime/app/pages/flows/[name].vue +0 -750
- package/dist/runtime/app/pages/flows/index.d.vue.ts +0 -3
- package/dist/runtime/app/pages/flows/index.vue +0 -381
- package/dist/runtime/app/pages/flows/index.vue.d.ts +0 -3
- package/dist/runtime/app/pages/queues/index.d.vue.ts +0 -3
- package/dist/runtime/app/pages/queues/index.vue +0 -236
- package/dist/runtime/app/pages/queues/index.vue.d.ts +0 -3
- package/dist/runtime/app/pages/queues/job.d.vue.ts +0 -3
- package/dist/runtime/app/pages/queues/job.vue +0 -261
- package/dist/runtime/app/pages/queues/job.vue.d.ts +0 -3
- package/dist/runtime/app/pages/queues/jobs.d.vue.ts +0 -3
- package/dist/runtime/app/pages/queues/jobs.vue +0 -595
- package/dist/runtime/app/pages/queues/jobs.vue.d.ts +0 -3
- package/dist/runtime/app/pages/settings/scheduler.d.vue.ts +0 -3
- package/dist/runtime/app/pages/settings/scheduler.vue +0 -310
- package/dist/runtime/app/pages/settings/scheduler.vue.d.ts +0 -3
- package/dist/runtime/app/pages/triggers/[name]/edit.d.vue.ts +0 -3
- package/dist/runtime/app/pages/triggers/[name]/edit.vue +0 -429
- package/dist/runtime/app/pages/triggers/[name]/edit.vue.d.ts +0 -3
- package/dist/runtime/app/pages/triggers/[name].d.vue.ts +0 -3
- package/dist/runtime/app/pages/triggers/[name].vue +0 -870
- package/dist/runtime/app/pages/triggers/[name].vue.d.ts +0 -3
- package/dist/runtime/app/pages/triggers/index.d.vue.ts +0 -3
- package/dist/runtime/app/pages/triggers/index.vue +0 -525
- package/dist/runtime/app/pages/triggers/index.vue.d.ts +0 -3
- package/dist/runtime/app/pages/triggers/new.d.vue.ts +0 -3
- package/dist/runtime/app/pages/triggers/new.vue +0 -610
- package/dist/runtime/app/pages/triggers/new.vue.d.ts +0 -3
- package/dist/runtime/server/api/_flows/[name]/clear-history.delete.d.ts +0 -10
- package/dist/runtime/server/api/_flows/[name]/clear-history.delete.js +0 -49
- package/dist/runtime/server/api/_flows/[name]/runs/[runId]/cancel.post.d.ts +0 -2
- package/dist/runtime/server/api/_flows/[name]/runs/[runId]/cancel.post.js +0 -21
- package/dist/runtime/server/api/_flows/[name]/runs/[runId]/restart.post.d.ts +0 -2
- package/dist/runtime/server/api/_flows/[name]/runs/[runId]/restart.post.js +0 -21
- package/dist/runtime/server/api/_flows/[name]/runs.get.d.ts +0 -17
- package/dist/runtime/server/api/_flows/[name]/runs.get.js +0 -64
- package/dist/runtime/server/api/_flows/[name]/start.post.d.ts +0 -2
- package/dist/runtime/server/api/_flows/[name]/start.post.js +0 -9
- package/dist/runtime/server/api/_flows/index.get.d.ts +0 -7
- package/dist/runtime/server/api/_flows/index.get.js +0 -5
- package/dist/runtime/server/api/_flows/recent-runs.get.d.ts +0 -15
- package/dist/runtime/server/api/_flows/recent-runs.get.js +0 -67
- package/dist/runtime/server/api/_flows/ws.d.ts +0 -80
- package/dist/runtime/server/api/_flows/ws.js +0 -309
- package/dist/runtime/server/api/_queues/[name]/job/[id].get.d.ts +0 -2
- package/dist/runtime/server/api/_queues/[name]/job/[id].get.js +0 -14
- package/dist/runtime/server/api/_queues/[name]/job/index.get.d.ts +0 -2
- package/dist/runtime/server/api/_queues/[name]/job/index.get.js +0 -39
- package/dist/runtime/server/api/_queues/index.get.d.ts +0 -2
- package/dist/runtime/server/api/_queues/index.get.js +0 -106
- package/dist/runtime/server/api/_queues/ws.d.ts +0 -48
- package/dist/runtime/server/api/_queues/ws.js +0 -215
- package/dist/runtime/server/api/_scheduler/jobs.get.d.ts +0 -19
- package/dist/runtime/server/api/_scheduler/jobs.get.js +0 -36
- package/dist/runtime/server/api/_triggers/[name]/events.get.d.ts +0 -6
- package/dist/runtime/server/api/_triggers/[name]/events.get.js +0 -43
- package/dist/runtime/server/api/_triggers/[name]/index.get.d.ts +0 -6
- package/dist/runtime/server/api/_triggers/[name]/index.get.js +0 -76
- package/dist/runtime/server/api/_triggers/[name].delete.d.ts +0 -7
- package/dist/runtime/server/api/_triggers/[name].delete.js +0 -37
- package/dist/runtime/server/api/_triggers/[name].patch.d.ts +0 -7
- package/dist/runtime/server/api/_triggers/[name].patch.js +0 -117
- package/dist/runtime/server/api/_triggers/index.get.d.ts +0 -6
- package/dist/runtime/server/api/_triggers/index.get.js +0 -44
- package/dist/runtime/server/api/_triggers/index.post.d.ts +0 -7
- package/dist/runtime/server/api/_triggers/index.post.js +0 -124
- package/dist/runtime/server/api/_triggers/stats.get.d.ts +0 -6
- package/dist/runtime/server/api/_triggers/stats.get.js +0 -41
- package/dist/runtime/server/api/_triggers/ws.d.ts +0 -74
- package/dist/runtime/server/api/_triggers/ws.js +0 -315
- /package/dist/runtime/app/pages/{flows/[name].d.vue.ts → workers.d.vue.ts} +0 -0
- /package/dist/runtime/app/pages/{flows/[name].vue.d.ts → workers.vue.d.ts} +0 -0
|
@@ -1,64 +1,48 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
2
|
+
<NUtilsComponentRouter
|
|
3
3
|
v-slot="{ component }"
|
|
4
4
|
:routes="routes"
|
|
5
5
|
base="p"
|
|
6
6
|
mode="query"
|
|
7
7
|
>
|
|
8
|
-
<
|
|
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
|
-
</
|
|
14
|
-
</
|
|
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
|
|
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: "
|
|
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
|
-
"/
|
|
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
|
+
<StatCard
|
|
49
|
+
icon="i-lucide-server"
|
|
50
|
+
:count="namedWorkers.length"
|
|
51
|
+
label="Workers"
|
|
52
|
+
variant="blue"
|
|
53
|
+
/>
|
|
54
|
+
<StatCard
|
|
55
|
+
icon="i-lucide-zap"
|
|
56
|
+
:count="totalActiveInvocations"
|
|
57
|
+
label="Active invocations"
|
|
58
|
+
variant="amber"
|
|
59
|
+
/>
|
|
60
|
+
<StatCard
|
|
61
|
+
icon="i-lucide-cpu"
|
|
62
|
+
:count="totalFunctions"
|
|
63
|
+
label="Functions"
|
|
64
|
+
variant="purple"
|
|
65
|
+
/>
|
|
66
|
+
<StatCard
|
|
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,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.listWorkers()
|
|
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" ? Array.isArray(workers.value) ? workers.value : [workers.value] : [],
|
|
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.
|
|
3
|
+
"version": "1.0.0-alpha.1",
|
|
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.
|
|
26
|
+
"@iconify-json/devicon": "^1.2.61",
|
|
27
27
|
"@iconify-json/heroicons": "1.2.3",
|
|
28
|
-
"@iconify-json/lucide": "^1.2.
|
|
29
|
-
"@
|
|
30
|
-
"@nuxt/
|
|
28
|
+
"@iconify-json/lucide": "^1.2.98",
|
|
29
|
+
"@nhealth/nutils": "^0.0.3",
|
|
30
|
+
"@nuxt/kit": "4.4.2",
|
|
31
|
+
"@nuxt/ui": "4.5.1",
|
|
31
32
|
"@vue-flow/background": "^1.3.2",
|
|
32
33
|
"@vue-flow/controls": "^1.1.3",
|
|
33
|
-
"@vue-flow/core": "^1.48.
|
|
34
|
+
"@vue-flow/core": "^1.48.2",
|
|
34
35
|
"@vue-flow/minimap": "^1.5.4",
|
|
35
36
|
"defu": "^6.1.4",
|
|
36
37
|
"json-editor-vue": "^0.18.1",
|
|
37
|
-
"nuxt": "4.
|
|
38
|
+
"nuxt": "4.4.2",
|
|
38
39
|
"pathe": "^2.0.3",
|
|
39
|
-
"zod": "^4.
|
|
40
|
+
"zod": "^4.3.6"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"@nuxt/module-builder": "^1.0.2",
|
|
43
|
-
"@nuxt/schema": "4.
|
|
44
|
-
"@types/node": "^25.
|
|
44
|
+
"@nuxt/schema": "4.4.2",
|
|
45
|
+
"@types/node": "^25.5.0",
|
|
45
46
|
"typescript": "latest",
|
|
46
|
-
"vitest": "^4.0
|
|
47
|
+
"vitest": "^4.1.0"
|
|
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
|
-
};
|