blokctl 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/build/index.d.ts +2 -0
- package/dist/commands/build/index.js +210 -0
- package/dist/commands/config/index.d.ts +1 -0
- package/dist/commands/config/index.js +46 -0
- package/dist/commands/cost/index.d.ts +1 -0
- package/dist/commands/cost/index.js +74 -0
- package/dist/commands/create/node.d.ts +2 -0
- package/dist/commands/create/node.js +541 -0
- package/dist/commands/create/project.d.ts +2 -0
- package/dist/commands/create/project.js +941 -0
- package/dist/commands/create/utils/Examples.d.ts +39 -0
- package/dist/commands/create/utils/Examples.js +983 -0
- package/dist/commands/create/workflow.d.ts +2 -0
- package/dist/commands/create/workflow.js +109 -0
- package/dist/commands/deploy/index.d.ts +2 -0
- package/dist/commands/deploy/index.js +176 -0
- package/dist/commands/dev/index.d.ts +2 -0
- package/dist/commands/dev/index.js +190 -0
- package/dist/commands/generate/GenerationAnalytics.d.ts +61 -0
- package/dist/commands/generate/GenerationAnalytics.js +162 -0
- package/dist/commands/generate/GenerationAnalytics.test.d.ts +1 -0
- package/dist/commands/generate/GenerationAnalytics.test.js +407 -0
- package/dist/commands/generate/NodeFileWriter.d.ts +5 -0
- package/dist/commands/generate/NodeFileWriter.js +240 -0
- package/dist/commands/generate/NodeGenerator.d.ts +20 -0
- package/dist/commands/generate/NodeGenerator.js +181 -0
- package/dist/commands/generate/NodeGenerator.test.d.ts +1 -0
- package/dist/commands/generate/NodeGenerator.test.js +101 -0
- package/dist/commands/generate/PromptVersioning.d.ts +25 -0
- package/dist/commands/generate/PromptVersioning.js +71 -0
- package/dist/commands/generate/PromptVersioning.test.d.ts +1 -0
- package/dist/commands/generate/PromptVersioning.test.js +120 -0
- package/dist/commands/generate/RegisterNode.d.ts +3 -0
- package/dist/commands/generate/RegisterNode.js +37 -0
- package/dist/commands/generate/RuntimeGenerator.d.ts +40 -0
- package/dist/commands/generate/RuntimeGenerator.js +369 -0
- package/dist/commands/generate/RuntimeGenerator.test.d.ts +1 -0
- package/dist/commands/generate/RuntimeGenerator.test.js +553 -0
- package/dist/commands/generate/TriggerGenerator.d.ts +22 -0
- package/dist/commands/generate/TriggerGenerator.js +220 -0
- package/dist/commands/generate/TriggerGenerator.test.d.ts +1 -0
- package/dist/commands/generate/TriggerGenerator.test.js +209 -0
- package/dist/commands/generate/WorkflowGenerator.d.ts +20 -0
- package/dist/commands/generate/WorkflowGenerator.js +131 -0
- package/dist/commands/generate/WorkflowGenerator.test.d.ts +1 -0
- package/dist/commands/generate/WorkflowGenerator.test.js +77 -0
- package/dist/commands/generate/e2e/NodeGenerator.e2e.test.d.ts +1 -0
- package/dist/commands/generate/e2e/NodeGenerator.e2e.test.js +216 -0
- package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.d.ts +1 -0
- package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.js +759 -0
- package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.d.ts +1 -0
- package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.js +295 -0
- package/dist/commands/generate/e2e/WorkflowGenerator.e2e.test.d.ts +1 -0
- package/dist/commands/generate/e2e/WorkflowGenerator.e2e.test.js +353 -0
- package/dist/commands/generate/index.d.ts +1 -0
- package/dist/commands/generate/index.js +418 -0
- package/dist/commands/generate/prompts/create-fn-node.system.d.ts +5 -0
- package/dist/commands/generate/prompts/create-fn-node.system.js +256 -0
- package/dist/commands/generate/prompts/create-node-manifest.system.d.ts +4 -0
- package/dist/commands/generate/prompts/create-node-manifest.system.js +41 -0
- package/dist/commands/generate/prompts/create-node.system.d.ts +5 -0
- package/dist/commands/generate/prompts/create-node.system.js +114 -0
- package/dist/commands/generate/prompts/create-readme.system.d.ts +4 -0
- package/dist/commands/generate/prompts/create-readme.system.js +83 -0
- package/dist/commands/generate/prompts/create-runtime.system.d.ts +5 -0
- package/dist/commands/generate/prompts/create-runtime.system.js +284 -0
- package/dist/commands/generate/prompts/create-trigger.system.d.ts +5 -0
- package/dist/commands/generate/prompts/create-trigger.system.js +293 -0
- package/dist/commands/generate/prompts/create-workflow.system.d.ts +5 -0
- package/dist/commands/generate/prompts/create-workflow.system.js +476 -0
- package/dist/commands/generate/prompts/register-node.system.d.ts +4 -0
- package/dist/commands/generate/prompts/register-node.system.js +26 -0
- package/dist/commands/generate/validators/CompilationValidator.d.ts +9 -0
- package/dist/commands/generate/validators/CompilationValidator.js +86 -0
- package/dist/commands/generate/validators/CompilationValidator.test.d.ts +1 -0
- package/dist/commands/generate/validators/CompilationValidator.test.js +161 -0
- package/dist/commands/generate/validators/NodeValidator.d.ts +18 -0
- package/dist/commands/generate/validators/NodeValidator.js +217 -0
- package/dist/commands/generate/validators/NodeValidator.test.d.ts +1 -0
- package/dist/commands/generate/validators/NodeValidator.test.js +281 -0
- package/dist/commands/generate/validators/WorkflowValidator.d.ts +6 -0
- package/dist/commands/generate/validators/WorkflowValidator.js +301 -0
- package/dist/commands/generate/validators/WorkflowValidator.test.d.ts +1 -0
- package/dist/commands/generate/validators/WorkflowValidator.test.js +647 -0
- package/dist/commands/generate/validators/index.d.ts +4 -0
- package/dist/commands/generate/validators/index.js +2 -0
- package/dist/commands/graph/index.d.ts +1 -0
- package/dist/commands/graph/index.js +69 -0
- package/dist/commands/install/index.d.ts +1 -0
- package/dist/commands/install/index.js +4 -0
- package/dist/commands/install/node.d.ts +4 -0
- package/dist/commands/install/node.js +136 -0
- package/dist/commands/install/workflow.d.ts +4 -0
- package/dist/commands/install/workflow.js +62 -0
- package/dist/commands/login/index.d.ts +2 -0
- package/dist/commands/login/index.js +77 -0
- package/dist/commands/logout/index.d.ts +2 -0
- package/dist/commands/logout/index.js +20 -0
- package/dist/commands/marketplace/runtime.d.ts +54 -0
- package/dist/commands/marketplace/runtime.js +350 -0
- package/dist/commands/migrate/index.d.ts +1 -0
- package/dist/commands/migrate/index.js +14 -0
- package/dist/commands/migrate/node.d.ts +2 -0
- package/dist/commands/migrate/node.js +110 -0
- package/dist/commands/monitor/index.d.ts +1 -0
- package/dist/commands/monitor/index.js +28 -0
- package/dist/commands/monitor/monitor-component.d.ts +1 -0
- package/dist/commands/monitor/monitor-component.js +271 -0
- package/dist/commands/monitor/static/index.html +2124 -0
- package/dist/commands/monitor/static-web-server.d.ts +1 -0
- package/dist/commands/monitor/static-web-server.js +89 -0
- package/dist/commands/profile/index.d.ts +1 -0
- package/dist/commands/profile/index.js +112 -0
- package/dist/commands/publish/index.d.ts +1 -0
- package/dist/commands/publish/index.js +4 -0
- package/dist/commands/publish/node.d.ts +4 -0
- package/dist/commands/publish/node.js +231 -0
- package/dist/commands/publish/workflow.d.ts +4 -0
- package/dist/commands/publish/workflow.js +165 -0
- package/dist/commands/search/docs.d.ts +17 -0
- package/dist/commands/search/docs.js +179 -0
- package/dist/commands/search/index.d.ts +1 -0
- package/dist/commands/search/index.js +5 -0
- package/dist/commands/search/indexer.d.ts +10 -0
- package/dist/commands/search/indexer.js +265 -0
- package/dist/commands/search/nodes.d.ts +4 -0
- package/dist/commands/search/nodes.js +101 -0
- package/dist/commands/search/workflow.d.ts +4 -0
- package/dist/commands/search/workflow.js +100 -0
- package/dist/commands/trace/index.d.ts +1 -0
- package/dist/commands/trace/index.js +26 -0
- package/dist/commands/trace/startStudio.d.ts +8 -0
- package/dist/commands/trace/startStudio.js +116 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +186 -0
- package/dist/services/commander.d.ts +9 -0
- package/dist/services/commander.js +20 -0
- package/dist/services/constants.d.ts +1 -0
- package/dist/services/constants.js +3 -0
- package/dist/services/local-token-manager.d.ts +14 -0
- package/dist/services/local-token-manager.js +99 -0
- package/dist/services/non-interactive.d.ts +5 -0
- package/dist/services/non-interactive.js +30 -0
- package/dist/services/package-manager.d.ts +35 -0
- package/dist/services/package-manager.js +111 -0
- package/dist/services/posthog.d.ts +31 -0
- package/dist/services/posthog.js +159 -0
- package/dist/services/registry-manager.d.ts +9 -0
- package/dist/services/registry-manager.js +26 -0
- package/dist/services/runtime-detector.d.ts +23 -0
- package/dist/services/runtime-detector.js +181 -0
- package/dist/services/runtime-setup.d.ts +36 -0
- package/dist/services/runtime-setup.js +250 -0
- package/dist/services/utils.d.ts +2 -0
- package/dist/services/utils.js +29 -0
- package/dist/services/workflow-loader.d.ts +30 -0
- package/dist/services/workflow-loader.js +46 -0
- package/dist/studio-dist/assets/charts-Dso0hPUR.js +68 -0
- package/dist/studio-dist/assets/graph-CsV2nWGn.js +23 -0
- package/dist/studio-dist/assets/icons-zP8LLgPh.js +311 -0
- package/dist/studio-dist/assets/index-CLyEkXMx.css +1 -0
- package/dist/studio-dist/assets/index-CNXFX_ar.js +27 -0
- package/dist/studio-dist/assets/react-vendor--Eh9ivFN.js +17 -0
- package/dist/studio-dist/assets/tanstack-query-CiM1U6F5.js +1 -0
- package/dist/studio-dist/assets/tanstack-router-Btjy0MKq.js +25 -0
- package/dist/studio-dist/assets/tanstack-table-DhwRvuH2.js +22 -0
- package/dist/studio-dist/favicon.svg +5 -0
- package/dist/studio-dist/index.html +21 -0
- package/package.json +75 -0
|
@@ -0,0 +1,2124 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" class="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>Dashboard - blok</title>
|
|
6
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
7
|
+
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3"></script>
|
|
8
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
9
|
+
<script>
|
|
10
|
+
tailwind.config = {
|
|
11
|
+
darkMode: "class",
|
|
12
|
+
};
|
|
13
|
+
</script>
|
|
14
|
+
<script src="https://unpkg.com/feather-icons"></script>
|
|
15
|
+
<link
|
|
16
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap"
|
|
17
|
+
rel="stylesheet"
|
|
18
|
+
/>
|
|
19
|
+
|
|
20
|
+
<link
|
|
21
|
+
href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;600&display=swap"
|
|
22
|
+
rel="stylesheet"
|
|
23
|
+
/>
|
|
24
|
+
|
|
25
|
+
<style>
|
|
26
|
+
html {
|
|
27
|
+
font-family: "Inter", sans-serif !important;
|
|
28
|
+
image-rendering: optimizeQuality;
|
|
29
|
+
image-resolution: inherit;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.metric-value,
|
|
33
|
+
.log-line,
|
|
34
|
+
.canvas-label {
|
|
35
|
+
font-family: "Roboto Mono", monospace !important;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
canvas:fullscreen {
|
|
39
|
+
width: 100vw !important;
|
|
40
|
+
height: 100vh !important;
|
|
41
|
+
background-color: #ffffff !important;
|
|
42
|
+
}
|
|
43
|
+
</style>
|
|
44
|
+
</head>
|
|
45
|
+
<body class="bg-gray-100 text-gray-900 dark:bg-[#1d2536] dark:text-white">
|
|
46
|
+
<nav
|
|
47
|
+
class="bg-white dark:bg-gray-900 shadow-sm px-6 py-3 sticky top-0 z-50 flex items-center justify-between border-b border-gray-200 dark:border-gray-700"
|
|
48
|
+
>
|
|
49
|
+
<div class="flex flex-col">
|
|
50
|
+
<h1
|
|
51
|
+
class="text-lg font-semibold tracking-tight text-gray-900 dark:text-white"
|
|
52
|
+
>
|
|
53
|
+
Observability
|
|
54
|
+
</h1>
|
|
55
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
|
56
|
+
Realtime usage, latency, and error insights for all workflows and
|
|
57
|
+
nodes.
|
|
58
|
+
</p>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="flex items-center justify-center gap-1">
|
|
61
|
+
<!-- Filters container -->
|
|
62
|
+
<div
|
|
63
|
+
class="flex items-start items-end gap-4 ml-4 md:ml-8 mt-4 md:mt-0 relative z-40 w-full flex-col xl:flex-row xl:mr-2"
|
|
64
|
+
>
|
|
65
|
+
<!-- Sections filter dropdown -->
|
|
66
|
+
<div class="relative flex items-center gap-2">
|
|
67
|
+
<label
|
|
68
|
+
for="section-filter"
|
|
69
|
+
class="text-xs text-gray-400 dark:text-blue-500 rounded px-[6px] py-[9.25px] bg-gray-100 dark:bg-gray-800 font-semibold w-[74px] text-center"
|
|
70
|
+
>Sections</label
|
|
71
|
+
>
|
|
72
|
+
<div class="relative">
|
|
73
|
+
<div
|
|
74
|
+
id="section-filter-trigger"
|
|
75
|
+
class="flex items-center justify-between border border-gray-300 dark:border-gray-600 rounded px-3 py-1.5 text-sm text-gray-800 dark:text-white w-56 cursor-pointer select-none"
|
|
76
|
+
>
|
|
77
|
+
<span id="section-filter-placeholder">All</span>
|
|
78
|
+
<svg
|
|
79
|
+
id="section-filter-chevron"
|
|
80
|
+
class="w-4 h-4 ml-2 transition-transform transform text-gray-500 dark:text-gray-300"
|
|
81
|
+
fill="none"
|
|
82
|
+
stroke="currentColor"
|
|
83
|
+
stroke-width="2"
|
|
84
|
+
viewBox="0 0 24 24"
|
|
85
|
+
>
|
|
86
|
+
<path
|
|
87
|
+
stroke-linecap="round"
|
|
88
|
+
stroke-linejoin="round"
|
|
89
|
+
d="M19 9l-7 7-7-7"
|
|
90
|
+
/>
|
|
91
|
+
</svg>
|
|
92
|
+
</div>
|
|
93
|
+
<div
|
|
94
|
+
id="section-filter-dropdown"
|
|
95
|
+
class="absolute mt-1 z-50 bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded shadow-md w-full md:w-56 max-h-60 overflow-y-auto hidden"
|
|
96
|
+
>
|
|
97
|
+
<div class="px-3 py-2 text-xs text-gray-400 dark:text-gray-500">
|
|
98
|
+
Selected (All)
|
|
99
|
+
</div>
|
|
100
|
+
<div id="section-filter-options" class="flex flex-col"></div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
<!-- Workflows filter dropdown -->
|
|
105
|
+
<div class="relative flex items-center gap-2">
|
|
106
|
+
<label
|
|
107
|
+
for="workflow-filter"
|
|
108
|
+
class="text-xs text-gray-400 dark:text-blue-500 rounded px-[6px] py-[9.25px] bg-gray-100 dark:bg-gray-800 font-semibold w-[74px] text-center"
|
|
109
|
+
>Workflows</label
|
|
110
|
+
>
|
|
111
|
+
<div class="relative">
|
|
112
|
+
<div
|
|
113
|
+
id="workflow-filter-trigger"
|
|
114
|
+
class="flex items-center justify-between border border-gray-300 dark:border-gray-600 rounded px-3 py-1.5 text-sm text-gray-800 dark:text-white w-56 cursor-pointer select-none"
|
|
115
|
+
>
|
|
116
|
+
<span id="workflow-filter-placeholder">All</span>
|
|
117
|
+
<svg
|
|
118
|
+
id="workflow-filter-chevron"
|
|
119
|
+
class="w-4 h-4 ml-2 transition-transform transform text-gray-500 dark:text-gray-300"
|
|
120
|
+
fill="none"
|
|
121
|
+
stroke="currentColor"
|
|
122
|
+
stroke-width="2"
|
|
123
|
+
viewBox="0 0 24 24"
|
|
124
|
+
>
|
|
125
|
+
<path
|
|
126
|
+
stroke-linecap="round"
|
|
127
|
+
stroke-linejoin="round"
|
|
128
|
+
d="M19 9l-7 7-7-7"
|
|
129
|
+
/>
|
|
130
|
+
</svg>
|
|
131
|
+
</div>
|
|
132
|
+
<div
|
|
133
|
+
id="workflow-filter-dropdown"
|
|
134
|
+
class="absolute mt-1 z-50 bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded shadow-md w-full md:w-56 max-h-60 overflow-y-auto hidden"
|
|
135
|
+
>
|
|
136
|
+
<div class="px-3 py-2 text-xs text-gray-400 dark:text-gray-500">
|
|
137
|
+
Selected (0)
|
|
138
|
+
</div>
|
|
139
|
+
<div id="workflow-filter-options" class="flex flex-col"></div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<!-- Nodes filter dropdown -->
|
|
145
|
+
<div class="relative flex items-center gap-2">
|
|
146
|
+
<label
|
|
147
|
+
for="node-filter"
|
|
148
|
+
class="text-xs text-gray-400 dark:text-blue-500 rounded px-[6px] py-[9.25px] bg-gray-100 dark:bg-gray-800 font-semibold w-[74px] text-center"
|
|
149
|
+
>Nodes</label
|
|
150
|
+
>
|
|
151
|
+
<div class="relative">
|
|
152
|
+
<div
|
|
153
|
+
id="node-filter-trigger"
|
|
154
|
+
class="flex items-center justify-between border border-gray-300 dark:border-gray-600 rounded px-3 py-1.5 text-sm text-gray-800 dark:text-white w-56 cursor-pointer select-none"
|
|
155
|
+
>
|
|
156
|
+
<span id="node-filter-placeholder">All</span>
|
|
157
|
+
<svg
|
|
158
|
+
id="node-filter-chevron"
|
|
159
|
+
class="w-4 h-4 ml-2 transition-transform transform text-gray-500 dark:text-gray-300"
|
|
160
|
+
fill="none"
|
|
161
|
+
stroke="currentColor"
|
|
162
|
+
stroke-width="2"
|
|
163
|
+
viewBox="0 0 24 24"
|
|
164
|
+
>
|
|
165
|
+
<path
|
|
166
|
+
stroke-linecap="round"
|
|
167
|
+
stroke-linejoin="round"
|
|
168
|
+
d="M19 9l-7 7-7-7"
|
|
169
|
+
/>
|
|
170
|
+
</svg>
|
|
171
|
+
</div>
|
|
172
|
+
<div
|
|
173
|
+
id="node-filter-dropdown"
|
|
174
|
+
class="absolute mt-1 z-50 bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded shadow-md w-full md:w-56 max-h-60 overflow-y-auto hidden"
|
|
175
|
+
>
|
|
176
|
+
<div class="px-3 py-2 text-xs text-gray-400 dark:text-gray-500">
|
|
177
|
+
Selected (0)
|
|
178
|
+
</div>
|
|
179
|
+
<div id="node-filter-options" class="flex flex-col"></div>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<!-- Dark Mode Toggle -->
|
|
186
|
+
<button
|
|
187
|
+
onclick="toggleDarkMode()"
|
|
188
|
+
aria-label="Toggle Dark Mode"
|
|
189
|
+
class="flex items-center justify-center w-9 h-9 hover:bg-gray-100 dark:hover:bg-gray-800 transition hidden xl:block"
|
|
190
|
+
>
|
|
191
|
+
<span
|
|
192
|
+
id="darkModeIcon"
|
|
193
|
+
class="flex items-center justify-center h-9 text-gray-700 dark:text-gray-200"
|
|
194
|
+
></span>
|
|
195
|
+
</button>
|
|
196
|
+
</div>
|
|
197
|
+
</nav>
|
|
198
|
+
|
|
199
|
+
<main class="pt-0 pb-6 px-6 py-6 space-y-6">
|
|
200
|
+
<script>
|
|
201
|
+
function toggleSection(id) {
|
|
202
|
+
const section = document.getElementById(id);
|
|
203
|
+
const isHidden = section.classList.toggle("hidden");
|
|
204
|
+
|
|
205
|
+
localStorage.setItem(`section:${id}`, isHidden);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function restoreToggles() {
|
|
209
|
+
document.querySelectorAll("[id$='-body']").forEach((section) => {
|
|
210
|
+
const saved = localStorage.getItem(`section:${section.id}`);
|
|
211
|
+
if (saved === "true") {
|
|
212
|
+
section.classList.add("hidden");
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
document.addEventListener("DOMContentLoaded", restoreToggles);
|
|
218
|
+
|
|
219
|
+
window.addEventListener("scroll", () => {
|
|
220
|
+
localStorage.setItem("scrollPosition", window.scrollY);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
window.addEventListener("load", () => {
|
|
224
|
+
const savedScroll = localStorage.getItem("scrollPosition");
|
|
225
|
+
if (savedScroll !== null) {
|
|
226
|
+
setTimeout(() => {
|
|
227
|
+
requestAnimationFrame(() => {});
|
|
228
|
+
}, 200);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
</script>
|
|
232
|
+
|
|
233
|
+
<!-- OVERVIEW -->
|
|
234
|
+
<section
|
|
235
|
+
class="bg-white dark:bg-gray-900 rounded-lg shadow-sm"
|
|
236
|
+
data-section-id="System Overview"
|
|
237
|
+
>
|
|
238
|
+
<div
|
|
239
|
+
class="flex items-center justify-between px-5 py-4 border-b border-gray-200 dark:border-gray-700"
|
|
240
|
+
>
|
|
241
|
+
<div>
|
|
242
|
+
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
|
243
|
+
System Overview
|
|
244
|
+
</h2>
|
|
245
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
246
|
+
Aggregated metrics across all workflows and nodes in real time.
|
|
247
|
+
</p>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<button
|
|
251
|
+
onclick="toggleSection('overview-body', this)"
|
|
252
|
+
aria-label="Toggle Overview"
|
|
253
|
+
class="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-600 dark:text-gray-300 transition"
|
|
254
|
+
>
|
|
255
|
+
<i data-feather="chevron-down" class="w-5 h-5"></i>
|
|
256
|
+
</button>
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
<div
|
|
260
|
+
id="overview-body"
|
|
261
|
+
class="p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
|
|
262
|
+
data-draggable-section="overview-body"
|
|
263
|
+
>
|
|
264
|
+
<!-- Total Requests -->
|
|
265
|
+
<div
|
|
266
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
267
|
+
data-id="overview_requests"
|
|
268
|
+
draggable="true"
|
|
269
|
+
>
|
|
270
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
271
|
+
Total Requests
|
|
272
|
+
</h3>
|
|
273
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
274
|
+
All workflow executions (last 1m).
|
|
275
|
+
</p>
|
|
276
|
+
<div class="flex-grow relative">
|
|
277
|
+
<canvas
|
|
278
|
+
id="overview_requests"
|
|
279
|
+
class="absolute inset-0 w-full h-full"
|
|
280
|
+
></canvas>
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
<!-- Errors -->
|
|
285
|
+
<div
|
|
286
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
287
|
+
data-id="overview_errors"
|
|
288
|
+
draggable="true"
|
|
289
|
+
>
|
|
290
|
+
<h3 class="text-sm font-medium text-red-600 dark:text-red-400">
|
|
291
|
+
Error Count
|
|
292
|
+
</h3>
|
|
293
|
+
<p class="text-xs text-red-500 dark:text-red-300 mb-2">
|
|
294
|
+
Failures detected across workflows.
|
|
295
|
+
</p>
|
|
296
|
+
<div class="flex-grow relative">
|
|
297
|
+
<canvas
|
|
298
|
+
id="overview_errors"
|
|
299
|
+
class="absolute inset-0 w-full h-full"
|
|
300
|
+
></canvas>
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
|
|
304
|
+
<!-- Risk -->
|
|
305
|
+
<div
|
|
306
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
307
|
+
data-id="overview_request_risk"
|
|
308
|
+
draggable="true"
|
|
309
|
+
>
|
|
310
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
311
|
+
Execution Error Risk (%)
|
|
312
|
+
</h3>
|
|
313
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
314
|
+
Failures as % of total requests.
|
|
315
|
+
</p>
|
|
316
|
+
<div class="flex-grow relative">
|
|
317
|
+
<canvas
|
|
318
|
+
id="overview_request_risk"
|
|
319
|
+
class="absolute inset-0 w-full h-full"
|
|
320
|
+
></canvas>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
|
|
324
|
+
<!-- Time -->
|
|
325
|
+
<div
|
|
326
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
327
|
+
data-id="overview_time"
|
|
328
|
+
draggable="true"
|
|
329
|
+
>
|
|
330
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
331
|
+
Avg. Execution Time (ms)
|
|
332
|
+
</h3>
|
|
333
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
334
|
+
Mean duration per workflow.
|
|
335
|
+
</p>
|
|
336
|
+
<div class="flex-grow relative">
|
|
337
|
+
<canvas
|
|
338
|
+
id="overview_time"
|
|
339
|
+
class="absolute inset-0 w-full h-full"
|
|
340
|
+
></canvas>
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
|
|
344
|
+
<!-- CPU -->
|
|
345
|
+
<div
|
|
346
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
347
|
+
data-id="overview_cpu"
|
|
348
|
+
draggable="true"
|
|
349
|
+
>
|
|
350
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
351
|
+
CPU Usage
|
|
352
|
+
</h3>
|
|
353
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
354
|
+
Average CPU used (1m).
|
|
355
|
+
</p>
|
|
356
|
+
<div class="flex-grow relative">
|
|
357
|
+
<canvas
|
|
358
|
+
id="overview_cpu"
|
|
359
|
+
class="absolute inset-0 w-full h-full"
|
|
360
|
+
></canvas>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
<!-- CPU Risk -->
|
|
365
|
+
<div
|
|
366
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
367
|
+
data-id="overview_cpu_risk"
|
|
368
|
+
draggable="true"
|
|
369
|
+
>
|
|
370
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
371
|
+
CPU Risk (%)
|
|
372
|
+
</h3>
|
|
373
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
374
|
+
Used vs allocatable CPU cores.
|
|
375
|
+
</p>
|
|
376
|
+
<div class="flex-grow relative">
|
|
377
|
+
<canvas
|
|
378
|
+
id="overview_cpu_risk"
|
|
379
|
+
class="absolute inset-0 w-full h-full"
|
|
380
|
+
></canvas>
|
|
381
|
+
</div>
|
|
382
|
+
</div>
|
|
383
|
+
|
|
384
|
+
<!-- Memory -->
|
|
385
|
+
<div
|
|
386
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
387
|
+
data-id="overview_memory"
|
|
388
|
+
draggable="true"
|
|
389
|
+
>
|
|
390
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
391
|
+
Memory Usage
|
|
392
|
+
</h3>
|
|
393
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
394
|
+
Avg memory used across workflows.
|
|
395
|
+
</p>
|
|
396
|
+
<div class="flex-grow relative">
|
|
397
|
+
<canvas
|
|
398
|
+
id="overview_memory"
|
|
399
|
+
class="absolute inset-0 w-full h-full"
|
|
400
|
+
></canvas>
|
|
401
|
+
</div>
|
|
402
|
+
</div>
|
|
403
|
+
|
|
404
|
+
<!-- Memory Risk -->
|
|
405
|
+
<div
|
|
406
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
407
|
+
data-id="overview_memory_risk"
|
|
408
|
+
draggable="true"
|
|
409
|
+
>
|
|
410
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
411
|
+
Memory Risk (%)
|
|
412
|
+
</h3>
|
|
413
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
414
|
+
Used vs memory quota per node.
|
|
415
|
+
</p>
|
|
416
|
+
<div class="flex-grow relative">
|
|
417
|
+
<canvas
|
|
418
|
+
id="overview_memory_risk"
|
|
419
|
+
class="absolute inset-0 w-full h-full"
|
|
420
|
+
></canvas>
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
</div>
|
|
424
|
+
</section>
|
|
425
|
+
|
|
426
|
+
<!-- WORKFLOWS -->
|
|
427
|
+
<section
|
|
428
|
+
class="bg-white dark:bg-gray-900 rounded-lg shadow-sm"
|
|
429
|
+
data-section-id="Workflow Metrics"
|
|
430
|
+
>
|
|
431
|
+
<div
|
|
432
|
+
class="flex items-center justify-between px-5 py-4 border-b border-gray-200 dark:border-gray-700"
|
|
433
|
+
>
|
|
434
|
+
<div>
|
|
435
|
+
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
|
436
|
+
Workflow Metrics
|
|
437
|
+
</h2>
|
|
438
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
439
|
+
Insights per workflow — track usage, performance, and fault rates.
|
|
440
|
+
</p>
|
|
441
|
+
</div>
|
|
442
|
+
<button
|
|
443
|
+
onclick="toggleSection('workflows-body', this)"
|
|
444
|
+
aria-label="Toggle Workflows"
|
|
445
|
+
class="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-600 dark:text-gray-300 transition"
|
|
446
|
+
>
|
|
447
|
+
<i data-feather="chevron-down" class="w-5 h-5"></i>
|
|
448
|
+
</button>
|
|
449
|
+
</div>
|
|
450
|
+
|
|
451
|
+
<div
|
|
452
|
+
id="workflows-body"
|
|
453
|
+
class="p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
|
|
454
|
+
data-draggable-section="workflows-body"
|
|
455
|
+
>
|
|
456
|
+
<!-- Total Requests -->
|
|
457
|
+
<div
|
|
458
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
459
|
+
data-id="workflow_requests"
|
|
460
|
+
draggable="true"
|
|
461
|
+
>
|
|
462
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
463
|
+
Requests per Workflow
|
|
464
|
+
</h3>
|
|
465
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
466
|
+
Number of executions per workflow.
|
|
467
|
+
</p>
|
|
468
|
+
<div class="flex-grow relative">
|
|
469
|
+
<canvas
|
|
470
|
+
id="workflow_requests"
|
|
471
|
+
class="absolute inset-0 w-full h-full"
|
|
472
|
+
></canvas>
|
|
473
|
+
</div>
|
|
474
|
+
</div>
|
|
475
|
+
|
|
476
|
+
<!-- Errors -->
|
|
477
|
+
<div
|
|
478
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
479
|
+
data-id="workflow_errors"
|
|
480
|
+
draggable="true"
|
|
481
|
+
>
|
|
482
|
+
<h3 class="text-sm font-medium text-red-600 dark:text-red-400">
|
|
483
|
+
Errors per Workflow
|
|
484
|
+
</h3>
|
|
485
|
+
<p class="text-xs text-red-500 dark:text-red-300 mb-2">
|
|
486
|
+
Failures by workflow.
|
|
487
|
+
</p>
|
|
488
|
+
<div class="flex-grow relative">
|
|
489
|
+
<canvas
|
|
490
|
+
id="workflow_errors"
|
|
491
|
+
class="absolute inset-0 w-full h-full"
|
|
492
|
+
></canvas>
|
|
493
|
+
</div>
|
|
494
|
+
</div>
|
|
495
|
+
|
|
496
|
+
<!-- Error Risk -->
|
|
497
|
+
<div
|
|
498
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
499
|
+
data-id="workflow_request_risk"
|
|
500
|
+
draggable="true"
|
|
501
|
+
>
|
|
502
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
503
|
+
Execution Error Risk (%)
|
|
504
|
+
</h3>
|
|
505
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
506
|
+
Failures as % of total attempts per workflow.
|
|
507
|
+
</p>
|
|
508
|
+
<div class="flex-grow relative">
|
|
509
|
+
<canvas
|
|
510
|
+
id="workflow_request_risk"
|
|
511
|
+
class="absolute inset-0 w-full h-full"
|
|
512
|
+
></canvas>
|
|
513
|
+
</div>
|
|
514
|
+
</div>
|
|
515
|
+
|
|
516
|
+
<!-- Execution Time -->
|
|
517
|
+
<div
|
|
518
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
519
|
+
data-id="workflow_time"
|
|
520
|
+
draggable="true"
|
|
521
|
+
>
|
|
522
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
523
|
+
Execution Time (s)
|
|
524
|
+
</h3>
|
|
525
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
526
|
+
Avg. duration per workflow.
|
|
527
|
+
</p>
|
|
528
|
+
<div class="flex-grow relative">
|
|
529
|
+
<canvas
|
|
530
|
+
id="workflow_time"
|
|
531
|
+
class="absolute inset-0 w-full h-full"
|
|
532
|
+
></canvas>
|
|
533
|
+
</div>
|
|
534
|
+
</div>
|
|
535
|
+
|
|
536
|
+
<!-- CPU Usage -->
|
|
537
|
+
<div
|
|
538
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
539
|
+
data-id="workflow_cpu"
|
|
540
|
+
draggable="true"
|
|
541
|
+
>
|
|
542
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
543
|
+
CPU Usage (s)
|
|
544
|
+
</h3>
|
|
545
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
546
|
+
Avg. CPU consumption by workflow.
|
|
547
|
+
</p>
|
|
548
|
+
<div class="flex-grow relative">
|
|
549
|
+
<canvas
|
|
550
|
+
id="workflow_cpu"
|
|
551
|
+
class="absolute inset-0 w-full h-full"
|
|
552
|
+
></canvas>
|
|
553
|
+
</div>
|
|
554
|
+
</div>
|
|
555
|
+
|
|
556
|
+
<!-- CPU Risk -->
|
|
557
|
+
<div
|
|
558
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
559
|
+
data-id="workflow_cpu_risk"
|
|
560
|
+
draggable="true"
|
|
561
|
+
>
|
|
562
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
563
|
+
CPU Risk (%)
|
|
564
|
+
</h3>
|
|
565
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
566
|
+
% of allocated CPU cores used.
|
|
567
|
+
</p>
|
|
568
|
+
<div class="flex-grow relative">
|
|
569
|
+
<canvas
|
|
570
|
+
id="workflow_cpu_risk"
|
|
571
|
+
class="absolute inset-0 w-full h-full"
|
|
572
|
+
></canvas>
|
|
573
|
+
</div>
|
|
574
|
+
</div>
|
|
575
|
+
|
|
576
|
+
<!-- Memory Usage -->
|
|
577
|
+
<div
|
|
578
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
579
|
+
data-id="workflow_memory"
|
|
580
|
+
draggable="true"
|
|
581
|
+
>
|
|
582
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
583
|
+
Memory Usage (MB)
|
|
584
|
+
</h3>
|
|
585
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
586
|
+
Avg. memory per workflow.
|
|
587
|
+
</p>
|
|
588
|
+
<div class="flex-grow relative">
|
|
589
|
+
<canvas
|
|
590
|
+
id="workflow_memory"
|
|
591
|
+
class="absolute inset-0 w-full h-full"
|
|
592
|
+
></canvas>
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
595
|
+
|
|
596
|
+
<!-- Memory Risk -->
|
|
597
|
+
<div
|
|
598
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
599
|
+
data-id="workflow_memory_risk"
|
|
600
|
+
draggable="true"
|
|
601
|
+
>
|
|
602
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
603
|
+
Memory Risk (%)
|
|
604
|
+
</h3>
|
|
605
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
606
|
+
% of memory quota utilized.
|
|
607
|
+
</p>
|
|
608
|
+
<div class="flex-grow relative">
|
|
609
|
+
<canvas
|
|
610
|
+
id="workflow_memory_risk"
|
|
611
|
+
class="absolute inset-0 w-full h-full"
|
|
612
|
+
></canvas>
|
|
613
|
+
</div>
|
|
614
|
+
</div>
|
|
615
|
+
</div>
|
|
616
|
+
</section>
|
|
617
|
+
|
|
618
|
+
<!-- NODES -->
|
|
619
|
+
<section
|
|
620
|
+
class="bg-white dark:bg-gray-900 rounded-lg shadow-sm"
|
|
621
|
+
data-section-id="Node Metrics"
|
|
622
|
+
>
|
|
623
|
+
<div
|
|
624
|
+
class="flex items-center justify-between px-5 py-4 border-b border-gray-200 dark:border-gray-700"
|
|
625
|
+
>
|
|
626
|
+
<div>
|
|
627
|
+
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
|
628
|
+
Node Metrics
|
|
629
|
+
</h2>
|
|
630
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
631
|
+
Monitor blok performance and health by individual node.
|
|
632
|
+
</p>
|
|
633
|
+
</div>
|
|
634
|
+
<button
|
|
635
|
+
onclick="toggleSection('nodes-body', this)"
|
|
636
|
+
aria-label="Toggle Nodes"
|
|
637
|
+
class="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-600 dark:text-gray-300 transition"
|
|
638
|
+
>
|
|
639
|
+
<i data-feather="chevron-down" class="w-5 h-5"></i>
|
|
640
|
+
</button>
|
|
641
|
+
</div>
|
|
642
|
+
|
|
643
|
+
<div
|
|
644
|
+
id="nodes-body"
|
|
645
|
+
class="p-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
|
|
646
|
+
data-draggable-section="nodes-body"
|
|
647
|
+
>
|
|
648
|
+
<!-- Requests -->
|
|
649
|
+
<div
|
|
650
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
651
|
+
data-id="node_requests"
|
|
652
|
+
draggable="true"
|
|
653
|
+
>
|
|
654
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
655
|
+
Requests per Node
|
|
656
|
+
</h3>
|
|
657
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
658
|
+
How often each node executes.
|
|
659
|
+
</p>
|
|
660
|
+
<div class="flex-grow relative">
|
|
661
|
+
<canvas
|
|
662
|
+
id="node_requests"
|
|
663
|
+
class="absolute inset-0 w-full h-full"
|
|
664
|
+
></canvas>
|
|
665
|
+
</div>
|
|
666
|
+
</div>
|
|
667
|
+
|
|
668
|
+
<!-- Errors -->
|
|
669
|
+
<div
|
|
670
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
671
|
+
data-id="node_errors"
|
|
672
|
+
draggable="true"
|
|
673
|
+
>
|
|
674
|
+
<h3 class="text-sm font-medium text-red-600 dark:text-red-400">
|
|
675
|
+
Errors per Node
|
|
676
|
+
</h3>
|
|
677
|
+
<p class="text-xs text-red-500 dark:text-red-300 mb-2">
|
|
678
|
+
Failures encountered per node.
|
|
679
|
+
</p>
|
|
680
|
+
<div class="flex-grow relative">
|
|
681
|
+
<canvas
|
|
682
|
+
id="node_errors"
|
|
683
|
+
class="absolute inset-0 w-full h-full"
|
|
684
|
+
></canvas>
|
|
685
|
+
</div>
|
|
686
|
+
</div>
|
|
687
|
+
|
|
688
|
+
<!-- Execution Risk -->
|
|
689
|
+
<div
|
|
690
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
691
|
+
data-id="node_request_risk"
|
|
692
|
+
draggable="true"
|
|
693
|
+
>
|
|
694
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
695
|
+
Execution Error Risk (%)
|
|
696
|
+
</h3>
|
|
697
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
698
|
+
Failed executions as percentage of total.
|
|
699
|
+
</p>
|
|
700
|
+
<div class="flex-grow relative">
|
|
701
|
+
<canvas
|
|
702
|
+
id="node_request_risk"
|
|
703
|
+
class="absolute inset-0 w-full h-full"
|
|
704
|
+
></canvas>
|
|
705
|
+
</div>
|
|
706
|
+
</div>
|
|
707
|
+
|
|
708
|
+
<!-- Execution Time -->
|
|
709
|
+
<div
|
|
710
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
711
|
+
data-id="node_time"
|
|
712
|
+
draggable="true"
|
|
713
|
+
>
|
|
714
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
715
|
+
Execution Time (s)
|
|
716
|
+
</h3>
|
|
717
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
718
|
+
Average duration of node execution.
|
|
719
|
+
</p>
|
|
720
|
+
<div class="flex-grow relative">
|
|
721
|
+
<canvas
|
|
722
|
+
id="node_time"
|
|
723
|
+
class="absolute inset-0 w-full h-full"
|
|
724
|
+
></canvas>
|
|
725
|
+
</div>
|
|
726
|
+
</div>
|
|
727
|
+
|
|
728
|
+
<!-- CPU Usage -->
|
|
729
|
+
<div
|
|
730
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
731
|
+
data-id="node_cpu"
|
|
732
|
+
draggable="true"
|
|
733
|
+
>
|
|
734
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
735
|
+
CPU Usage (s)
|
|
736
|
+
</h3>
|
|
737
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
738
|
+
Avg. CPU load per node.
|
|
739
|
+
</p>
|
|
740
|
+
<div class="flex-grow relative">
|
|
741
|
+
<canvas
|
|
742
|
+
id="node_cpu"
|
|
743
|
+
class="absolute inset-0 w-full h-full"
|
|
744
|
+
></canvas>
|
|
745
|
+
</div>
|
|
746
|
+
</div>
|
|
747
|
+
|
|
748
|
+
<!-- CPU Risk -->
|
|
749
|
+
<div
|
|
750
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
751
|
+
data-id="node_cpu_risk"
|
|
752
|
+
draggable="true"
|
|
753
|
+
>
|
|
754
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
755
|
+
CPU Risk (%)
|
|
756
|
+
</h3>
|
|
757
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
758
|
+
CPU usage as % of allocated cores.
|
|
759
|
+
</p>
|
|
760
|
+
<div class="flex-grow relative">
|
|
761
|
+
<canvas
|
|
762
|
+
id="node_cpu_risk"
|
|
763
|
+
class="absolute inset-0 w-full h-full"
|
|
764
|
+
></canvas>
|
|
765
|
+
</div>
|
|
766
|
+
</div>
|
|
767
|
+
|
|
768
|
+
<!-- Memory Usage -->
|
|
769
|
+
<div
|
|
770
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
771
|
+
data-id="node_memory"
|
|
772
|
+
draggable="true"
|
|
773
|
+
>
|
|
774
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
775
|
+
Memory Usage (MB)
|
|
776
|
+
</h3>
|
|
777
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
778
|
+
Avg. RAM usage per node.
|
|
779
|
+
</p>
|
|
780
|
+
<div class="flex-grow relative">
|
|
781
|
+
<canvas
|
|
782
|
+
id="node_memory"
|
|
783
|
+
class="absolute inset-0 w-full h-full"
|
|
784
|
+
></canvas>
|
|
785
|
+
</div>
|
|
786
|
+
</div>
|
|
787
|
+
|
|
788
|
+
<!-- Memory Risk -->
|
|
789
|
+
<div
|
|
790
|
+
class="bg-white dark:bg-gray-800 p-4 rounded shadow flex flex-col h-[300px]"
|
|
791
|
+
data-id="node_memory_risk"
|
|
792
|
+
draggable="true"
|
|
793
|
+
>
|
|
794
|
+
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
795
|
+
Memory Risk (%)
|
|
796
|
+
</h3>
|
|
797
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
|
798
|
+
Memory used as % of available memory per node.
|
|
799
|
+
</p>
|
|
800
|
+
<div class="flex-grow relative">
|
|
801
|
+
<canvas
|
|
802
|
+
id="node_memory_risk"
|
|
803
|
+
class="absolute inset-0 w-full h-full"
|
|
804
|
+
></canvas>
|
|
805
|
+
</div>
|
|
806
|
+
</div>
|
|
807
|
+
</div>
|
|
808
|
+
</section>
|
|
809
|
+
|
|
810
|
+
<section
|
|
811
|
+
class="bg-white dark:bg-gray-900 rounded-lg shadow-sm mt-6 overflow-x-auto"
|
|
812
|
+
data-section-id="Workflow Table"
|
|
813
|
+
>
|
|
814
|
+
<div class="px-5 py-4 border-b border-gray-200 dark:border-gray-700">
|
|
815
|
+
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
|
816
|
+
Workflow Metrics
|
|
817
|
+
</h2>
|
|
818
|
+
<p class="text-sm text-gray-600 dark:text-gray-400">
|
|
819
|
+
Real-time snapshot of key performance indicators across all
|
|
820
|
+
workflows.
|
|
821
|
+
</p>
|
|
822
|
+
</div>
|
|
823
|
+
|
|
824
|
+
<table
|
|
825
|
+
id="workflow-metrics-table"
|
|
826
|
+
class="min-w-full text-sm text-left divide-y divide-gray-200 dark:divide-gray-800"
|
|
827
|
+
>
|
|
828
|
+
<thead
|
|
829
|
+
class="bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs uppercase tracking-wider"
|
|
830
|
+
>
|
|
831
|
+
<tr>
|
|
832
|
+
<th scope="col" class="px-4 py-3 font-medium">Workflow</th>
|
|
833
|
+
<th scope="col" class="px-4 py-3 font-medium text-right">
|
|
834
|
+
Requests
|
|
835
|
+
</th>
|
|
836
|
+
<th scope="col" class="px-4 py-3 font-medium text-right">
|
|
837
|
+
Errors
|
|
838
|
+
</th>
|
|
839
|
+
<th scope="col" class="px-4 py-3 font-medium text-right">
|
|
840
|
+
Exec Time (ms)
|
|
841
|
+
</th>
|
|
842
|
+
<th scope="col" class="px-4 py-3 font-medium text-right">
|
|
843
|
+
CPU (%)
|
|
844
|
+
</th>
|
|
845
|
+
<th scope="col" class="px-4 py-3 font-medium text-right">
|
|
846
|
+
Memory (MB)
|
|
847
|
+
</th>
|
|
848
|
+
</tr>
|
|
849
|
+
</thead>
|
|
850
|
+
<tbody
|
|
851
|
+
id="workflow-metrics-body"
|
|
852
|
+
class="divide-y divide-gray-100 dark:divide-gray-700 bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-100"
|
|
853
|
+
>
|
|
854
|
+
<!-- JavaScript dynamically fills this -->
|
|
855
|
+
</tbody>
|
|
856
|
+
</table>
|
|
857
|
+
</section>
|
|
858
|
+
|
|
859
|
+
<section
|
|
860
|
+
class="bg-white dark:bg-gray-900 rounded-lg shadow-sm mt-6 overflow-x-auto"
|
|
861
|
+
data-section-id="Node Table"
|
|
862
|
+
>
|
|
863
|
+
<div class="px-5 py-4 border-b border-gray-200 dark:border-gray-700">
|
|
864
|
+
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
|
865
|
+
Nodes Metrics
|
|
866
|
+
</h2>
|
|
867
|
+
<p class="text-sm text-gray-600 dark:text-gray-400">
|
|
868
|
+
Real-time snapshot of key performance indicators across all nodes.
|
|
869
|
+
</p>
|
|
870
|
+
</div>
|
|
871
|
+
|
|
872
|
+
<table
|
|
873
|
+
id="node-metrics-table"
|
|
874
|
+
class="min-w-full text-sm text-left divide-y divide-gray-200 dark:divide-gray-800"
|
|
875
|
+
>
|
|
876
|
+
<thead
|
|
877
|
+
class="bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs uppercase tracking-wider"
|
|
878
|
+
>
|
|
879
|
+
<tr>
|
|
880
|
+
<th scope="col" class="px-4 py-3 font-medium">Workflow</th>
|
|
881
|
+
<th scope="col" class="px-4 py-3 font-medium text-right">
|
|
882
|
+
Requests
|
|
883
|
+
</th>
|
|
884
|
+
<th scope="col" class="px-4 py-3 font-medium text-right">
|
|
885
|
+
Errors
|
|
886
|
+
</th>
|
|
887
|
+
<th scope="col" class="px-4 py-3 font-medium text-right">
|
|
888
|
+
Exec Time (ms)
|
|
889
|
+
</th>
|
|
890
|
+
<th scope="col" class="px-4 py-3 font-medium text-right">
|
|
891
|
+
CPU (%)
|
|
892
|
+
</th>
|
|
893
|
+
<th scope="col" class="px-4 py-3 font-medium text-right">
|
|
894
|
+
Memory (MB)
|
|
895
|
+
</th>
|
|
896
|
+
</tr>
|
|
897
|
+
</thead>
|
|
898
|
+
<tbody
|
|
899
|
+
id="nodes-metrics-body"
|
|
900
|
+
class="divide-y divide-gray-100 dark:divide-gray-700 bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-100"
|
|
901
|
+
>
|
|
902
|
+
<!-- JavaScript dynamically fills this -->
|
|
903
|
+
</tbody>
|
|
904
|
+
</table>
|
|
905
|
+
</section>
|
|
906
|
+
|
|
907
|
+
<!-- System Logs -->
|
|
908
|
+
<section
|
|
909
|
+
class="bg-white dark:bg-gray-900 rounded shadow"
|
|
910
|
+
data-section-id="System Logs"
|
|
911
|
+
>
|
|
912
|
+
<div
|
|
913
|
+
class="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700"
|
|
914
|
+
>
|
|
915
|
+
<div class="flex items-center gap-2">
|
|
916
|
+
<!-- Info icon -->
|
|
917
|
+
<svg
|
|
918
|
+
class="w-5 h-5 text-blue-500 dark:text-blue-400"
|
|
919
|
+
fill="currentColor"
|
|
920
|
+
viewBox="0 0 20 20"
|
|
921
|
+
aria-hidden="true"
|
|
922
|
+
>
|
|
923
|
+
<path
|
|
924
|
+
d="M10 2a8 8 0 100 16 8 8 0 000-16zM9 7h2v6H9V7zm0 7h2v2H9v-2z"
|
|
925
|
+
/>
|
|
926
|
+
</svg>
|
|
927
|
+
<div>
|
|
928
|
+
<h2 class="text-base font-semibold text-gray-900 dark:text-white">
|
|
929
|
+
System Logs
|
|
930
|
+
</h2>
|
|
931
|
+
<p class="text-sm text-gray-600 dark:text-gray-300">
|
|
932
|
+
Most recent 50 log entries from Loki (info level).
|
|
933
|
+
</p>
|
|
934
|
+
</div>
|
|
935
|
+
</div>
|
|
936
|
+
<button
|
|
937
|
+
onclick="toggleSection('loki-body')"
|
|
938
|
+
aria-label="Toggle Overview"
|
|
939
|
+
class="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-600 dark:text-gray-300 transition"
|
|
940
|
+
>
|
|
941
|
+
<i data-feather="chevron-down" class="w-5 h-5"></i>
|
|
942
|
+
</button>
|
|
943
|
+
</div>
|
|
944
|
+
<div
|
|
945
|
+
id="loki-body"
|
|
946
|
+
class="p-4 max-h-[400px] overflow-y-auto bg-black text-gray-300 text-xs font-mono rounded"
|
|
947
|
+
>
|
|
948
|
+
<ul id="lokiLogs" class="space-y-1"></ul>
|
|
949
|
+
</div>
|
|
950
|
+
</section>
|
|
951
|
+
|
|
952
|
+
<!-- Error Logs -->
|
|
953
|
+
<section
|
|
954
|
+
class="bg-white dark:bg-gray-900 rounded shadow mt-6"
|
|
955
|
+
data-section-id="Error Logs"
|
|
956
|
+
>
|
|
957
|
+
<div
|
|
958
|
+
class="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700"
|
|
959
|
+
>
|
|
960
|
+
<div class="flex items-center gap-2">
|
|
961
|
+
<!-- Error icon -->
|
|
962
|
+
<svg
|
|
963
|
+
class="w-5 h-5 text-red-500 dark:text-red-400"
|
|
964
|
+
fill="currentColor"
|
|
965
|
+
viewBox="0 0 20 20"
|
|
966
|
+
aria-hidden="true"
|
|
967
|
+
>
|
|
968
|
+
<path
|
|
969
|
+
fill-rule="evenodd"
|
|
970
|
+
d="M10 2a8 8 0 100 16 8 8 0 000-16zM9 7h2v4H9V7zm0 5h2v2H9v-2z"
|
|
971
|
+
clip-rule="evenodd"
|
|
972
|
+
/>
|
|
973
|
+
</svg>
|
|
974
|
+
<div>
|
|
975
|
+
<h2
|
|
976
|
+
class="text-base font-semibold text-red-600 dark:text-red-400"
|
|
977
|
+
>
|
|
978
|
+
Error Logs
|
|
979
|
+
</h2>
|
|
980
|
+
<p class="text-sm text-gray-600 dark:text-gray-300">
|
|
981
|
+
Last 50 error-level entries logged to Loki.
|
|
982
|
+
</p>
|
|
983
|
+
</div>
|
|
984
|
+
</div>
|
|
985
|
+
<button
|
|
986
|
+
onclick="toggleSection('loki-error-body')"
|
|
987
|
+
aria-label="Toggle Overview"
|
|
988
|
+
class="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-600 dark:text-gray-300 transition"
|
|
989
|
+
>
|
|
990
|
+
<i data-feather="chevron-down" class="w-5 h-5"></i>
|
|
991
|
+
</button>
|
|
992
|
+
</div>
|
|
993
|
+
<div
|
|
994
|
+
id="loki-error-body"
|
|
995
|
+
class="p-4 max-h-[400px] overflow-y-auto bg-black text-red-300 text-xs font-mono rounded"
|
|
996
|
+
>
|
|
997
|
+
<ul id="lokiErrorLogs" class="space-y-1"></ul>
|
|
998
|
+
</div>
|
|
999
|
+
</section>
|
|
1000
|
+
</main>
|
|
1001
|
+
|
|
1002
|
+
<script>
|
|
1003
|
+
const prometheusBase = "http://localhost:4040";
|
|
1004
|
+
const prometheusBaseToken = undefined;
|
|
1005
|
+
|
|
1006
|
+
const queries = {
|
|
1007
|
+
overview: {
|
|
1008
|
+
requests: "sum(increase(workflow_total[1m]))",
|
|
1009
|
+
time: `sum(avg_over_time(workflow_time[1m]))`,
|
|
1010
|
+
errors: "sum(increase(workflow_errors_total[1m]))",
|
|
1011
|
+
cpu: "sum(avg_over_time(workflow_cpu[1m]))",
|
|
1012
|
+
memory: "sum(avg_over_time(workflow_memory[1m]))",
|
|
1013
|
+
risk_cpu:
|
|
1014
|
+
"sum(avg_over_time(workflow_cpu[1m])) / sum(avg_over_time(workflow_cpu_total[1m])) * 100",
|
|
1015
|
+
risk_memory:
|
|
1016
|
+
"sum(avg_over_time(workflow_memory[1m])) / sum(avg_over_time(workflow_memory_total[1m])) * 100",
|
|
1017
|
+
request_risk:
|
|
1018
|
+
"sum(increase(workflow_errors_total[1m])) / (sum(increase(workflow_total[1m])) + sum(increase(workflow_errors_total[1m]))) * 100",
|
|
1019
|
+
},
|
|
1020
|
+
workflows: {
|
|
1021
|
+
requests:
|
|
1022
|
+
"(sum(increase(workflow_total[1m])) by (workflow_path)) > 0",
|
|
1023
|
+
time: "sum(avg_over_time(workflow_time[1m])) by (workflow_path)",
|
|
1024
|
+
errors:
|
|
1025
|
+
"(sum(increase(workflow_errors_total[1m])) by (workflow_path)) > 0",
|
|
1026
|
+
cpu: "sum(avg_over_time(workflow_cpu[1m])) by (workflow_path)",
|
|
1027
|
+
memory: "sum(avg_over_time(workflow_memory[1m])) by (workflow_path)",
|
|
1028
|
+
risk_cpu:
|
|
1029
|
+
"sum by (workflow_path)(avg_over_time(workflow_cpu[1m])) / sum by (workflow_path)(avg_over_time(workflow_cpu_total[1m])) * 100",
|
|
1030
|
+
risk_memory:
|
|
1031
|
+
"sum by (workflow_path)(avg_over_time(workflow_memory[1m])) / sum by (workflow_path)(avg_over_time(workflow_memory_total[1m])) * 100",
|
|
1032
|
+
request_risk:
|
|
1033
|
+
"(sum(increase(workflow_errors_total[1m])) by (workflow_path) / (sum(increase(workflow_total[1m])) by (workflow_path) + sum(increase(workflow_errors_total[1m])) by (workflow_path)) * 100) > 0",
|
|
1034
|
+
},
|
|
1035
|
+
nodes: {
|
|
1036
|
+
requests: "(sum(increase(node_total[1m])) by (node_name)) > 0",
|
|
1037
|
+
time: "sum(increase(node_time[1m])) by (node_name)",
|
|
1038
|
+
errors: "(sum(increase(node_errors_total[1m])) by (node_name)) > 0",
|
|
1039
|
+
cpu: "avg_over_time(node_cpu[1m])",
|
|
1040
|
+
memory: "sum(increase(node_memory[1m])) by (node_name)",
|
|
1041
|
+
risk_cpu:
|
|
1042
|
+
"sum by (node_name, workflow_path)(avg_over_time(node_cpu[1m])) / sum by (node_name, workflow_path)(avg_over_time(node_cpu_total[1m])) * 100",
|
|
1043
|
+
risk_memory:
|
|
1044
|
+
"sum by (node_name, workflow_path)(avg_over_time(node_memory[1m])) / sum by (node_name, workflow_path)(avg_over_time(node_memory_total[1m])) * 100",
|
|
1045
|
+
request_risk:
|
|
1046
|
+
"(sum(increase(node_errors_total[1m])) by (node_name) / (sum(increase(node_total[1m])) by (node_name) + sum(increase(node_errors_total[1m])) by (node_name)) * 100) > 0",
|
|
1047
|
+
},
|
|
1048
|
+
};
|
|
1049
|
+
|
|
1050
|
+
const charts = {};
|
|
1051
|
+
|
|
1052
|
+
function createChart(ctx, label, useThreshold = false) {
|
|
1053
|
+
let opts1 = {
|
|
1054
|
+
type: "line",
|
|
1055
|
+
data: { datasets: [] },
|
|
1056
|
+
options: {
|
|
1057
|
+
animation: false,
|
|
1058
|
+
elements: {
|
|
1059
|
+
point: {
|
|
1060
|
+
radius: 0,
|
|
1061
|
+
hoverRadius: 6,
|
|
1062
|
+
hitRadius: 6,
|
|
1063
|
+
},
|
|
1064
|
+
},
|
|
1065
|
+
interaction: {
|
|
1066
|
+
mode: "nearest",
|
|
1067
|
+
intersect: false,
|
|
1068
|
+
},
|
|
1069
|
+
responsive: true,
|
|
1070
|
+
maintainAspectRatio: false,
|
|
1071
|
+
scales: {
|
|
1072
|
+
x: {
|
|
1073
|
+
type: "time",
|
|
1074
|
+
time: { unit: "minute" },
|
|
1075
|
+
title: { display: true, text: "Time" },
|
|
1076
|
+
},
|
|
1077
|
+
y: { beginAtZero: true },
|
|
1078
|
+
},
|
|
1079
|
+
plugins: {
|
|
1080
|
+
legend: {
|
|
1081
|
+
display: true,
|
|
1082
|
+
},
|
|
1083
|
+
tooltip: {
|
|
1084
|
+
enabled: true,
|
|
1085
|
+
},
|
|
1086
|
+
},
|
|
1087
|
+
},
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
let opts2 = {
|
|
1091
|
+
type: "line",
|
|
1092
|
+
data: { datasets: [] },
|
|
1093
|
+
options: {
|
|
1094
|
+
animation: false,
|
|
1095
|
+
responsive: true,
|
|
1096
|
+
maintainAspectRatio: false,
|
|
1097
|
+
scales: {
|
|
1098
|
+
x: {
|
|
1099
|
+
type: "time",
|
|
1100
|
+
time: { unit: "minute" },
|
|
1101
|
+
title: { display: true, text: "Time" },
|
|
1102
|
+
},
|
|
1103
|
+
y: {
|
|
1104
|
+
beginAtZero: true,
|
|
1105
|
+
max: 100,
|
|
1106
|
+
title: { display: true, text: "Risk (%)" },
|
|
1107
|
+
},
|
|
1108
|
+
},
|
|
1109
|
+
plugins: {
|
|
1110
|
+
legend: { display: true },
|
|
1111
|
+
tooltip: { enabled: true },
|
|
1112
|
+
},
|
|
1113
|
+
},
|
|
1114
|
+
plugins: useThreshold ? [thresholdBackgroundPlugin] : [],
|
|
1115
|
+
};
|
|
1116
|
+
|
|
1117
|
+
let opts3 = {
|
|
1118
|
+
type: "line",
|
|
1119
|
+
data: { datasets: [] },
|
|
1120
|
+
options: {
|
|
1121
|
+
animation: false,
|
|
1122
|
+
responsive: true,
|
|
1123
|
+
maintainAspectRatio: false,
|
|
1124
|
+
scales: {
|
|
1125
|
+
x: {
|
|
1126
|
+
type: "time",
|
|
1127
|
+
time: { unit: "minute" },
|
|
1128
|
+
title: { display: true, text: "Time" },
|
|
1129
|
+
},
|
|
1130
|
+
y: {
|
|
1131
|
+
beginAtZero: true,
|
|
1132
|
+
max: 100,
|
|
1133
|
+
title: { display: true, text: "Risk (%)" },
|
|
1134
|
+
},
|
|
1135
|
+
},
|
|
1136
|
+
plugins: {
|
|
1137
|
+
legend: { display: true },
|
|
1138
|
+
tooltip: { enabled: true },
|
|
1139
|
+
},
|
|
1140
|
+
},
|
|
1141
|
+
plugins: useThreshold ? [thresholdErrorsBackgroundPlugin] : [],
|
|
1142
|
+
};
|
|
1143
|
+
|
|
1144
|
+
if (
|
|
1145
|
+
label.includes("overview_request_risk") ||
|
|
1146
|
+
label.includes("workflow_request_risk") ||
|
|
1147
|
+
label.includes("node_request_risk")
|
|
1148
|
+
) {
|
|
1149
|
+
opts2 = opts3;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
return new Chart(ctx, useThreshold ? opts2 : opts1);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
function parseSeries(result, groupLabel, color) {
|
|
1156
|
+
const palette = [
|
|
1157
|
+
"#3b82f6",
|
|
1158
|
+
"#10b981",
|
|
1159
|
+
"#f59e0b",
|
|
1160
|
+
"#8b5cf6",
|
|
1161
|
+
"#ec4899",
|
|
1162
|
+
"#ef4444",
|
|
1163
|
+
];
|
|
1164
|
+
let i = 0;
|
|
1165
|
+
|
|
1166
|
+
return result.map((serie) => {
|
|
1167
|
+
const label = serie.metric[groupLabel] || "unknown";
|
|
1168
|
+
const datasetColor = color || palette[i++ % palette.length];
|
|
1169
|
+
return {
|
|
1170
|
+
label,
|
|
1171
|
+
data: serie.values.map(([ts, val]) => ({
|
|
1172
|
+
x: new Date(ts * 1000),
|
|
1173
|
+
y: parseFloat(val),
|
|
1174
|
+
})),
|
|
1175
|
+
borderColor: datasetColor,
|
|
1176
|
+
backgroundColor: datasetColor + "33",
|
|
1177
|
+
fill: true,
|
|
1178
|
+
};
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
async function fetchMetric(query) {
|
|
1183
|
+
const end = Math.floor(Date.now() / 1000);
|
|
1184
|
+
const start = end - 600;
|
|
1185
|
+
const params = new URLSearchParams({ query, start, end, step: "60" });
|
|
1186
|
+
const res = prometheusBaseToken
|
|
1187
|
+
? await fetch(`${prometheusBase}/api/metrics?${params}`, {
|
|
1188
|
+
headers: {
|
|
1189
|
+
Authorization: `Bearer ${prometheusBaseToken}`,
|
|
1190
|
+
Accept: "application/json",
|
|
1191
|
+
"Accept-Encoding": "identity",
|
|
1192
|
+
},
|
|
1193
|
+
})
|
|
1194
|
+
: await fetch(`${prometheusBase}/api/metrics?${params}`);
|
|
1195
|
+
const json = await res.json();
|
|
1196
|
+
return json.data?.result || [];
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
function buildMatcher(label, valuesSet) {
|
|
1200
|
+
if (!valuesSet || !(valuesSet instanceof Set)) return ""; // ⚠️ Previene el error
|
|
1201
|
+
|
|
1202
|
+
const values = Array.from(valuesSet);
|
|
1203
|
+
if (values.includes("__all__")) return "";
|
|
1204
|
+
if (values.length === 0) return `${label}=~"^$"`;
|
|
1205
|
+
|
|
1206
|
+
const regex = values
|
|
1207
|
+
.map((v) =>
|
|
1208
|
+
v.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&").replace(/\\/g, "\\\\")
|
|
1209
|
+
)
|
|
1210
|
+
.join("|");
|
|
1211
|
+
|
|
1212
|
+
return `${label}=~"${regex}"`;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
async function updateChart(
|
|
1216
|
+
id,
|
|
1217
|
+
query,
|
|
1218
|
+
isGrouped = false,
|
|
1219
|
+
groupLabel = "",
|
|
1220
|
+
forceColor = null
|
|
1221
|
+
) {
|
|
1222
|
+
const canvas = document.getElementById(id);
|
|
1223
|
+
if (!canvas) return;
|
|
1224
|
+
const ctx = canvas.getContext("2d");
|
|
1225
|
+
|
|
1226
|
+
if (!charts[id]) {
|
|
1227
|
+
let thresholdPluginActive = id.includes("risk");
|
|
1228
|
+
charts[id] = createChart(ctx, id, thresholdPluginActive);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
let finalQuery = query;
|
|
1232
|
+
|
|
1233
|
+
if (id.startsWith("workflow_")) {
|
|
1234
|
+
const matcher = buildMatcher(
|
|
1235
|
+
"workflow_path",
|
|
1236
|
+
window.selectedFilters?.workflow
|
|
1237
|
+
);
|
|
1238
|
+
|
|
1239
|
+
if (matcher) {
|
|
1240
|
+
finalQuery = query.replace(
|
|
1241
|
+
/workflow_(total|errors_total|time|cpu_total|cpu|memory_total|memory)\b/g,
|
|
1242
|
+
(metric) => `${metric}{${matcher}}`
|
|
1243
|
+
);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
if (id.startsWith("overview_")) {
|
|
1248
|
+
const matcher = buildMatcher(
|
|
1249
|
+
"workflow_path",
|
|
1250
|
+
window.selectedFilters?.workflow
|
|
1251
|
+
);
|
|
1252
|
+
|
|
1253
|
+
if (matcher) {
|
|
1254
|
+
finalQuery = query.replace(
|
|
1255
|
+
/workflow_(total|errors_total|time|cpu_total|cpu|memory_total|memory)\b/g,
|
|
1256
|
+
(metric) => `${metric}{${matcher}}`
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
if (id.startsWith("node_")) {
|
|
1262
|
+
const matcher = buildMatcher(
|
|
1263
|
+
"node_name",
|
|
1264
|
+
window.selectedFilters?.node
|
|
1265
|
+
);
|
|
1266
|
+
|
|
1267
|
+
if (matcher) {
|
|
1268
|
+
finalQuery = query.replace(
|
|
1269
|
+
/node_(total|errors_total|time|cpu_total|cpu|memory_total|memory)\b/g,
|
|
1270
|
+
(metric) => `${metric}{${matcher}}`
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
const result = await fetchMetric(finalQuery);
|
|
1276
|
+
const datasets = isGrouped
|
|
1277
|
+
? parseSeries(result, groupLabel, forceColor)
|
|
1278
|
+
: [
|
|
1279
|
+
{
|
|
1280
|
+
label: id,
|
|
1281
|
+
data:
|
|
1282
|
+
result[0]?.values.map(([ts, val]) => ({
|
|
1283
|
+
x: new Date(ts * 1000),
|
|
1284
|
+
y: parseFloat(val),
|
|
1285
|
+
})) || [],
|
|
1286
|
+
borderColor: forceColor || "#2563eb",
|
|
1287
|
+
backgroundColor: (forceColor || "#2563eb") + "33",
|
|
1288
|
+
fill: true,
|
|
1289
|
+
},
|
|
1290
|
+
];
|
|
1291
|
+
|
|
1292
|
+
charts[id].data.datasets = datasets;
|
|
1293
|
+
charts[id].update();
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
async function updateAllCharts() {
|
|
1297
|
+
// OVERVIEW
|
|
1298
|
+
await updateChart("overview_requests", queries.overview.requests);
|
|
1299
|
+
await updateChart("overview_time", queries.overview.time);
|
|
1300
|
+
await updateChart(
|
|
1301
|
+
"overview_errors",
|
|
1302
|
+
queries.overview.errors,
|
|
1303
|
+
false,
|
|
1304
|
+
"",
|
|
1305
|
+
"#dc2626"
|
|
1306
|
+
);
|
|
1307
|
+
await updateChart("overview_cpu", queries.overview.cpu);
|
|
1308
|
+
await updateChart("overview_memory", queries.overview.memory);
|
|
1309
|
+
|
|
1310
|
+
// WORKFLOWS
|
|
1311
|
+
await updateChart(
|
|
1312
|
+
"workflow_requests",
|
|
1313
|
+
queries.workflows.requests,
|
|
1314
|
+
true,
|
|
1315
|
+
"workflow_path"
|
|
1316
|
+
);
|
|
1317
|
+
await updateChart(
|
|
1318
|
+
"workflow_time",
|
|
1319
|
+
queries.workflows.time,
|
|
1320
|
+
true,
|
|
1321
|
+
"workflow_path"
|
|
1322
|
+
);
|
|
1323
|
+
await updateChart(
|
|
1324
|
+
"workflow_errors",
|
|
1325
|
+
queries.workflows.errors,
|
|
1326
|
+
true,
|
|
1327
|
+
"workflow_path",
|
|
1328
|
+
"#dc2626"
|
|
1329
|
+
);
|
|
1330
|
+
await updateChart(
|
|
1331
|
+
"workflow_cpu",
|
|
1332
|
+
queries.workflows.cpu,
|
|
1333
|
+
true,
|
|
1334
|
+
"workflow_path"
|
|
1335
|
+
);
|
|
1336
|
+
await updateChart(
|
|
1337
|
+
"workflow_memory",
|
|
1338
|
+
queries.workflows.memory,
|
|
1339
|
+
true,
|
|
1340
|
+
"workflow_path"
|
|
1341
|
+
);
|
|
1342
|
+
|
|
1343
|
+
// NODES
|
|
1344
|
+
await updateChart(
|
|
1345
|
+
"node_requests",
|
|
1346
|
+
queries.nodes.requests,
|
|
1347
|
+
true,
|
|
1348
|
+
"node_name"
|
|
1349
|
+
);
|
|
1350
|
+
await updateChart("node_time", queries.nodes.time, true, "node_name");
|
|
1351
|
+
await updateChart(
|
|
1352
|
+
"node_memory",
|
|
1353
|
+
queries.nodes.memory,
|
|
1354
|
+
true,
|
|
1355
|
+
"node_name"
|
|
1356
|
+
);
|
|
1357
|
+
await updateChart("node_cpu", queries.nodes.cpu, true, "node_name");
|
|
1358
|
+
await updateChart(
|
|
1359
|
+
"node_errors",
|
|
1360
|
+
queries.nodes.errors,
|
|
1361
|
+
true,
|
|
1362
|
+
"node_name"
|
|
1363
|
+
);
|
|
1364
|
+
|
|
1365
|
+
// RISK
|
|
1366
|
+
await updateChart(
|
|
1367
|
+
"node_cpu_risk",
|
|
1368
|
+
queries.nodes.risk_cpu,
|
|
1369
|
+
true,
|
|
1370
|
+
"node_name"
|
|
1371
|
+
);
|
|
1372
|
+
await updateChart(
|
|
1373
|
+
"node_memory_risk",
|
|
1374
|
+
queries.nodes.risk_memory,
|
|
1375
|
+
true,
|
|
1376
|
+
"node_name"
|
|
1377
|
+
);
|
|
1378
|
+
await updateChart(
|
|
1379
|
+
"node_request_risk",
|
|
1380
|
+
queries.nodes.request_risk,
|
|
1381
|
+
true,
|
|
1382
|
+
"node_name"
|
|
1383
|
+
);
|
|
1384
|
+
|
|
1385
|
+
await updateChart(
|
|
1386
|
+
"workflow_cpu_risk",
|
|
1387
|
+
queries.workflows.risk_cpu,
|
|
1388
|
+
true,
|
|
1389
|
+
"workflow_path"
|
|
1390
|
+
);
|
|
1391
|
+
await updateChart(
|
|
1392
|
+
"workflow_memory_risk",
|
|
1393
|
+
queries.workflows.risk_memory,
|
|
1394
|
+
true,
|
|
1395
|
+
"workflow_path"
|
|
1396
|
+
);
|
|
1397
|
+
await updateChart(
|
|
1398
|
+
"workflow_request_risk",
|
|
1399
|
+
queries.workflows.request_risk,
|
|
1400
|
+
true,
|
|
1401
|
+
"workflow_path"
|
|
1402
|
+
);
|
|
1403
|
+
|
|
1404
|
+
await updateChart("overview_cpu_risk", queries.overview.risk_cpu);
|
|
1405
|
+
await updateChart("overview_memory_risk", queries.overview.risk_memory);
|
|
1406
|
+
await updateChart(
|
|
1407
|
+
"overview_request_risk",
|
|
1408
|
+
queries.overview.request_risk
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
async function updateLokiLogs() {
|
|
1413
|
+
try {
|
|
1414
|
+
const end = BigInt(Date.now()) * 1000000n;
|
|
1415
|
+
const start = end - 3600n * 1000000000n;
|
|
1416
|
+
|
|
1417
|
+
const params = new URLSearchParams({
|
|
1418
|
+
query: '{service_name="blok-http"} | json | level="info"',
|
|
1419
|
+
limit: "50",
|
|
1420
|
+
start: start.toString(),
|
|
1421
|
+
end: end.toString(),
|
|
1422
|
+
direction: "backward",
|
|
1423
|
+
});
|
|
1424
|
+
|
|
1425
|
+
const res = await fetch(
|
|
1426
|
+
`http://localhost:3200/loki/api/v1/query_range?${params}`
|
|
1427
|
+
);
|
|
1428
|
+
const json = await res.json();
|
|
1429
|
+
|
|
1430
|
+
const container = document.getElementById("lokiLogs");
|
|
1431
|
+
container.innerHTML = "";
|
|
1432
|
+
|
|
1433
|
+
const streams = json.data?.result || [];
|
|
1434
|
+
|
|
1435
|
+
for (let i = 0; i < streams.length; i++) {
|
|
1436
|
+
const stream = streams[i];
|
|
1437
|
+
for (let j = 0; j < stream.values.length; j++) {
|
|
1438
|
+
const [ts, line] = stream.values[j];
|
|
1439
|
+
const date = new Date(Number(ts) / 1000000);
|
|
1440
|
+
const item = document.createElement("li");
|
|
1441
|
+
const log_model = JSON.parse(line);
|
|
1442
|
+
|
|
1443
|
+
item.textContent = `[${date.toLocaleTimeString()}] ${
|
|
1444
|
+
log_model.app
|
|
1445
|
+
}:${log_model.env}:${log_model.workflow_path} ${
|
|
1446
|
+
log_model.message
|
|
1447
|
+
}`;
|
|
1448
|
+
container.appendChild(item);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
document.getElementById("loki-body").scrollTop =
|
|
1453
|
+
document.getElementById("loki-body").scrollHeight;
|
|
1454
|
+
} catch (err) {
|
|
1455
|
+
console.error("Failed to load Loki logs:", err);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
async function updateLokiErrors() {
|
|
1460
|
+
try {
|
|
1461
|
+
const end = BigInt(Date.now()) * 1000000n;
|
|
1462
|
+
const start = end - 3600n * 1000000000n;
|
|
1463
|
+
|
|
1464
|
+
const params = new URLSearchParams({
|
|
1465
|
+
query: '{service_name="blok-http"} | json | level="error"',
|
|
1466
|
+
limit: "50",
|
|
1467
|
+
start: start.toString(),
|
|
1468
|
+
end: end.toString(),
|
|
1469
|
+
direction: "backward",
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
const res = await fetch(
|
|
1473
|
+
`http://localhost:3200/loki/api/v1/query_range?${params}`
|
|
1474
|
+
);
|
|
1475
|
+
const json = await res.json();
|
|
1476
|
+
|
|
1477
|
+
const container = document.getElementById("lokiErrorLogs");
|
|
1478
|
+
container.innerHTML = "";
|
|
1479
|
+
|
|
1480
|
+
const streams = json.data?.result || [];
|
|
1481
|
+
|
|
1482
|
+
for (let i = 0; i < streams.length; i++) {
|
|
1483
|
+
const stream = streams[i];
|
|
1484
|
+
for (let j = 0; j < stream.values.length; j++) {
|
|
1485
|
+
const [ts, line] = stream.values[j];
|
|
1486
|
+
const date = new Date(Number(ts) / 1000000);
|
|
1487
|
+
const item = document.createElement("li");
|
|
1488
|
+
const log_model = JSON.parse(line);
|
|
1489
|
+
|
|
1490
|
+
item.textContent = `[${date.toLocaleTimeString()}] ${
|
|
1491
|
+
log_model.app
|
|
1492
|
+
}:${log_model.env}:${
|
|
1493
|
+
log_model.workflow_path || log_model.workflow_name
|
|
1494
|
+
} ${log_model.message}: ${log_model.stack}`;
|
|
1495
|
+
container.appendChild(item);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
document.getElementById("loki-error-body").scrollTop =
|
|
1500
|
+
document.getElementById("loki-error-body").scrollHeight;
|
|
1501
|
+
} catch (err) {
|
|
1502
|
+
console.error("Failed to load Loki error logs:", err);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
async function updateWorkflowTable() {
|
|
1507
|
+
const queries = {
|
|
1508
|
+
requests: "sum(increase(workflow_total[1m])) by (workflow_path)",
|
|
1509
|
+
errors: "sum(increase(workflow_errors_total[1m])) by (workflow_path)",
|
|
1510
|
+
time: "sum(increase(workflow_time[1m])) by (workflow_path)",
|
|
1511
|
+
cpu: "(sum(increase(workflow_cpu[1m])) by (workflow_path) / sum(increase(workflow_total[1m])) by (workflow_path)) * 100",
|
|
1512
|
+
memory: "sum(increase(workflow_memory[1m])) by (workflow_path)",
|
|
1513
|
+
};
|
|
1514
|
+
|
|
1515
|
+
const endpoint = `${prometheusBase}/api/metrics`;
|
|
1516
|
+
const selectedWorkflows =
|
|
1517
|
+
window.selectedFilters?.workflow || new Set(["__all__"]);
|
|
1518
|
+
const filterMatcher = buildMatcher("workflow_path", selectedWorkflows);
|
|
1519
|
+
|
|
1520
|
+
const results = {};
|
|
1521
|
+
|
|
1522
|
+
await Promise.all(
|
|
1523
|
+
Object.entries(queries).map(async ([key, query]) => {
|
|
1524
|
+
const effectiveQuery = filterMatcher
|
|
1525
|
+
? query.replace(
|
|
1526
|
+
/workflow_(total|errors_total|time|cpu|memory)/g,
|
|
1527
|
+
(match) => `${match}{${filterMatcher}}`
|
|
1528
|
+
)
|
|
1529
|
+
: query;
|
|
1530
|
+
|
|
1531
|
+
const res = await fetch(
|
|
1532
|
+
`${endpoint}?query=${encodeURIComponent(effectiveQuery)}`,
|
|
1533
|
+
{
|
|
1534
|
+
headers: {
|
|
1535
|
+
"x-table": "true",
|
|
1536
|
+
},
|
|
1537
|
+
}
|
|
1538
|
+
);
|
|
1539
|
+
const json = await res.json();
|
|
1540
|
+
if (json.status === "success") {
|
|
1541
|
+
for (const entry of json.data.result) {
|
|
1542
|
+
const workflow =
|
|
1543
|
+
key === "requests"
|
|
1544
|
+
? entry.metric.workflow_path || "unknown"
|
|
1545
|
+
: entry.metric.workflow_path || "unknown";
|
|
1546
|
+
const value = parseFloat(entry.value[1]);
|
|
1547
|
+
if (!results[workflow]) results[workflow] = {};
|
|
1548
|
+
results[workflow][key] = value;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
})
|
|
1552
|
+
);
|
|
1553
|
+
|
|
1554
|
+
const tbody = document.getElementById("workflow-metrics-body");
|
|
1555
|
+
tbody.innerHTML = "";
|
|
1556
|
+
|
|
1557
|
+
Object.entries(results)
|
|
1558
|
+
.sort(([, a], [, b]) => (b.requests || 0) - (a.requests || 0))
|
|
1559
|
+
.forEach(([workflow, metrics]) => {
|
|
1560
|
+
const row = document.createElement("tr");
|
|
1561
|
+
row.className =
|
|
1562
|
+
"hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors duration-200";
|
|
1563
|
+
|
|
1564
|
+
row.innerHTML = `
|
|
1565
|
+
<td class="px-4 py-3 font-medium text-gray-900 dark:text-white whitespace-nowrap">
|
|
1566
|
+
${workflow}
|
|
1567
|
+
</td>
|
|
1568
|
+
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">
|
|
1569
|
+
${Math.round(metrics.requests || 0).toLocaleString()}
|
|
1570
|
+
</td>
|
|
1571
|
+
<td class="px-4 py-3 text-right ${
|
|
1572
|
+
metrics.errors > 0
|
|
1573
|
+
? "text-red-600 dark:text-red-400"
|
|
1574
|
+
: "text-gray-700 dark:text-gray-300"
|
|
1575
|
+
}">
|
|
1576
|
+
${Math.round(metrics.errors || 0).toLocaleString()}
|
|
1577
|
+
</td>
|
|
1578
|
+
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">
|
|
1579
|
+
${(metrics.time || 0).toFixed(2)} ms
|
|
1580
|
+
</td>
|
|
1581
|
+
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">
|
|
1582
|
+
${(metrics.cpu || 0).toFixed(1)} %
|
|
1583
|
+
</td>
|
|
1584
|
+
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">
|
|
1585
|
+
${(metrics.memory || 0).toFixed(1)}
|
|
1586
|
+
</td>
|
|
1587
|
+
`;
|
|
1588
|
+
|
|
1589
|
+
tbody.appendChild(row);
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
async function updateNodeTable() {
|
|
1594
|
+
const queries = {
|
|
1595
|
+
requests: "sum(increase(node_total[1m])) by (node_name)",
|
|
1596
|
+
errors: "sum(increase(node_errors_total[1m])) by (node_name)",
|
|
1597
|
+
time: "sum(increase(node_time[1m])) by (node_name)",
|
|
1598
|
+
cpu: "(sum(increase(node_cpu[1m])) by (node_name) / sum(increase(node_total[1m])) by (node_name)) * 100",
|
|
1599
|
+
memory: "sum(increase(node_memory[1m])) by (node_name)",
|
|
1600
|
+
};
|
|
1601
|
+
|
|
1602
|
+
const endpoint = `${prometheusBase}/api/metrics`;
|
|
1603
|
+
const selectedNodes =
|
|
1604
|
+
window.selectedFilters?.node || new Set(["__all__"]);
|
|
1605
|
+
const filterMatcher = buildMatcher("node_name", selectedNodes);
|
|
1606
|
+
|
|
1607
|
+
const results = {};
|
|
1608
|
+
|
|
1609
|
+
await Promise.all(
|
|
1610
|
+
Object.entries(queries).map(async ([key, query]) => {
|
|
1611
|
+
const effectiveQuery = filterMatcher
|
|
1612
|
+
? query.replace(
|
|
1613
|
+
/node_(total|errors_total|time|cpu|memory)/g,
|
|
1614
|
+
(match) => `${match}{${filterMatcher}}`
|
|
1615
|
+
)
|
|
1616
|
+
: query;
|
|
1617
|
+
const res = await fetch(
|
|
1618
|
+
`${endpoint}?query=${encodeURIComponent(effectiveQuery)}`,
|
|
1619
|
+
{
|
|
1620
|
+
headers: {
|
|
1621
|
+
"x-table": "true",
|
|
1622
|
+
},
|
|
1623
|
+
}
|
|
1624
|
+
);
|
|
1625
|
+
const json = await res.json();
|
|
1626
|
+
if (json.status === "success") {
|
|
1627
|
+
for (const entry of json.data.result) {
|
|
1628
|
+
const workflow =
|
|
1629
|
+
key === "requests"
|
|
1630
|
+
? entry.metric.node_name || "unknown"
|
|
1631
|
+
: entry.metric.node_name || "unknown";
|
|
1632
|
+
const value = parseFloat(entry.value[1]);
|
|
1633
|
+
if (!results[workflow]) results[workflow] = {};
|
|
1634
|
+
results[workflow][key] = value;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
})
|
|
1638
|
+
);
|
|
1639
|
+
|
|
1640
|
+
const tbody = document.getElementById("nodes-metrics-body");
|
|
1641
|
+
tbody.innerHTML = "";
|
|
1642
|
+
|
|
1643
|
+
Object.entries(results)
|
|
1644
|
+
.sort(([, a], [, b]) => (b.requests || 0) - (a.requests || 0))
|
|
1645
|
+
.forEach(([workflow, metrics]) => {
|
|
1646
|
+
const row = document.createElement("tr");
|
|
1647
|
+
row.className =
|
|
1648
|
+
"hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors duration-200";
|
|
1649
|
+
|
|
1650
|
+
row.innerHTML = `
|
|
1651
|
+
<td class="px-4 py-3 font-medium text-gray-900 dark:text-white whitespace-nowrap">
|
|
1652
|
+
${workflow}
|
|
1653
|
+
</td>
|
|
1654
|
+
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">
|
|
1655
|
+
${Math.round(metrics.requests || 0).toLocaleString()}
|
|
1656
|
+
</td>
|
|
1657
|
+
<td class="px-4 py-3 text-right ${
|
|
1658
|
+
metrics.errors > 0
|
|
1659
|
+
? "text-red-600 dark:text-red-400"
|
|
1660
|
+
: "text-gray-700 dark:text-gray-300"
|
|
1661
|
+
}">
|
|
1662
|
+
${Math.round(metrics.errors || 0).toLocaleString()}
|
|
1663
|
+
</td>
|
|
1664
|
+
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">
|
|
1665
|
+
${(metrics.time || 0).toFixed(2)} ms
|
|
1666
|
+
</td>
|
|
1667
|
+
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">
|
|
1668
|
+
${(metrics.cpu || 0).toFixed(1)} %
|
|
1669
|
+
</td>
|
|
1670
|
+
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">
|
|
1671
|
+
${(metrics.memory || 0).toFixed(1)}
|
|
1672
|
+
</td>
|
|
1673
|
+
`;
|
|
1674
|
+
|
|
1675
|
+
tbody.appendChild(row);
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
const thresholdBackgroundPlugin = {
|
|
1680
|
+
id: "thresholdBackground",
|
|
1681
|
+
beforeDraw: (chart) => {
|
|
1682
|
+
const { ctx, chartArea: area, scales } = chart;
|
|
1683
|
+
if (!area || !scales?.y) return;
|
|
1684
|
+
|
|
1685
|
+
const y100 = scales.y.getPixelForValue(100);
|
|
1686
|
+
const y90 = scales.y.getPixelForValue(90);
|
|
1687
|
+
const y70 = scales.y.getPixelForValue(70);
|
|
1688
|
+
const y0 = scales.y.getPixelForValue(0);
|
|
1689
|
+
|
|
1690
|
+
// Red: 90–100%
|
|
1691
|
+
ctx.fillStyle = "#fee2e2";
|
|
1692
|
+
ctx.fillRect(area.left, y100, area.width, y90 - y100);
|
|
1693
|
+
|
|
1694
|
+
// Yellow: 70–90%
|
|
1695
|
+
ctx.fillStyle = "#fef9c3";
|
|
1696
|
+
ctx.fillRect(area.left, y90, area.width, y70 - y90);
|
|
1697
|
+
|
|
1698
|
+
// Green: 0–70%
|
|
1699
|
+
ctx.fillStyle = "#dcfce7";
|
|
1700
|
+
ctx.fillRect(area.left, y70, area.width, y0 - y70);
|
|
1701
|
+
},
|
|
1702
|
+
};
|
|
1703
|
+
|
|
1704
|
+
const thresholdErrorsBackgroundPlugin = {
|
|
1705
|
+
id: "thresholdBackground",
|
|
1706
|
+
beforeDraw: (chart) => {
|
|
1707
|
+
const { ctx, chartArea: area, scales } = chart;
|
|
1708
|
+
if (!area || !scales?.y) return;
|
|
1709
|
+
|
|
1710
|
+
const y100 = scales.y.getPixelForValue(100);
|
|
1711
|
+
const y90 = scales.y.getPixelForValue(10);
|
|
1712
|
+
const y70 = scales.y.getPixelForValue(5);
|
|
1713
|
+
const y0 = scales.y.getPixelForValue(0);
|
|
1714
|
+
|
|
1715
|
+
// Red: 90–100%
|
|
1716
|
+
ctx.fillStyle = "#fee2e2";
|
|
1717
|
+
ctx.fillRect(area.left, y100, area.width, y90 - y100);
|
|
1718
|
+
|
|
1719
|
+
// Yellow: 70–90%
|
|
1720
|
+
ctx.fillStyle = "#fef9c3";
|
|
1721
|
+
ctx.fillRect(area.left, y90, area.width, y70 - y90);
|
|
1722
|
+
|
|
1723
|
+
// Green: 0–70%
|
|
1724
|
+
ctx.fillStyle = "#dcfce7";
|
|
1725
|
+
ctx.fillRect(area.left, y70, area.width, y0 - y70);
|
|
1726
|
+
},
|
|
1727
|
+
};
|
|
1728
|
+
|
|
1729
|
+
function applySectionFilterVisibility() {
|
|
1730
|
+
const selected = window.selectedFilters?.section;
|
|
1731
|
+
if (!selected) return;
|
|
1732
|
+
|
|
1733
|
+
const allVisible = selected.has("__all__");
|
|
1734
|
+
|
|
1735
|
+
document
|
|
1736
|
+
.querySelectorAll("section[data-section-id]")
|
|
1737
|
+
.forEach((section) => {
|
|
1738
|
+
const sectionName = section.getAttribute("data-section-id");
|
|
1739
|
+
const shouldShow = allVisible || selected.has(sectionName);
|
|
1740
|
+
section.style.display = shouldShow ? "" : "none";
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
function refreshDashboard() {
|
|
1745
|
+
updateAllCharts();
|
|
1746
|
+
updateWorkflowTable();
|
|
1747
|
+
updateNodeTable();
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
function setTheme(theme) {
|
|
1751
|
+
document.documentElement.classList.toggle("dark", theme === "dark");
|
|
1752
|
+
localStorage.setItem("theme", theme);
|
|
1753
|
+
updateDarkIcon(theme);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
function toggleDarkMode() {
|
|
1757
|
+
const currentTheme = document.documentElement.classList.contains("dark")
|
|
1758
|
+
? "dark"
|
|
1759
|
+
: "light";
|
|
1760
|
+
const newTheme = currentTheme === "dark" ? "light" : "dark";
|
|
1761
|
+
setTheme(newTheme);
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
function updateDarkIcon(theme) {
|
|
1765
|
+
const iconEl = document.getElementById("darkModeIcon");
|
|
1766
|
+
iconEl.innerHTML = "";
|
|
1767
|
+
const icon = document.createElement("i");
|
|
1768
|
+
icon.setAttribute("data-feather", theme === "dark" ? "sun" : "moon");
|
|
1769
|
+
iconEl.appendChild(icon);
|
|
1770
|
+
feather.replace();
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
// 💡 INIT: Always respect localStorage on first load
|
|
1774
|
+
(function () {
|
|
1775
|
+
const storedTheme = localStorage.getItem("theme");
|
|
1776
|
+
|
|
1777
|
+
if (storedTheme === "dark") {
|
|
1778
|
+
document.documentElement.classList.add("dark");
|
|
1779
|
+
} else if (storedTheme === "light") {
|
|
1780
|
+
document.documentElement.classList.remove("dark");
|
|
1781
|
+
} else {
|
|
1782
|
+
// First visit: use system preference
|
|
1783
|
+
const prefersDark = window.matchMedia(
|
|
1784
|
+
"(prefers-color-scheme: dark)"
|
|
1785
|
+
).matches;
|
|
1786
|
+
const systemTheme = prefersDark ? "dark" : "light";
|
|
1787
|
+
setTheme(systemTheme);
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
updateDarkIcon(storedTheme || "light");
|
|
1792
|
+
})();
|
|
1793
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
1794
|
+
document.querySelectorAll("canvas").forEach((canvas) => {
|
|
1795
|
+
canvas.ondblclick = async () => {
|
|
1796
|
+
goFullScreen(canvas);
|
|
1797
|
+
};
|
|
1798
|
+
});
|
|
1799
|
+
});
|
|
1800
|
+
function goFullScreen(canvas) {
|
|
1801
|
+
if (canvas.requestFullScreen) canvas.requestFullScreen();
|
|
1802
|
+
else if (canvas.webkitRequestFullScreen)
|
|
1803
|
+
canvas.webkitRequestFullScreen();
|
|
1804
|
+
else if (canvas.mozRequestFullScreen) canvas.mozRequestFullScreen();
|
|
1805
|
+
|
|
1806
|
+
// Get the DPR and size of the canvas
|
|
1807
|
+
const dpr = window.devicePixelRatio;
|
|
1808
|
+
const rect = canvas.getBoundingClientRect();
|
|
1809
|
+
|
|
1810
|
+
// Set the "actual" size of the canvas
|
|
1811
|
+
canvas.width = rect.width * 2 * dpr;
|
|
1812
|
+
canvas.height = rect.height * 2 * dpr;
|
|
1813
|
+
|
|
1814
|
+
// Scale the context to ensure correct drawing operations
|
|
1815
|
+
ctx.scale(dpr, dpr);
|
|
1816
|
+
|
|
1817
|
+
// Set the "drawn" size of the canvas
|
|
1818
|
+
canvas.style.width = `${canvas.width}px`;
|
|
1819
|
+
canvas.style.height = `${canvas.height}px`;
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
1823
|
+
const containers = document.querySelectorAll(
|
|
1824
|
+
"[data-draggable-section]"
|
|
1825
|
+
);
|
|
1826
|
+
|
|
1827
|
+
containers.forEach((container) => {
|
|
1828
|
+
const sectionId = container.getAttribute("data-draggable-section");
|
|
1829
|
+
let draggedItem = null;
|
|
1830
|
+
|
|
1831
|
+
// Restaurar orden
|
|
1832
|
+
const savedOrder = JSON.parse(
|
|
1833
|
+
localStorage.getItem(`dashboard-order-${sectionId}`)
|
|
1834
|
+
);
|
|
1835
|
+
if (savedOrder) {
|
|
1836
|
+
savedOrder.forEach((id) => {
|
|
1837
|
+
const item = container.querySelector(`[data-id="${id}"]`);
|
|
1838
|
+
if (item) container.appendChild(item);
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
// Guardar orden
|
|
1843
|
+
const saveOrder = () => {
|
|
1844
|
+
const order = Array.from(container.children).map((item) =>
|
|
1845
|
+
item.getAttribute("data-id")
|
|
1846
|
+
);
|
|
1847
|
+
localStorage.setItem(
|
|
1848
|
+
`dashboard-order-${sectionId}`,
|
|
1849
|
+
JSON.stringify(order)
|
|
1850
|
+
);
|
|
1851
|
+
};
|
|
1852
|
+
|
|
1853
|
+
// Eventos drag and drop
|
|
1854
|
+
container.addEventListener("dragstart", (e) => {
|
|
1855
|
+
if (e.target.matches("[draggable]")) {
|
|
1856
|
+
draggedItem = e.target;
|
|
1857
|
+
e.dataTransfer.effectAllowed = "move";
|
|
1858
|
+
}
|
|
1859
|
+
});
|
|
1860
|
+
|
|
1861
|
+
container.addEventListener("dragover", (e) => {
|
|
1862
|
+
e.preventDefault();
|
|
1863
|
+
const target = e.target.closest("[draggable]");
|
|
1864
|
+
if (target && target !== draggedItem) {
|
|
1865
|
+
const rect = target.getBoundingClientRect();
|
|
1866
|
+
const next = e.clientY - rect.top > rect.height / 2;
|
|
1867
|
+
container.insertBefore(
|
|
1868
|
+
draggedItem,
|
|
1869
|
+
next ? target.nextSibling : target
|
|
1870
|
+
);
|
|
1871
|
+
}
|
|
1872
|
+
});
|
|
1873
|
+
|
|
1874
|
+
container.addEventListener("drop", (e) => {
|
|
1875
|
+
e.preventDefault();
|
|
1876
|
+
saveOrder();
|
|
1877
|
+
});
|
|
1878
|
+
});
|
|
1879
|
+
});
|
|
1880
|
+
|
|
1881
|
+
function persistSelectedFilters() {
|
|
1882
|
+
const filtersToSave = {};
|
|
1883
|
+
for (const key in window.selectedFilters) {
|
|
1884
|
+
filtersToSave[key] = Array.from(window.selectedFilters[key]);
|
|
1885
|
+
}
|
|
1886
|
+
localStorage.setItem(
|
|
1887
|
+
"dashboard:selectedFilters",
|
|
1888
|
+
JSON.stringify(filtersToSave)
|
|
1889
|
+
);
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
async function loadFilterOptionsV2() {
|
|
1893
|
+
const filters = [
|
|
1894
|
+
{
|
|
1895
|
+
id: "workflow",
|
|
1896
|
+
url: "http://localhost:4040/api/metrics",
|
|
1897
|
+
label: "workflow_path",
|
|
1898
|
+
},
|
|
1899
|
+
{
|
|
1900
|
+
id: "node",
|
|
1901
|
+
url: "http://localhost:4040/api/metrics",
|
|
1902
|
+
label: "node_name",
|
|
1903
|
+
},
|
|
1904
|
+
{
|
|
1905
|
+
id: "section",
|
|
1906
|
+
static: true,
|
|
1907
|
+
values: [
|
|
1908
|
+
"System Overview",
|
|
1909
|
+
"Workflow Metrics",
|
|
1910
|
+
"Node Metrics",
|
|
1911
|
+
"Workflow Table",
|
|
1912
|
+
"Node Table",
|
|
1913
|
+
"System Logs",
|
|
1914
|
+
"Error Logs",
|
|
1915
|
+
],
|
|
1916
|
+
},
|
|
1917
|
+
];
|
|
1918
|
+
|
|
1919
|
+
window.selectedFilters = {
|
|
1920
|
+
workflow: new Set(["__all__"]),
|
|
1921
|
+
node: new Set(["__all__"]),
|
|
1922
|
+
};
|
|
1923
|
+
|
|
1924
|
+
const savedFilters = JSON.parse(
|
|
1925
|
+
localStorage.getItem("dashboard:selectedFilters") || "{}"
|
|
1926
|
+
);
|
|
1927
|
+
|
|
1928
|
+
for (const filter of filters) {
|
|
1929
|
+
let data;
|
|
1930
|
+
|
|
1931
|
+
if (filter.static) {
|
|
1932
|
+
data = { data: filter.values };
|
|
1933
|
+
} else {
|
|
1934
|
+
const response = await fetch(filter.url, {
|
|
1935
|
+
headers: {
|
|
1936
|
+
"x-label": filter.label,
|
|
1937
|
+
},
|
|
1938
|
+
});
|
|
1939
|
+
data = await response.json();
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
const optionsContainer = document.getElementById(
|
|
1943
|
+
`${filter.id}-filter-options`
|
|
1944
|
+
);
|
|
1945
|
+
const placeholder = document.getElementById(
|
|
1946
|
+
`${filter.id}-filter-placeholder`
|
|
1947
|
+
);
|
|
1948
|
+
const dropdown = document.getElementById(
|
|
1949
|
+
`${filter.id}-filter-dropdown`
|
|
1950
|
+
);
|
|
1951
|
+
const trigger = document.getElementById(
|
|
1952
|
+
`${filter.id}-filter-trigger`
|
|
1953
|
+
);
|
|
1954
|
+
const selectedLabel = dropdown.querySelector("div.text-xs");
|
|
1955
|
+
|
|
1956
|
+
const saved = savedFilters[filter.id];
|
|
1957
|
+
window.selectedFilters[filter.id] = saved
|
|
1958
|
+
? new Set(saved)
|
|
1959
|
+
: new Set(["__all__"]);
|
|
1960
|
+
|
|
1961
|
+
trigger.addEventListener("click", () => {
|
|
1962
|
+
dropdown.classList.toggle("hidden");
|
|
1963
|
+
const chevron = document.getElementById(
|
|
1964
|
+
`${filter.id}-filter-chevron`
|
|
1965
|
+
);
|
|
1966
|
+
chevron.classList.toggle("rotate-180");
|
|
1967
|
+
});
|
|
1968
|
+
|
|
1969
|
+
const allOption = document.createElement("label");
|
|
1970
|
+
allOption.className =
|
|
1971
|
+
"flex items-center px-3 py-1.5 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-800 dark:text-gray-200 cursor-pointer";
|
|
1972
|
+
allOption.innerHTML = `
|
|
1973
|
+
<input type="checkbox" id="${filter.id}-opt-all" value="__all__" class="mr-2 text-blue-500 bg-gray-700 border-gray-600 rounded focus:ring-blue-500">
|
|
1974
|
+
All
|
|
1975
|
+
`;
|
|
1976
|
+
const allInput = allOption.querySelector("input");
|
|
1977
|
+
// Event handler for "All" checkbox item
|
|
1978
|
+
allInput.addEventListener("change", () => {
|
|
1979
|
+
const checkboxes = optionsContainer.querySelectorAll(
|
|
1980
|
+
"input[type='checkbox']"
|
|
1981
|
+
);
|
|
1982
|
+
window.selectedFilters[filter.id] = new Set();
|
|
1983
|
+
|
|
1984
|
+
checkboxes.forEach((cb) => {
|
|
1985
|
+
cb.checked = allInput.checked;
|
|
1986
|
+
if (allInput.checked && cb.value !== "__all__") {
|
|
1987
|
+
window.selectedFilters[filter.id].add(cb.value);
|
|
1988
|
+
}
|
|
1989
|
+
});
|
|
1990
|
+
|
|
1991
|
+
if (allInput.checked) {
|
|
1992
|
+
window.selectedFilters[filter.id] = new Set(["__all__"]);
|
|
1993
|
+
placeholder.textContent = "All";
|
|
1994
|
+
selectedLabel.textContent = `Selected (All)`;
|
|
1995
|
+
} else {
|
|
1996
|
+
placeholder.textContent = "Enter variable value";
|
|
1997
|
+
selectedLabel.textContent = `Selected (0)`;
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
persistSelectedFilters();
|
|
2001
|
+
refreshDashboard();
|
|
2002
|
+
applySectionFilterVisibility();
|
|
2003
|
+
});
|
|
2004
|
+
|
|
2005
|
+
optionsContainer.appendChild(allOption);
|
|
2006
|
+
const validItems = data.data.filter(
|
|
2007
|
+
(item) => typeof item === "string" && item.trim() !== "" && item !== "undefined"
|
|
2008
|
+
);
|
|
2009
|
+
validItems.forEach((item) => {
|
|
2010
|
+
const id = `${filter.id}-opt-${item}`;
|
|
2011
|
+
const option = document.createElement("label");
|
|
2012
|
+
option.className =
|
|
2013
|
+
"flex items-center px-3 py-1.5 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-800 dark:text-gray-200 cursor-pointer";
|
|
2014
|
+
|
|
2015
|
+
option.innerHTML = `
|
|
2016
|
+
<input type="checkbox" id="${id}" value="${item}" class="mr-2 text-blue-500 bg-gray-700 border-gray-600 rounded focus:ring-blue-500">
|
|
2017
|
+
${item}
|
|
2018
|
+
`;
|
|
2019
|
+
|
|
2020
|
+
const input = option.querySelector("input");
|
|
2021
|
+
input.addEventListener("change", () => {
|
|
2022
|
+
requestAnimationFrame(() => {
|
|
2023
|
+
const checkboxes = optionsContainer.querySelectorAll(
|
|
2024
|
+
"input[type='checkbox']:not([value='__all__'])"
|
|
2025
|
+
);
|
|
2026
|
+
const checkedBoxes = optionsContainer.querySelectorAll(
|
|
2027
|
+
"input[type='checkbox']:not([value='__all__']):checked"
|
|
2028
|
+
);
|
|
2029
|
+
|
|
2030
|
+
// Limpiar y reconstruir el Set según el estado actual
|
|
2031
|
+
window.selectedFilters[filter.id] = new Set();
|
|
2032
|
+
checkedBoxes.forEach((cb) =>
|
|
2033
|
+
window.selectedFilters[filter.id].add(cb.value)
|
|
2034
|
+
);
|
|
2035
|
+
|
|
2036
|
+
if (checkedBoxes.length === checkboxes.length) {
|
|
2037
|
+
allInput.checked = true;
|
|
2038
|
+
window.selectedFilters[filter.id] = new Set(["__all__"]);
|
|
2039
|
+
placeholder.textContent = "All";
|
|
2040
|
+
selectedLabel.textContent = `Selected (All)`;
|
|
2041
|
+
} else {
|
|
2042
|
+
allInput.checked = false;
|
|
2043
|
+
const count = checkedBoxes.length;
|
|
2044
|
+
placeholder.textContent =
|
|
2045
|
+
count > 0 ? `${count} selected` : "Enter variable value";
|
|
2046
|
+
selectedLabel.textContent = `Selected (${count})`;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
persistSelectedFilters();
|
|
2050
|
+
refreshDashboard();
|
|
2051
|
+
applySectionFilterVisibility();
|
|
2052
|
+
});
|
|
2053
|
+
});
|
|
2054
|
+
|
|
2055
|
+
optionsContainer.appendChild(option);
|
|
2056
|
+
});
|
|
2057
|
+
|
|
2058
|
+
// Preseleccionar todos al cargar
|
|
2059
|
+
requestAnimationFrame(() => {
|
|
2060
|
+
const allSelected =
|
|
2061
|
+
window.selectedFilters[filter.id].has("__all__");
|
|
2062
|
+
const checkboxes = optionsContainer.querySelectorAll(
|
|
2063
|
+
"input[type='checkbox']:not([value='__all__'])"
|
|
2064
|
+
);
|
|
2065
|
+
|
|
2066
|
+
let selectedCount = 0;
|
|
2067
|
+
checkboxes.forEach((cb) => {
|
|
2068
|
+
cb.checked =
|
|
2069
|
+
allSelected || window.selectedFilters[filter.id].has(cb.value);
|
|
2070
|
+
if (cb.checked) selectedCount++;
|
|
2071
|
+
});
|
|
2072
|
+
|
|
2073
|
+
allInput.checked = allSelected;
|
|
2074
|
+
placeholder.textContent = allSelected
|
|
2075
|
+
? "All"
|
|
2076
|
+
: selectedCount > 0
|
|
2077
|
+
? `${selectedCount} selected`
|
|
2078
|
+
: "Enter variable value";
|
|
2079
|
+
selectedLabel.textContent = allSelected
|
|
2080
|
+
? `Selected (All)`
|
|
2081
|
+
: `Selected (${selectedCount})`;
|
|
2082
|
+
|
|
2083
|
+
refreshDashboard();
|
|
2084
|
+
applySectionFilterVisibility();
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
document.addEventListener("DOMContentLoaded", async () => {
|
|
2090
|
+
await loadFilterOptionsV2(); // Espera a que los filtros estén cargados
|
|
2091
|
+
applySectionFilterVisibility();
|
|
2092
|
+
|
|
2093
|
+
setInterval(() => {
|
|
2094
|
+
updateAllCharts();
|
|
2095
|
+
updateLokiLogs();
|
|
2096
|
+
updateLokiErrors();
|
|
2097
|
+
updateWorkflowTable();
|
|
2098
|
+
updateNodeTable();
|
|
2099
|
+
}, 5000);
|
|
2100
|
+
|
|
2101
|
+
// Cerrar dropdowns al hacer clic fuera
|
|
2102
|
+
document.addEventListener("click", (event) => {
|
|
2103
|
+
const dropdowns = ["workflow", "node", "section"];
|
|
2104
|
+
dropdowns.forEach((id) => {
|
|
2105
|
+
const trigger = document.getElementById(`${id}-filter-trigger`);
|
|
2106
|
+
const dropdown = document.getElementById(`${id}-filter-dropdown`);
|
|
2107
|
+
const chevron = document.getElementById(`${id}-filter-chevron`);
|
|
2108
|
+
|
|
2109
|
+
if (!trigger || !dropdown || !chevron) return;
|
|
2110
|
+
|
|
2111
|
+
const clickedOutside =
|
|
2112
|
+
!trigger.contains(event.target) &&
|
|
2113
|
+
!dropdown.contains(event.target);
|
|
2114
|
+
|
|
2115
|
+
if (clickedOutside) {
|
|
2116
|
+
dropdown.classList.add("hidden");
|
|
2117
|
+
chevron.classList.remove("rotate-180");
|
|
2118
|
+
}
|
|
2119
|
+
});
|
|
2120
|
+
});
|
|
2121
|
+
});
|
|
2122
|
+
</script>
|
|
2123
|
+
</body>
|
|
2124
|
+
</html>
|