@dotsetlabs/bellwether 2.1.0 → 2.1.2
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/CHANGELOG.md +35 -0
- package/README.md +48 -31
- package/dist/cli/commands/check.js +49 -6
- package/dist/cli/commands/dashboard.d.ts +3 -0
- package/dist/cli/commands/dashboard.js +69 -0
- package/dist/cli/commands/discover.js +24 -2
- package/dist/cli/commands/explore.js +49 -6
- package/dist/cli/commands/watch.js +12 -1
- package/dist/cli/index.js +27 -34
- package/dist/cli/utils/headers.d.ts +12 -0
- package/dist/cli/utils/headers.js +63 -0
- package/dist/config/defaults.d.ts +2 -0
- package/dist/config/defaults.js +2 -0
- package/dist/config/template.js +12 -0
- package/dist/config/validator.d.ts +38 -18
- package/dist/config/validator.js +10 -0
- package/dist/constants/core.d.ts +4 -2
- package/dist/constants/core.js +13 -2
- package/dist/dashboard/index.d.ts +3 -0
- package/dist/dashboard/index.js +6 -0
- package/dist/dashboard/runtime/artifact-index.d.ts +45 -0
- package/dist/dashboard/runtime/artifact-index.js +238 -0
- package/dist/dashboard/runtime/command-profiles.d.ts +764 -0
- package/dist/dashboard/runtime/command-profiles.js +691 -0
- package/dist/dashboard/runtime/config-service.d.ts +21 -0
- package/dist/dashboard/runtime/config-service.js +73 -0
- package/dist/dashboard/runtime/job-runner.d.ts +26 -0
- package/dist/dashboard/runtime/job-runner.js +292 -0
- package/dist/dashboard/security/input-validation.d.ts +3 -0
- package/dist/dashboard/security/input-validation.js +27 -0
- package/dist/dashboard/security/localhost-guard.d.ts +5 -0
- package/dist/dashboard/security/localhost-guard.js +52 -0
- package/dist/dashboard/server.d.ts +14 -0
- package/dist/dashboard/server.js +293 -0
- package/dist/dashboard/types.d.ts +55 -0
- package/dist/dashboard/types.js +2 -0
- package/dist/dashboard/ui.d.ts +2 -0
- package/dist/dashboard/ui.js +2264 -0
- package/dist/discovery/discovery.js +20 -1
- package/dist/discovery/types.d.ts +1 -1
- package/dist/docs/contract.js +7 -1
- package/dist/errors/retry.js +15 -1
- package/dist/errors/types.d.ts +10 -0
- package/dist/errors/types.js +28 -0
- package/dist/logging/logger.js +5 -2
- package/dist/transport/env-filter.d.ts +6 -0
- package/dist/transport/env-filter.js +76 -0
- package/dist/transport/http-transport.js +10 -0
- package/dist/transport/mcp-client.d.ts +16 -9
- package/dist/transport/mcp-client.js +119 -88
- package/dist/transport/sse-transport.js +19 -0
- package/dist/version.js +2 -2
- package/package.json +5 -15
- package/man/bellwether.1 +0 -204
- package/man/bellwether.1.md +0 -148
|
@@ -0,0 +1,2264 @@
|
|
|
1
|
+
export function renderDashboardHtml() {
|
|
2
|
+
return `<!doctype html>
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
|
+
<title>Bellwether Dashboard</title>
|
|
8
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
--bg: #f2f5f8;
|
|
11
|
+
--bg-elevated: #f8fbff;
|
|
12
|
+
--panel: #ffffff;
|
|
13
|
+
--panel-soft: #fbfcfd;
|
|
14
|
+
--text: #122033;
|
|
15
|
+
--muted: #5b6775;
|
|
16
|
+
--muted-strong: #3a4a5c;
|
|
17
|
+
--border: #d7dfe8;
|
|
18
|
+
--border-strong: #c2cdd8;
|
|
19
|
+
--accent: #0f766e;
|
|
20
|
+
--accent-strong: #0b5f59;
|
|
21
|
+
--info: #0c4a6e;
|
|
22
|
+
--danger: #b42318;
|
|
23
|
+
--success: #067647;
|
|
24
|
+
--warn: #b54708;
|
|
25
|
+
--console-bg: #0f172a;
|
|
26
|
+
--console-text: #dbe5f1;
|
|
27
|
+
--radius-panel: 16px;
|
|
28
|
+
--radius-control: 10px;
|
|
29
|
+
--shadow-sm: 0 2px 10px rgba(15, 23, 42, 0.05);
|
|
30
|
+
--shadow-md: 0 8px 24px rgba(15, 23, 42, 0.08);
|
|
31
|
+
--shadow-focus: 0 0 0 3px rgba(15, 118, 110, 0.2);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
* {
|
|
35
|
+
box-sizing: border-box;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
body {
|
|
39
|
+
margin: 0;
|
|
40
|
+
font-family: "IBM Plex Sans", "Segoe UI", Helvetica, Arial, sans-serif;
|
|
41
|
+
color: var(--text);
|
|
42
|
+
line-height: 1.42;
|
|
43
|
+
background:
|
|
44
|
+
radial-gradient(circle at top left, #c9f5e2 0%, transparent 31%),
|
|
45
|
+
radial-gradient(circle at bottom right, #c7dbff 0%, transparent 26%),
|
|
46
|
+
var(--bg);
|
|
47
|
+
min-height: 100vh;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.container {
|
|
51
|
+
max-width: 1440px;
|
|
52
|
+
margin: 0 auto;
|
|
53
|
+
padding: 20px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.header {
|
|
57
|
+
display: flex;
|
|
58
|
+
justify-content: space-between;
|
|
59
|
+
align-items: flex-end;
|
|
60
|
+
gap: 12px;
|
|
61
|
+
margin-bottom: 14px;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.title {
|
|
65
|
+
margin: 0;
|
|
66
|
+
font-size: 28px;
|
|
67
|
+
letter-spacing: 0.2px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.subtitle {
|
|
71
|
+
margin: 4px 0 0;
|
|
72
|
+
color: var(--muted);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.status-pill {
|
|
76
|
+
display: inline-flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
border-radius: 999px;
|
|
79
|
+
padding: 6px 12px;
|
|
80
|
+
background: #e8eef3;
|
|
81
|
+
color: #25313d;
|
|
82
|
+
font-size: 13px;
|
|
83
|
+
font-weight: 600;
|
|
84
|
+
border: 1px solid transparent;
|
|
85
|
+
transition: background-color 140ms ease, color 140ms ease, border-color 140ms ease;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.status-pill.tone-info {
|
|
89
|
+
background: #dbeafe;
|
|
90
|
+
color: #1d4ed8;
|
|
91
|
+
border-color: #bfdbfe;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.status-pill.tone-success {
|
|
95
|
+
background: #dcfce7;
|
|
96
|
+
color: #166534;
|
|
97
|
+
border-color: #bbf7d0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.status-pill.tone-warning {
|
|
101
|
+
background: #fff7ed;
|
|
102
|
+
color: #9a3412;
|
|
103
|
+
border-color: #fed7aa;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.status-pill.tone-danger {
|
|
107
|
+
background: #fee2e2;
|
|
108
|
+
color: #991b1b;
|
|
109
|
+
border-color: #fecaca;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.panel {
|
|
113
|
+
background: var(--panel);
|
|
114
|
+
border: 1px solid var(--border);
|
|
115
|
+
border-radius: var(--radius-panel);
|
|
116
|
+
box-shadow: var(--shadow-sm);
|
|
117
|
+
transition: box-shadow 180ms ease, border-color 180ms ease, transform 180ms ease;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.panel:hover {
|
|
121
|
+
box-shadow: var(--shadow-md);
|
|
122
|
+
border-color: var(--border-strong);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.section {
|
|
126
|
+
padding: 16px;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
h2 {
|
|
130
|
+
margin: 0 0 12px;
|
|
131
|
+
font-size: 18px;
|
|
132
|
+
letter-spacing: 0.15px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
h3 {
|
|
136
|
+
margin: 0 0 10px;
|
|
137
|
+
font-size: 15px;
|
|
138
|
+
letter-spacing: 0.1px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.overview-grid {
|
|
142
|
+
display: grid;
|
|
143
|
+
grid-template-columns: repeat(8, minmax(0, 1fr));
|
|
144
|
+
gap: 10px;
|
|
145
|
+
margin-bottom: 14px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.stat-card {
|
|
149
|
+
padding: 12px;
|
|
150
|
+
border: 1px solid var(--border);
|
|
151
|
+
border-radius: 12px;
|
|
152
|
+
background: linear-gradient(180deg, #ffffff 0%, #f7fafc 100%);
|
|
153
|
+
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.stat-label {
|
|
157
|
+
color: var(--muted);
|
|
158
|
+
font-size: 11px;
|
|
159
|
+
text-transform: uppercase;
|
|
160
|
+
letter-spacing: 0.4px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.stat-value {
|
|
164
|
+
margin-top: 6px;
|
|
165
|
+
font-size: 22px;
|
|
166
|
+
font-weight: 700;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.controls-grid {
|
|
170
|
+
display: grid;
|
|
171
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
172
|
+
gap: 10px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.grid-2 {
|
|
176
|
+
display: grid;
|
|
177
|
+
grid-template-columns: 1fr 1fr;
|
|
178
|
+
gap: 12px;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.grid-3 {
|
|
182
|
+
display: grid;
|
|
183
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
184
|
+
gap: 10px;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.grid-4 {
|
|
188
|
+
display: grid;
|
|
189
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
190
|
+
gap: 10px;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.field {
|
|
194
|
+
display: flex;
|
|
195
|
+
flex-direction: column;
|
|
196
|
+
gap: 6px;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.field label {
|
|
200
|
+
font-size: 12px;
|
|
201
|
+
color: var(--muted);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.field input,
|
|
205
|
+
.field select,
|
|
206
|
+
.field textarea {
|
|
207
|
+
width: 100%;
|
|
208
|
+
border: 1px solid var(--border);
|
|
209
|
+
border-radius: var(--radius-control);
|
|
210
|
+
padding: 9px 10px;
|
|
211
|
+
font-size: 13px;
|
|
212
|
+
background: #fff;
|
|
213
|
+
font-family: inherit;
|
|
214
|
+
transition: border-color 130ms ease, box-shadow 130ms ease, background-color 130ms ease;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.field textarea {
|
|
218
|
+
min-height: 120px;
|
|
219
|
+
resize: vertical;
|
|
220
|
+
font-family: "SF Mono", "Roboto Mono", Menlo, Consolas, monospace;
|
|
221
|
+
font-size: 12px;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.field input:focus,
|
|
225
|
+
.field select:focus,
|
|
226
|
+
.field textarea:focus {
|
|
227
|
+
outline: none;
|
|
228
|
+
border-color: #7dc7c1;
|
|
229
|
+
box-shadow: var(--shadow-focus);
|
|
230
|
+
background: #fcffff;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.actions {
|
|
234
|
+
display: flex;
|
|
235
|
+
flex-wrap: wrap;
|
|
236
|
+
gap: 8px;
|
|
237
|
+
margin-top: 10px;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.baseline-actions {
|
|
241
|
+
display: grid;
|
|
242
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
243
|
+
gap: 10px;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.advanced-grid {
|
|
247
|
+
display: grid;
|
|
248
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
249
|
+
gap: 10px;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.subpanel {
|
|
253
|
+
border: 1px solid var(--border);
|
|
254
|
+
border-radius: 12px;
|
|
255
|
+
background: var(--panel-soft);
|
|
256
|
+
padding: 12px;
|
|
257
|
+
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.75);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
button {
|
|
261
|
+
border: 0;
|
|
262
|
+
border-radius: var(--radius-control);
|
|
263
|
+
padding: 9px 12px;
|
|
264
|
+
font-size: 13px;
|
|
265
|
+
font-weight: 600;
|
|
266
|
+
cursor: pointer;
|
|
267
|
+
transition: transform 100ms ease, box-shadow 130ms ease, background-color 130ms ease, color 130ms ease;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
button.primary {
|
|
271
|
+
background: var(--accent);
|
|
272
|
+
color: #ffffff;
|
|
273
|
+
box-shadow: 0 4px 10px rgba(15, 118, 110, 0.22);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
button.primary:hover {
|
|
277
|
+
background: var(--accent-strong);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
button.secondary {
|
|
281
|
+
background: #e8eef3;
|
|
282
|
+
color: #243141;
|
|
283
|
+
border: 1px solid #d8e3ec;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
button.secondary:hover {
|
|
287
|
+
background: #dbe5ee;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
button.danger {
|
|
291
|
+
background: #fce7e7;
|
|
292
|
+
color: var(--danger);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
button.danger:hover {
|
|
296
|
+
background: #f8d2d2;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
button:active {
|
|
300
|
+
transform: translateY(1px);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
button:disabled {
|
|
304
|
+
opacity: 0.55;
|
|
305
|
+
cursor: not-allowed;
|
|
306
|
+
box-shadow: none;
|
|
307
|
+
transform: none;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
button:focus-visible {
|
|
311
|
+
outline: none;
|
|
312
|
+
box-shadow: var(--shadow-focus);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.line {
|
|
316
|
+
margin: 8px 0;
|
|
317
|
+
height: 1px;
|
|
318
|
+
background: #e8edf2;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.layout {
|
|
322
|
+
display: grid;
|
|
323
|
+
grid-template-columns: 390px 1fr;
|
|
324
|
+
gap: 12px;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.run-list-controls {
|
|
328
|
+
display: grid;
|
|
329
|
+
grid-template-columns: 1fr 150px;
|
|
330
|
+
gap: 8px;
|
|
331
|
+
margin-bottom: 10px;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.run-list {
|
|
335
|
+
display: flex;
|
|
336
|
+
flex-direction: column;
|
|
337
|
+
gap: 8px;
|
|
338
|
+
max-height: 640px;
|
|
339
|
+
overflow: auto;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.run-row {
|
|
343
|
+
border: 1px solid var(--border);
|
|
344
|
+
border-radius: 10px;
|
|
345
|
+
padding: 10px;
|
|
346
|
+
background: #fbfcfd;
|
|
347
|
+
display: flex;
|
|
348
|
+
flex-direction: column;
|
|
349
|
+
gap: 6px;
|
|
350
|
+
transition: border-color 130ms ease, box-shadow 130ms ease, transform 130ms ease;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.run-row:hover {
|
|
354
|
+
border-color: var(--border-strong);
|
|
355
|
+
box-shadow: 0 4px 14px rgba(15, 23, 42, 0.08);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.run-row.active {
|
|
359
|
+
border-color: #86b9ff;
|
|
360
|
+
box-shadow: 0 0 0 2px rgba(134, 185, 255, 0.25);
|
|
361
|
+
background: #f6fbff;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.run-head {
|
|
365
|
+
display: flex;
|
|
366
|
+
justify-content: space-between;
|
|
367
|
+
align-items: center;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.run-actions {
|
|
371
|
+
display: flex;
|
|
372
|
+
gap: 8px;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.mono {
|
|
376
|
+
font-family: "SF Mono", "Roboto Mono", Menlo, Consolas, monospace;
|
|
377
|
+
font-size: 12px;
|
|
378
|
+
color: var(--muted);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.tag {
|
|
382
|
+
border-radius: 999px;
|
|
383
|
+
padding: 4px 8px;
|
|
384
|
+
font-size: 11px;
|
|
385
|
+
font-weight: 700;
|
|
386
|
+
text-transform: uppercase;
|
|
387
|
+
letter-spacing: 0.4px;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.tag.running {
|
|
391
|
+
background: #e0f2fe;
|
|
392
|
+
color: #075985;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.tag.completed {
|
|
396
|
+
background: #d1fadf;
|
|
397
|
+
color: var(--success);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.tag.failed {
|
|
401
|
+
background: #fee4e2;
|
|
402
|
+
color: var(--danger);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.tag.cancelled {
|
|
406
|
+
background: #fff4e5;
|
|
407
|
+
color: var(--warn);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
pre {
|
|
411
|
+
margin: 0;
|
|
412
|
+
border-radius: 12px;
|
|
413
|
+
border: 1px solid var(--border);
|
|
414
|
+
background: #f8fafc;
|
|
415
|
+
padding: 10px;
|
|
416
|
+
min-height: 220px;
|
|
417
|
+
max-height: min(52vh, 420px);
|
|
418
|
+
overflow: auto;
|
|
419
|
+
font-family: "SF Mono", "Roboto Mono", Menlo, Consolas, monospace;
|
|
420
|
+
font-size: 12px;
|
|
421
|
+
line-height: 1.46;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
#consoleOutput {
|
|
425
|
+
border: 1px solid #1f2a3d;
|
|
426
|
+
background: var(--console-bg);
|
|
427
|
+
color: var(--console-text);
|
|
428
|
+
min-height: min(55vh, 680px);
|
|
429
|
+
max-height: min(55vh, 680px);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.status-text {
|
|
433
|
+
margin-top: 8px;
|
|
434
|
+
color: var(--muted);
|
|
435
|
+
font-size: 12px;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.status-text.tone-info {
|
|
439
|
+
color: var(--info);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.status-text.tone-success {
|
|
443
|
+
color: var(--success);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.status-text.tone-warning {
|
|
447
|
+
color: var(--warn);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.status-text.tone-danger {
|
|
451
|
+
color: var(--danger);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.inline-check {
|
|
455
|
+
display: flex;
|
|
456
|
+
align-items: center;
|
|
457
|
+
gap: 6px;
|
|
458
|
+
font-size: 12px;
|
|
459
|
+
color: var(--muted);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.top-controls {
|
|
463
|
+
display: flex;
|
|
464
|
+
align-items: center;
|
|
465
|
+
gap: 8px;
|
|
466
|
+
flex-wrap: wrap;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.jump-links {
|
|
470
|
+
display: flex;
|
|
471
|
+
flex-wrap: wrap;
|
|
472
|
+
gap: 8px;
|
|
473
|
+
padding: 10px;
|
|
474
|
+
margin-bottom: 12px;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.jump-link {
|
|
478
|
+
text-decoration: none;
|
|
479
|
+
color: var(--muted-strong);
|
|
480
|
+
background: #eef3f8;
|
|
481
|
+
border: 1px solid #d7e2ec;
|
|
482
|
+
border-radius: 999px;
|
|
483
|
+
padding: 6px 10px;
|
|
484
|
+
font-size: 12px;
|
|
485
|
+
font-weight: 600;
|
|
486
|
+
transition: background-color 130ms ease, border-color 130ms ease, color 130ms ease;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.jump-link:hover {
|
|
490
|
+
background: #e2ebf3;
|
|
491
|
+
border-color: #c8d6e5;
|
|
492
|
+
color: #1f3247;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.jump-link:focus-visible {
|
|
496
|
+
outline: none;
|
|
497
|
+
box-shadow: var(--shadow-focus);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
.section-gap {
|
|
501
|
+
margin-top: 12px;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.span-2 {
|
|
505
|
+
grid-column: span 2;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.mt-10 {
|
|
509
|
+
margin-top: 10px;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.mt-8 {
|
|
513
|
+
margin-top: 8px;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.check-offset {
|
|
517
|
+
margin-top: 22px;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.top-controls-between {
|
|
521
|
+
justify-content: space-between;
|
|
522
|
+
margin-bottom: 8px;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.no-margin {
|
|
526
|
+
margin: 0;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.mb-8 {
|
|
530
|
+
margin-bottom: 8px;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
::-webkit-scrollbar {
|
|
534
|
+
width: 10px;
|
|
535
|
+
height: 10px;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
::-webkit-scrollbar-thumb {
|
|
539
|
+
background: #c6d2de;
|
|
540
|
+
border-radius: 999px;
|
|
541
|
+
border: 2px solid rgba(0, 0, 0, 0);
|
|
542
|
+
background-clip: padding-box;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
::-webkit-scrollbar-track {
|
|
546
|
+
background: transparent;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
@media (prefers-reduced-motion: reduce) {
|
|
550
|
+
* {
|
|
551
|
+
animation-duration: 0.01ms !important;
|
|
552
|
+
animation-iteration-count: 1 !important;
|
|
553
|
+
transition-duration: 0.01ms !important;
|
|
554
|
+
scroll-behavior: auto !important;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
@media (max-width: 1320px) {
|
|
559
|
+
.overview-grid {
|
|
560
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.layout {
|
|
564
|
+
grid-template-columns: 1fr;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
@media (max-width: 1120px) {
|
|
569
|
+
.controls-grid,
|
|
570
|
+
.grid-4,
|
|
571
|
+
.grid-3,
|
|
572
|
+
.advanced-grid,
|
|
573
|
+
.grid-2,
|
|
574
|
+
.baseline-actions,
|
|
575
|
+
.run-list-controls {
|
|
576
|
+
grid-template-columns: 1fr;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.overview-grid {
|
|
580
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
#consoleOutput {
|
|
584
|
+
min-height: 45vh;
|
|
585
|
+
max-height: 45vh;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
@media (max-width: 760px) {
|
|
590
|
+
.container {
|
|
591
|
+
padding: 12px;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.header {
|
|
595
|
+
flex-direction: column;
|
|
596
|
+
align-items: flex-start;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.status-pill {
|
|
600
|
+
width: 100%;
|
|
601
|
+
justify-content: center;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.overview-grid {
|
|
605
|
+
grid-template-columns: 1fr;
|
|
606
|
+
gap: 8px;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.actions button {
|
|
610
|
+
width: 100%;
|
|
611
|
+
justify-content: center;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.jump-links {
|
|
615
|
+
padding: 8px;
|
|
616
|
+
gap: 6px;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
.jump-link {
|
|
620
|
+
flex: 1 1 calc(50% - 6px);
|
|
621
|
+
text-align: center;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
.run-actions {
|
|
625
|
+
flex-wrap: wrap;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.run-actions button {
|
|
629
|
+
width: 100%;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
</style>
|
|
633
|
+
</head>
|
|
634
|
+
<body>
|
|
635
|
+
<div class="container">
|
|
636
|
+
<div class="header">
|
|
637
|
+
<div>
|
|
638
|
+
<h1 class="title">Bellwether Dashboard</h1>
|
|
639
|
+
<p class="subtitle">Milestone 3: advanced workflows, richer summaries, and stronger run controls.</p>
|
|
640
|
+
</div>
|
|
641
|
+
<span id="connectionStatus" class="status-pill tone-info" aria-live="polite">Loading...</span>
|
|
642
|
+
</div>
|
|
643
|
+
|
|
644
|
+
<nav class="panel jump-links" aria-label="Dashboard sections">
|
|
645
|
+
<a class="jump-link" href="#section-quick">Quick Runs</a>
|
|
646
|
+
<a class="jump-link" href="#section-baseline">Baselines</a>
|
|
647
|
+
<a class="jump-link" href="#section-advanced">Advanced</a>
|
|
648
|
+
<a class="jump-link" href="#section-config">Config + Artifacts</a>
|
|
649
|
+
<a class="jump-link" href="#section-runs">Runs + Console</a>
|
|
650
|
+
</nav>
|
|
651
|
+
|
|
652
|
+
<section class="overview-grid" id="section-overview">
|
|
653
|
+
<article class="panel stat-card">
|
|
654
|
+
<div class="stat-label">Total Runs</div>
|
|
655
|
+
<div id="overviewTotalRuns" class="stat-value">0</div>
|
|
656
|
+
</article>
|
|
657
|
+
<article class="panel stat-card">
|
|
658
|
+
<div class="stat-label">Running</div>
|
|
659
|
+
<div id="overviewRunningRuns" class="stat-value">0</div>
|
|
660
|
+
</article>
|
|
661
|
+
<article class="panel stat-card">
|
|
662
|
+
<div class="stat-label">Existing Artifacts</div>
|
|
663
|
+
<div id="overviewArtifacts" class="stat-value">0</div>
|
|
664
|
+
</article>
|
|
665
|
+
<article class="panel stat-card">
|
|
666
|
+
<div class="stat-label">Last Run Status</div>
|
|
667
|
+
<div id="overviewLastStatus" class="stat-value">-</div>
|
|
668
|
+
</article>
|
|
669
|
+
<article class="panel stat-card">
|
|
670
|
+
<div class="stat-label">Last Check Drift</div>
|
|
671
|
+
<div id="overviewCheckDrift" class="stat-value">-</div>
|
|
672
|
+
</article>
|
|
673
|
+
<article class="panel stat-card">
|
|
674
|
+
<div class="stat-label">Check Tools</div>
|
|
675
|
+
<div id="overviewCheckTools" class="stat-value">-</div>
|
|
676
|
+
</article>
|
|
677
|
+
<article class="panel stat-card">
|
|
678
|
+
<div class="stat-label">Explore Tools</div>
|
|
679
|
+
<div id="overviewExploreTools" class="stat-value">-</div>
|
|
680
|
+
</article>
|
|
681
|
+
<article class="panel stat-card">
|
|
682
|
+
<div class="stat-label">Golden Outputs</div>
|
|
683
|
+
<div id="overviewGoldenOutputs" class="stat-value">-</div>
|
|
684
|
+
</article>
|
|
685
|
+
</section>
|
|
686
|
+
|
|
687
|
+
<section class="panel section" id="section-quick">
|
|
688
|
+
<h2>Quick Runs</h2>
|
|
689
|
+
<div class="controls-grid">
|
|
690
|
+
<div class="field">
|
|
691
|
+
<label for="quickProfile">Profile</label>
|
|
692
|
+
<select id="quickProfile">
|
|
693
|
+
<option value="check">check</option>
|
|
694
|
+
<option value="explore">explore</option>
|
|
695
|
+
<option value="validate-config">validate-config</option>
|
|
696
|
+
</select>
|
|
697
|
+
</div>
|
|
698
|
+
<div class="field">
|
|
699
|
+
<label for="configPath">Config Path (workspace-relative)</label>
|
|
700
|
+
<input id="configPath" type="text" placeholder="bellwether.yaml" />
|
|
701
|
+
</div>
|
|
702
|
+
<div class="field">
|
|
703
|
+
<label for="serverCommand">Server Command (check/explore)</label>
|
|
704
|
+
<input id="serverCommand" type="text" placeholder="npx @mcp/your-server" />
|
|
705
|
+
</div>
|
|
706
|
+
<div class="field">
|
|
707
|
+
<label for="serverArgs">Server Args (check/explore)</label>
|
|
708
|
+
<input id="serverArgs" type="text" placeholder="/tmp --flag value" />
|
|
709
|
+
</div>
|
|
710
|
+
</div>
|
|
711
|
+
<div class="actions">
|
|
712
|
+
<button id="quickRunButton" class="primary">Run Profile</button>
|
|
713
|
+
<button id="cancelButton" class="secondary" disabled>Cancel Active Run</button>
|
|
714
|
+
<button id="cancelAllRunsButton" class="danger">Cancel All Running</button>
|
|
715
|
+
<button id="refreshRunsButton" class="secondary">Refresh Runs</button>
|
|
716
|
+
</div>
|
|
717
|
+
</section>
|
|
718
|
+
|
|
719
|
+
<section class="panel section section-gap" id="section-baseline">
|
|
720
|
+
<h2>Baseline Actions</h2>
|
|
721
|
+
<div class="baseline-actions">
|
|
722
|
+
<article class="subpanel">
|
|
723
|
+
<h3>Save</h3>
|
|
724
|
+
<div class="grid-2">
|
|
725
|
+
<div class="field">
|
|
726
|
+
<label for="baselineSavePath">Path (optional)</label>
|
|
727
|
+
<input id="baselineSavePath" type="text" placeholder="./bellwether-baseline.json" />
|
|
728
|
+
</div>
|
|
729
|
+
<div class="field">
|
|
730
|
+
<label for="baselineSaveReport">Report (optional)</label>
|
|
731
|
+
<input id="baselineSaveReport" type="text" placeholder=".bellwether/bellwether-check.json" />
|
|
732
|
+
</div>
|
|
733
|
+
</div>
|
|
734
|
+
<label class="inline-check">
|
|
735
|
+
<input id="baselineSaveForce" type="checkbox" />
|
|
736
|
+
Force overwrite
|
|
737
|
+
</label>
|
|
738
|
+
<div class="actions">
|
|
739
|
+
<button id="baselineSaveButton" class="secondary">Run baseline save</button>
|
|
740
|
+
</div>
|
|
741
|
+
</article>
|
|
742
|
+
|
|
743
|
+
<article class="subpanel">
|
|
744
|
+
<h3>Compare</h3>
|
|
745
|
+
<div class="grid-2">
|
|
746
|
+
<div class="field">
|
|
747
|
+
<label for="baselineComparePath">Baseline Path (optional)</label>
|
|
748
|
+
<input id="baselineComparePath" type="text" placeholder="./bellwether-baseline.json" />
|
|
749
|
+
</div>
|
|
750
|
+
<div class="field">
|
|
751
|
+
<label for="baselineCompareReport">Report (optional)</label>
|
|
752
|
+
<input id="baselineCompareReport" type="text" placeholder=".bellwether/bellwether-check.json" />
|
|
753
|
+
</div>
|
|
754
|
+
</div>
|
|
755
|
+
<div class="grid-3">
|
|
756
|
+
<div class="field">
|
|
757
|
+
<label for="baselineCompareFormat">Format</label>
|
|
758
|
+
<select id="baselineCompareFormat">
|
|
759
|
+
<option value="">(default)</option>
|
|
760
|
+
<option value="text">text</option>
|
|
761
|
+
<option value="json">json</option>
|
|
762
|
+
<option value="markdown">markdown</option>
|
|
763
|
+
<option value="compact">compact</option>
|
|
764
|
+
</select>
|
|
765
|
+
</div>
|
|
766
|
+
<label class="inline-check">
|
|
767
|
+
<input id="baselineCompareFailOnDrift" type="checkbox" />
|
|
768
|
+
Fail on drift
|
|
769
|
+
</label>
|
|
770
|
+
<label class="inline-check">
|
|
771
|
+
<input id="baselineCompareIgnoreVersion" type="checkbox" />
|
|
772
|
+
Ignore version mismatch
|
|
773
|
+
</label>
|
|
774
|
+
</div>
|
|
775
|
+
<div class="actions">
|
|
776
|
+
<button id="baselineCompareButton" class="secondary">Run baseline compare</button>
|
|
777
|
+
</div>
|
|
778
|
+
</article>
|
|
779
|
+
|
|
780
|
+
<article class="subpanel">
|
|
781
|
+
<h3>Show</h3>
|
|
782
|
+
<div class="field">
|
|
783
|
+
<label for="baselineShowPath">Baseline Path (optional)</label>
|
|
784
|
+
<input id="baselineShowPath" type="text" placeholder="./bellwether-baseline.json" />
|
|
785
|
+
</div>
|
|
786
|
+
<div class="grid-3">
|
|
787
|
+
<label class="inline-check">
|
|
788
|
+
<input id="baselineShowJson" type="checkbox" />
|
|
789
|
+
JSON
|
|
790
|
+
</label>
|
|
791
|
+
<label class="inline-check">
|
|
792
|
+
<input id="baselineShowTools" type="checkbox" />
|
|
793
|
+
Tools only
|
|
794
|
+
</label>
|
|
795
|
+
<label class="inline-check">
|
|
796
|
+
<input id="baselineShowAssertions" type="checkbox" />
|
|
797
|
+
Assertions only
|
|
798
|
+
</label>
|
|
799
|
+
</div>
|
|
800
|
+
<div class="actions">
|
|
801
|
+
<button id="baselineShowButton" class="secondary">Run baseline show</button>
|
|
802
|
+
</div>
|
|
803
|
+
</article>
|
|
804
|
+
|
|
805
|
+
<article class="subpanel">
|
|
806
|
+
<h3>Diff</h3>
|
|
807
|
+
<div class="grid-2">
|
|
808
|
+
<div class="field">
|
|
809
|
+
<label for="baselineDiffPath1">Path 1 (required)</label>
|
|
810
|
+
<input id="baselineDiffPath1" type="text" placeholder="v1-baseline.json" />
|
|
811
|
+
</div>
|
|
812
|
+
<div class="field">
|
|
813
|
+
<label for="baselineDiffPath2">Path 2 (required)</label>
|
|
814
|
+
<input id="baselineDiffPath2" type="text" placeholder="v2-baseline.json" />
|
|
815
|
+
</div>
|
|
816
|
+
</div>
|
|
817
|
+
<div class="grid-3">
|
|
818
|
+
<div class="field">
|
|
819
|
+
<label for="baselineDiffFormat">Format</label>
|
|
820
|
+
<select id="baselineDiffFormat">
|
|
821
|
+
<option value="">(default)</option>
|
|
822
|
+
<option value="text">text</option>
|
|
823
|
+
<option value="json">json</option>
|
|
824
|
+
<option value="markdown">markdown</option>
|
|
825
|
+
<option value="compact">compact</option>
|
|
826
|
+
</select>
|
|
827
|
+
</div>
|
|
828
|
+
<label class="inline-check">
|
|
829
|
+
<input id="baselineDiffIgnoreVersion" type="checkbox" />
|
|
830
|
+
Ignore version mismatch
|
|
831
|
+
</label>
|
|
832
|
+
</div>
|
|
833
|
+
<div class="actions">
|
|
834
|
+
<button id="baselineDiffButton" class="secondary">Run baseline diff</button>
|
|
835
|
+
</div>
|
|
836
|
+
</article>
|
|
837
|
+
|
|
838
|
+
<article class="subpanel span-2">
|
|
839
|
+
<h3>Accept</h3>
|
|
840
|
+
<div class="controls-grid">
|
|
841
|
+
<div class="field">
|
|
842
|
+
<label for="baselineAcceptPath">Baseline Path (optional)</label>
|
|
843
|
+
<input id="baselineAcceptPath" type="text" placeholder="./bellwether-baseline.json" />
|
|
844
|
+
</div>
|
|
845
|
+
<div class="field">
|
|
846
|
+
<label for="baselineAcceptReport">Report (optional)</label>
|
|
847
|
+
<input id="baselineAcceptReport" type="text" placeholder=".bellwether/bellwether-check.json" />
|
|
848
|
+
</div>
|
|
849
|
+
<div class="field">
|
|
850
|
+
<label for="baselineAcceptReason">Reason (optional)</label>
|
|
851
|
+
<input id="baselineAcceptReason" type="text" placeholder="Intentional schema update" />
|
|
852
|
+
</div>
|
|
853
|
+
<div class="field">
|
|
854
|
+
<label for="baselineAcceptBy">Accepted By (optional)</label>
|
|
855
|
+
<input id="baselineAcceptBy" type="text" placeholder="Your name" />
|
|
856
|
+
</div>
|
|
857
|
+
</div>
|
|
858
|
+
<div class="grid-3">
|
|
859
|
+
<label class="inline-check">
|
|
860
|
+
<input id="baselineAcceptDryRun" type="checkbox" />
|
|
861
|
+
Dry run
|
|
862
|
+
</label>
|
|
863
|
+
<label class="inline-check">
|
|
864
|
+
<input id="baselineAcceptForce" type="checkbox" />
|
|
865
|
+
Force (for breaking changes)
|
|
866
|
+
</label>
|
|
867
|
+
</div>
|
|
868
|
+
<div class="actions">
|
|
869
|
+
<button id="baselineAcceptButton" class="secondary">Run baseline accept</button>
|
|
870
|
+
</div>
|
|
871
|
+
</article>
|
|
872
|
+
</div>
|
|
873
|
+
</section>
|
|
874
|
+
|
|
875
|
+
<section class="panel section section-gap" id="section-advanced">
|
|
876
|
+
<h2>Advanced Workflows</h2>
|
|
877
|
+
<div id="advancedStatus" class="status-text tone-info" aria-live="polite">Ready.</div>
|
|
878
|
+
<div class="advanced-grid mt-10">
|
|
879
|
+
<article class="subpanel">
|
|
880
|
+
<h3>Discover</h3>
|
|
881
|
+
<div class="grid-4">
|
|
882
|
+
<div class="field">
|
|
883
|
+
<label for="discoverTransport">Transport</label>
|
|
884
|
+
<select id="discoverTransport">
|
|
885
|
+
<option value="stdio">stdio</option>
|
|
886
|
+
<option value="sse">sse</option>
|
|
887
|
+
<option value="streamable-http">streamable-http</option>
|
|
888
|
+
</select>
|
|
889
|
+
</div>
|
|
890
|
+
<div class="field">
|
|
891
|
+
<label for="discoverUrl">Remote URL (for sse/http)</label>
|
|
892
|
+
<input id="discoverUrl" type="text" placeholder="http://localhost:3000/sse" />
|
|
893
|
+
</div>
|
|
894
|
+
<div class="field">
|
|
895
|
+
<label for="discoverSessionId">Session ID (optional)</label>
|
|
896
|
+
<input id="discoverSessionId" type="text" placeholder="session-id" />
|
|
897
|
+
</div>
|
|
898
|
+
<div class="field">
|
|
899
|
+
<label for="discoverTimeout">Timeout (ms)</label>
|
|
900
|
+
<input id="discoverTimeout" type="text" placeholder="30000" />
|
|
901
|
+
</div>
|
|
902
|
+
</div>
|
|
903
|
+
<div class="grid-2 mt-8">
|
|
904
|
+
<div class="field">
|
|
905
|
+
<label for="discoverCommand">Server Command (stdio)</label>
|
|
906
|
+
<input id="discoverCommand" type="text" placeholder="npx @mcp/server" />
|
|
907
|
+
</div>
|
|
908
|
+
<div class="field">
|
|
909
|
+
<label for="discoverArgs">Server Args (stdio)</label>
|
|
910
|
+
<input id="discoverArgs" type="text" placeholder="--flag value" />
|
|
911
|
+
</div>
|
|
912
|
+
</div>
|
|
913
|
+
<label class="inline-check mt-8">
|
|
914
|
+
<input id="discoverJson" type="checkbox" />
|
|
915
|
+
Output JSON
|
|
916
|
+
</label>
|
|
917
|
+
<div class="actions">
|
|
918
|
+
<button id="discoverRunButton" class="secondary">Run discover</button>
|
|
919
|
+
</div>
|
|
920
|
+
</article>
|
|
921
|
+
|
|
922
|
+
<article class="subpanel">
|
|
923
|
+
<h3>Watch</h3>
|
|
924
|
+
<div class="grid-2">
|
|
925
|
+
<div class="field">
|
|
926
|
+
<label for="watchCommand">Server Command (optional override)</label>
|
|
927
|
+
<input id="watchCommand" type="text" placeholder="npx @mcp/server" />
|
|
928
|
+
</div>
|
|
929
|
+
<div class="field">
|
|
930
|
+
<label for="watchArgs">Server Args (optional override)</label>
|
|
931
|
+
<input id="watchArgs" type="text" placeholder="--stdio" />
|
|
932
|
+
</div>
|
|
933
|
+
</div>
|
|
934
|
+
<div class="actions">
|
|
935
|
+
<button id="watchRunButton" class="secondary">Start watch</button>
|
|
936
|
+
<span class="status-text">Watch runs until cancelled.</span>
|
|
937
|
+
</div>
|
|
938
|
+
</article>
|
|
939
|
+
|
|
940
|
+
<article class="subpanel">
|
|
941
|
+
<h3>Registry</h3>
|
|
942
|
+
<div class="grid-3">
|
|
943
|
+
<div class="field">
|
|
944
|
+
<label for="registryQuery">Query (optional)</label>
|
|
945
|
+
<input id="registryQuery" type="text" placeholder="filesystem" />
|
|
946
|
+
</div>
|
|
947
|
+
<div class="field">
|
|
948
|
+
<label for="registryLimit">Limit</label>
|
|
949
|
+
<input id="registryLimit" type="text" placeholder="10" />
|
|
950
|
+
</div>
|
|
951
|
+
<label class="inline-check check-offset">
|
|
952
|
+
<input id="registryJson" type="checkbox" />
|
|
953
|
+
Output JSON
|
|
954
|
+
</label>
|
|
955
|
+
</div>
|
|
956
|
+
<div class="actions">
|
|
957
|
+
<button id="registryRunButton" class="secondary">Run registry search</button>
|
|
958
|
+
</div>
|
|
959
|
+
</article>
|
|
960
|
+
|
|
961
|
+
<article class="subpanel">
|
|
962
|
+
<h3>Contract</h3>
|
|
963
|
+
<div class="line"></div>
|
|
964
|
+
<h3>Validate</h3>
|
|
965
|
+
<div class="grid-2">
|
|
966
|
+
<div class="field">
|
|
967
|
+
<label for="contractValidateCommand">Server Command</label>
|
|
968
|
+
<input id="contractValidateCommand" type="text" placeholder="npx @mcp/server" />
|
|
969
|
+
</div>
|
|
970
|
+
<div class="field">
|
|
971
|
+
<label for="contractValidateArgs">Server Args</label>
|
|
972
|
+
<input id="contractValidateArgs" type="text" placeholder="--stdio" />
|
|
973
|
+
</div>
|
|
974
|
+
</div>
|
|
975
|
+
<div class="grid-4 mt-8">
|
|
976
|
+
<div class="field">
|
|
977
|
+
<label for="contractValidatePath">Contract Path (optional)</label>
|
|
978
|
+
<input id="contractValidatePath" type="text" placeholder=".bellwether/contract.bellwether.yaml" />
|
|
979
|
+
</div>
|
|
980
|
+
<div class="field">
|
|
981
|
+
<label for="contractValidateMode">Mode</label>
|
|
982
|
+
<select id="contractValidateMode">
|
|
983
|
+
<option value="">(default)</option>
|
|
984
|
+
<option value="strict">strict</option>
|
|
985
|
+
<option value="lenient">lenient</option>
|
|
986
|
+
<option value="report">report</option>
|
|
987
|
+
</select>
|
|
988
|
+
</div>
|
|
989
|
+
<div class="field">
|
|
990
|
+
<label for="contractValidateFormat">Format</label>
|
|
991
|
+
<select id="contractValidateFormat">
|
|
992
|
+
<option value="">(default)</option>
|
|
993
|
+
<option value="text">text</option>
|
|
994
|
+
<option value="json">json</option>
|
|
995
|
+
<option value="markdown">markdown</option>
|
|
996
|
+
</select>
|
|
997
|
+
</div>
|
|
998
|
+
<div class="field">
|
|
999
|
+
<label for="contractValidateTimeout">Timeout (ms)</label>
|
|
1000
|
+
<input id="contractValidateTimeout" type="text" placeholder="30000" />
|
|
1001
|
+
</div>
|
|
1002
|
+
</div>
|
|
1003
|
+
<label class="inline-check mt-8">
|
|
1004
|
+
<input id="contractValidateFail" type="checkbox" />
|
|
1005
|
+
Fail on violation
|
|
1006
|
+
</label>
|
|
1007
|
+
<div class="actions">
|
|
1008
|
+
<button id="contractValidateButton" class="secondary">Run contract validate</button>
|
|
1009
|
+
</div>
|
|
1010
|
+
|
|
1011
|
+
<div class="line"></div>
|
|
1012
|
+
<h3>Generate</h3>
|
|
1013
|
+
<div class="grid-2">
|
|
1014
|
+
<div class="field">
|
|
1015
|
+
<label for="contractGenerateCommand">Server Command</label>
|
|
1016
|
+
<input id="contractGenerateCommand" type="text" placeholder="npx @mcp/server" />
|
|
1017
|
+
</div>
|
|
1018
|
+
<div class="field">
|
|
1019
|
+
<label for="contractGenerateArgs">Server Args</label>
|
|
1020
|
+
<input id="contractGenerateArgs" type="text" placeholder="--stdio" />
|
|
1021
|
+
</div>
|
|
1022
|
+
</div>
|
|
1023
|
+
<div class="grid-3 mt-8">
|
|
1024
|
+
<div class="field">
|
|
1025
|
+
<label for="contractGenerateOutput">Output Path (optional)</label>
|
|
1026
|
+
<input id="contractGenerateOutput" type="text" placeholder=".bellwether/contract.bellwether.yaml" />
|
|
1027
|
+
</div>
|
|
1028
|
+
<div class="field">
|
|
1029
|
+
<label for="contractGenerateTimeout">Timeout (ms)</label>
|
|
1030
|
+
<input id="contractGenerateTimeout" type="text" placeholder="30000" />
|
|
1031
|
+
</div>
|
|
1032
|
+
<label class="inline-check check-offset">
|
|
1033
|
+
<input id="contractGenerateForce" type="checkbox" />
|
|
1034
|
+
Force overwrite
|
|
1035
|
+
</label>
|
|
1036
|
+
</div>
|
|
1037
|
+
<div class="actions">
|
|
1038
|
+
<button id="contractGenerateButton" class="secondary">Run contract generate</button>
|
|
1039
|
+
</div>
|
|
1040
|
+
|
|
1041
|
+
<div class="line"></div>
|
|
1042
|
+
<h3>Show</h3>
|
|
1043
|
+
<div class="grid-2">
|
|
1044
|
+
<div class="field">
|
|
1045
|
+
<label for="contractShowPath">Contract Path (optional)</label>
|
|
1046
|
+
<input id="contractShowPath" type="text" placeholder=".bellwether/contract.bellwether.yaml" />
|
|
1047
|
+
</div>
|
|
1048
|
+
<label class="inline-check check-offset">
|
|
1049
|
+
<input id="contractShowJson" type="checkbox" />
|
|
1050
|
+
Output JSON
|
|
1051
|
+
</label>
|
|
1052
|
+
</div>
|
|
1053
|
+
<div class="actions">
|
|
1054
|
+
<button id="contractShowButton" class="secondary">Run contract show</button>
|
|
1055
|
+
</div>
|
|
1056
|
+
</article>
|
|
1057
|
+
|
|
1058
|
+
<article class="subpanel span-2">
|
|
1059
|
+
<h3>Golden Outputs</h3>
|
|
1060
|
+
<div class="line"></div>
|
|
1061
|
+
<h3>Save</h3>
|
|
1062
|
+
<div class="grid-4">
|
|
1063
|
+
<div class="field">
|
|
1064
|
+
<label for="goldenSaveTool">Tool Name</label>
|
|
1065
|
+
<input id="goldenSaveTool" type="text" placeholder="search" />
|
|
1066
|
+
</div>
|
|
1067
|
+
<div class="field">
|
|
1068
|
+
<label for="goldenSaveArgs">Args JSON (optional)</label>
|
|
1069
|
+
<input id="goldenSaveArgs" type="text" placeholder='{"q":"weather"}' />
|
|
1070
|
+
</div>
|
|
1071
|
+
<div class="field">
|
|
1072
|
+
<label for="goldenSaveMode">Mode</label>
|
|
1073
|
+
<select id="goldenSaveMode">
|
|
1074
|
+
<option value="">(default)</option>
|
|
1075
|
+
<option value="exact">exact</option>
|
|
1076
|
+
<option value="structural">structural</option>
|
|
1077
|
+
<option value="semantic">semantic</option>
|
|
1078
|
+
</select>
|
|
1079
|
+
</div>
|
|
1080
|
+
<div class="field">
|
|
1081
|
+
<label for="goldenSaveAllowedDrift">Allowed Drift (comma-separated)</label>
|
|
1082
|
+
<input id="goldenSaveAllowedDrift" type="text" placeholder="$.timestamp,$.id" />
|
|
1083
|
+
</div>
|
|
1084
|
+
</div>
|
|
1085
|
+
<div class="grid-3 mt-8">
|
|
1086
|
+
<div class="field">
|
|
1087
|
+
<label for="goldenSaveDescription">Description (optional)</label>
|
|
1088
|
+
<input id="goldenSaveDescription" type="text" placeholder="Golden output for API search" />
|
|
1089
|
+
</div>
|
|
1090
|
+
<label class="inline-check check-offset">
|
|
1091
|
+
<input id="goldenSaveDisableTimestamps" type="checkbox" />
|
|
1092
|
+
Disable timestamp normalization
|
|
1093
|
+
</label>
|
|
1094
|
+
<label class="inline-check check-offset">
|
|
1095
|
+
<input id="goldenSaveDisableUuids" type="checkbox" />
|
|
1096
|
+
Disable UUID normalization
|
|
1097
|
+
</label>
|
|
1098
|
+
</div>
|
|
1099
|
+
<div class="actions">
|
|
1100
|
+
<button id="goldenSaveButton" class="secondary">Run golden save</button>
|
|
1101
|
+
</div>
|
|
1102
|
+
|
|
1103
|
+
<div class="line"></div>
|
|
1104
|
+
<h3>Compare / List / Delete</h3>
|
|
1105
|
+
<div class="grid-4">
|
|
1106
|
+
<div class="field">
|
|
1107
|
+
<label for="goldenCompareTool">Compare Tool (optional)</label>
|
|
1108
|
+
<input id="goldenCompareTool" type="text" placeholder="search" />
|
|
1109
|
+
</div>
|
|
1110
|
+
<div class="field">
|
|
1111
|
+
<label for="goldenCompareFormat">Compare Format</label>
|
|
1112
|
+
<select id="goldenCompareFormat">
|
|
1113
|
+
<option value="">(default)</option>
|
|
1114
|
+
<option value="text">text</option>
|
|
1115
|
+
<option value="json">json</option>
|
|
1116
|
+
<option value="markdown">markdown</option>
|
|
1117
|
+
</select>
|
|
1118
|
+
</div>
|
|
1119
|
+
<label class="inline-check check-offset">
|
|
1120
|
+
<input id="goldenCompareFailOnDrift" type="checkbox" />
|
|
1121
|
+
Fail on drift
|
|
1122
|
+
</label>
|
|
1123
|
+
<div class="field">
|
|
1124
|
+
<label for="goldenListFormat">List Format</label>
|
|
1125
|
+
<select id="goldenListFormat">
|
|
1126
|
+
<option value="">(default)</option>
|
|
1127
|
+
<option value="text">text</option>
|
|
1128
|
+
<option value="json">json</option>
|
|
1129
|
+
</select>
|
|
1130
|
+
</div>
|
|
1131
|
+
</div>
|
|
1132
|
+
<div class="grid-2 mt-8">
|
|
1133
|
+
<div class="field">
|
|
1134
|
+
<label for="goldenDeleteTool">Delete Tool</label>
|
|
1135
|
+
<input id="goldenDeleteTool" type="text" placeholder="search" />
|
|
1136
|
+
</div>
|
|
1137
|
+
<label class="inline-check check-offset">
|
|
1138
|
+
<input id="goldenDeleteAll" type="checkbox" />
|
|
1139
|
+
Delete all matching inputs
|
|
1140
|
+
</label>
|
|
1141
|
+
</div>
|
|
1142
|
+
<div class="actions">
|
|
1143
|
+
<button id="goldenCompareButton" class="secondary">Run golden compare</button>
|
|
1144
|
+
<button id="goldenListButton" class="secondary">Run golden list</button>
|
|
1145
|
+
<button id="goldenDeleteButton" class="secondary">Run golden delete</button>
|
|
1146
|
+
</div>
|
|
1147
|
+
</article>
|
|
1148
|
+
</div>
|
|
1149
|
+
</section>
|
|
1150
|
+
|
|
1151
|
+
<section class="grid-2 section-gap" id="section-config">
|
|
1152
|
+
<article class="panel section">
|
|
1153
|
+
<h2>Config Editor</h2>
|
|
1154
|
+
<div class="actions">
|
|
1155
|
+
<button id="configLoadButton" class="secondary">Load Config</button>
|
|
1156
|
+
<button id="configSaveButton" class="secondary">Save Config</button>
|
|
1157
|
+
<button id="configValidateButton" class="secondary">Validate Config</button>
|
|
1158
|
+
</div>
|
|
1159
|
+
<div class="status-text" id="configStatus" aria-live="polite">No config loaded.</div>
|
|
1160
|
+
<div class="line"></div>
|
|
1161
|
+
<div class="field">
|
|
1162
|
+
<label for="configEditor">YAML</label>
|
|
1163
|
+
<textarea id="configEditor" spellcheck="false"></textarea>
|
|
1164
|
+
</div>
|
|
1165
|
+
<div class="status-text" id="configValidationResult" aria-live="polite"></div>
|
|
1166
|
+
</article>
|
|
1167
|
+
|
|
1168
|
+
<article class="panel section">
|
|
1169
|
+
<h2>Artifact Browser</h2>
|
|
1170
|
+
<div class="actions">
|
|
1171
|
+
<button id="artifactsRefreshButton" class="secondary">Refresh Artifacts</button>
|
|
1172
|
+
</div>
|
|
1173
|
+
<div class="field">
|
|
1174
|
+
<label for="artifactSelect">Artifact</label>
|
|
1175
|
+
<select id="artifactSelect"></select>
|
|
1176
|
+
</div>
|
|
1177
|
+
<div class="status-text" id="artifactMeta" aria-live="polite">No artifact selected.</div>
|
|
1178
|
+
<div class="line"></div>
|
|
1179
|
+
<pre id="artifactOutput"></pre>
|
|
1180
|
+
</article>
|
|
1181
|
+
</section>
|
|
1182
|
+
|
|
1183
|
+
<section class="layout section-gap" id="section-runs">
|
|
1184
|
+
<article class="panel section">
|
|
1185
|
+
<h2>Run History</h2>
|
|
1186
|
+
<div class="run-list-controls">
|
|
1187
|
+
<input id="runSearchInput" type="text" placeholder="Filter by profile or command" />
|
|
1188
|
+
<select id="runStatusFilter">
|
|
1189
|
+
<option value="all">all statuses</option>
|
|
1190
|
+
<option value="running">running</option>
|
|
1191
|
+
<option value="completed">completed</option>
|
|
1192
|
+
<option value="failed">failed</option>
|
|
1193
|
+
<option value="cancelled">cancelled</option>
|
|
1194
|
+
</select>
|
|
1195
|
+
</div>
|
|
1196
|
+
<div id="runList" class="run-list"></div>
|
|
1197
|
+
</article>
|
|
1198
|
+
|
|
1199
|
+
<article class="panel section">
|
|
1200
|
+
<div class="top-controls top-controls-between">
|
|
1201
|
+
<h2 class="no-margin">Console</h2>
|
|
1202
|
+
<div class="top-controls">
|
|
1203
|
+
<select id="consoleStreamFilter">
|
|
1204
|
+
<option value="all">all streams</option>
|
|
1205
|
+
<option value="stdout">stdout</option>
|
|
1206
|
+
<option value="stderr">stderr</option>
|
|
1207
|
+
</select>
|
|
1208
|
+
<label class="inline-check">
|
|
1209
|
+
<input id="consoleAutoScroll" type="checkbox" checked />
|
|
1210
|
+
Auto-scroll
|
|
1211
|
+
</label>
|
|
1212
|
+
<button id="clearConsoleButton" class="secondary">Clear Console</button>
|
|
1213
|
+
</div>
|
|
1214
|
+
</div>
|
|
1215
|
+
<div id="consoleMeta" class="mono mb-8">No run selected.</div>
|
|
1216
|
+
<pre id="consoleOutput"></pre>
|
|
1217
|
+
</article>
|
|
1218
|
+
</section>
|
|
1219
|
+
</div>
|
|
1220
|
+
|
|
1221
|
+
<script>
|
|
1222
|
+
(function () {
|
|
1223
|
+
var connectionStatus = document.getElementById('connectionStatus');
|
|
1224
|
+
var quickProfile = document.getElementById('quickProfile');
|
|
1225
|
+
var configPathInput = document.getElementById('configPath');
|
|
1226
|
+
var serverCommandInput = document.getElementById('serverCommand');
|
|
1227
|
+
var serverArgsInput = document.getElementById('serverArgs');
|
|
1228
|
+
var quickRunButton = document.getElementById('quickRunButton');
|
|
1229
|
+
var cancelButton = document.getElementById('cancelButton');
|
|
1230
|
+
var cancelAllRunsButton = document.getElementById('cancelAllRunsButton');
|
|
1231
|
+
var refreshRunsButton = document.getElementById('refreshRunsButton');
|
|
1232
|
+
|
|
1233
|
+
var runSearchInput = document.getElementById('runSearchInput');
|
|
1234
|
+
var runStatusFilter = document.getElementById('runStatusFilter');
|
|
1235
|
+
var runList = document.getElementById('runList');
|
|
1236
|
+
|
|
1237
|
+
var consoleOutput = document.getElementById('consoleOutput');
|
|
1238
|
+
var consoleMeta = document.getElementById('consoleMeta');
|
|
1239
|
+
var clearConsoleButton = document.getElementById('clearConsoleButton');
|
|
1240
|
+
var consoleStreamFilter = document.getElementById('consoleStreamFilter');
|
|
1241
|
+
var consoleAutoScroll = document.getElementById('consoleAutoScroll');
|
|
1242
|
+
|
|
1243
|
+
var overviewTotalRuns = document.getElementById('overviewTotalRuns');
|
|
1244
|
+
var overviewRunningRuns = document.getElementById('overviewRunningRuns');
|
|
1245
|
+
var overviewArtifacts = document.getElementById('overviewArtifacts');
|
|
1246
|
+
var overviewLastStatus = document.getElementById('overviewLastStatus');
|
|
1247
|
+
var overviewCheckDrift = document.getElementById('overviewCheckDrift');
|
|
1248
|
+
var overviewCheckTools = document.getElementById('overviewCheckTools');
|
|
1249
|
+
var overviewExploreTools = document.getElementById('overviewExploreTools');
|
|
1250
|
+
var overviewGoldenOutputs = document.getElementById('overviewGoldenOutputs');
|
|
1251
|
+
|
|
1252
|
+
var advancedStatus = document.getElementById('advancedStatus');
|
|
1253
|
+
|
|
1254
|
+
var configLoadButton = document.getElementById('configLoadButton');
|
|
1255
|
+
var configSaveButton = document.getElementById('configSaveButton');
|
|
1256
|
+
var configValidateButton = document.getElementById('configValidateButton');
|
|
1257
|
+
var configEditor = document.getElementById('configEditor');
|
|
1258
|
+
var configStatus = document.getElementById('configStatus');
|
|
1259
|
+
var configValidationResult = document.getElementById('configValidationResult');
|
|
1260
|
+
|
|
1261
|
+
var artifactsRefreshButton = document.getElementById('artifactsRefreshButton');
|
|
1262
|
+
var artifactSelect = document.getElementById('artifactSelect');
|
|
1263
|
+
var artifactMeta = document.getElementById('artifactMeta');
|
|
1264
|
+
var artifactOutput = document.getElementById('artifactOutput');
|
|
1265
|
+
|
|
1266
|
+
var baselineSaveButton = document.getElementById('baselineSaveButton');
|
|
1267
|
+
var baselineCompareButton = document.getElementById('baselineCompareButton');
|
|
1268
|
+
var baselineShowButton = document.getElementById('baselineShowButton');
|
|
1269
|
+
var baselineDiffButton = document.getElementById('baselineDiffButton');
|
|
1270
|
+
var baselineAcceptButton = document.getElementById('baselineAcceptButton');
|
|
1271
|
+
|
|
1272
|
+
var discoverRunButton = document.getElementById('discoverRunButton');
|
|
1273
|
+
var watchRunButton = document.getElementById('watchRunButton');
|
|
1274
|
+
var registryRunButton = document.getElementById('registryRunButton');
|
|
1275
|
+
var contractValidateButton = document.getElementById('contractValidateButton');
|
|
1276
|
+
var contractGenerateButton = document.getElementById('contractGenerateButton');
|
|
1277
|
+
var contractShowButton = document.getElementById('contractShowButton');
|
|
1278
|
+
var goldenSaveButton = document.getElementById('goldenSaveButton');
|
|
1279
|
+
var goldenCompareButton = document.getElementById('goldenCompareButton');
|
|
1280
|
+
var goldenListButton = document.getElementById('goldenListButton');
|
|
1281
|
+
var goldenDeleteButton = document.getElementById('goldenDeleteButton');
|
|
1282
|
+
|
|
1283
|
+
var currentRunId = null;
|
|
1284
|
+
var currentRunStatus = null;
|
|
1285
|
+
var eventSource = null;
|
|
1286
|
+
var runsTimer = null;
|
|
1287
|
+
var cachedArtifacts = [];
|
|
1288
|
+
var allRuns = [];
|
|
1289
|
+
var consoleEntries = [];
|
|
1290
|
+
var consoleRenderScheduled = false;
|
|
1291
|
+
|
|
1292
|
+
function clearStatusTone(element) {
|
|
1293
|
+
element.classList.remove('tone-info');
|
|
1294
|
+
element.classList.remove('tone-success');
|
|
1295
|
+
element.classList.remove('tone-warning');
|
|
1296
|
+
element.classList.remove('tone-danger');
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
function inferToneFromText(text) {
|
|
1300
|
+
var normalized = String(text || '').toLowerCase();
|
|
1301
|
+
if (
|
|
1302
|
+
normalized.includes('failed') ||
|
|
1303
|
+
normalized.includes('error') ||
|
|
1304
|
+
normalized.includes('invalid') ||
|
|
1305
|
+
normalized.includes('cancelled')
|
|
1306
|
+
) {
|
|
1307
|
+
return 'danger';
|
|
1308
|
+
}
|
|
1309
|
+
if (
|
|
1310
|
+
normalized.includes('warning') ||
|
|
1311
|
+
normalized.includes('drift') ||
|
|
1312
|
+
normalized.includes('disconnected')
|
|
1313
|
+
) {
|
|
1314
|
+
return 'warning';
|
|
1315
|
+
}
|
|
1316
|
+
if (
|
|
1317
|
+
normalized.includes('connected') ||
|
|
1318
|
+
normalized.includes('saved') ||
|
|
1319
|
+
normalized.includes('valid') ||
|
|
1320
|
+
normalized.includes('complete') ||
|
|
1321
|
+
normalized.includes('started')
|
|
1322
|
+
) {
|
|
1323
|
+
return 'success';
|
|
1324
|
+
}
|
|
1325
|
+
return 'info';
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
function setConnectionStatus(text, tone) {
|
|
1329
|
+
connectionStatus.textContent = text;
|
|
1330
|
+
clearStatusTone(connectionStatus);
|
|
1331
|
+
connectionStatus.classList.add('tone-' + (tone || inferToneFromText(text)));
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
function setAdvancedStatus(text, tone) {
|
|
1335
|
+
advancedStatus.textContent = text;
|
|
1336
|
+
clearStatusTone(advancedStatus);
|
|
1337
|
+
advancedStatus.classList.add('tone-' + (tone || inferToneFromText(text)));
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
function setConsoleMeta(text) {
|
|
1341
|
+
consoleMeta.textContent = text;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
function closeEventSource() {
|
|
1345
|
+
if (eventSource) {
|
|
1346
|
+
eventSource.close();
|
|
1347
|
+
eventSource = null;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
function updateCancelButtonState() {
|
|
1352
|
+
cancelButton.disabled = !(currentRunId && currentRunStatus === 'running');
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
function trimOrUndefined(value) {
|
|
1356
|
+
if (!value) {
|
|
1357
|
+
return undefined;
|
|
1358
|
+
}
|
|
1359
|
+
var trimmed = String(value).trim();
|
|
1360
|
+
return trimmed ? trimmed : undefined;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
function parseOptionalPositiveInt(value, label) {
|
|
1364
|
+
var parsedValue = trimOrUndefined(value);
|
|
1365
|
+
if (!parsedValue) {
|
|
1366
|
+
return undefined;
|
|
1367
|
+
}
|
|
1368
|
+
if (!/^[0-9]+$/.test(parsedValue)) {
|
|
1369
|
+
throw new Error(label + ' must be a positive integer.');
|
|
1370
|
+
}
|
|
1371
|
+
var parsed = Number.parseInt(parsedValue, 10);
|
|
1372
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
1373
|
+
throw new Error(label + ' must be a positive integer.');
|
|
1374
|
+
}
|
|
1375
|
+
return parsed;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
function tokenize(input) {
|
|
1379
|
+
if (!input || !input.trim()) {
|
|
1380
|
+
return [];
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
var tokens = [];
|
|
1384
|
+
var current = '';
|
|
1385
|
+
var quote = '';
|
|
1386
|
+
var escaped = false;
|
|
1387
|
+
for (var i = 0; i < input.length; i += 1) {
|
|
1388
|
+
var ch = input[i];
|
|
1389
|
+
if (escaped) {
|
|
1390
|
+
current += ch;
|
|
1391
|
+
escaped = false;
|
|
1392
|
+
continue;
|
|
1393
|
+
}
|
|
1394
|
+
if (ch === '\\') {
|
|
1395
|
+
escaped = true;
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
if ((ch === '"' || ch === "'") && !quote) {
|
|
1399
|
+
quote = ch;
|
|
1400
|
+
continue;
|
|
1401
|
+
}
|
|
1402
|
+
if (ch === quote) {
|
|
1403
|
+
quote = '';
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
if (!quote && /\s/.test(ch)) {
|
|
1407
|
+
if (current) {
|
|
1408
|
+
tokens.push(current);
|
|
1409
|
+
current = '';
|
|
1410
|
+
}
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1413
|
+
current += ch;
|
|
1414
|
+
}
|
|
1415
|
+
if (current) {
|
|
1416
|
+
tokens.push(current);
|
|
1417
|
+
}
|
|
1418
|
+
return tokens;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
function formatTimestamp(value) {
|
|
1422
|
+
if (!value) {
|
|
1423
|
+
return '--:--:--';
|
|
1424
|
+
}
|
|
1425
|
+
try {
|
|
1426
|
+
return new Date(value).toLocaleTimeString();
|
|
1427
|
+
} catch (_error) {
|
|
1428
|
+
return '--:--:--';
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
function renderConsole() {
|
|
1433
|
+
consoleRenderScheduled = false;
|
|
1434
|
+
var streamFilter = consoleStreamFilter.value;
|
|
1435
|
+
var lines = [];
|
|
1436
|
+
for (var i = 0; i < consoleEntries.length; i += 1) {
|
|
1437
|
+
var entry = consoleEntries[i];
|
|
1438
|
+
if (streamFilter !== 'all' && entry.stream !== streamFilter) {
|
|
1439
|
+
continue;
|
|
1440
|
+
}
|
|
1441
|
+
lines.push('[' + formatTimestamp(entry.timestamp) + '][' + entry.stream + '] ' + entry.line);
|
|
1442
|
+
}
|
|
1443
|
+
consoleOutput.textContent = lines.join('\n');
|
|
1444
|
+
if (consoleAutoScroll.checked) {
|
|
1445
|
+
consoleOutput.scrollTop = consoleOutput.scrollHeight;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
function scheduleConsoleRender() {
|
|
1450
|
+
if (consoleRenderScheduled) {
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
consoleRenderScheduled = true;
|
|
1454
|
+
window.requestAnimationFrame(function () {
|
|
1455
|
+
renderConsole();
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
function clearConsoleEntries() {
|
|
1460
|
+
consoleEntries = [];
|
|
1461
|
+
scheduleConsoleRender();
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
function appendConsoleChunk(stream, text, timestamp) {
|
|
1465
|
+
if (!text) {
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
var normalized = String(text).replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
1470
|
+
var lines = normalized.split('\n');
|
|
1471
|
+
|
|
1472
|
+
for (var i = 0; i < lines.length; i += 1) {
|
|
1473
|
+
if (i === lines.length - 1 && lines[i] === '') {
|
|
1474
|
+
continue;
|
|
1475
|
+
}
|
|
1476
|
+
consoleEntries.push({
|
|
1477
|
+
stream: stream,
|
|
1478
|
+
line: lines[i],
|
|
1479
|
+
timestamp: timestamp || new Date().toISOString(),
|
|
1480
|
+
});
|
|
1481
|
+
if (consoleEntries.length > 5000) {
|
|
1482
|
+
consoleEntries.shift();
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
scheduleConsoleRender();
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
async function request(method, url, body) {
|
|
1490
|
+
var response = await fetch(url, {
|
|
1491
|
+
method: method,
|
|
1492
|
+
headers: body ? { 'Content-Type': 'application/json' } : undefined,
|
|
1493
|
+
body: body ? JSON.stringify(body) : undefined
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
var payload;
|
|
1497
|
+
try {
|
|
1498
|
+
payload = await response.json();
|
|
1499
|
+
} catch (_error) {
|
|
1500
|
+
payload = {};
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
if (!response.ok) {
|
|
1504
|
+
throw new Error(payload.error || 'Request failed.');
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
return payload;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
function getConfigPath() {
|
|
1511
|
+
return trimOrUndefined(configPathInput.value);
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
function addConfigPath(args) {
|
|
1515
|
+
var configPath = getConfigPath();
|
|
1516
|
+
if (configPath) {
|
|
1517
|
+
args.configPath = configPath;
|
|
1518
|
+
}
|
|
1519
|
+
return args;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
async function refreshOverview() {
|
|
1523
|
+
var query = getConfigPath() ? '?configPath=' + encodeURIComponent(getConfigPath()) : '';
|
|
1524
|
+
var payload = await request('GET', '/api/overview' + query);
|
|
1525
|
+
var summary = payload.summary || {};
|
|
1526
|
+
var reports = payload.reports || {};
|
|
1527
|
+
|
|
1528
|
+
overviewTotalRuns.textContent = String(summary.totalRuns || 0);
|
|
1529
|
+
overviewRunningRuns.textContent = String(summary.runningRuns || 0);
|
|
1530
|
+
overviewArtifacts.textContent = String(summary.existingArtifacts || 0);
|
|
1531
|
+
overviewLastStatus.textContent = summary.lastRun ? String(summary.lastRun.status) : '-';
|
|
1532
|
+
overviewCheckDrift.textContent = summary.lastCheckDriftSeverity || '-';
|
|
1533
|
+
|
|
1534
|
+
overviewCheckTools.textContent =
|
|
1535
|
+
reports.check && reports.check.toolCount !== undefined ? String(reports.check.toolCount) : '-';
|
|
1536
|
+
overviewExploreTools.textContent =
|
|
1537
|
+
reports.explore && reports.explore.toolCount !== undefined
|
|
1538
|
+
? String(reports.explore.toolCount)
|
|
1539
|
+
: '-';
|
|
1540
|
+
overviewGoldenOutputs.textContent =
|
|
1541
|
+
reports.golden && reports.golden.outputCount !== undefined
|
|
1542
|
+
? String(reports.golden.outputCount)
|
|
1543
|
+
: '-';
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
function matchesRunFilter(run) {
|
|
1547
|
+
var statusFilter = runStatusFilter.value;
|
|
1548
|
+
var search = trimOrUndefined(runSearchInput.value);
|
|
1549
|
+
|
|
1550
|
+
if (statusFilter !== 'all' && run.status !== statusFilter) {
|
|
1551
|
+
return false;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
if (!search) {
|
|
1555
|
+
return true;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
var haystack = (run.profile + ' ' + run.commandLine + ' ' + run.runId).toLowerCase();
|
|
1559
|
+
return haystack.includes(search.toLowerCase());
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
async function cancelRun(runId) {
|
|
1563
|
+
await request('POST', '/api/runs/' + encodeURIComponent(runId) + '/cancel');
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
function renderRuns() {
|
|
1567
|
+
runList.innerHTML = '';
|
|
1568
|
+
|
|
1569
|
+
var filtered = allRuns.filter(matchesRunFilter);
|
|
1570
|
+
if (filtered.length === 0) {
|
|
1571
|
+
var empty = document.createElement('div');
|
|
1572
|
+
empty.className = 'mono';
|
|
1573
|
+
empty.textContent = 'No runs match current filters.';
|
|
1574
|
+
runList.appendChild(empty);
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
filtered.forEach(function (run) {
|
|
1579
|
+
var row = document.createElement('div');
|
|
1580
|
+
row.className = 'run-row';
|
|
1581
|
+
if (run.runId === currentRunId) {
|
|
1582
|
+
row.classList.add('active');
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
var head = document.createElement('div');
|
|
1586
|
+
head.className = 'run-head';
|
|
1587
|
+
var headTitle = document.createElement('strong');
|
|
1588
|
+
headTitle.textContent = run.profile;
|
|
1589
|
+
var headTag = document.createElement('span');
|
|
1590
|
+
headTag.className = 'tag ' + run.status;
|
|
1591
|
+
headTag.textContent = run.status;
|
|
1592
|
+
head.appendChild(headTitle);
|
|
1593
|
+
head.appendChild(headTag);
|
|
1594
|
+
|
|
1595
|
+
var id = document.createElement('div');
|
|
1596
|
+
id.className = 'mono';
|
|
1597
|
+
id.textContent = run.runId;
|
|
1598
|
+
|
|
1599
|
+
var cmd = document.createElement('div');
|
|
1600
|
+
cmd.className = 'mono';
|
|
1601
|
+
cmd.textContent = run.commandLine;
|
|
1602
|
+
|
|
1603
|
+
var meta = document.createElement('div');
|
|
1604
|
+
meta.className = 'mono';
|
|
1605
|
+
var duration = run.durationMs ? run.durationMs + ' ms' : '-';
|
|
1606
|
+
var exitCode = run.exitCode === undefined ? '-' : String(run.exitCode);
|
|
1607
|
+
meta.textContent =
|
|
1608
|
+
'Exit: ' +
|
|
1609
|
+
exitCode +
|
|
1610
|
+
' | Duration: ' +
|
|
1611
|
+
duration +
|
|
1612
|
+
' | Started: ' +
|
|
1613
|
+
formatTimestamp(run.startedAt);
|
|
1614
|
+
|
|
1615
|
+
var actions = document.createElement('div');
|
|
1616
|
+
actions.className = 'run-actions';
|
|
1617
|
+
|
|
1618
|
+
var viewButton = document.createElement('button');
|
|
1619
|
+
viewButton.className = 'secondary';
|
|
1620
|
+
viewButton.textContent = 'View';
|
|
1621
|
+
viewButton.addEventListener('click', function () {
|
|
1622
|
+
void attachToRun(run.runId);
|
|
1623
|
+
});
|
|
1624
|
+
actions.appendChild(viewButton);
|
|
1625
|
+
|
|
1626
|
+
if (run.status === 'running') {
|
|
1627
|
+
var cancelRunButton = document.createElement('button');
|
|
1628
|
+
cancelRunButton.className = 'danger';
|
|
1629
|
+
cancelRunButton.textContent = 'Cancel';
|
|
1630
|
+
cancelRunButton.addEventListener('click', async function () {
|
|
1631
|
+
cancelRunButton.disabled = true;
|
|
1632
|
+
try {
|
|
1633
|
+
await cancelRun(run.runId);
|
|
1634
|
+
await refreshRuns();
|
|
1635
|
+
await refreshOverview();
|
|
1636
|
+
} catch (error) {
|
|
1637
|
+
setConnectionStatus(String(error.message || error));
|
|
1638
|
+
} finally {
|
|
1639
|
+
cancelRunButton.disabled = false;
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
actions.appendChild(cancelRunButton);
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
row.appendChild(head);
|
|
1646
|
+
row.appendChild(id);
|
|
1647
|
+
row.appendChild(cmd);
|
|
1648
|
+
row.appendChild(meta);
|
|
1649
|
+
row.appendChild(actions);
|
|
1650
|
+
runList.appendChild(row);
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
async function refreshRuns() {
|
|
1655
|
+
var payload = await request('GET', '/api/runs');
|
|
1656
|
+
allRuns = payload.runs || [];
|
|
1657
|
+
renderRuns();
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
async function fetchRun(runId) {
|
|
1661
|
+
var payload = await request('GET', '/api/runs/' + encodeURIComponent(runId));
|
|
1662
|
+
return payload.run;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
async function attachToRun(runId) {
|
|
1666
|
+
closeEventSource();
|
|
1667
|
+
|
|
1668
|
+
var run = await fetchRun(runId);
|
|
1669
|
+
currentRunId = run.runId;
|
|
1670
|
+
currentRunStatus = run.status;
|
|
1671
|
+
updateCancelButtonState();
|
|
1672
|
+
renderRuns();
|
|
1673
|
+
|
|
1674
|
+
setConsoleMeta(run.profile + ' | ' + run.commandLine);
|
|
1675
|
+
clearConsoleEntries();
|
|
1676
|
+
if (run.output && Array.isArray(run.output)) {
|
|
1677
|
+
run.output.forEach(function (chunk) {
|
|
1678
|
+
appendConsoleChunk(chunk.stream, chunk.text, chunk.timestamp);
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
eventSource = new EventSource('/api/runs/' + encodeURIComponent(runId) + '/events');
|
|
1683
|
+
setConnectionStatus('Streaming run ' + runId.slice(0, 8));
|
|
1684
|
+
|
|
1685
|
+
eventSource.addEventListener('run.snapshot', function (event) {
|
|
1686
|
+
var snapshot = JSON.parse(event.data).run;
|
|
1687
|
+
currentRunStatus = snapshot.status;
|
|
1688
|
+
updateCancelButtonState();
|
|
1689
|
+
renderRuns();
|
|
1690
|
+
setConsoleMeta(snapshot.profile + ' | ' + snapshot.commandLine);
|
|
1691
|
+
clearConsoleEntries();
|
|
1692
|
+
(snapshot.output || []).forEach(function (chunk) {
|
|
1693
|
+
appendConsoleChunk(chunk.stream, chunk.text, chunk.timestamp);
|
|
1694
|
+
});
|
|
1695
|
+
});
|
|
1696
|
+
|
|
1697
|
+
eventSource.addEventListener('run.output', function (event) {
|
|
1698
|
+
var payload = JSON.parse(event.data);
|
|
1699
|
+
appendConsoleChunk(payload.stream, payload.text, payload.timestamp);
|
|
1700
|
+
});
|
|
1701
|
+
|
|
1702
|
+
eventSource.addEventListener('run.exit', async function (event) {
|
|
1703
|
+
var summary = JSON.parse(event.data).run;
|
|
1704
|
+
currentRunStatus = summary.status;
|
|
1705
|
+
updateCancelButtonState();
|
|
1706
|
+
renderRuns();
|
|
1707
|
+
setConnectionStatus('Run ' + summary.status + ' (exit ' + summary.exitCode + ')');
|
|
1708
|
+
await refreshRuns();
|
|
1709
|
+
await refreshOverview();
|
|
1710
|
+
await refreshArtifacts();
|
|
1711
|
+
});
|
|
1712
|
+
|
|
1713
|
+
eventSource.onerror = function () {
|
|
1714
|
+
closeEventSource();
|
|
1715
|
+
setConnectionStatus('Run stream disconnected');
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
async function startRun(payload) {
|
|
1720
|
+
var created = await request('POST', '/api/runs', payload);
|
|
1721
|
+
await attachToRun(created.runId);
|
|
1722
|
+
await refreshRuns();
|
|
1723
|
+
await refreshOverview();
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
async function runQuickProfile() {
|
|
1727
|
+
quickRunButton.disabled = true;
|
|
1728
|
+
setConnectionStatus('Starting run...');
|
|
1729
|
+
|
|
1730
|
+
var profile = quickProfile.value;
|
|
1731
|
+
var args = {
|
|
1732
|
+
configPath: getConfigPath()
|
|
1733
|
+
};
|
|
1734
|
+
|
|
1735
|
+
if (profile === 'check' || profile === 'explore') {
|
|
1736
|
+
args.serverCommand = trimOrUndefined(serverCommandInput.value);
|
|
1737
|
+
args.serverArgs = tokenize(serverArgsInput.value);
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
try {
|
|
1741
|
+
await startRun({
|
|
1742
|
+
profile: profile,
|
|
1743
|
+
args: args
|
|
1744
|
+
});
|
|
1745
|
+
} catch (error) {
|
|
1746
|
+
setConnectionStatus(String(error.message || error));
|
|
1747
|
+
} finally {
|
|
1748
|
+
quickRunButton.disabled = false;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
async function cancelCurrentRun() {
|
|
1753
|
+
if (!currentRunId) {
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
cancelButton.disabled = true;
|
|
1757
|
+
setConnectionStatus('Cancelling run...');
|
|
1758
|
+
try {
|
|
1759
|
+
await request('POST', '/api/runs/' + encodeURIComponent(currentRunId) + '/cancel');
|
|
1760
|
+
} catch (error) {
|
|
1761
|
+
setConnectionStatus(String(error.message || error));
|
|
1762
|
+
} finally {
|
|
1763
|
+
updateCancelButtonState();
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
async function cancelAllRunningRuns() {
|
|
1768
|
+
cancelAllRunsButton.disabled = true;
|
|
1769
|
+
try {
|
|
1770
|
+
var running = allRuns.filter(function (run) {
|
|
1771
|
+
return run.status === 'running';
|
|
1772
|
+
});
|
|
1773
|
+
if (running.length === 0) {
|
|
1774
|
+
setConnectionStatus('No running runs to cancel.');
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
for (var i = 0; i < running.length; i += 1) {
|
|
1779
|
+
try {
|
|
1780
|
+
await request('POST', '/api/runs/' + encodeURIComponent(running[i].runId) + '/cancel');
|
|
1781
|
+
} catch (_error) {
|
|
1782
|
+
// Keep going so one failure does not block the rest.
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
setConnectionStatus('Cancelled running runs.');
|
|
1787
|
+
await refreshRuns();
|
|
1788
|
+
await refreshOverview();
|
|
1789
|
+
} catch (error) {
|
|
1790
|
+
setConnectionStatus(String(error.message || error));
|
|
1791
|
+
} finally {
|
|
1792
|
+
cancelAllRunsButton.disabled = false;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
async function runBaselineSave() {
|
|
1797
|
+
await startRun({
|
|
1798
|
+
profile: 'baseline.save',
|
|
1799
|
+
args: addConfigPath({
|
|
1800
|
+
baselinePath: trimOrUndefined(document.getElementById('baselineSavePath').value),
|
|
1801
|
+
reportPath: trimOrUndefined(document.getElementById('baselineSaveReport').value),
|
|
1802
|
+
force: document.getElementById('baselineSaveForce').checked
|
|
1803
|
+
})
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
async function runBaselineCompare() {
|
|
1808
|
+
await startRun({
|
|
1809
|
+
profile: 'baseline.compare',
|
|
1810
|
+
args: addConfigPath({
|
|
1811
|
+
baselinePath: trimOrUndefined(document.getElementById('baselineComparePath').value),
|
|
1812
|
+
reportPath: trimOrUndefined(document.getElementById('baselineCompareReport').value),
|
|
1813
|
+
format: trimOrUndefined(document.getElementById('baselineCompareFormat').value),
|
|
1814
|
+
failOnDrift: document.getElementById('baselineCompareFailOnDrift').checked,
|
|
1815
|
+
ignoreVersionMismatch: document.getElementById('baselineCompareIgnoreVersion').checked
|
|
1816
|
+
})
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
async function runBaselineShow() {
|
|
1821
|
+
await startRun({
|
|
1822
|
+
profile: 'baseline.show',
|
|
1823
|
+
args: addConfigPath({
|
|
1824
|
+
baselinePath: trimOrUndefined(document.getElementById('baselineShowPath').value),
|
|
1825
|
+
json: document.getElementById('baselineShowJson').checked,
|
|
1826
|
+
tools: document.getElementById('baselineShowTools').checked,
|
|
1827
|
+
assertions: document.getElementById('baselineShowAssertions').checked
|
|
1828
|
+
})
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
async function runBaselineDiff() {
|
|
1833
|
+
var path1 = trimOrUndefined(document.getElementById('baselineDiffPath1').value);
|
|
1834
|
+
var path2 = trimOrUndefined(document.getElementById('baselineDiffPath2').value);
|
|
1835
|
+
if (!path1 || !path2) {
|
|
1836
|
+
throw new Error('Baseline diff requires both path1 and path2.');
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
await startRun({
|
|
1840
|
+
profile: 'baseline.diff',
|
|
1841
|
+
args: addConfigPath({
|
|
1842
|
+
path1: path1,
|
|
1843
|
+
path2: path2,
|
|
1844
|
+
format: trimOrUndefined(document.getElementById('baselineDiffFormat').value),
|
|
1845
|
+
ignoreVersionMismatch: document.getElementById('baselineDiffIgnoreVersion').checked
|
|
1846
|
+
})
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
async function runBaselineAccept() {
|
|
1851
|
+
await startRun({
|
|
1852
|
+
profile: 'baseline.accept',
|
|
1853
|
+
args: addConfigPath({
|
|
1854
|
+
baselinePath: trimOrUndefined(document.getElementById('baselineAcceptPath').value),
|
|
1855
|
+
reportPath: trimOrUndefined(document.getElementById('baselineAcceptReport').value),
|
|
1856
|
+
reason: trimOrUndefined(document.getElementById('baselineAcceptReason').value),
|
|
1857
|
+
acceptedBy: trimOrUndefined(document.getElementById('baselineAcceptBy').value),
|
|
1858
|
+
dryRun: document.getElementById('baselineAcceptDryRun').checked,
|
|
1859
|
+
force: document.getElementById('baselineAcceptForce').checked
|
|
1860
|
+
})
|
|
1861
|
+
});
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
async function runDiscover() {
|
|
1865
|
+
var transport = document.getElementById('discoverTransport').value;
|
|
1866
|
+
await startRun({
|
|
1867
|
+
profile: 'discover',
|
|
1868
|
+
args: addConfigPath({
|
|
1869
|
+
transport: transport,
|
|
1870
|
+
url: trimOrUndefined(document.getElementById('discoverUrl').value),
|
|
1871
|
+
sessionId: trimOrUndefined(document.getElementById('discoverSessionId').value),
|
|
1872
|
+
timeout: parseOptionalPositiveInt(document.getElementById('discoverTimeout').value, 'Discover timeout'),
|
|
1873
|
+
json: document.getElementById('discoverJson').checked,
|
|
1874
|
+
serverCommand: trimOrUndefined(document.getElementById('discoverCommand').value),
|
|
1875
|
+
serverArgs: tokenize(document.getElementById('discoverArgs').value)
|
|
1876
|
+
})
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
async function runWatch() {
|
|
1881
|
+
await startRun({
|
|
1882
|
+
profile: 'watch',
|
|
1883
|
+
args: addConfigPath({
|
|
1884
|
+
serverCommand: trimOrUndefined(document.getElementById('watchCommand').value),
|
|
1885
|
+
serverArgs: tokenize(document.getElementById('watchArgs').value)
|
|
1886
|
+
})
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
async function runRegistry() {
|
|
1891
|
+
await startRun({
|
|
1892
|
+
profile: 'registry.search',
|
|
1893
|
+
args: addConfigPath({
|
|
1894
|
+
query: trimOrUndefined(document.getElementById('registryQuery').value),
|
|
1895
|
+
limit: parseOptionalPositiveInt(document.getElementById('registryLimit').value, 'Registry limit'),
|
|
1896
|
+
json: document.getElementById('registryJson').checked
|
|
1897
|
+
})
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
async function runContractValidate() {
|
|
1902
|
+
var serverCommand = trimOrUndefined(document.getElementById('contractValidateCommand').value);
|
|
1903
|
+
if (!serverCommand) {
|
|
1904
|
+
throw new Error('Contract validate requires server command.');
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
await startRun({
|
|
1908
|
+
profile: 'contract.validate',
|
|
1909
|
+
args: addConfigPath({
|
|
1910
|
+
serverCommand: serverCommand,
|
|
1911
|
+
serverArgs: tokenize(document.getElementById('contractValidateArgs').value),
|
|
1912
|
+
contractPath: trimOrUndefined(document.getElementById('contractValidatePath').value),
|
|
1913
|
+
mode: trimOrUndefined(document.getElementById('contractValidateMode').value),
|
|
1914
|
+
format: trimOrUndefined(document.getElementById('contractValidateFormat').value),
|
|
1915
|
+
timeout: parseOptionalPositiveInt(document.getElementById('contractValidateTimeout').value, 'Contract validate timeout'),
|
|
1916
|
+
failOnViolation: document.getElementById('contractValidateFail').checked
|
|
1917
|
+
})
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
async function runContractGenerate() {
|
|
1922
|
+
var serverCommand = trimOrUndefined(document.getElementById('contractGenerateCommand').value);
|
|
1923
|
+
if (!serverCommand) {
|
|
1924
|
+
throw new Error('Contract generate requires server command.');
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
await startRun({
|
|
1928
|
+
profile: 'contract.generate',
|
|
1929
|
+
args: addConfigPath({
|
|
1930
|
+
serverCommand: serverCommand,
|
|
1931
|
+
serverArgs: tokenize(document.getElementById('contractGenerateArgs').value),
|
|
1932
|
+
outputPath: trimOrUndefined(document.getElementById('contractGenerateOutput').value),
|
|
1933
|
+
timeout: parseOptionalPositiveInt(document.getElementById('contractGenerateTimeout').value, 'Contract generate timeout'),
|
|
1934
|
+
force: document.getElementById('contractGenerateForce').checked
|
|
1935
|
+
})
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
async function runContractShow() {
|
|
1940
|
+
await startRun({
|
|
1941
|
+
profile: 'contract.show',
|
|
1942
|
+
args: addConfigPath({
|
|
1943
|
+
path: trimOrUndefined(document.getElementById('contractShowPath').value),
|
|
1944
|
+
json: document.getElementById('contractShowJson').checked
|
|
1945
|
+
})
|
|
1946
|
+
});
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
async function runGoldenSave() {
|
|
1950
|
+
var toolName = trimOrUndefined(document.getElementById('goldenSaveTool').value);
|
|
1951
|
+
if (!toolName) {
|
|
1952
|
+
throw new Error('Golden save requires tool name.');
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
await startRun({
|
|
1956
|
+
profile: 'golden.save',
|
|
1957
|
+
args: addConfigPath({
|
|
1958
|
+
toolName: toolName,
|
|
1959
|
+
argsJson: trimOrUndefined(document.getElementById('goldenSaveArgs').value),
|
|
1960
|
+
mode: trimOrUndefined(document.getElementById('goldenSaveMode').value),
|
|
1961
|
+
allowedDrift: trimOrUndefined(document.getElementById('goldenSaveAllowedDrift').value),
|
|
1962
|
+
normalizeTimestamps: document.getElementById('goldenSaveDisableTimestamps').checked ? false : undefined,
|
|
1963
|
+
normalizeUuids: document.getElementById('goldenSaveDisableUuids').checked ? false : undefined,
|
|
1964
|
+
description: trimOrUndefined(document.getElementById('goldenSaveDescription').value)
|
|
1965
|
+
})
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
async function runGoldenCompare() {
|
|
1970
|
+
await startRun({
|
|
1971
|
+
profile: 'golden.compare',
|
|
1972
|
+
args: addConfigPath({
|
|
1973
|
+
toolName: trimOrUndefined(document.getElementById('goldenCompareTool').value),
|
|
1974
|
+
format: trimOrUndefined(document.getElementById('goldenCompareFormat').value),
|
|
1975
|
+
failOnDrift: document.getElementById('goldenCompareFailOnDrift').checked
|
|
1976
|
+
})
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
async function runGoldenList() {
|
|
1981
|
+
await startRun({
|
|
1982
|
+
profile: 'golden.list',
|
|
1983
|
+
args: addConfigPath({
|
|
1984
|
+
format: trimOrUndefined(document.getElementById('goldenListFormat').value)
|
|
1985
|
+
})
|
|
1986
|
+
});
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
async function runGoldenDelete() {
|
|
1990
|
+
var toolName = trimOrUndefined(document.getElementById('goldenDeleteTool').value);
|
|
1991
|
+
if (!toolName) {
|
|
1992
|
+
throw new Error('Golden delete requires tool name.');
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
await startRun({
|
|
1996
|
+
profile: 'golden.delete',
|
|
1997
|
+
args: addConfigPath({
|
|
1998
|
+
toolName: toolName,
|
|
1999
|
+
all: document.getElementById('goldenDeleteAll').checked
|
|
2000
|
+
})
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
async function runWithButton(button, label, fn) {
|
|
2005
|
+
button.disabled = true;
|
|
2006
|
+
setAdvancedStatus('Starting ' + label + '...');
|
|
2007
|
+
try {
|
|
2008
|
+
await fn();
|
|
2009
|
+
setAdvancedStatus('Started ' + label + '.', 'success');
|
|
2010
|
+
} catch (error) {
|
|
2011
|
+
setAdvancedStatus(String(error.message || error), 'danger');
|
|
2012
|
+
} finally {
|
|
2013
|
+
button.disabled = false;
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
async function loadConfigEditor() {
|
|
2018
|
+
configLoadButton.disabled = true;
|
|
2019
|
+
try {
|
|
2020
|
+
var query = getConfigPath() ? '?path=' + encodeURIComponent(getConfigPath()) : '';
|
|
2021
|
+
var payload = await request('GET', '/api/config' + query);
|
|
2022
|
+
configEditor.value = payload.config.content || '';
|
|
2023
|
+
configStatus.textContent =
|
|
2024
|
+
(payload.config.exists ? 'Loaded: ' : 'No config file at: ') + payload.config.workspacePath;
|
|
2025
|
+
if (!getConfigPath() && payload.config.workspacePath) {
|
|
2026
|
+
configPathInput.value = payload.config.workspacePath;
|
|
2027
|
+
}
|
|
2028
|
+
} catch (error) {
|
|
2029
|
+
configStatus.textContent = String(error.message || error);
|
|
2030
|
+
} finally {
|
|
2031
|
+
configLoadButton.disabled = false;
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
async function saveConfigEditor() {
|
|
2036
|
+
configSaveButton.disabled = true;
|
|
2037
|
+
try {
|
|
2038
|
+
var payload = await request('PUT', '/api/config', {
|
|
2039
|
+
path: getConfigPath(),
|
|
2040
|
+
content: configEditor.value
|
|
2041
|
+
});
|
|
2042
|
+
configStatus.textContent = 'Saved: ' + payload.config.workspacePath;
|
|
2043
|
+
} catch (error) {
|
|
2044
|
+
configStatus.textContent = String(error.message || error);
|
|
2045
|
+
} finally {
|
|
2046
|
+
configSaveButton.disabled = false;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
async function validateConfigEditor() {
|
|
2051
|
+
configValidateButton.disabled = true;
|
|
2052
|
+
configValidationResult.textContent = 'Validating...';
|
|
2053
|
+
try {
|
|
2054
|
+
var payload = await request('POST', '/api/config/validate', {
|
|
2055
|
+
path: getConfigPath(),
|
|
2056
|
+
content: configEditor.value
|
|
2057
|
+
});
|
|
2058
|
+
var warningText = (payload.warnings || []).length
|
|
2059
|
+
? ' Warnings: ' + payload.warnings.join(' | ')
|
|
2060
|
+
: '';
|
|
2061
|
+
configValidationResult.textContent = 'Config valid.' + warningText;
|
|
2062
|
+
} catch (error) {
|
|
2063
|
+
configValidationResult.textContent = String(error.message || error);
|
|
2064
|
+
} finally {
|
|
2065
|
+
configValidateButton.disabled = false;
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
function renderArtifactList(artifacts) {
|
|
2070
|
+
cachedArtifacts = artifacts || [];
|
|
2071
|
+
artifactSelect.innerHTML = '';
|
|
2072
|
+
|
|
2073
|
+
if (cachedArtifacts.length === 0) {
|
|
2074
|
+
var option = document.createElement('option');
|
|
2075
|
+
option.value = '';
|
|
2076
|
+
option.textContent = 'No artifacts available';
|
|
2077
|
+
artifactSelect.appendChild(option);
|
|
2078
|
+
artifactMeta.textContent = 'No artifacts found.';
|
|
2079
|
+
artifactOutput.textContent = '';
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
cachedArtifacts.forEach(function (artifact) {
|
|
2084
|
+
var option = document.createElement('option');
|
|
2085
|
+
option.value = artifact.id;
|
|
2086
|
+
option.textContent = artifact.label + (artifact.exists ? '' : ' (missing)');
|
|
2087
|
+
artifactSelect.appendChild(option);
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
async function refreshArtifacts() {
|
|
2092
|
+
try {
|
|
2093
|
+
var query = getConfigPath() ? '?configPath=' + encodeURIComponent(getConfigPath()) : '';
|
|
2094
|
+
var payload = await request('GET', '/api/artifacts' + query);
|
|
2095
|
+
renderArtifactList(payload.artifacts || []);
|
|
2096
|
+
|
|
2097
|
+
if (artifactSelect.value) {
|
|
2098
|
+
await loadArtifact(artifactSelect.value);
|
|
2099
|
+
} else if (cachedArtifacts.length > 0) {
|
|
2100
|
+
artifactSelect.value = cachedArtifacts[0].id;
|
|
2101
|
+
await loadArtifact(cachedArtifacts[0].id);
|
|
2102
|
+
}
|
|
2103
|
+
} catch (error) {
|
|
2104
|
+
artifactMeta.textContent = String(error.message || error);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
async function loadArtifact(artifactId) {
|
|
2109
|
+
if (!artifactId) {
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
try {
|
|
2113
|
+
var query = getConfigPath() ? '?configPath=' + encodeURIComponent(getConfigPath()) : '';
|
|
2114
|
+
var payload = await request(
|
|
2115
|
+
'GET',
|
|
2116
|
+
'/api/artifacts/' + encodeURIComponent(artifactId) + query
|
|
2117
|
+
);
|
|
2118
|
+
var artifact = payload.artifact;
|
|
2119
|
+
|
|
2120
|
+
artifactMeta.textContent =
|
|
2121
|
+
artifact.label +
|
|
2122
|
+
' | ' +
|
|
2123
|
+
artifact.path +
|
|
2124
|
+
' | ' +
|
|
2125
|
+
(artifact.exists ? 'exists' : 'missing');
|
|
2126
|
+
|
|
2127
|
+
if (payload.parsedJson !== undefined) {
|
|
2128
|
+
artifactOutput.textContent = JSON.stringify(payload.parsedJson, null, 2);
|
|
2129
|
+
} else {
|
|
2130
|
+
artifactOutput.textContent = payload.raw || '';
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
if (payload.parseError) {
|
|
2134
|
+
artifactMeta.textContent += ' | JSON parse error: ' + payload.parseError;
|
|
2135
|
+
}
|
|
2136
|
+
} catch (error) {
|
|
2137
|
+
artifactMeta.textContent = String(error.message || error);
|
|
2138
|
+
artifactOutput.textContent = '';
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
async function periodicRefresh() {
|
|
2143
|
+
try {
|
|
2144
|
+
await refreshRuns();
|
|
2145
|
+
await refreshOverview();
|
|
2146
|
+
} catch (_error) {
|
|
2147
|
+
// Ignore periodic refresh errors; manual actions still report explicitly.
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
async function bootstrap() {
|
|
2152
|
+
try {
|
|
2153
|
+
var health = await request('GET', '/api/health');
|
|
2154
|
+
setConnectionStatus('Connected to Bellwether ' + health.version);
|
|
2155
|
+
|
|
2156
|
+
await loadConfigEditor();
|
|
2157
|
+
await refreshRuns();
|
|
2158
|
+
await refreshOverview();
|
|
2159
|
+
await refreshArtifacts();
|
|
2160
|
+
runsTimer = window.setInterval(periodicRefresh, 3000);
|
|
2161
|
+
} catch (error) {
|
|
2162
|
+
setConnectionStatus(String(error.message || error));
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
quickRunButton.addEventListener('click', function () {
|
|
2167
|
+
void runQuickProfile();
|
|
2168
|
+
});
|
|
2169
|
+
cancelButton.addEventListener('click', function () {
|
|
2170
|
+
void cancelCurrentRun();
|
|
2171
|
+
});
|
|
2172
|
+
cancelAllRunsButton.addEventListener('click', function () {
|
|
2173
|
+
void cancelAllRunningRuns();
|
|
2174
|
+
});
|
|
2175
|
+
refreshRunsButton.addEventListener('click', async function () {
|
|
2176
|
+
await refreshRuns();
|
|
2177
|
+
await refreshOverview();
|
|
2178
|
+
});
|
|
2179
|
+
|
|
2180
|
+
runSearchInput.addEventListener('input', renderRuns);
|
|
2181
|
+
runStatusFilter.addEventListener('change', renderRuns);
|
|
2182
|
+
|
|
2183
|
+
baselineSaveButton.addEventListener('click', function () {
|
|
2184
|
+
void runWithButton(baselineSaveButton, 'baseline save', runBaselineSave);
|
|
2185
|
+
});
|
|
2186
|
+
baselineCompareButton.addEventListener('click', function () {
|
|
2187
|
+
void runWithButton(baselineCompareButton, 'baseline compare', runBaselineCompare);
|
|
2188
|
+
});
|
|
2189
|
+
baselineShowButton.addEventListener('click', function () {
|
|
2190
|
+
void runWithButton(baselineShowButton, 'baseline show', runBaselineShow);
|
|
2191
|
+
});
|
|
2192
|
+
baselineDiffButton.addEventListener('click', function () {
|
|
2193
|
+
void runWithButton(baselineDiffButton, 'baseline diff', runBaselineDiff);
|
|
2194
|
+
});
|
|
2195
|
+
baselineAcceptButton.addEventListener('click', function () {
|
|
2196
|
+
void runWithButton(baselineAcceptButton, 'baseline accept', runBaselineAccept);
|
|
2197
|
+
});
|
|
2198
|
+
|
|
2199
|
+
discoverRunButton.addEventListener('click', function () {
|
|
2200
|
+
void runWithButton(discoverRunButton, 'discover', runDiscover);
|
|
2201
|
+
});
|
|
2202
|
+
watchRunButton.addEventListener('click', function () {
|
|
2203
|
+
void runWithButton(watchRunButton, 'watch', runWatch);
|
|
2204
|
+
});
|
|
2205
|
+
registryRunButton.addEventListener('click', function () {
|
|
2206
|
+
void runWithButton(registryRunButton, 'registry search', runRegistry);
|
|
2207
|
+
});
|
|
2208
|
+
contractValidateButton.addEventListener('click', function () {
|
|
2209
|
+
void runWithButton(contractValidateButton, 'contract validate', runContractValidate);
|
|
2210
|
+
});
|
|
2211
|
+
contractGenerateButton.addEventListener('click', function () {
|
|
2212
|
+
void runWithButton(contractGenerateButton, 'contract generate', runContractGenerate);
|
|
2213
|
+
});
|
|
2214
|
+
contractShowButton.addEventListener('click', function () {
|
|
2215
|
+
void runWithButton(contractShowButton, 'contract show', runContractShow);
|
|
2216
|
+
});
|
|
2217
|
+
goldenSaveButton.addEventListener('click', function () {
|
|
2218
|
+
void runWithButton(goldenSaveButton, 'golden save', runGoldenSave);
|
|
2219
|
+
});
|
|
2220
|
+
goldenCompareButton.addEventListener('click', function () {
|
|
2221
|
+
void runWithButton(goldenCompareButton, 'golden compare', runGoldenCompare);
|
|
2222
|
+
});
|
|
2223
|
+
goldenListButton.addEventListener('click', function () {
|
|
2224
|
+
void runWithButton(goldenListButton, 'golden list', runGoldenList);
|
|
2225
|
+
});
|
|
2226
|
+
goldenDeleteButton.addEventListener('click', function () {
|
|
2227
|
+
void runWithButton(goldenDeleteButton, 'golden delete', runGoldenDelete);
|
|
2228
|
+
});
|
|
2229
|
+
|
|
2230
|
+
clearConsoleButton.addEventListener('click', clearConsoleEntries);
|
|
2231
|
+
consoleStreamFilter.addEventListener('change', renderConsole);
|
|
2232
|
+
consoleAutoScroll.addEventListener('change', renderConsole);
|
|
2233
|
+
|
|
2234
|
+
configLoadButton.addEventListener('click', function () {
|
|
2235
|
+
void loadConfigEditor();
|
|
2236
|
+
});
|
|
2237
|
+
configSaveButton.addEventListener('click', function () {
|
|
2238
|
+
void saveConfigEditor();
|
|
2239
|
+
});
|
|
2240
|
+
configValidateButton.addEventListener('click', function () {
|
|
2241
|
+
void validateConfigEditor();
|
|
2242
|
+
});
|
|
2243
|
+
|
|
2244
|
+
artifactsRefreshButton.addEventListener('click', function () {
|
|
2245
|
+
void refreshArtifacts();
|
|
2246
|
+
});
|
|
2247
|
+
artifactSelect.addEventListener('change', function () {
|
|
2248
|
+
void loadArtifact(artifactSelect.value);
|
|
2249
|
+
});
|
|
2250
|
+
|
|
2251
|
+
window.addEventListener('beforeunload', function () {
|
|
2252
|
+
closeEventSource();
|
|
2253
|
+
if (runsTimer) {
|
|
2254
|
+
clearInterval(runsTimer);
|
|
2255
|
+
}
|
|
2256
|
+
});
|
|
2257
|
+
|
|
2258
|
+
bootstrap();
|
|
2259
|
+
})();
|
|
2260
|
+
</script>
|
|
2261
|
+
</body>
|
|
2262
|
+
</html>`;
|
|
2263
|
+
}
|
|
2264
|
+
//# sourceMappingURL=ui.js.map
|