@thotischner/observability-mcp 1.3.3 → 1.4.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/config/loader.test.js +3 -3
- package/dist/connectors/loader.d.ts +43 -0
- package/dist/connectors/loader.js +170 -0
- package/dist/connectors/loki.js +3 -2
- package/dist/connectors/registry.d.ts +3 -0
- package/dist/connectors/registry.js +16 -16
- package/dist/connectors/tls.test.js +3 -3
- package/dist/index.js +91 -7
- package/dist/metrics/instrument-connector.d.ts +8 -0
- package/dist/metrics/instrument-connector.js +41 -0
- package/dist/metrics/self.d.ts +12 -0
- package/dist/metrics/self.js +61 -0
- package/dist/openapi.d.ts +2 -0
- package/dist/openapi.js +186 -0
- package/dist/sdk/index.d.ts +46 -0
- package/dist/sdk/index.js +13 -0
- package/dist/sdk/manifest-schema.d.ts +27 -0
- package/dist/sdk/manifest-schema.js +36 -0
- package/dist/sdk/manifest-schema.test.d.ts +1 -0
- package/dist/sdk/manifest-schema.test.js +50 -0
- package/dist/tools/get-service-health.js +3 -2
- package/dist/ui/index.html +568 -111
- package/dist/util/sanitize.d.ts +1 -0
- package/dist/util/sanitize.js +6 -0
- package/package.json +13 -5
package/dist/ui/index.html
CHANGED
|
@@ -4,122 +4,481 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Observability MCP Gateway</title>
|
|
7
|
+
<link rel="preconnect" href="https://rsms.me/">
|
|
8
|
+
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
|
9
|
+
<style>
|
|
10
|
+
@supports (font-variation-settings: normal) {
|
|
11
|
+
:root { font-family: 'Inter var', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
|
|
12
|
+
}
|
|
13
|
+
</style>
|
|
7
14
|
<style>
|
|
8
15
|
:root {
|
|
9
|
-
|
|
10
|
-
--
|
|
11
|
-
--
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
16
|
+
/* Surface — neutral slate, three tiers of elevation */
|
|
17
|
+
--bg: #0b0d12;
|
|
18
|
+
--surface: #11141b;
|
|
19
|
+
--surface-2: #161a23;
|
|
20
|
+
--surface-3: #1c212c;
|
|
21
|
+
--border: rgba(255,255,255,0.06);
|
|
22
|
+
--border-strong: rgba(255,255,255,0.10);
|
|
23
|
+
|
|
24
|
+
/* Text — four levels */
|
|
25
|
+
--text: #e8ecf2;
|
|
26
|
+
--text-muted: #a1a8b3;
|
|
27
|
+
--text-dim: #6b7280;
|
|
28
|
+
--text-inv: #0b0d12;
|
|
29
|
+
|
|
30
|
+
/* One accent — refined cyan-blue, not GitHub blue */
|
|
31
|
+
--accent: #4f8cff;
|
|
32
|
+
--accent-2: #6ea3ff;
|
|
33
|
+
--accent-soft: rgba(79,140,255,0.10);
|
|
34
|
+
--accent-ring: rgba(79,140,255,0.35);
|
|
35
|
+
|
|
36
|
+
/* Semantic — desaturated, professional */
|
|
37
|
+
--success: #4ade80;
|
|
38
|
+
--success-soft: rgba(74,222,128,0.10);
|
|
39
|
+
--warning: #f5b341;
|
|
40
|
+
--warning-soft: rgba(245,179,65,0.10);
|
|
41
|
+
--danger: #ef5b6e;
|
|
42
|
+
--danger-soft: rgba(239,91,110,0.10);
|
|
43
|
+
--info: #a78bfa;
|
|
44
|
+
--info-soft: rgba(167,139,250,0.10);
|
|
45
|
+
|
|
46
|
+
/* Legacy aliases so nothing breaks during the rolling refactor */
|
|
47
|
+
--text2: var(--text-muted);
|
|
48
|
+
--surface2: var(--surface-2);
|
|
49
|
+
--green: var(--success);
|
|
50
|
+
--red: var(--danger);
|
|
51
|
+
--yellow: var(--warning);
|
|
52
|
+
--purple: var(--info);
|
|
53
|
+
|
|
54
|
+
/* Type scale (Major-Third-ish) */
|
|
55
|
+
--fs-xs: 11px;
|
|
56
|
+
--fs-sm: 12px;
|
|
57
|
+
--fs-md: 13px;
|
|
58
|
+
--fs-lg: 14px;
|
|
59
|
+
--fs-xl: 16px;
|
|
60
|
+
--fs-2xl: 20px;
|
|
61
|
+
--fs-3xl: 28px;
|
|
62
|
+
--fs-4xl: 36px;
|
|
63
|
+
|
|
64
|
+
/* Spacing — 4-based scale */
|
|
65
|
+
--sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
|
|
66
|
+
--sp-5: 20px; --sp-6: 24px; --sp-8: 32px; --sp-10: 40px;
|
|
67
|
+
|
|
68
|
+
/* Radii */
|
|
69
|
+
--radius-sm: 6px;
|
|
70
|
+
--radius: 8px;
|
|
71
|
+
--radius-lg: 12px;
|
|
72
|
+
--radius-pill: 999px;
|
|
73
|
+
|
|
74
|
+
/* Elevation */
|
|
75
|
+
--shadow-sm: 0 1px 2px rgba(0,0,0,0.3);
|
|
76
|
+
--shadow: 0 4px 12px rgba(0,0,0,0.25), 0 0 0 1px var(--border);
|
|
77
|
+
--shadow-lg: 0 12px 32px rgba(0,0,0,0.35), 0 0 0 1px var(--border-strong);
|
|
78
|
+
|
|
79
|
+
/* Motion */
|
|
80
|
+
--ease: cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
81
|
+
--t-fast: 120ms;
|
|
82
|
+
}
|
|
83
|
+
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
84
|
+
html { -webkit-text-size-adjust: 100%; }
|
|
85
|
+
body {
|
|
86
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
87
|
+
font-feature-settings: 'cv11', 'ss01', 'ss03';
|
|
88
|
+
font-size: var(--fs-md);
|
|
89
|
+
line-height: 1.5;
|
|
90
|
+
background: var(--bg);
|
|
91
|
+
color: var(--text);
|
|
92
|
+
-webkit-font-smoothing: antialiased;
|
|
93
|
+
text-rendering: optimizeLegibility;
|
|
94
|
+
}
|
|
95
|
+
code, .mono, pre { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-variant-ligatures: none; }
|
|
96
|
+
a { color: var(--accent); text-decoration: none; }
|
|
97
|
+
a:hover { color: var(--accent-2); }
|
|
98
|
+
:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; border-radius: 4px; }
|
|
99
|
+
|
|
100
|
+
.header {
|
|
101
|
+
background: linear-gradient(180deg, var(--surface), var(--surface) 70%, rgba(0,0,0,0.0));
|
|
102
|
+
border-bottom: 1px solid var(--border);
|
|
103
|
+
padding: var(--sp-3) var(--sp-6);
|
|
104
|
+
display: flex; align-items: center; gap: var(--sp-4);
|
|
105
|
+
position: sticky; top: 0; z-index: 20;
|
|
106
|
+
backdrop-filter: saturate(160%) blur(8px);
|
|
107
|
+
}
|
|
108
|
+
.header h1 { font-size: var(--fs-lg); font-weight: 600; letter-spacing: -0.01em; }
|
|
109
|
+
.header h1::before {
|
|
110
|
+
content: ''; display: inline-block; width: 14px; height: 14px;
|
|
111
|
+
margin-right: 10px; vertical-align: -2px; border-radius: 3px;
|
|
112
|
+
background: linear-gradient(135deg, var(--accent), var(--info));
|
|
113
|
+
box-shadow: 0 0 0 1px var(--border-strong) inset;
|
|
114
|
+
}
|
|
115
|
+
.badge {
|
|
116
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
117
|
+
padding: 3px 10px;
|
|
118
|
+
border-radius: var(--radius-pill);
|
|
119
|
+
font-size: var(--fs-xs); font-weight: 600;
|
|
120
|
+
letter-spacing: 0.02em;
|
|
121
|
+
}
|
|
122
|
+
.badge::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; box-shadow: 0 0 6px currentColor; }
|
|
123
|
+
.badge-ok { background: var(--success-soft); color: var(--success); border: 1px solid rgba(74,222,128,0.25); }
|
|
124
|
+
.badge-err { background: var(--danger-soft); color: var(--danger); border: 1px solid rgba(239,91,110,0.30); }
|
|
125
|
+
|
|
126
|
+
.nav { display: flex; gap: 2px; margin-left: var(--sp-6); }
|
|
127
|
+
.nav-btn {
|
|
128
|
+
background: none; border: none;
|
|
129
|
+
color: var(--text-muted);
|
|
130
|
+
padding: 6px 14px;
|
|
131
|
+
border-radius: var(--radius-sm);
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
font-size: var(--fs-md); font-weight: 500;
|
|
134
|
+
transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
|
|
135
|
+
}
|
|
136
|
+
.nav-btn:hover { background: var(--surface-2); color: var(--text); }
|
|
137
|
+
.nav-btn.active { background: var(--surface-2); color: var(--text); box-shadow: inset 0 -2px 0 0 var(--accent); }
|
|
138
|
+
.container { max-width: 1240px; margin: 0 auto; padding: var(--sp-6); }
|
|
139
|
+
.page { display: none; } .page.active { display: block; animation: fadeIn 180ms var(--ease); }
|
|
140
|
+
@keyframes fadeIn { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: none; } }
|
|
141
|
+
|
|
142
|
+
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-4); margin-bottom: var(--sp-6); }
|
|
143
|
+
.stat-card {
|
|
144
|
+
background: var(--surface);
|
|
145
|
+
border: 1px solid var(--border);
|
|
146
|
+
border-radius: var(--radius);
|
|
147
|
+
padding: var(--sp-5);
|
|
148
|
+
position: relative; overflow: hidden;
|
|
149
|
+
text-align: left;
|
|
150
|
+
transition: border-color var(--t-fast) var(--ease);
|
|
151
|
+
}
|
|
152
|
+
.stat-card:hover { border-color: var(--border-strong); }
|
|
153
|
+
.stat-card::after {
|
|
154
|
+
content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
|
|
155
|
+
background: linear-gradient(180deg, var(--accent), transparent);
|
|
156
|
+
opacity: 0.5;
|
|
157
|
+
}
|
|
158
|
+
.stat-card .label {
|
|
159
|
+
font-size: var(--fs-xs); color: var(--text-muted);
|
|
160
|
+
text-transform: uppercase; letter-spacing: 0.08em; font-weight: 500;
|
|
161
|
+
margin-bottom: var(--sp-2);
|
|
162
|
+
}
|
|
163
|
+
.stat-card .value {
|
|
164
|
+
font-size: var(--fs-3xl); font-weight: 600; letter-spacing: -0.02em;
|
|
165
|
+
color: var(--text); font-variant-numeric: tabular-nums; line-height: 1.1;
|
|
166
|
+
}
|
|
167
|
+
.stat-card .context {
|
|
168
|
+
font-size: var(--fs-xs); color: var(--text-muted);
|
|
169
|
+
margin-top: var(--sp-2);
|
|
170
|
+
font-variant-numeric: tabular-nums;
|
|
171
|
+
}
|
|
172
|
+
.stat-card .context.good { color: var(--success, #3fb950); }
|
|
173
|
+
.stat-card .context.warn { color: var(--warn, #d29922); }
|
|
174
|
+
|
|
175
|
+
.live-indicator {
|
|
176
|
+
display: inline-flex; align-items: center; gap: var(--sp-2);
|
|
177
|
+
font-size: var(--fs-xs); color: var(--text-muted);
|
|
178
|
+
text-transform: uppercase; letter-spacing: 0.08em;
|
|
179
|
+
}
|
|
180
|
+
.live-indicator::before {
|
|
181
|
+
content: ''; width: 8px; height: 8px; border-radius: 50%;
|
|
182
|
+
background: var(--success, #3fb950);
|
|
183
|
+
box-shadow: 0 0 0 0 rgba(63, 185, 80, 0.5);
|
|
184
|
+
animation: live-pulse 2s var(--ease) infinite;
|
|
185
|
+
}
|
|
186
|
+
@keyframes live-pulse {
|
|
187
|
+
0% { box-shadow: 0 0 0 0 rgba(63, 185, 80, 0.4); }
|
|
188
|
+
70% { box-shadow: 0 0 0 6px rgba(63, 185, 80, 0); }
|
|
189
|
+
100% { box-shadow: 0 0 0 0 rgba(63, 185, 80, 0); }
|
|
190
|
+
}
|
|
191
|
+
.dashboard-meta {
|
|
192
|
+
display: flex; align-items: baseline; justify-content: space-between;
|
|
193
|
+
gap: var(--sp-4); margin-bottom: var(--sp-4);
|
|
194
|
+
}
|
|
195
|
+
.dashboard-meta h1 {
|
|
196
|
+
font-size: var(--fs-lg); font-weight: 600; color: var(--text);
|
|
197
|
+
letter-spacing: -0.01em; margin: 0;
|
|
198
|
+
}
|
|
199
|
+
.card {
|
|
200
|
+
background: var(--surface);
|
|
201
|
+
border: 1px solid var(--border);
|
|
202
|
+
border-radius: var(--radius);
|
|
203
|
+
padding: var(--sp-5);
|
|
204
|
+
margin-bottom: var(--sp-4);
|
|
205
|
+
}
|
|
206
|
+
.card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--sp-4); }
|
|
207
|
+
.card-header h2 { font-size: var(--fs-lg); font-weight: 600; letter-spacing: -0.01em; color: var(--text); }
|
|
208
|
+
.source-row {
|
|
209
|
+
display: flex; align-items: center; gap: var(--sp-3);
|
|
210
|
+
padding: var(--sp-3) var(--sp-4);
|
|
211
|
+
border: 1px solid var(--border);
|
|
212
|
+
border-radius: var(--radius-sm);
|
|
213
|
+
margin-bottom: var(--sp-2);
|
|
214
|
+
background: var(--surface);
|
|
215
|
+
transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
|
|
216
|
+
}
|
|
217
|
+
.source-row:hover { background: var(--surface-2); border-color: var(--border-strong); }
|
|
218
|
+
.dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; position: relative; }
|
|
219
|
+
.dot-up { background: var(--success); box-shadow: 0 0 0 3px var(--success-soft); }
|
|
220
|
+
.dot-up::after { content: ''; position: absolute; inset: -4px; border-radius: 50%; background: var(--success); opacity: 0.4; animation: pulse 2s var(--ease) infinite; }
|
|
221
|
+
@keyframes pulse { 0%, 100% { opacity: 0.4; transform: scale(1); } 50% { opacity: 0; transform: scale(1.8); } }
|
|
222
|
+
.dot-down { background: var(--danger); box-shadow: 0 0 0 3px var(--danger-soft); }
|
|
223
|
+
.dot-disabled { background: var(--text-dim); box-shadow: 0 0 0 3px rgba(107,114,128,0.15); }
|
|
39
224
|
.source-info { flex: 1; min-width: 0; }
|
|
40
|
-
.source-info .name { font-weight: 600; font-size:
|
|
41
|
-
.source-info .url {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
.
|
|
47
|
-
.tag
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
.
|
|
56
|
-
.
|
|
225
|
+
.source-info .name { font-weight: 600; font-size: var(--fs-lg); letter-spacing: -0.01em; }
|
|
226
|
+
.source-info .url {
|
|
227
|
+
color: var(--text-muted); font-size: var(--fs-sm);
|
|
228
|
+
font-family: 'JetBrains Mono', ui-monospace, monospace; font-variant-ligatures: none;
|
|
229
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
230
|
+
}
|
|
231
|
+
.source-actions { display: flex; gap: var(--sp-1); flex-shrink: 0; }
|
|
232
|
+
.tag {
|
|
233
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
234
|
+
padding: 2px 8px;
|
|
235
|
+
border-radius: var(--radius-pill);
|
|
236
|
+
font-size: var(--fs-xs); font-weight: 500;
|
|
237
|
+
letter-spacing: 0.02em;
|
|
238
|
+
margin-left: 6px;
|
|
239
|
+
}
|
|
240
|
+
.tag-metrics { color: var(--accent); background: var(--accent-soft); border: 1px solid rgba(79,140,255,0.25); }
|
|
241
|
+
.tag-logs { color: var(--info); background: var(--info-soft); border: 1px solid rgba(167,139,250,0.25); }
|
|
242
|
+
.tag-type { color: var(--text-muted); background: var(--surface-2); border: 1px solid var(--border-strong); text-transform: capitalize; }
|
|
243
|
+
.tag-latency { color: var(--text-muted); font-size: var(--fs-xs); font-variant-numeric: tabular-nums; }
|
|
244
|
+
.service-row {
|
|
245
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
246
|
+
padding: var(--sp-3) var(--sp-4);
|
|
247
|
+
border: 1px solid var(--border);
|
|
248
|
+
border-radius: var(--radius-sm);
|
|
249
|
+
margin-bottom: var(--sp-2);
|
|
250
|
+
background: var(--surface);
|
|
251
|
+
transition: background var(--t-fast) var(--ease);
|
|
252
|
+
}
|
|
253
|
+
.service-row:hover { background: var(--surface-2); }
|
|
254
|
+
.service-row .name { font-weight: 600; font-size: var(--fs-lg); }
|
|
255
|
+
.btn {
|
|
256
|
+
padding: 7px 14px;
|
|
257
|
+
border-radius: var(--radius-sm);
|
|
258
|
+
font: 500 var(--fs-md)/1 inherit;
|
|
259
|
+
cursor: pointer;
|
|
260
|
+
border: 1px solid var(--border-strong);
|
|
261
|
+
background: var(--surface-2);
|
|
262
|
+
color: var(--text);
|
|
263
|
+
transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
|
|
264
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
265
|
+
}
|
|
266
|
+
.btn:hover { background: var(--surface-3); border-color: rgba(255,255,255,0.18); }
|
|
267
|
+
.btn:active { transform: translateY(1px); }
|
|
268
|
+
.btn-primary {
|
|
269
|
+
background: var(--accent);
|
|
270
|
+
border-color: var(--accent);
|
|
271
|
+
color: var(--text-inv);
|
|
272
|
+
font-weight: 600;
|
|
273
|
+
}
|
|
274
|
+
.btn-primary:hover { background: var(--accent-2); border-color: var(--accent-2); }
|
|
275
|
+
.btn-ghost { background: none; border-color: transparent; color: var(--text-muted); }
|
|
276
|
+
.btn-ghost:hover { background: var(--surface-2); color: var(--text); border-color: transparent; }
|
|
277
|
+
.btn-danger { background: none; color: var(--danger); border-color: transparent; }
|
|
278
|
+
.btn-danger:hover { background: var(--danger-soft); border-color: transparent; }
|
|
279
|
+
.btn-sm { padding: 4px 10px; font-size: var(--fs-sm); }
|
|
280
|
+
.btn-icon { background: none; border: none; color: var(--text-muted); cursor: pointer; padding: 4px 8px; border-radius: var(--radius-sm); font-size: var(--fs-xl); transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease); }
|
|
281
|
+
.btn-icon:hover { background: var(--surface-2); color: var(--text); }
|
|
282
|
+
.toggle { position: relative; width: 38px; height: 22px; cursor: pointer; flex-shrink: 0; }
|
|
57
283
|
.toggle input { display: none; }
|
|
58
|
-
.toggle .slider {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
.
|
|
74
|
-
.
|
|
75
|
-
.
|
|
76
|
-
.
|
|
77
|
-
|
|
78
|
-
|
|
284
|
+
.toggle .slider {
|
|
285
|
+
position: absolute; inset: 0;
|
|
286
|
+
background: var(--surface-3);
|
|
287
|
+
border: 1px solid var(--border-strong);
|
|
288
|
+
border-radius: var(--radius-pill);
|
|
289
|
+
transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
|
|
290
|
+
}
|
|
291
|
+
.toggle .slider::before {
|
|
292
|
+
content: ''; position: absolute;
|
|
293
|
+
width: 16px; height: 16px; left: 2px; top: 2px;
|
|
294
|
+
background: var(--text);
|
|
295
|
+
border-radius: 50%;
|
|
296
|
+
transition: transform var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
|
|
297
|
+
box-shadow: 0 1px 2px rgba(0,0,0,0.4);
|
|
298
|
+
}
|
|
299
|
+
.toggle input:checked + .slider { background: var(--accent); border-color: var(--accent); }
|
|
300
|
+
.toggle input:checked + .slider::before { transform: translateX(16px); background: #fff; }
|
|
301
|
+
.toggle:hover .slider { border-color: rgba(255,255,255,0.18); }
|
|
302
|
+
.modal-overlay {
|
|
303
|
+
display: none; position: fixed; inset: 0;
|
|
304
|
+
background: rgba(8,10,15,0.72);
|
|
305
|
+
backdrop-filter: blur(4px);
|
|
306
|
+
z-index: 100; align-items: center; justify-content: center;
|
|
307
|
+
}
|
|
308
|
+
.modal-overlay.open { display: flex; animation: fadeIn 140ms var(--ease); }
|
|
309
|
+
.modal {
|
|
310
|
+
background: var(--surface);
|
|
311
|
+
border: 1px solid var(--border-strong);
|
|
312
|
+
border-radius: var(--radius-lg);
|
|
313
|
+
box-shadow: var(--shadow-lg);
|
|
314
|
+
width: 560px; max-width: 92vw; max-height: 88vh;
|
|
315
|
+
overflow-y: auto;
|
|
316
|
+
animation: modalIn 180ms var(--ease);
|
|
317
|
+
}
|
|
318
|
+
@keyframes modalIn { from { opacity: 0; transform: translateY(8px) scale(0.98); } to { opacity: 1; transform: none; } }
|
|
319
|
+
.modal-header { display: flex; align-items: center; justify-content: space-between; padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--border); }
|
|
320
|
+
.modal-header h3 { font-size: var(--fs-xl); font-weight: 600; letter-spacing: -0.01em; }
|
|
321
|
+
.modal-body { padding: var(--sp-5); }
|
|
322
|
+
.modal-footer { display: flex; justify-content: flex-end; gap: var(--sp-2); padding: var(--sp-4) var(--sp-5); border-top: 1px solid var(--border); background: var(--surface); position: sticky; bottom: 0; }
|
|
323
|
+
.form-group { margin-bottom: var(--sp-4); }
|
|
324
|
+
.form-group label { display: block; font-size: var(--fs-sm); font-weight: 500; color: var(--text-muted); margin-bottom: 6px; letter-spacing: 0.02em; }
|
|
325
|
+
.form-group input, .form-group select, .form-group textarea {
|
|
326
|
+
width: 100%; padding: 9px 12px;
|
|
327
|
+
background: var(--bg);
|
|
328
|
+
border: 1px solid var(--border-strong);
|
|
329
|
+
border-radius: var(--radius-sm);
|
|
330
|
+
color: var(--text);
|
|
331
|
+
font: var(--fs-lg)/1.4 inherit;
|
|
332
|
+
transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
|
|
333
|
+
}
|
|
334
|
+
.form-group input:hover, .form-group select:hover, .form-group textarea:hover { border-color: rgba(255,255,255,0.16); }
|
|
335
|
+
.form-group input:focus, .form-group select:focus, .form-group textarea:focus {
|
|
336
|
+
outline: none;
|
|
337
|
+
border-color: var(--accent);
|
|
338
|
+
box-shadow: 0 0 0 3px var(--accent-ring);
|
|
339
|
+
}
|
|
340
|
+
.form-group select {
|
|
341
|
+
appearance: none;
|
|
342
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23a1a8b3'%3E%3Cpath d='M2 4l4 4 4-4'/%3E%3C/svg%3E");
|
|
343
|
+
background-repeat: no-repeat; background-position: right 12px center;
|
|
344
|
+
padding-right: 36px;
|
|
345
|
+
}
|
|
346
|
+
.form-group textarea { resize: vertical; min-height: 96px; font: var(--fs-md)/1.55 'JetBrains Mono', ui-monospace, monospace; }
|
|
347
|
+
.form-hint { font-size: var(--fs-xs); color: var(--text-dim); margin-top: 6px; line-height: 1.5; }
|
|
348
|
+
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }
|
|
349
|
+
.form-row-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: var(--sp-3); }
|
|
350
|
+
.test-result { padding: 10px 14px; border-radius: var(--radius-sm); font-size: var(--fs-md); margin-top: var(--sp-3); display: none; }
|
|
79
351
|
.test-result.show { display: block; }
|
|
80
|
-
.test-result.success { background:
|
|
81
|
-
.test-result.failure { background:
|
|
82
|
-
.endpoint-bar {
|
|
83
|
-
|
|
84
|
-
|
|
352
|
+
.test-result.success { background: var(--success-soft); border: 1px solid rgba(74,222,128,0.30); color: var(--success); }
|
|
353
|
+
.test-result.failure { background: var(--danger-soft); border: 1px solid rgba(239,91,110,0.35); color: var(--danger); }
|
|
354
|
+
.endpoint-bar {
|
|
355
|
+
background: var(--surface);
|
|
356
|
+
border: 1px solid var(--border);
|
|
357
|
+
border-radius: var(--radius);
|
|
358
|
+
padding: 10px 16px;
|
|
359
|
+
font: var(--fs-md)/1 'JetBrains Mono', ui-monospace, monospace;
|
|
360
|
+
color: var(--accent);
|
|
361
|
+
margin-bottom: var(--sp-6);
|
|
362
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
363
|
+
}
|
|
364
|
+
.endpoint-bar::before { content: 'POST'; display: inline-block; padding: 2px 6px; margin-right: 10px; background: var(--accent-soft); color: var(--accent); border-radius: 3px; font-size: var(--fs-xs); font-weight: 700; font-family: 'Inter var', sans-serif; }
|
|
365
|
+
.empty { color: var(--text-dim); text-align: center; padding: var(--sp-8) var(--sp-4); font-size: var(--fs-md); }
|
|
366
|
+
.spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid var(--border-strong); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.6s linear infinite; }
|
|
85
367
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
86
368
|
/* Tabs inside settings */
|
|
87
|
-
.tabs { display: flex; gap: 0; border-bottom: 1px solid var(--border); margin-bottom:
|
|
88
|
-
.tab-btn { background: none; border: none; border-bottom: 2px solid transparent; color: var(--
|
|
369
|
+
.tabs { display: flex; gap: 0; border-bottom: 1px solid var(--border); margin-bottom: var(--sp-5); }
|
|
370
|
+
.tab-btn { background: none; border: none; border-bottom: 2px solid transparent; color: var(--text-muted); padding: 10px 18px; cursor: pointer; font-size: var(--fs-md); font-weight: 500; transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease); }
|
|
89
371
|
.tab-btn:hover { color: var(--text); }
|
|
90
|
-
.tab-btn.active { color: var(--
|
|
372
|
+
.tab-btn.active { color: var(--text); border-bottom-color: var(--accent); }
|
|
91
373
|
.tab-content { display: none; } .tab-content.active { display: block; }
|
|
92
374
|
/* Threshold cards */
|
|
93
|
-
.threshold-group { display: grid; grid-template-columns: 1fr 1fr; gap:
|
|
94
|
-
.threshold-card { background: var(--
|
|
95
|
-
.threshold-card h4 { font-size:
|
|
375
|
+
.threshold-group { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); margin-bottom: var(--sp-5); }
|
|
376
|
+
.threshold-card { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: var(--sp-4); }
|
|
377
|
+
.threshold-card h4 { font-size: var(--fs-md); font-weight: 600; margin-bottom: var(--sp-3); letter-spacing: 0.01em; }
|
|
96
378
|
/* Metric table */
|
|
97
379
|
.metric-table { width: 100%; border-collapse: collapse; }
|
|
98
|
-
.metric-table th { text-align: left; font-size:
|
|
99
|
-
.metric-table td { padding:
|
|
100
|
-
.metric-table .query { font-family: monospace; font-size:
|
|
101
|
-
.metric-table tr
|
|
102
|
-
.
|
|
103
|
-
.toast
|
|
380
|
+
.metric-table th { text-align: left; font-size: var(--fs-xs); color: var(--text-muted); font-weight: 500; padding: 8px 10px; border-bottom: 1px solid var(--border-strong); text-transform: uppercase; letter-spacing: 0.06em; }
|
|
381
|
+
.metric-table td { padding: 10px; border-bottom: 1px solid var(--border); font-size: var(--fs-md); }
|
|
382
|
+
.metric-table .query { font-family: 'JetBrains Mono', ui-monospace, monospace; font-variant-ligatures: none; font-size: var(--fs-xs); color: var(--text-muted); max-width: 400px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
383
|
+
.metric-table tr { transition: background var(--t-fast) var(--ease); }
|
|
384
|
+
.metric-table tbody tr:hover td { background: var(--surface-2); }
|
|
385
|
+
.toast {
|
|
386
|
+
position: fixed; bottom: var(--sp-6); right: var(--sp-6);
|
|
387
|
+
background: var(--surface);
|
|
388
|
+
color: var(--text);
|
|
389
|
+
padding: 12px 18px;
|
|
390
|
+
border-radius: var(--radius);
|
|
391
|
+
border: 1px solid var(--border-strong);
|
|
392
|
+
box-shadow: var(--shadow);
|
|
393
|
+
font-size: var(--fs-md); font-weight: 500;
|
|
394
|
+
display: flex; align-items: center; gap: 10px;
|
|
395
|
+
opacity: 0; transform: translateY(8px);
|
|
396
|
+
transition: opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
|
|
397
|
+
pointer-events: none; z-index: 200;
|
|
398
|
+
}
|
|
399
|
+
.toast::before { content: ''; width: 8px; height: 8px; border-radius: 50%; background: var(--success); box-shadow: 0 0 0 3px var(--success-soft); }
|
|
400
|
+
.toast.show { opacity: 1; transform: none; }
|
|
401
|
+
.toast.toast-error::before { background: var(--danger); box-shadow: 0 0 0 3px var(--danger-soft); }
|
|
104
402
|
/* Health cards */
|
|
105
|
-
.health-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding:
|
|
106
|
-
.health-card
|
|
107
|
-
.health-card .hc-
|
|
108
|
-
.health-card .hc-
|
|
109
|
-
.health-card .hc-score
|
|
110
|
-
.health-card .hc-score.
|
|
111
|
-
.health-card .hc-score.
|
|
112
|
-
.health-card .hc-
|
|
113
|
-
.hc-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
.hc-
|
|
118
|
-
.
|
|
119
|
-
.hc-
|
|
120
|
-
.hc-
|
|
121
|
-
.hc-
|
|
122
|
-
.
|
|
403
|
+
.health-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--sp-5); transition: border-color var(--t-fast) var(--ease); }
|
|
404
|
+
.health-card:hover { border-color: var(--border-strong); }
|
|
405
|
+
.health-card .hc-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--sp-4); gap: var(--sp-3); }
|
|
406
|
+
.health-card .hc-name { font-size: var(--fs-xl); font-weight: 600; letter-spacing: -0.01em; }
|
|
407
|
+
.health-card .hc-score { font-size: var(--fs-3xl); font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: -0.02em; line-height: 1; }
|
|
408
|
+
.health-card .hc-score.healthy { color: var(--success); }
|
|
409
|
+
.health-card .hc-score.degraded { color: var(--warning); }
|
|
410
|
+
.health-card .hc-score.critical { color: var(--danger); }
|
|
411
|
+
.health-card .hc-spark {
|
|
412
|
+
display: block; width: 100%; height: 36px;
|
|
413
|
+
margin: var(--sp-1) 0 var(--sp-3); opacity: 0.85;
|
|
414
|
+
}
|
|
415
|
+
.health-card .hc-spark.healthy .spark-fill { fill: rgba(63, 185, 80, 0.12); }
|
|
416
|
+
.health-card .hc-spark.healthy .spark-line { stroke: var(--success); }
|
|
417
|
+
.health-card .hc-spark.degraded .spark-fill { fill: rgba(210, 153, 34, 0.12); }
|
|
418
|
+
.health-card .hc-spark.degraded .spark-line { stroke: var(--warning); }
|
|
419
|
+
.health-card .hc-spark.critical .spark-fill { fill: rgba(248, 81, 73, 0.12); }
|
|
420
|
+
.health-card .hc-spark.critical .spark-line { stroke: var(--danger); }
|
|
421
|
+
.health-card .hc-spark .spark-line { stroke-width: 1.5; fill: none; }
|
|
422
|
+
.health-card .hc-spark .spark-dot { fill: currentColor; }
|
|
423
|
+
.health-card .hc-spark .spark-empty {
|
|
424
|
+
stroke: var(--border); stroke-dasharray: 3 3; stroke-width: 1; fill: none;
|
|
425
|
+
}
|
|
426
|
+
.health-card .hc-status {
|
|
427
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
428
|
+
padding: 3px 10px;
|
|
429
|
+
border-radius: var(--radius-pill);
|
|
430
|
+
font-size: var(--fs-xs); font-weight: 600;
|
|
431
|
+
text-transform: uppercase; letter-spacing: 0.06em;
|
|
432
|
+
}
|
|
433
|
+
.hc-status::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
|
|
434
|
+
.hc-status.healthy { background: var(--success-soft); color: var(--success); border: 1px solid rgba(74,222,128,0.25); }
|
|
435
|
+
.hc-status.degraded { background: var(--warning-soft); color: var(--warning); border: 1px solid rgba(245,179,65,0.30); }
|
|
436
|
+
.hc-status.critical { background: var(--danger-soft); color: var(--danger); border: 1px solid rgba(239,91,110,0.35); }
|
|
437
|
+
.hc-metrics { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2); margin-top: var(--sp-3); }
|
|
438
|
+
.hc-metric { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px 12px; }
|
|
439
|
+
.hc-metric .label { font-size: var(--fs-xs); color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em; font-weight: 500; }
|
|
440
|
+
.hc-metric .val { font-size: var(--fs-lg); font-weight: 600; margin-top: 4px; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
|
|
441
|
+
.hc-anomalies { margin-top: var(--sp-3); }
|
|
442
|
+
.hc-anomaly {
|
|
443
|
+
background: var(--danger-soft);
|
|
444
|
+
border: 1px solid rgba(239,91,110,0.30);
|
|
445
|
+
border-left: 3px solid var(--danger);
|
|
446
|
+
border-radius: var(--radius-sm);
|
|
447
|
+
padding: 10px 12px; margin-top: 6px;
|
|
448
|
+
font-size: var(--fs-sm); color: var(--text); line-height: 1.5;
|
|
449
|
+
}
|
|
450
|
+
.hc-correlation {
|
|
451
|
+
background: var(--info-soft);
|
|
452
|
+
border: 1px solid rgba(167,139,250,0.25);
|
|
453
|
+
border-left: 3px solid var(--info);
|
|
454
|
+
border-radius: var(--radius-sm);
|
|
455
|
+
padding: 10px 12px; margin-top: 6px;
|
|
456
|
+
font-size: var(--fs-sm); color: var(--text); line-height: 1.5;
|
|
457
|
+
}
|
|
458
|
+
.info-footer {
|
|
459
|
+
max-width: 1240px; margin: var(--sp-8) auto var(--sp-6);
|
|
460
|
+
padding: var(--sp-4) var(--sp-6);
|
|
461
|
+
border-top: 1px solid var(--border);
|
|
462
|
+
color: var(--text-muted);
|
|
463
|
+
font-size: var(--fs-xs);
|
|
464
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-3);
|
|
465
|
+
letter-spacing: 0.02em;
|
|
466
|
+
}
|
|
467
|
+
.info-footer code { color: var(--text); font-size: var(--fs-xs); }
|
|
468
|
+
.info-footer strong { color: var(--text); font-weight: 600; }
|
|
469
|
+
.footer-sep { color: var(--text-dim); }
|
|
470
|
+
.footer-plugins { display: inline-flex; flex-wrap: wrap; gap: var(--sp-1); }
|
|
471
|
+
.footer-pill {
|
|
472
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
473
|
+
padding: 2px 8px;
|
|
474
|
+
background: var(--surface-2);
|
|
475
|
+
border: 1px solid var(--border-strong);
|
|
476
|
+
border-radius: var(--radius-pill);
|
|
477
|
+
font-size: var(--fs-xs);
|
|
478
|
+
color: var(--text);
|
|
479
|
+
}
|
|
480
|
+
.footer-pill-meta { color: var(--text-muted); font-weight: 400; }
|
|
481
|
+
|
|
123
482
|
@media (max-width: 768px) { .stats { grid-template-columns: repeat(2, 1fr); } .threshold-group { grid-template-columns: 1fr; } .form-row, .form-row-3 { grid-template-columns: 1fr; } }
|
|
124
483
|
</style>
|
|
125
484
|
</head>
|
|
@@ -141,11 +500,31 @@
|
|
|
141
500
|
<div class="container">
|
|
142
501
|
<!-- ===== Dashboard ===== -->
|
|
143
502
|
<div class="page active" id="page-dashboard">
|
|
503
|
+
<div class="dashboard-meta">
|
|
504
|
+
<h1>Overview</h1>
|
|
505
|
+
<span class="live-indicator" id="dash-live">Live</span>
|
|
506
|
+
</div>
|
|
144
507
|
<div class="stats">
|
|
145
|
-
<div class="stat-card"
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
508
|
+
<div class="stat-card">
|
|
509
|
+
<div class="label">Data Sources</div>
|
|
510
|
+
<div class="value" id="stat-sources">-</div>
|
|
511
|
+
<div class="context" id="stat-sources-ctx">configured</div>
|
|
512
|
+
</div>
|
|
513
|
+
<div class="stat-card">
|
|
514
|
+
<div class="label">Sources Up</div>
|
|
515
|
+
<div class="value" id="stat-sources-up">-</div>
|
|
516
|
+
<div class="context" id="stat-sources-up-ctx">connected</div>
|
|
517
|
+
</div>
|
|
518
|
+
<div class="stat-card">
|
|
519
|
+
<div class="label">Services</div>
|
|
520
|
+
<div class="value" id="stat-services">-</div>
|
|
521
|
+
<div class="context" id="stat-services-ctx">discovered</div>
|
|
522
|
+
</div>
|
|
523
|
+
<div class="stat-card">
|
|
524
|
+
<div class="label">MCP Tools</div>
|
|
525
|
+
<div class="value" id="stat-tools">6</div>
|
|
526
|
+
<div class="context">available</div>
|
|
527
|
+
</div>
|
|
149
528
|
</div>
|
|
150
529
|
<div class="endpoint-bar">
|
|
151
530
|
<span>MCP Endpoint: <strong id="mcp-url">http://localhost:3000/mcp</strong></span>
|
|
@@ -386,9 +765,29 @@ async function loadSettingsData() {
|
|
|
386
765
|
]); populateSettingsForm(); populateHealthForm(); populateMetricsSourceSelect(); } catch(e){ console.error(e); }
|
|
387
766
|
}
|
|
388
767
|
function updateStats() {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
768
|
+
const total = sourcesData.length;
|
|
769
|
+
const up = sourcesData.filter(s=>s.status==='up').length;
|
|
770
|
+
const enabled = sourcesData.filter(s=>s.enabled).length;
|
|
771
|
+
document.getElementById('stat-sources').textContent = total;
|
|
772
|
+
document.getElementById('stat-sources-up').textContent = up;
|
|
773
|
+
document.getElementById('stat-services').textContent = servicesData.length;
|
|
774
|
+
|
|
775
|
+
const srcCtx = document.getElementById('stat-sources-ctx');
|
|
776
|
+
if (srcCtx) srcCtx.textContent = enabled === total ? `${enabled} enabled` : `${enabled}/${total} enabled`;
|
|
777
|
+
|
|
778
|
+
const upCtx = document.getElementById('stat-sources-up-ctx');
|
|
779
|
+
if (upCtx) {
|
|
780
|
+
upCtx.textContent = total === 0 ? 'no sources' : `${up}/${total} connected`;
|
|
781
|
+
upCtx.classList.toggle('good', total > 0 && up === total);
|
|
782
|
+
upCtx.classList.toggle('warn', total > 0 && up < total);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const svcCtx = document.getElementById('stat-services-ctx');
|
|
786
|
+
if (svcCtx) {
|
|
787
|
+
const backends = new Set(servicesData.map(s => s.source || s.sourceName).filter(Boolean));
|
|
788
|
+
svcCtx.textContent = backends.size > 0 ? `across ${backends.size} backend${backends.size===1?'':'s'}` : 'discovered';
|
|
789
|
+
}
|
|
790
|
+
|
|
392
791
|
const allUp=sourcesData.length>0&&sourcesData.filter(s=>s.enabled).every(s=>s.status==='up');
|
|
393
792
|
const b=document.getElementById('status-badge');
|
|
394
793
|
b.textContent=sourcesData.length===0?'No sources':allUp?'All systems operational':'Issues detected';
|
|
@@ -631,6 +1030,39 @@ function saveMetric() {
|
|
|
631
1030
|
// --- Health Dashboard ---
|
|
632
1031
|
let healthData={};
|
|
633
1032
|
let healthInterval=null;
|
|
1033
|
+
// Score history per service, kept client-side. The server doesn't yet
|
|
1034
|
+
// expose a per-service score timeseries — this gives an at-a-glance trend
|
|
1035
|
+
// for the last ~7.5 minutes (30 points × 15s refresh).
|
|
1036
|
+
const SPARK_MAX = 30;
|
|
1037
|
+
const scoreHistory = {};
|
|
1038
|
+
function pushScore(name, score) {
|
|
1039
|
+
if (typeof score !== 'number') return;
|
|
1040
|
+
const arr = scoreHistory[name] || (scoreHistory[name] = []);
|
|
1041
|
+
arr.push(score);
|
|
1042
|
+
if (arr.length > SPARK_MAX) arr.shift();
|
|
1043
|
+
}
|
|
1044
|
+
function sparkSvg(name, status) {
|
|
1045
|
+
const pts = scoreHistory[name] || [];
|
|
1046
|
+
const w = 100, h = 36, pad = 2;
|
|
1047
|
+
if (pts.length < 2) {
|
|
1048
|
+
// Placeholder dashed midline until we have ≥2 samples.
|
|
1049
|
+
return `<svg class="hc-spark ${status}" viewBox="0 0 ${w} ${h}" preserveAspectRatio="none" aria-hidden="true">`
|
|
1050
|
+
+ `<line class="spark-empty" x1="0" y1="${h/2}" x2="${w}" y2="${h/2}"/></svg>`;
|
|
1051
|
+
}
|
|
1052
|
+
const min = 0, max = 100;
|
|
1053
|
+
const step = (w - pad*2) / (pts.length - 1);
|
|
1054
|
+
const y = v => h - pad - ((v - min) / (max - min)) * (h - pad*2);
|
|
1055
|
+
const coords = pts.map((v, i) => `${pad + i*step},${y(v)}`);
|
|
1056
|
+
const line = coords.join(' ');
|
|
1057
|
+
const area = `M${coords[0]} L${line.split(' ').join(' L')} L${pad + (pts.length-1)*step},${h-pad} L${pad},${h-pad} Z`;
|
|
1058
|
+
const last = coords[coords.length - 1].split(',');
|
|
1059
|
+
return `<svg class="hc-spark ${status}" viewBox="0 0 ${w} ${h}" preserveAspectRatio="none" aria-hidden="true">`
|
|
1060
|
+
+ `<path class="spark-fill" d="${area}"/>`
|
|
1061
|
+
+ `<polyline class="spark-line" points="${line}"/>`
|
|
1062
|
+
+ `<circle class="spark-dot" cx="${last[0]}" cy="${last[1]}" r="1.8"/>`
|
|
1063
|
+
+ `</svg>`;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
634
1066
|
async function loadHealthData() {
|
|
635
1067
|
try {
|
|
636
1068
|
healthData=await(await fetch('/api/health')).json();
|
|
@@ -647,11 +1079,13 @@ function renderHealthCards() {
|
|
|
647
1079
|
const s=h.status||'healthy';
|
|
648
1080
|
const m=h.signals?.metrics||{};
|
|
649
1081
|
const l=h.signals?.logs||{};
|
|
1082
|
+
pushScore(name, h.score);
|
|
650
1083
|
return `<div class="health-card">
|
|
651
1084
|
<div class="hc-header">
|
|
652
1085
|
<div><span class="hc-name">${esc(name)}</span> <span class="hc-status ${s}">${s}</span></div>
|
|
653
1086
|
<span class="hc-score ${s}">${h.score ?? '-'}</span>
|
|
654
1087
|
</div>
|
|
1088
|
+
${sparkSvg(name, s)}
|
|
655
1089
|
<div class="hc-metrics">
|
|
656
1090
|
<div class="hc-metric"><div class="label">CPU</div><div class="val">${typeof m.cpu==='number'?m.cpu.toFixed(1)+'%':'-'}</div></div>
|
|
657
1091
|
<div class="hc-metric"><div class="label">Memory</div><div class="val">${typeof m.memory==='number'?m.memory.toFixed(0)+' MB':'-'}</div></div>
|
|
@@ -669,7 +1103,30 @@ function renderHealthCards() {
|
|
|
669
1103
|
// --- Utils ---
|
|
670
1104
|
function esc(s){const d=document.createElement('div');d.textContent=s;return d.innerHTML;}
|
|
671
1105
|
async function refresh(){await Promise.all([loadSources(),loadServices()]);}
|
|
672
|
-
|
|
1106
|
+
|
|
1107
|
+
async function loadInfo(){
|
|
1108
|
+
try{
|
|
1109
|
+
const r=await fetch('/api/info'); if(!r.ok) return;
|
|
1110
|
+
const d=await r.json();
|
|
1111
|
+
const footer=document.getElementById('info-footer');
|
|
1112
|
+
if(!footer) return;
|
|
1113
|
+
const plugins=(d.plugins||[]).map(p=>`<span class="footer-pill">${esc(p.name)}<span class="footer-pill-meta">${esc(p.source)}${p.version?` · ${esc(p.version)}`:''}</span></span>`).join('');
|
|
1114
|
+
const sha=d.build&&d.build.commit?` · <code class="mono">${esc(d.build.commit.slice(0,7))}</code>`:'';
|
|
1115
|
+
footer.innerHTML=`
|
|
1116
|
+
<span><strong>${esc(d.name)}</strong> v${esc(d.version)}${sha}</span>
|
|
1117
|
+
<span class="footer-sep">·</span>
|
|
1118
|
+
<span>MCP ${esc(d.mcpProtocolVersion)}</span>
|
|
1119
|
+
<span class="footer-sep">·</span>
|
|
1120
|
+
<span>${esc(d.runtime?d.runtime.node:'')} ${esc(d.runtime?d.runtime.platform+'/'+d.runtime.arch:'')}</span>
|
|
1121
|
+
<span class="footer-sep">·</span>
|
|
1122
|
+
<span class="footer-plugins">${plugins||'<em>no plugins loaded</em>'}</span>
|
|
1123
|
+
`;
|
|
1124
|
+
}catch{/* server too old or /api/info disabled */}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
(async()=>{await loadTypes();await refresh();await loadInfo();setInterval(refresh,15000);})();
|
|
673
1128
|
</script>
|
|
1129
|
+
|
|
1130
|
+
<footer id="info-footer" class="info-footer"><span class="spinner"></span> loading server info…</footer>
|
|
674
1131
|
</body>
|
|
675
1132
|
</html>
|