labgate 0.5.10 → 0.5.11
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/README.md +50 -2
- package/dist/cli.js +356 -27
- package/dist/cli.js.map +1 -1
- package/dist/lib/cluster-mcp.d.ts +33 -0
- package/dist/lib/cluster-mcp.js +313 -0
- package/dist/lib/cluster-mcp.js.map +1 -0
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.js +7 -3
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/container.d.ts +10 -0
- package/dist/lib/container.js +164 -32
- package/dist/lib/container.js.map +1 -1
- package/dist/lib/dataset-mcp.d.ts +20 -0
- package/dist/lib/dataset-mcp.js +809 -0
- package/dist/lib/dataset-mcp.js.map +1 -0
- package/dist/lib/init.js +2 -2
- package/dist/lib/results-mcp.d.ts +9 -0
- package/dist/lib/results-mcp.js +205 -0
- package/dist/lib/results-mcp.js.map +1 -0
- package/dist/lib/results-store.d.ts +61 -0
- package/dist/lib/results-store.js +319 -0
- package/dist/lib/results-store.js.map +1 -0
- package/dist/lib/slurm-mcp.js +1 -1
- package/dist/lib/slurm-mcp.js.map +1 -1
- package/dist/lib/test/integration-harness.d.ts +4 -0
- package/dist/lib/test/integration-harness.js +13 -1
- package/dist/lib/test/integration-harness.js.map +1 -1
- package/dist/lib/ui.html +2068 -351
- package/dist/lib/ui.js +701 -0
- package/dist/lib/ui.js.map +1 -1
- package/dist/mcp-bundles/cluster-mcp.bundle.mjs +30235 -0
- package/dist/mcp-bundles/dataset-mcp.bundle.mjs +30968 -0
- package/dist/mcp-bundles/results-mcp.bundle.mjs +30449 -0
- package/dist/mcp-bundles/slurm-mcp.bundle.mjs +30501 -0
- package/package.json +4 -2
package/dist/lib/ui.html
CHANGED
|
@@ -4,35 +4,80 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>LabGate Settings</title>
|
|
7
|
-
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='20' fill='%
|
|
7
|
+
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='20' fill='%231A7A3A'/><text x='50' y='68' font-size='52' font-family='system-ui' font-weight='700' fill='white' text-anchor='middle'>L</text></svg>">
|
|
8
8
|
<style>
|
|
9
|
+
@font-face {
|
|
10
|
+
font-family: 'Geist';
|
|
11
|
+
src: url('/fonts/Geist-Regular.woff2') format('woff2');
|
|
12
|
+
font-weight: 400;
|
|
13
|
+
font-style: normal;
|
|
14
|
+
font-display: swap;
|
|
15
|
+
}
|
|
16
|
+
@font-face {
|
|
17
|
+
font-family: 'Geist';
|
|
18
|
+
src: url('/fonts/Geist-Medium.woff2') format('woff2');
|
|
19
|
+
font-weight: 500;
|
|
20
|
+
font-style: normal;
|
|
21
|
+
font-display: swap;
|
|
22
|
+
}
|
|
23
|
+
@font-face {
|
|
24
|
+
font-family: 'Geist';
|
|
25
|
+
src: url('/fonts/Geist-SemiBold.woff2') format('woff2');
|
|
26
|
+
font-weight: 600;
|
|
27
|
+
font-style: normal;
|
|
28
|
+
font-display: swap;
|
|
29
|
+
}
|
|
30
|
+
@font-face {
|
|
31
|
+
font-family: 'Geist';
|
|
32
|
+
src: url('/fonts/Geist-Bold.woff2') format('woff2');
|
|
33
|
+
font-weight: 700;
|
|
34
|
+
font-style: normal;
|
|
35
|
+
font-display: swap;
|
|
36
|
+
}
|
|
37
|
+
@font-face {
|
|
38
|
+
font-family: 'GeistMono';
|
|
39
|
+
src: url('/fonts/GeistMono-Regular.woff2') format('woff2');
|
|
40
|
+
font-weight: 400;
|
|
41
|
+
font-style: normal;
|
|
42
|
+
font-display: swap;
|
|
43
|
+
}
|
|
44
|
+
@font-face {
|
|
45
|
+
font-family: 'GeistMono';
|
|
46
|
+
src: url('/fonts/GeistMono-Medium.woff2') format('woff2');
|
|
47
|
+
font-weight: 500;
|
|
48
|
+
font-style: normal;
|
|
49
|
+
font-display: swap;
|
|
50
|
+
}
|
|
51
|
+
|
|
9
52
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
10
53
|
|
|
11
54
|
:root {
|
|
12
|
-
--bg-primary: #
|
|
13
|
-
--bg-secondary: #
|
|
14
|
-
--text-primary: #
|
|
15
|
-
--text-secondary: #
|
|
16
|
-
--text-muted: #
|
|
17
|
-
--border-color: #
|
|
18
|
-
--card-bg: #
|
|
19
|
-
--
|
|
20
|
-
--
|
|
21
|
-
--
|
|
22
|
-
--
|
|
55
|
+
--bg-primary: #FAFDF8;
|
|
56
|
+
--bg-secondary: #F0F4EC;
|
|
57
|
+
--text-primary: #0C1A0E;
|
|
58
|
+
--text-secondary: #4A6350;
|
|
59
|
+
--text-muted: #8A9E8E;
|
|
60
|
+
--border-color: #D5E0D5;
|
|
61
|
+
--card-bg: #FFFFFF;
|
|
62
|
+
--accent: #1A7A3A;
|
|
63
|
+
--accent-light: #E8F5E9;
|
|
64
|
+
--radius: 12px;
|
|
65
|
+
--radius-sm: 8px;
|
|
66
|
+
--green: #16a34a;
|
|
67
|
+
--red: #dc2626;
|
|
23
68
|
--blue: #0969da;
|
|
24
|
-
--yellow: #
|
|
69
|
+
--yellow: #f59e0b;
|
|
25
70
|
}
|
|
26
71
|
|
|
27
72
|
body {
|
|
28
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
|
73
|
+
font-family: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
29
74
|
color: var(--text-primary);
|
|
30
75
|
background: var(--bg-primary);
|
|
31
|
-
line-height: 1.
|
|
76
|
+
line-height: 1.5;
|
|
32
77
|
-webkit-font-smoothing: antialiased;
|
|
33
78
|
}
|
|
34
79
|
|
|
35
|
-
code { font-family: '
|
|
80
|
+
code { font-family: 'GeistMono', monospace; }
|
|
36
81
|
|
|
37
82
|
/* ── Layout ─────────────────────────────────── */
|
|
38
83
|
.header {
|
|
@@ -52,7 +97,7 @@
|
|
|
52
97
|
.config-path {
|
|
53
98
|
font-size: 0.8125rem;
|
|
54
99
|
color: var(--text-muted);
|
|
55
|
-
font-family: '
|
|
100
|
+
font-family: 'GeistMono', monospace;
|
|
56
101
|
}
|
|
57
102
|
|
|
58
103
|
.container {
|
|
@@ -86,13 +131,42 @@
|
|
|
86
131
|
|
|
87
132
|
.tab:hover { color: var(--text-primary); }
|
|
88
133
|
.tab.active {
|
|
89
|
-
color: var(--
|
|
90
|
-
border-bottom-color: var(--
|
|
134
|
+
color: var(--accent);
|
|
135
|
+
border-bottom-color: var(--accent);
|
|
91
136
|
}
|
|
92
137
|
|
|
93
138
|
.tab-panel { display: none; }
|
|
94
139
|
.tab-panel.active { display: block; }
|
|
95
140
|
|
|
141
|
+
/* ── Sub-tabs (within Settings) ──────────────── */
|
|
142
|
+
.sub-tabs {
|
|
143
|
+
display: flex;
|
|
144
|
+
gap: 2px;
|
|
145
|
+
border-bottom: 1px solid var(--border-color);
|
|
146
|
+
margin-bottom: 24px;
|
|
147
|
+
overflow-x: auto;
|
|
148
|
+
}
|
|
149
|
+
.sub-tab {
|
|
150
|
+
padding: 7px 13px;
|
|
151
|
+
font-size: 0.8125rem;
|
|
152
|
+
font-weight: 500;
|
|
153
|
+
color: var(--text-muted);
|
|
154
|
+
background: none;
|
|
155
|
+
border: none;
|
|
156
|
+
border-bottom: 2px solid transparent;
|
|
157
|
+
cursor: pointer;
|
|
158
|
+
white-space: nowrap;
|
|
159
|
+
transition: all 0.15s;
|
|
160
|
+
font-family: inherit;
|
|
161
|
+
}
|
|
162
|
+
.sub-tab:hover { color: var(--text-primary); }
|
|
163
|
+
.sub-tab.active {
|
|
164
|
+
color: var(--accent);
|
|
165
|
+
border-bottom-color: var(--accent);
|
|
166
|
+
}
|
|
167
|
+
.sub-panel { display: none; }
|
|
168
|
+
.sub-panel.active { display: block; }
|
|
169
|
+
|
|
96
170
|
/* ── Cards ──────────────────────────────────── */
|
|
97
171
|
.card {
|
|
98
172
|
background: var(--card-bg);
|
|
@@ -178,7 +252,7 @@
|
|
|
178
252
|
border: none;
|
|
179
253
|
}
|
|
180
254
|
|
|
181
|
-
.toggle:checked { background: var(--
|
|
255
|
+
.toggle:checked { background: var(--accent); }
|
|
182
256
|
|
|
183
257
|
.toggle::after {
|
|
184
258
|
content: '';
|
|
@@ -214,7 +288,7 @@
|
|
|
214
288
|
align-items: center;
|
|
215
289
|
padding: 6px 10px;
|
|
216
290
|
font-size: 0.8125rem;
|
|
217
|
-
font-family: '
|
|
291
|
+
font-family: 'GeistMono', monospace;
|
|
218
292
|
border-bottom: 1px solid var(--border-color);
|
|
219
293
|
}
|
|
220
294
|
|
|
@@ -244,7 +318,7 @@
|
|
|
244
318
|
flex: 1;
|
|
245
319
|
padding: 6px 10px;
|
|
246
320
|
font-size: 0.8125rem;
|
|
247
|
-
font-family: '
|
|
321
|
+
font-family: 'GeistMono', monospace;
|
|
248
322
|
border: 1px solid var(--border-color);
|
|
249
323
|
border-radius: 6px;
|
|
250
324
|
background: var(--bg-primary);
|
|
@@ -299,7 +373,7 @@
|
|
|
299
373
|
padding: 8px 24px;
|
|
300
374
|
font-size: 0.875rem;
|
|
301
375
|
font-weight: 500;
|
|
302
|
-
background: var(--
|
|
376
|
+
background: var(--accent);
|
|
303
377
|
color: #fff;
|
|
304
378
|
border: none;
|
|
305
379
|
border-radius: 8px;
|
|
@@ -308,7 +382,7 @@
|
|
|
308
382
|
transition: background 0.15s;
|
|
309
383
|
}
|
|
310
384
|
|
|
311
|
-
.btn-save:hover { background: #
|
|
385
|
+
.btn-save:hover { background: #15632F; }
|
|
312
386
|
|
|
313
387
|
.btn-reset {
|
|
314
388
|
padding: 8px 16px;
|
|
@@ -341,7 +415,7 @@
|
|
|
341
415
|
}
|
|
342
416
|
|
|
343
417
|
.toast.visible { transform: translateX(0); }
|
|
344
|
-
.toast.success { background:
|
|
418
|
+
.toast.success { background: var(--accent); }
|
|
345
419
|
.toast.error { background: var(--red); }
|
|
346
420
|
|
|
347
421
|
/* ── Sessions & Logs ───────────────────────── */
|
|
@@ -364,7 +438,7 @@
|
|
|
364
438
|
padding: 8px 12px;
|
|
365
439
|
border-bottom: 1px solid var(--border-color);
|
|
366
440
|
color: var(--text-secondary);
|
|
367
|
-
font-family: '
|
|
441
|
+
font-family: 'GeistMono', monospace;
|
|
368
442
|
font-size: 0.75rem;
|
|
369
443
|
}
|
|
370
444
|
|
|
@@ -403,7 +477,7 @@
|
|
|
403
477
|
}
|
|
404
478
|
|
|
405
479
|
.mount-item:last-child { border-bottom: none; }
|
|
406
|
-
.mount-item .mount-path { flex: 1; font-family: '
|
|
480
|
+
.mount-item .mount-path { flex: 1; font-family: 'GeistMono', monospace; }
|
|
407
481
|
.mount-item .mount-mode {
|
|
408
482
|
font-size: 0.75rem;
|
|
409
483
|
padding: 2px 6px;
|
|
@@ -414,19 +488,324 @@
|
|
|
414
488
|
.mount-item .mount-mode.ro { background: #dbeafe; color: #1e40af; }
|
|
415
489
|
|
|
416
490
|
/* ── Dataset editor ──────────────────────── */
|
|
417
|
-
|
|
491
|
+
|
|
492
|
+
/* Empty state */
|
|
493
|
+
.dataset-empty {
|
|
494
|
+
text-align: center;
|
|
495
|
+
padding: 56px 24px 48px;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.dataset-empty-icon {
|
|
499
|
+
display: inline-flex;
|
|
500
|
+
align-items: center;
|
|
501
|
+
justify-content: center;
|
|
502
|
+
width: 64px;
|
|
503
|
+
height: 64px;
|
|
504
|
+
margin-bottom: 20px;
|
|
505
|
+
background: var(--bg-secondary);
|
|
506
|
+
border-radius: 16px;
|
|
507
|
+
color: var(--text-muted);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.dataset-empty-icon svg { width: 28px; height: 28px; }
|
|
511
|
+
|
|
512
|
+
.dataset-empty-title {
|
|
513
|
+
font-size: 0.9375rem;
|
|
514
|
+
font-weight: 600;
|
|
515
|
+
color: var(--text-primary);
|
|
516
|
+
margin-bottom: 6px;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.dataset-empty-subtitle {
|
|
520
|
+
font-size: 0.8125rem;
|
|
521
|
+
color: var(--text-muted);
|
|
522
|
+
line-height: 1.5;
|
|
523
|
+
max-width: 340px;
|
|
524
|
+
margin: 0 auto;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/* Dataset cards grid */
|
|
528
|
+
@keyframes dataset-fade-in {
|
|
529
|
+
from { opacity: 0; transform: translateY(6px); }
|
|
530
|
+
to { opacity: 1; transform: translateY(0); }
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
.dataset-grid {
|
|
534
|
+
display: grid;
|
|
535
|
+
gap: 10px;
|
|
536
|
+
margin-bottom: 4px;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.dataset-card {
|
|
540
|
+
background: var(--card-bg);
|
|
541
|
+
border: 1px solid var(--border-color);
|
|
542
|
+
border-radius: var(--radius-sm);
|
|
543
|
+
padding: 14px 16px;
|
|
544
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
545
|
+
animation: dataset-fade-in 0.25s ease-out;
|
|
546
|
+
position: relative;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.dataset-card:hover {
|
|
550
|
+
border-color: #B8CBB8;
|
|
551
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.dataset-card-header {
|
|
418
555
|
display: flex;
|
|
419
556
|
align-items: center;
|
|
420
557
|
gap: 10px;
|
|
421
|
-
|
|
558
|
+
margin-bottom: 8px;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.dataset-card-icon {
|
|
562
|
+
display: flex;
|
|
563
|
+
align-items: center;
|
|
564
|
+
justify-content: center;
|
|
565
|
+
width: 32px;
|
|
566
|
+
height: 32px;
|
|
567
|
+
border-radius: 8px;
|
|
568
|
+
background: var(--bg-secondary);
|
|
569
|
+
color: var(--text-muted);
|
|
570
|
+
flex-shrink: 0;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.dataset-card-icon svg { width: 16px; height: 16px; }
|
|
574
|
+
|
|
575
|
+
.dataset-card-title {
|
|
576
|
+
display: flex;
|
|
577
|
+
align-items: center;
|
|
578
|
+
gap: 8px;
|
|
579
|
+
flex: 1;
|
|
580
|
+
min-width: 0;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.dataset-card .dataset-name {
|
|
584
|
+
font-weight: 600;
|
|
585
|
+
font-size: 0.875rem;
|
|
586
|
+
color: var(--text-primary);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
.dataset-card .mount-mode {
|
|
590
|
+
font-size: 0.6875rem;
|
|
591
|
+
padding: 2px 8px;
|
|
592
|
+
border-radius: 10px;
|
|
593
|
+
font-weight: 600;
|
|
594
|
+
text-transform: uppercase;
|
|
595
|
+
letter-spacing: 0.03em;
|
|
596
|
+
flex-shrink: 0;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.mount-mode.ro { background: #dbeafe; color: #1e40af; }
|
|
600
|
+
.mount-mode.rw { background: #fef3c7; color: #92400e; }
|
|
601
|
+
|
|
602
|
+
.dataset-card .remove-btn {
|
|
603
|
+
background: none;
|
|
604
|
+
border: none;
|
|
605
|
+
color: var(--text-muted);
|
|
606
|
+
cursor: pointer;
|
|
607
|
+
font-size: 1rem;
|
|
608
|
+
width: 28px;
|
|
609
|
+
height: 28px;
|
|
610
|
+
display: flex;
|
|
611
|
+
align-items: center;
|
|
612
|
+
justify-content: center;
|
|
613
|
+
border-radius: 6px;
|
|
614
|
+
opacity: 0;
|
|
615
|
+
transition: opacity 0.15s, background 0.15s, color 0.15s;
|
|
616
|
+
flex-shrink: 0;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
.dataset-card:hover .remove-btn { opacity: 1; }
|
|
620
|
+
.dataset-card .remove-btn:hover { background: #fef2f2; color: var(--red); }
|
|
621
|
+
|
|
622
|
+
.dataset-card-desc {
|
|
422
623
|
font-size: 0.8125rem;
|
|
423
|
-
|
|
624
|
+
color: var(--text-secondary);
|
|
625
|
+
padding-left: 42px;
|
|
626
|
+
margin-bottom: 10px;
|
|
627
|
+
line-height: 1.4;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
.dataset-card-paths {
|
|
631
|
+
display: flex;
|
|
632
|
+
align-items: center;
|
|
633
|
+
gap: 6px;
|
|
634
|
+
padding-left: 42px;
|
|
635
|
+
flex-wrap: wrap;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.dataset-card .dataset-path {
|
|
639
|
+
font-family: 'GeistMono', monospace;
|
|
640
|
+
font-size: 0.6875rem;
|
|
641
|
+
color: var(--text-secondary);
|
|
642
|
+
background: var(--bg-primary);
|
|
643
|
+
padding: 3px 8px;
|
|
644
|
+
border-radius: 6px;
|
|
645
|
+
border: 1px solid var(--border-color);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
.dataset-card .dataset-path-container {
|
|
649
|
+
color: var(--blue);
|
|
650
|
+
border-color: #dbeafe;
|
|
651
|
+
background: #f0f7ff;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.dataset-path-arrow {
|
|
655
|
+
color: var(--text-muted);
|
|
656
|
+
display: flex;
|
|
657
|
+
align-items: center;
|
|
658
|
+
flex-shrink: 0;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
.dataset-count {
|
|
662
|
+
font-size: 0.75rem;
|
|
663
|
+
font-weight: 600;
|
|
664
|
+
color: var(--text-muted);
|
|
665
|
+
background: var(--bg-secondary);
|
|
666
|
+
padding: 2px 10px;
|
|
667
|
+
border-radius: 10px;
|
|
668
|
+
white-space: nowrap;
|
|
669
|
+
flex-shrink: 0;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/* Dataset restart notice */
|
|
673
|
+
.dataset-notice {
|
|
674
|
+
display: flex;
|
|
675
|
+
align-items: center;
|
|
676
|
+
gap: 8px;
|
|
677
|
+
padding: 10px 14px;
|
|
678
|
+
margin-bottom: 12px;
|
|
679
|
+
font-size: 0.8125rem;
|
|
680
|
+
color: #92400e;
|
|
681
|
+
background: #fffbeb;
|
|
682
|
+
border: 1px solid #fef3c7;
|
|
683
|
+
border-radius: 8px;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
.dataset-notice svg { flex-shrink: 0; color: #d97706; }
|
|
687
|
+
.dataset-notice-text { flex: 1; }
|
|
688
|
+
|
|
689
|
+
.dataset-notice-btn {
|
|
690
|
+
flex-shrink: 0;
|
|
691
|
+
padding: 5px 14px;
|
|
692
|
+
font-size: 0.75rem;
|
|
693
|
+
font-weight: 600;
|
|
694
|
+
background: #f59e0b;
|
|
695
|
+
color: #fff;
|
|
696
|
+
border: none;
|
|
697
|
+
border-radius: 6px;
|
|
698
|
+
cursor: pointer;
|
|
699
|
+
font-family: inherit;
|
|
700
|
+
transition: background 0.15s;
|
|
701
|
+
white-space: nowrap;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
.dataset-notice-btn:hover { background: #d97706; }
|
|
705
|
+
.dataset-notice-btn:disabled { opacity: 0.6; cursor: default; }
|
|
706
|
+
|
|
707
|
+
/* Dataset add form */
|
|
708
|
+
.dataset-add-form {
|
|
709
|
+
background: var(--card-bg);
|
|
710
|
+
border: 1px solid var(--border-color);
|
|
711
|
+
border-radius: var(--radius-sm);
|
|
712
|
+
padding: 20px;
|
|
713
|
+
margin-top: 4px;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
.dataset-add-form .form-title {
|
|
717
|
+
font-size: 0.8125rem;
|
|
718
|
+
font-weight: 600;
|
|
719
|
+
color: var(--text-primary);
|
|
720
|
+
margin-bottom: 16px;
|
|
721
|
+
display: flex;
|
|
722
|
+
align-items: center;
|
|
723
|
+
gap: 8px;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
.dataset-add-form .form-title-icon {
|
|
727
|
+
display: flex;
|
|
728
|
+
align-items: center;
|
|
729
|
+
justify-content: center;
|
|
730
|
+
width: 24px;
|
|
731
|
+
height: 24px;
|
|
732
|
+
border-radius: 6px;
|
|
733
|
+
background: var(--accent);
|
|
734
|
+
color: #fff;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
.dataset-add-form .form-title-icon svg { width: 14px; height: 14px; }
|
|
738
|
+
|
|
739
|
+
.dataset-add-form .field-grid {
|
|
740
|
+
display: grid;
|
|
741
|
+
grid-template-columns: 2fr 1fr;
|
|
742
|
+
gap: 12px;
|
|
743
|
+
margin-bottom: 16px;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.dataset-add-form .field-full { grid-column: 1 / -1; }
|
|
747
|
+
|
|
748
|
+
.dataset-add-form .form-footer {
|
|
749
|
+
display: flex;
|
|
750
|
+
align-items: flex-end;
|
|
751
|
+
justify-content: space-between;
|
|
752
|
+
gap: 12px;
|
|
753
|
+
padding-top: 4px;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
.dataset-add-form .btn-add-dataset {
|
|
757
|
+
padding: 8px 20px;
|
|
758
|
+
font-size: 0.8125rem;
|
|
759
|
+
font-weight: 500;
|
|
760
|
+
background: var(--accent);
|
|
761
|
+
color: #fff;
|
|
762
|
+
border: none;
|
|
763
|
+
border-radius: 8px;
|
|
764
|
+
cursor: pointer;
|
|
765
|
+
font-family: inherit;
|
|
766
|
+
transition: background 0.15s;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
.dataset-add-form .btn-add-dataset:hover { background: #15632F; }
|
|
770
|
+
|
|
771
|
+
/* Mode pill toggle */
|
|
772
|
+
.mode-toggle {
|
|
773
|
+
display: inline-flex;
|
|
774
|
+
border: 1px solid var(--border-color);
|
|
775
|
+
border-radius: 8px;
|
|
776
|
+
overflow: hidden;
|
|
777
|
+
background: var(--bg-primary);
|
|
778
|
+
padding: 2px;
|
|
779
|
+
gap: 2px;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
.mode-toggle input[type="radio"] { display: none; }
|
|
783
|
+
|
|
784
|
+
.mode-toggle label {
|
|
785
|
+
padding: 6px 16px;
|
|
786
|
+
font-size: 0.8125rem;
|
|
787
|
+
font-weight: 500;
|
|
788
|
+
cursor: pointer;
|
|
789
|
+
color: var(--text-muted);
|
|
790
|
+
transition: all 0.15s;
|
|
791
|
+
user-select: none;
|
|
792
|
+
border-radius: 6px;
|
|
793
|
+
border: none;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
.mode-toggle label:hover { color: var(--text-secondary); }
|
|
797
|
+
|
|
798
|
+
.mode-toggle input[type="radio"]:checked + label.mode-ro {
|
|
799
|
+
background: #dbeafe;
|
|
800
|
+
color: #1e40af;
|
|
801
|
+
box-shadow: 0 1px 2px rgba(0,0,0,0.06);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
.mode-toggle input[type="radio"]:checked + label.mode-rw {
|
|
805
|
+
background: #fef3c7;
|
|
806
|
+
color: #92400e;
|
|
807
|
+
box-shadow: 0 1px 2px rgba(0,0,0,0.06);
|
|
424
808
|
}
|
|
425
|
-
.dataset-item:last-child { border-bottom: none; }
|
|
426
|
-
.dataset-item .dataset-name { font-weight: 600; min-width: 100px; }
|
|
427
|
-
.dataset-item .dataset-path { flex: 1; font-family: 'SF Mono', Monaco, Consolas, monospace; color: var(--text-secondary); }
|
|
428
|
-
.dataset-item .dataset-desc { color: var(--text-muted); font-style: italic; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
429
|
-
.dataset-item .mount-mode { font-size: 0.75rem; padding: 2px 6px; border-radius: 4px; font-weight: 500; }
|
|
430
809
|
|
|
431
810
|
/* ── Session cards ─────────────────────────── */
|
|
432
811
|
.sessions-summary {
|
|
@@ -449,7 +828,7 @@
|
|
|
449
828
|
transition: border-color 0.15s;
|
|
450
829
|
}
|
|
451
830
|
|
|
452
|
-
.session-card:hover { border-color: #
|
|
831
|
+
.session-card:hover { border-color: #B8CBB8; }
|
|
453
832
|
|
|
454
833
|
.session-card.waiting {
|
|
455
834
|
border-color: var(--yellow);
|
|
@@ -481,7 +860,7 @@
|
|
|
481
860
|
padding: 1px 4px;
|
|
482
861
|
border-radius: 3px;
|
|
483
862
|
font-size: 0.75rem;
|
|
484
|
-
font-family: '
|
|
863
|
+
font-family: 'GeistMono', monospace;
|
|
485
864
|
}
|
|
486
865
|
.btn-restart-stop {
|
|
487
866
|
padding: 4px 12px;
|
|
@@ -519,7 +898,7 @@
|
|
|
519
898
|
}
|
|
520
899
|
|
|
521
900
|
.session-id {
|
|
522
|
-
font-family: '
|
|
901
|
+
font-family: 'GeistMono', monospace;
|
|
523
902
|
font-size: 0.8125rem;
|
|
524
903
|
color: var(--text-secondary);
|
|
525
904
|
}
|
|
@@ -534,7 +913,7 @@
|
|
|
534
913
|
flex-shrink: 0;
|
|
535
914
|
}
|
|
536
915
|
|
|
537
|
-
.status-dot.active { background: var(--green); box-shadow: 0 0 6px rgba(
|
|
916
|
+
.status-dot.active { background: var(--green); box-shadow: 0 0 6px rgba(22,163,74,0.4); }
|
|
538
917
|
.status-dot.idle { background: var(--text-muted); }
|
|
539
918
|
.status-dot.running { background: var(--yellow); }
|
|
540
919
|
|
|
@@ -544,8 +923,8 @@
|
|
|
544
923
|
.status-dot.unknown { background: var(--text-muted); }
|
|
545
924
|
|
|
546
925
|
@keyframes pulse-dot {
|
|
547
|
-
0%, 100% { opacity: 1; box-shadow: 0 0 4px rgba(
|
|
548
|
-
50% { opacity: 0.5; box-shadow: 0 0 8px rgba(
|
|
926
|
+
0%, 100% { opacity: 1; box-shadow: 0 0 4px rgba(22,163,74,0.3); }
|
|
927
|
+
50% { opacity: 0.5; box-shadow: 0 0 8px rgba(22,163,74,0.6); }
|
|
549
928
|
}
|
|
550
929
|
|
|
551
930
|
.status-label {
|
|
@@ -589,7 +968,7 @@
|
|
|
589
968
|
|
|
590
969
|
.activity-detail {
|
|
591
970
|
color: var(--text-muted);
|
|
592
|
-
font-family: '
|
|
971
|
+
font-family: 'GeistMono', monospace;
|
|
593
972
|
font-size: 0.75rem;
|
|
594
973
|
overflow: hidden;
|
|
595
974
|
text-overflow: ellipsis;
|
|
@@ -635,7 +1014,7 @@
|
|
|
635
1014
|
gap: 10px;
|
|
636
1015
|
align-items: center;
|
|
637
1016
|
font-size: 0.625rem;
|
|
638
|
-
font-family: '
|
|
1017
|
+
font-family: 'GeistMono', monospace;
|
|
639
1018
|
color: var(--text-muted);
|
|
640
1019
|
pointer-events: none;
|
|
641
1020
|
box-shadow: 0 2px 8px rgba(0,0,0,0.12);
|
|
@@ -681,7 +1060,7 @@
|
|
|
681
1060
|
|
|
682
1061
|
.resource-label {
|
|
683
1062
|
font-size: 0.6875rem;
|
|
684
|
-
font-family: '
|
|
1063
|
+
font-family: 'GeistMono', monospace;
|
|
685
1064
|
color: var(--text-muted);
|
|
686
1065
|
min-width: 30px;
|
|
687
1066
|
}
|
|
@@ -764,61 +1143,14 @@
|
|
|
764
1143
|
box-shadow: 0 1px 2px rgba(0,0,0,0.06);
|
|
765
1144
|
}
|
|
766
1145
|
|
|
767
|
-
/* ──
|
|
768
|
-
.
|
|
769
|
-
display: flex;
|
|
770
|
-
gap: 12px;
|
|
1146
|
+
/* ── Blocked events ────────────────────────── */
|
|
1147
|
+
.blocked-events-list {
|
|
771
1148
|
margin-bottom: 16px;
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
display:
|
|
777
|
-
align-items: center;
|
|
778
|
-
gap: 8px;
|
|
779
|
-
padding: 10px 16px;
|
|
780
|
-
background: var(--card-bg);
|
|
781
|
-
border: 1px solid var(--border-color);
|
|
782
|
-
border-radius: var(--radius-sm);
|
|
783
|
-
flex: 1;
|
|
784
|
-
min-width: 140px;
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
.security-stat-info {
|
|
788
|
-
display: flex;
|
|
789
|
-
flex-direction: column;
|
|
790
|
-
gap: 1px;
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
.security-stat-value {
|
|
794
|
-
font-size: 1.125rem;
|
|
795
|
-
font-weight: 600;
|
|
796
|
-
line-height: 1.2;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
.security-stat-label {
|
|
800
|
-
font-size: 0.6875rem;
|
|
801
|
-
color: var(--text-muted);
|
|
802
|
-
text-transform: uppercase;
|
|
803
|
-
letter-spacing: 0.04em;
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
.security-stat.has-blocked {
|
|
807
|
-
border-color: var(--red);
|
|
808
|
-
background: #fef2f2;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
.security-stat.has-blocked .security-stat-value {
|
|
812
|
-
color: var(--red);
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
.blocked-events-list {
|
|
816
|
-
margin-top: 8px;
|
|
817
|
-
background: var(--card-bg);
|
|
818
|
-
border: 1px solid var(--border-color);
|
|
819
|
-
border-radius: var(--radius-sm);
|
|
820
|
-
overflow: hidden;
|
|
821
|
-
display: none;
|
|
1149
|
+
background: var(--card-bg);
|
|
1150
|
+
border: 1px solid var(--border-color);
|
|
1151
|
+
border-radius: var(--radius-sm);
|
|
1152
|
+
overflow: hidden;
|
|
1153
|
+
display: none;
|
|
822
1154
|
}
|
|
823
1155
|
|
|
824
1156
|
.blocked-events-list.visible { display: block; }
|
|
@@ -841,7 +1173,7 @@
|
|
|
841
1173
|
}
|
|
842
1174
|
|
|
843
1175
|
.blocked-event-cmd {
|
|
844
|
-
font-family: '
|
|
1176
|
+
font-family: 'GeistMono', monospace;
|
|
845
1177
|
font-weight: 500;
|
|
846
1178
|
color: var(--text-primary);
|
|
847
1179
|
}
|
|
@@ -881,7 +1213,7 @@
|
|
|
881
1213
|
display: inline-block;
|
|
882
1214
|
padding: 1px 5px;
|
|
883
1215
|
font-size: 0.5625rem;
|
|
884
|
-
font-family: '
|
|
1216
|
+
font-family: 'GeistMono', monospace;
|
|
885
1217
|
background: #e8e8e3;
|
|
886
1218
|
border-radius: 3px;
|
|
887
1219
|
color: #b0b0a8;
|
|
@@ -928,7 +1260,7 @@
|
|
|
928
1260
|
display: inline-block;
|
|
929
1261
|
padding: 1px 5px;
|
|
930
1262
|
font-size: 0.5625rem;
|
|
931
|
-
font-family: '
|
|
1263
|
+
font-family: 'GeistMono', monospace;
|
|
932
1264
|
background: #dbeafe;
|
|
933
1265
|
border-radius: 3px;
|
|
934
1266
|
color: #1e40af;
|
|
@@ -1007,7 +1339,7 @@
|
|
|
1007
1339
|
|
|
1008
1340
|
.session-detail span {
|
|
1009
1341
|
color: var(--text-secondary);
|
|
1010
|
-
font-family: '
|
|
1342
|
+
font-family: 'GeistMono', monospace;
|
|
1011
1343
|
font-size: 0.75rem;
|
|
1012
1344
|
overflow: hidden;
|
|
1013
1345
|
text-overflow: ellipsis;
|
|
@@ -1070,7 +1402,7 @@
|
|
|
1070
1402
|
background: var(--bg-secondary);
|
|
1071
1403
|
border: 1px solid var(--border-color);
|
|
1072
1404
|
border-radius: 6px;
|
|
1073
|
-
font-family: '
|
|
1405
|
+
font-family: 'GeistMono', monospace;
|
|
1074
1406
|
font-size: 0.8125rem;
|
|
1075
1407
|
color: var(--text-primary);
|
|
1076
1408
|
}
|
|
@@ -1094,7 +1426,7 @@
|
|
|
1094
1426
|
color: var(--text-secondary);
|
|
1095
1427
|
}
|
|
1096
1428
|
|
|
1097
|
-
/* ──
|
|
1429
|
+
/* ── Modal backdrop (shared) ────────────── */
|
|
1098
1430
|
.modal-backdrop {
|
|
1099
1431
|
position: fixed;
|
|
1100
1432
|
inset: 0;
|
|
@@ -1122,6 +1454,106 @@
|
|
|
1122
1454
|
box-shadow: 0 18px 48px rgba(0,0,0,0.15);
|
|
1123
1455
|
}
|
|
1124
1456
|
|
|
1457
|
+
/* ── Instructions sidebar (right panel) ── */
|
|
1458
|
+
#instructionsModal {
|
|
1459
|
+
background: rgba(0,0,0,0.25);
|
|
1460
|
+
padding: 0;
|
|
1461
|
+
display: none;
|
|
1462
|
+
align-items: stretch;
|
|
1463
|
+
justify-content: flex-end;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
#instructionsModal.visible {
|
|
1467
|
+
display: flex;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
#instructionsModal .instructions-modal {
|
|
1471
|
+
position: fixed;
|
|
1472
|
+
top: 0;
|
|
1473
|
+
right: 0;
|
|
1474
|
+
width: min(520px, 90vw);
|
|
1475
|
+
height: 100vh;
|
|
1476
|
+
max-height: 100vh;
|
|
1477
|
+
border-radius: 0;
|
|
1478
|
+
border: none;
|
|
1479
|
+
border-left: 1px solid var(--border-color);
|
|
1480
|
+
box-shadow: -4px 0 24px rgba(0,0,0,0.12);
|
|
1481
|
+
transform: translateX(100%);
|
|
1482
|
+
transition: transform 0.25s ease;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
#instructionsModal.visible .instructions-modal {
|
|
1486
|
+
transform: translateX(0);
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
/* ── Sidebar resize handle ────────────────── */
|
|
1490
|
+
.sidebar-resize-handle {
|
|
1491
|
+
position: absolute;
|
|
1492
|
+
top: 0;
|
|
1493
|
+
left: -3px;
|
|
1494
|
+
width: 6px;
|
|
1495
|
+
height: 100%;
|
|
1496
|
+
cursor: col-resize;
|
|
1497
|
+
z-index: 10;
|
|
1498
|
+
display: flex;
|
|
1499
|
+
align-items: center;
|
|
1500
|
+
justify-content: center;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
.sidebar-resize-handle::before {
|
|
1504
|
+
content: '';
|
|
1505
|
+
position: absolute;
|
|
1506
|
+
top: 0;
|
|
1507
|
+
left: 2px;
|
|
1508
|
+
width: 2px;
|
|
1509
|
+
height: 100%;
|
|
1510
|
+
background: transparent;
|
|
1511
|
+
transition: background 0.15s;
|
|
1512
|
+
border-radius: 1px;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
.sidebar-resize-handle:hover::before,
|
|
1516
|
+
.sidebar-resize-handle.dragging::before {
|
|
1517
|
+
background: var(--accent);
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
/* Sidebar collapse toggle */
|
|
1521
|
+
.sidebar-collapse-btn {
|
|
1522
|
+
position: absolute;
|
|
1523
|
+
top: 50%;
|
|
1524
|
+
left: -14px;
|
|
1525
|
+
transform: translateY(-50%);
|
|
1526
|
+
width: 14px;
|
|
1527
|
+
height: 40px;
|
|
1528
|
+
display: flex;
|
|
1529
|
+
align-items: center;
|
|
1530
|
+
justify-content: center;
|
|
1531
|
+
background: var(--card-bg);
|
|
1532
|
+
border: 1px solid var(--border-color);
|
|
1533
|
+
border-right: none;
|
|
1534
|
+
border-radius: 6px 0 0 6px;
|
|
1535
|
+
cursor: pointer;
|
|
1536
|
+
z-index: 11;
|
|
1537
|
+
color: var(--text-muted);
|
|
1538
|
+
transition: color 0.15s, background 0.15s, border-color 0.15s;
|
|
1539
|
+
padding: 0;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
.sidebar-collapse-btn:hover {
|
|
1543
|
+
color: var(--accent);
|
|
1544
|
+
border-color: var(--accent);
|
|
1545
|
+
background: var(--accent-light);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
.sidebar-collapse-btn svg {
|
|
1549
|
+
width: 10px;
|
|
1550
|
+
height: 10px;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
#instructionsModal .instructions-modal.resizing {
|
|
1554
|
+
transition: none;
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1125
1557
|
.instructions-header {
|
|
1126
1558
|
display: flex;
|
|
1127
1559
|
align-items: center;
|
|
@@ -1140,7 +1572,7 @@
|
|
|
1140
1572
|
.instructions-subtitle {
|
|
1141
1573
|
font-size: 0.6875rem;
|
|
1142
1574
|
color: var(--text-muted);
|
|
1143
|
-
font-family: '
|
|
1575
|
+
font-family: 'GeistMono', monospace;
|
|
1144
1576
|
}
|
|
1145
1577
|
|
|
1146
1578
|
.instructions-tabs {
|
|
@@ -1158,7 +1590,7 @@
|
|
|
1158
1590
|
background: transparent;
|
|
1159
1591
|
color: var(--text-muted);
|
|
1160
1592
|
cursor: pointer;
|
|
1161
|
-
font-family: '
|
|
1593
|
+
font-family: 'GeistMono', monospace;
|
|
1162
1594
|
transition: all 0.15s;
|
|
1163
1595
|
}
|
|
1164
1596
|
|
|
@@ -1173,7 +1605,7 @@
|
|
|
1173
1605
|
color: var(--text-muted);
|
|
1174
1606
|
padding: 8px 12px;
|
|
1175
1607
|
border-bottom: 1px solid var(--border-color);
|
|
1176
|
-
font-family: '
|
|
1608
|
+
font-family: 'GeistMono', monospace;
|
|
1177
1609
|
white-space: nowrap;
|
|
1178
1610
|
overflow: hidden;
|
|
1179
1611
|
text-overflow: ellipsis;
|
|
@@ -1207,14 +1639,14 @@
|
|
|
1207
1639
|
font-size: 0.6875rem;
|
|
1208
1640
|
line-height: 1.45;
|
|
1209
1641
|
color: var(--text-secondary);
|
|
1210
|
-
font-family: '
|
|
1642
|
+
font-family: 'GeistMono', monospace;
|
|
1211
1643
|
}
|
|
1212
1644
|
|
|
1213
1645
|
.instructions-editor {
|
|
1214
1646
|
width: 100%;
|
|
1215
|
-
min-height:
|
|
1216
|
-
|
|
1217
|
-
resize:
|
|
1647
|
+
min-height: 200px;
|
|
1648
|
+
flex: 1;
|
|
1649
|
+
resize: none;
|
|
1218
1650
|
border: none;
|
|
1219
1651
|
outline: none;
|
|
1220
1652
|
padding: 12px;
|
|
@@ -1222,7 +1654,7 @@
|
|
|
1222
1654
|
color: var(--text-primary);
|
|
1223
1655
|
font-size: 0.75rem;
|
|
1224
1656
|
line-height: 1.45;
|
|
1225
|
-
font-family: '
|
|
1657
|
+
font-family: 'GeistMono', monospace;
|
|
1226
1658
|
}
|
|
1227
1659
|
|
|
1228
1660
|
.instructions-actions {
|
|
@@ -1285,6 +1717,282 @@
|
|
|
1285
1717
|
min-width: 16px;
|
|
1286
1718
|
text-align: center;
|
|
1287
1719
|
}
|
|
1720
|
+
.results-toolbar {
|
|
1721
|
+
display: flex;
|
|
1722
|
+
gap: 8px;
|
|
1723
|
+
margin-bottom: 16px;
|
|
1724
|
+
align-items: center;
|
|
1725
|
+
flex-wrap: wrap;
|
|
1726
|
+
}
|
|
1727
|
+
.results-toolbar input[type="text"] {
|
|
1728
|
+
padding: 6px 10px;
|
|
1729
|
+
border: 1px solid var(--border-color);
|
|
1730
|
+
border-radius: 6px;
|
|
1731
|
+
background: var(--card-bg);
|
|
1732
|
+
color: var(--text-primary);
|
|
1733
|
+
font-size: 0.8125rem;
|
|
1734
|
+
font-family: 'GeistMono', monospace;
|
|
1735
|
+
}
|
|
1736
|
+
.results-toolbar input[type="text"]:focus {
|
|
1737
|
+
outline: none;
|
|
1738
|
+
border-color: var(--accent);
|
|
1739
|
+
}
|
|
1740
|
+
.results-toolbar .results-search {
|
|
1741
|
+
flex: 1;
|
|
1742
|
+
min-width: 220px;
|
|
1743
|
+
max-width: 420px;
|
|
1744
|
+
}
|
|
1745
|
+
.results-toolbar .results-tag {
|
|
1746
|
+
width: 160px;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
/* ── Results card grid ─────────────────── */
|
|
1750
|
+
@keyframes result-fade-in {
|
|
1751
|
+
from { opacity: 0; transform: translateY(6px); }
|
|
1752
|
+
to { opacity: 1; transform: translateY(0); }
|
|
1753
|
+
}
|
|
1754
|
+
.results-grid {
|
|
1755
|
+
display: grid;
|
|
1756
|
+
gap: 10px;
|
|
1757
|
+
}
|
|
1758
|
+
.result-card {
|
|
1759
|
+
background: var(--card-bg);
|
|
1760
|
+
border: 1px solid var(--border-color);
|
|
1761
|
+
border-radius: var(--radius-sm);
|
|
1762
|
+
padding: 14px 16px;
|
|
1763
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
1764
|
+
animation: result-fade-in 0.25s ease-out;
|
|
1765
|
+
position: relative;
|
|
1766
|
+
}
|
|
1767
|
+
.result-card:hover {
|
|
1768
|
+
border-color: #B8CBB8;
|
|
1769
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
|
1770
|
+
}
|
|
1771
|
+
.result-card-header {
|
|
1772
|
+
display: flex;
|
|
1773
|
+
align-items: center;
|
|
1774
|
+
gap: 10px;
|
|
1775
|
+
margin-bottom: 6px;
|
|
1776
|
+
}
|
|
1777
|
+
.result-card-icon {
|
|
1778
|
+
display: flex;
|
|
1779
|
+
align-items: center;
|
|
1780
|
+
justify-content: center;
|
|
1781
|
+
width: 32px;
|
|
1782
|
+
height: 32px;
|
|
1783
|
+
border-radius: 8px;
|
|
1784
|
+
background: var(--bg-secondary);
|
|
1785
|
+
color: var(--text-muted);
|
|
1786
|
+
flex-shrink: 0;
|
|
1787
|
+
}
|
|
1788
|
+
.result-card-icon svg { width: 16px; height: 16px; }
|
|
1789
|
+
.result-card-title {
|
|
1790
|
+
display: flex;
|
|
1791
|
+
align-items: center;
|
|
1792
|
+
gap: 8px;
|
|
1793
|
+
flex: 1;
|
|
1794
|
+
min-width: 0;
|
|
1795
|
+
}
|
|
1796
|
+
.result-card-title .result-name {
|
|
1797
|
+
font-weight: 600;
|
|
1798
|
+
font-size: 0.875rem;
|
|
1799
|
+
color: var(--text-primary);
|
|
1800
|
+
overflow: hidden;
|
|
1801
|
+
text-overflow: ellipsis;
|
|
1802
|
+
white-space: nowrap;
|
|
1803
|
+
}
|
|
1804
|
+
.result-source-badge {
|
|
1805
|
+
font-size: 0.6875rem;
|
|
1806
|
+
padding: 2px 8px;
|
|
1807
|
+
border-radius: 10px;
|
|
1808
|
+
font-weight: 600;
|
|
1809
|
+
text-transform: uppercase;
|
|
1810
|
+
letter-spacing: 0.03em;
|
|
1811
|
+
flex-shrink: 0;
|
|
1812
|
+
}
|
|
1813
|
+
.result-source-badge.source-claude { background: #f0e6ff; color: #7c3aed; }
|
|
1814
|
+
.result-source-badge.source-codex { background: #dbeafe; color: #1e40af; }
|
|
1815
|
+
.result-source-badge.source-default { background: var(--bg-secondary); color: var(--text-secondary); }
|
|
1816
|
+
.result-card-actions {
|
|
1817
|
+
display: flex;
|
|
1818
|
+
gap: 4px;
|
|
1819
|
+
margin-left: auto;
|
|
1820
|
+
opacity: 0;
|
|
1821
|
+
transition: opacity 0.15s;
|
|
1822
|
+
flex-shrink: 0;
|
|
1823
|
+
}
|
|
1824
|
+
.result-card:hover .result-card-actions { opacity: 1; }
|
|
1825
|
+
.result-card-actions button {
|
|
1826
|
+
background: none;
|
|
1827
|
+
border: none;
|
|
1828
|
+
color: var(--text-muted);
|
|
1829
|
+
cursor: pointer;
|
|
1830
|
+
font-size: 0.75rem;
|
|
1831
|
+
width: 28px;
|
|
1832
|
+
height: 28px;
|
|
1833
|
+
display: flex;
|
|
1834
|
+
align-items: center;
|
|
1835
|
+
justify-content: center;
|
|
1836
|
+
border-radius: 6px;
|
|
1837
|
+
transition: background 0.15s, color 0.15s;
|
|
1838
|
+
}
|
|
1839
|
+
.result-card-actions button:hover { background: var(--bg-secondary); color: var(--text-primary); }
|
|
1840
|
+
.result-card-actions button.danger:hover { background: #fef2f2; color: var(--red); }
|
|
1841
|
+
.result-card-summary {
|
|
1842
|
+
font-size: 0.8125rem;
|
|
1843
|
+
color: var(--text-secondary);
|
|
1844
|
+
padding-left: 42px;
|
|
1845
|
+
margin-bottom: 8px;
|
|
1846
|
+
line-height: 1.4;
|
|
1847
|
+
display: -webkit-box;
|
|
1848
|
+
-webkit-line-clamp: 2;
|
|
1849
|
+
-webkit-box-orient: vertical;
|
|
1850
|
+
overflow: hidden;
|
|
1851
|
+
}
|
|
1852
|
+
.result-card-tags {
|
|
1853
|
+
display: flex;
|
|
1854
|
+
gap: 4px;
|
|
1855
|
+
flex-wrap: wrap;
|
|
1856
|
+
padding-left: 42px;
|
|
1857
|
+
margin-bottom: 8px;
|
|
1858
|
+
}
|
|
1859
|
+
.result-tag-pill {
|
|
1860
|
+
display: inline-block;
|
|
1861
|
+
font-size: 0.6875rem;
|
|
1862
|
+
padding: 2px 8px;
|
|
1863
|
+
border-radius: 10px;
|
|
1864
|
+
background: #dbeafe;
|
|
1865
|
+
color: #1e40af;
|
|
1866
|
+
border: 1px solid #bfdbfe;
|
|
1867
|
+
font-family: 'GeistMono', monospace;
|
|
1868
|
+
white-space: nowrap;
|
|
1869
|
+
}
|
|
1870
|
+
.result-card-meta {
|
|
1871
|
+
display: flex;
|
|
1872
|
+
align-items: center;
|
|
1873
|
+
gap: 12px;
|
|
1874
|
+
padding-left: 42px;
|
|
1875
|
+
flex-wrap: wrap;
|
|
1876
|
+
}
|
|
1877
|
+
.result-meta-item {
|
|
1878
|
+
display: flex;
|
|
1879
|
+
align-items: center;
|
|
1880
|
+
gap: 4px;
|
|
1881
|
+
font-size: 0.6875rem;
|
|
1882
|
+
color: var(--text-muted);
|
|
1883
|
+
}
|
|
1884
|
+
.result-meta-item svg { width: 12px; height: 12px; flex-shrink: 0; }
|
|
1885
|
+
.result-meta-item span {
|
|
1886
|
+
font-family: 'GeistMono', monospace;
|
|
1887
|
+
font-size: 0.6875rem;
|
|
1888
|
+
}
|
|
1889
|
+
.result-card-details-toggle {
|
|
1890
|
+
display: flex;
|
|
1891
|
+
align-items: center;
|
|
1892
|
+
gap: 6px;
|
|
1893
|
+
padding: 8px 0 0 42px;
|
|
1894
|
+
margin-top: 8px;
|
|
1895
|
+
border-top: 1px solid var(--border-color);
|
|
1896
|
+
cursor: pointer;
|
|
1897
|
+
font-size: 0.75rem;
|
|
1898
|
+
color: var(--text-muted);
|
|
1899
|
+
user-select: none;
|
|
1900
|
+
}
|
|
1901
|
+
.result-card-details-toggle:hover { color: var(--text-secondary); }
|
|
1902
|
+
.result-card-details-toggle .chevron {
|
|
1903
|
+
transition: transform 0.2s;
|
|
1904
|
+
font-size: 0.625rem;
|
|
1905
|
+
}
|
|
1906
|
+
.result-card-details-toggle.expanded .chevron { transform: rotate(90deg); }
|
|
1907
|
+
.result-card-details-content {
|
|
1908
|
+
padding: 8px 0 4px 42px;
|
|
1909
|
+
font-size: 0.8125rem;
|
|
1910
|
+
color: var(--text-secondary);
|
|
1911
|
+
line-height: 1.5;
|
|
1912
|
+
white-space: pre-wrap;
|
|
1913
|
+
font-family: 'GeistMono', monospace;
|
|
1914
|
+
max-height: 200px;
|
|
1915
|
+
overflow-y: auto;
|
|
1916
|
+
display: none;
|
|
1917
|
+
}
|
|
1918
|
+
.result-card-details-content.visible { display: block; }
|
|
1919
|
+
.results-empty {
|
|
1920
|
+
text-align: center;
|
|
1921
|
+
padding: 56px 24px 48px;
|
|
1922
|
+
}
|
|
1923
|
+
.results-empty-icon {
|
|
1924
|
+
display: inline-flex;
|
|
1925
|
+
align-items: center;
|
|
1926
|
+
justify-content: center;
|
|
1927
|
+
width: 64px;
|
|
1928
|
+
height: 64px;
|
|
1929
|
+
margin-bottom: 20px;
|
|
1930
|
+
background: var(--bg-secondary);
|
|
1931
|
+
border-radius: 16px;
|
|
1932
|
+
color: var(--text-muted);
|
|
1933
|
+
}
|
|
1934
|
+
.results-empty-icon svg { width: 28px; height: 28px; }
|
|
1935
|
+
.results-empty-title {
|
|
1936
|
+
font-size: 0.9375rem;
|
|
1937
|
+
font-weight: 600;
|
|
1938
|
+
color: var(--text-primary);
|
|
1939
|
+
margin-bottom: 6px;
|
|
1940
|
+
}
|
|
1941
|
+
.results-empty-subtitle {
|
|
1942
|
+
font-size: 0.8125rem;
|
|
1943
|
+
color: var(--text-muted);
|
|
1944
|
+
line-height: 1.5;
|
|
1945
|
+
max-width: 340px;
|
|
1946
|
+
margin: 0 auto;
|
|
1947
|
+
}
|
|
1948
|
+
.btn-add-result {
|
|
1949
|
+
padding: 4px 12px;
|
|
1950
|
+
font-size: 0.75rem;
|
|
1951
|
+
font-weight: 500;
|
|
1952
|
+
background: var(--accent);
|
|
1953
|
+
color: #fff;
|
|
1954
|
+
border: 1px solid var(--accent);
|
|
1955
|
+
border-radius: 6px;
|
|
1956
|
+
cursor: pointer;
|
|
1957
|
+
font-family: inherit;
|
|
1958
|
+
transition: all 0.15s;
|
|
1959
|
+
}
|
|
1960
|
+
.btn-add-result:hover { background: #15692f; }
|
|
1961
|
+
|
|
1962
|
+
.result-form-grid {
|
|
1963
|
+
display: grid;
|
|
1964
|
+
grid-template-columns: 1fr 1fr;
|
|
1965
|
+
gap: 10px;
|
|
1966
|
+
margin-top: 10px;
|
|
1967
|
+
}
|
|
1968
|
+
.result-field-full { grid-column: 1 / -1; }
|
|
1969
|
+
.result-label {
|
|
1970
|
+
display: block;
|
|
1971
|
+
margin-bottom: 4px;
|
|
1972
|
+
font-size: 12px;
|
|
1973
|
+
color: var(--text-muted);
|
|
1974
|
+
font-weight: 600;
|
|
1975
|
+
}
|
|
1976
|
+
.result-input,
|
|
1977
|
+
.result-textarea {
|
|
1978
|
+
width: 100%;
|
|
1979
|
+
border: 1px solid var(--border-color);
|
|
1980
|
+
border-radius: 6px;
|
|
1981
|
+
padding: 8px 10px;
|
|
1982
|
+
background: var(--card-bg);
|
|
1983
|
+
color: var(--text-primary);
|
|
1984
|
+
font-size: 13px;
|
|
1985
|
+
font-family: 'GeistMono', monospace;
|
|
1986
|
+
}
|
|
1987
|
+
.result-textarea {
|
|
1988
|
+
min-height: 90px;
|
|
1989
|
+
resize: vertical;
|
|
1990
|
+
}
|
|
1991
|
+
.result-input:focus,
|
|
1992
|
+
.result-textarea:focus {
|
|
1993
|
+
outline: none;
|
|
1994
|
+
border-color: var(--accent);
|
|
1995
|
+
}
|
|
1288
1996
|
.slurm-summary {
|
|
1289
1997
|
display: flex;
|
|
1290
1998
|
gap: 12px;
|
|
@@ -1312,9 +2020,9 @@
|
|
|
1312
2020
|
margin-top: 2px;
|
|
1313
2021
|
}
|
|
1314
2022
|
.slurm-stat-pending .slurm-stat-value { color: #f59e0b; }
|
|
1315
|
-
.slurm-stat-running .slurm-stat-value { color: #
|
|
2023
|
+
.slurm-stat-running .slurm-stat-value { color: #16a34a; }
|
|
1316
2024
|
.slurm-stat-completed .slurm-stat-value { color: #60a5fa; }
|
|
1317
|
-
.slurm-stat-failed .slurm-stat-value { color: #
|
|
2025
|
+
.slurm-stat-failed .slurm-stat-value { color: #dc2626; }
|
|
1318
2026
|
.slurm-stat-other .slurm-stat-value { color: var(--muted); }
|
|
1319
2027
|
|
|
1320
2028
|
.slurm-filters {
|
|
@@ -1371,14 +2079,14 @@
|
|
|
1371
2079
|
text-transform: uppercase;
|
|
1372
2080
|
}
|
|
1373
2081
|
.slurm-state-PENDING { background: rgba(245,158,11,0.15); color: #f59e0b; }
|
|
1374
|
-
.slurm-state-RUNNING { background: rgba(
|
|
1375
|
-
.slurm-state-COMPLETING { background: rgba(
|
|
2082
|
+
.slurm-state-RUNNING { background: rgba(22,163,74,0.15); color: #16a34a; }
|
|
2083
|
+
.slurm-state-COMPLETING { background: rgba(22,163,74,0.1); color: #86efac; }
|
|
1376
2084
|
.slurm-state-COMPLETED { background: rgba(96,165,250,0.15); color: #60a5fa; }
|
|
1377
|
-
.slurm-state-FAILED { background: rgba(239,68,68,0.15); color: #
|
|
2085
|
+
.slurm-state-FAILED { background: rgba(239,68,68,0.15); color: #dc2626; }
|
|
1378
2086
|
.slurm-state-CANCELLED { background: rgba(156,163,175,0.15); color: #9ca3af; }
|
|
1379
2087
|
.slurm-state-TIMEOUT { background: rgba(249,115,22,0.15); color: #f97316; }
|
|
1380
|
-
.slurm-state-NODE_FAIL { background: rgba(239,68,68,0.15); color: #
|
|
1381
|
-
.slurm-state-OUT_OF_MEMORY { background: rgba(239,68,68,0.15); color: #
|
|
2088
|
+
.slurm-state-NODE_FAIL { background: rgba(239,68,68,0.15); color: #dc2626; }
|
|
2089
|
+
.slurm-state-OUT_OF_MEMORY { background: rgba(239,68,68,0.15); color: #dc2626; }
|
|
1382
2090
|
.slurm-state-UNKNOWN { background: rgba(156,163,175,0.1); color: #9ca3af; }
|
|
1383
2091
|
|
|
1384
2092
|
.slurm-output-btn {
|
|
@@ -1393,7 +2101,7 @@
|
|
|
1393
2101
|
margin-right: 4px;
|
|
1394
2102
|
}
|
|
1395
2103
|
.slurm-output-btn:hover { background: rgba(255,255,255,0.05); }
|
|
1396
|
-
.slurm-output-btn.has-content { color: #
|
|
2104
|
+
.slurm-output-btn.has-content { color: #16a34a; border-color: #16a34a; }
|
|
1397
2105
|
.slurm-output-btn.is-empty { color: var(--muted); }
|
|
1398
2106
|
.slurm-output-btn.not-found { color: var(--muted); opacity: 0.5; cursor: default; }
|
|
1399
2107
|
|
|
@@ -1420,7 +2128,7 @@
|
|
|
1420
2128
|
padding: 2px 8px;
|
|
1421
2129
|
cursor: pointer;
|
|
1422
2130
|
font-size: 11px;
|
|
1423
|
-
color: #
|
|
2131
|
+
color: #dc2626;
|
|
1424
2132
|
}
|
|
1425
2133
|
.slurm-cancel-btn:hover { background: rgba(239,68,68,0.1); }
|
|
1426
2134
|
.slurm-notes-cell { max-width:200px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
@@ -1434,7 +2142,7 @@
|
|
|
1434
2142
|
width: 6px;
|
|
1435
2143
|
height: 6px;
|
|
1436
2144
|
border-radius: 50%;
|
|
1437
|
-
background: #
|
|
2145
|
+
background: #16a34a;
|
|
1438
2146
|
margin-right: 4px;
|
|
1439
2147
|
animation: pulse 1.5s ease-in-out infinite;
|
|
1440
2148
|
}
|
|
@@ -1445,7 +2153,10 @@
|
|
|
1445
2153
|
.header { flex-direction: column; gap: 8px; }
|
|
1446
2154
|
.session-card-body { grid-template-columns: 1fr; }
|
|
1447
2155
|
.instructions-modal { width: 100%; max-height: 94vh; }
|
|
2156
|
+
#instructionsModal .instructions-modal { width: 100vw; max-height: 100vh; }
|
|
1448
2157
|
.instructions-editor { height: 48vh; min-height: 240px; }
|
|
2158
|
+
.result-card-meta { flex-direction: column; align-items: flex-start; }
|
|
2159
|
+
.result-form-grid { grid-template-columns: 1fr; }
|
|
1449
2160
|
}
|
|
1450
2161
|
/* ── Enterprise: locked fields ─────────────── */
|
|
1451
2162
|
.field-locked label::before { content: '\1F512 '; font-size: 0.75em; }
|
|
@@ -1455,7 +2166,8 @@
|
|
|
1455
2166
|
.field-locked .add-row { display: none; }
|
|
1456
2167
|
.list-item.locked { opacity: 0.7; }
|
|
1457
2168
|
.list-item.locked .remove-btn { display: none; }
|
|
1458
|
-
.list-item.locked::after { content: 'admin'; font-size: 0.65rem; color: var(--text-muted); margin-left: 8px; }
|
|
2169
|
+
.list-item.locked::after { content: 'set by admin'; font-size: 0.65rem; color: var(--text-muted); margin-left: 8px; }
|
|
2170
|
+
.set-by-admin-note { font-size: 0.7rem; color: var(--text-muted); font-weight: 500; margin-left: 6px; }
|
|
1459
2171
|
.enterprise-badge { font-size: 0.75rem; color: var(--text-muted); font-weight: 400; margin-left: 8px; }
|
|
1460
2172
|
.constraint-hint { font-size: 0.7rem; color: var(--text-muted); margin-left: 4px; }
|
|
1461
2173
|
.net-mode-pill.locked { opacity: 0.5; pointer-events: none; }
|
|
@@ -1471,7 +2183,7 @@
|
|
|
1471
2183
|
color: #92400e;
|
|
1472
2184
|
font-size: 0.8rem;
|
|
1473
2185
|
}
|
|
1474
|
-
.policy-editor { width: 100%; min-height: 300px; font-family: '
|
|
2186
|
+
.policy-editor { width: 100%; min-height: 300px; font-family: 'GeistMono', monospace; font-size: 0.8rem; padding: 12px; border: 1px solid var(--border-color); border-radius: var(--radius-sm); background: var(--bg-secondary); resize: vertical; }
|
|
1475
2187
|
.admin-table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
|
|
1476
2188
|
.admin-table th, .admin-table td { text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--border-color); }
|
|
1477
2189
|
.admin-table th { font-weight: 600; color: var(--text-secondary); }
|
|
@@ -1483,6 +2195,209 @@
|
|
|
1483
2195
|
.license-days.green { color: var(--green); }
|
|
1484
2196
|
.license-days.yellow { color: var(--yellow); }
|
|
1485
2197
|
.license-days.red { color: var(--red); }
|
|
2198
|
+
|
|
2199
|
+
/* ── MCP Servers ─────────────────────────── */
|
|
2200
|
+
.mcp-server-card {
|
|
2201
|
+
background: var(--card-bg);
|
|
2202
|
+
border: 1px solid var(--border-color);
|
|
2203
|
+
border-radius: var(--radius-sm);
|
|
2204
|
+
padding: 18px 20px;
|
|
2205
|
+
margin-bottom: 12px;
|
|
2206
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
2207
|
+
}
|
|
2208
|
+
.mcp-server-card:hover {
|
|
2209
|
+
border-color: #B8CBB8;
|
|
2210
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
|
2211
|
+
}
|
|
2212
|
+
.mcp-server-header {
|
|
2213
|
+
display: flex;
|
|
2214
|
+
align-items: center;
|
|
2215
|
+
gap: 10px;
|
|
2216
|
+
margin-bottom: 10px;
|
|
2217
|
+
}
|
|
2218
|
+
.mcp-server-icon {
|
|
2219
|
+
display: flex;
|
|
2220
|
+
align-items: center;
|
|
2221
|
+
justify-content: center;
|
|
2222
|
+
width: 32px;
|
|
2223
|
+
height: 32px;
|
|
2224
|
+
border-radius: 8px;
|
|
2225
|
+
background: var(--bg-secondary);
|
|
2226
|
+
color: var(--text-muted);
|
|
2227
|
+
flex-shrink: 0;
|
|
2228
|
+
}
|
|
2229
|
+
.mcp-server-icon svg { width: 16px; height: 16px; }
|
|
2230
|
+
.mcp-server-name {
|
|
2231
|
+
font-size: 0.9375rem;
|
|
2232
|
+
font-weight: 600;
|
|
2233
|
+
color: var(--text-primary);
|
|
2234
|
+
}
|
|
2235
|
+
.mcp-server-status {
|
|
2236
|
+
display: inline-block;
|
|
2237
|
+
font-size: 0.6875rem;
|
|
2238
|
+
font-weight: 600;
|
|
2239
|
+
padding: 2px 8px;
|
|
2240
|
+
border-radius: 9px;
|
|
2241
|
+
margin-left: auto;
|
|
2242
|
+
text-transform: uppercase;
|
|
2243
|
+
letter-spacing: 0.03em;
|
|
2244
|
+
}
|
|
2245
|
+
.mcp-server-status.active {
|
|
2246
|
+
background: rgba(22,163,74,0.15);
|
|
2247
|
+
color: #16a34a;
|
|
2248
|
+
}
|
|
2249
|
+
.mcp-server-status.inactive {
|
|
2250
|
+
background: rgba(163,163,163,0.15);
|
|
2251
|
+
color: var(--text-muted);
|
|
2252
|
+
}
|
|
2253
|
+
.mcp-server-status.partial {
|
|
2254
|
+
background: rgba(245,158,11,0.18);
|
|
2255
|
+
color: #92400e;
|
|
2256
|
+
}
|
|
2257
|
+
.mcp-server-states {
|
|
2258
|
+
display: flex;
|
|
2259
|
+
align-items: center;
|
|
2260
|
+
gap: 6px;
|
|
2261
|
+
margin-bottom: 10px;
|
|
2262
|
+
flex-wrap: wrap;
|
|
2263
|
+
}
|
|
2264
|
+
.mcp-state-pill {
|
|
2265
|
+
display: inline-flex;
|
|
2266
|
+
align-items: center;
|
|
2267
|
+
gap: 4px;
|
|
2268
|
+
font-size: 0.6875rem;
|
|
2269
|
+
font-weight: 600;
|
|
2270
|
+
border-radius: 999px;
|
|
2271
|
+
padding: 2px 8px;
|
|
2272
|
+
border: 1px solid var(--border-color);
|
|
2273
|
+
color: var(--text-muted);
|
|
2274
|
+
background: var(--bg-secondary);
|
|
2275
|
+
white-space: nowrap;
|
|
2276
|
+
}
|
|
2277
|
+
.mcp-state-pill.on {
|
|
2278
|
+
border-color: #bbf7d0;
|
|
2279
|
+
color: #166534;
|
|
2280
|
+
background: #f0fdf4;
|
|
2281
|
+
}
|
|
2282
|
+
.mcp-state-pill.off {
|
|
2283
|
+
border-color: #e5e7eb;
|
|
2284
|
+
color: #6b7280;
|
|
2285
|
+
background: #f9fafb;
|
|
2286
|
+
}
|
|
2287
|
+
.mcp-server-reason {
|
|
2288
|
+
font-size: 0.75rem;
|
|
2289
|
+
color: #92400e;
|
|
2290
|
+
background: #fffbeb;
|
|
2291
|
+
border: 1px solid #fef3c7;
|
|
2292
|
+
border-radius: 6px;
|
|
2293
|
+
padding: 6px 10px;
|
|
2294
|
+
margin-bottom: 12px;
|
|
2295
|
+
}
|
|
2296
|
+
.mcp-server-desc {
|
|
2297
|
+
font-size: 0.8125rem;
|
|
2298
|
+
color: var(--text-secondary);
|
|
2299
|
+
margin-bottom: 12px;
|
|
2300
|
+
}
|
|
2301
|
+
.mcp-server-command {
|
|
2302
|
+
font-size: 0.75rem;
|
|
2303
|
+
font-family: 'GeistMono', monospace;
|
|
2304
|
+
color: var(--text-muted);
|
|
2305
|
+
background: var(--bg-secondary);
|
|
2306
|
+
padding: 6px 10px;
|
|
2307
|
+
border-radius: 6px;
|
|
2308
|
+
margin-bottom: 14px;
|
|
2309
|
+
overflow-x: auto;
|
|
2310
|
+
white-space: nowrap;
|
|
2311
|
+
}
|
|
2312
|
+
.mcp-tools-label {
|
|
2313
|
+
font-size: 0.75rem;
|
|
2314
|
+
font-weight: 600;
|
|
2315
|
+
color: var(--text-secondary);
|
|
2316
|
+
text-transform: uppercase;
|
|
2317
|
+
letter-spacing: 0.05em;
|
|
2318
|
+
margin-bottom: 8px;
|
|
2319
|
+
}
|
|
2320
|
+
.mcp-tool-list { display: grid; gap: 0; }
|
|
2321
|
+
.mcp-tool-item {
|
|
2322
|
+
display: block;
|
|
2323
|
+
font-size: 0.8125rem;
|
|
2324
|
+
padding: 7px 0;
|
|
2325
|
+
border-bottom: 1px solid var(--border-color);
|
|
2326
|
+
}
|
|
2327
|
+
.mcp-tool-item:last-child { border-bottom: none; }
|
|
2328
|
+
.mcp-tool-main {
|
|
2329
|
+
display: flex;
|
|
2330
|
+
align-items: center;
|
|
2331
|
+
gap: 8px;
|
|
2332
|
+
margin-bottom: 2px;
|
|
2333
|
+
}
|
|
2334
|
+
.mcp-tool-name {
|
|
2335
|
+
font-family: 'GeistMono', monospace;
|
|
2336
|
+
font-size: 0.75rem;
|
|
2337
|
+
font-weight: 500;
|
|
2338
|
+
color: var(--text-primary);
|
|
2339
|
+
background: var(--bg-secondary);
|
|
2340
|
+
padding: 1px 6px;
|
|
2341
|
+
border-radius: 4px;
|
|
2342
|
+
white-space: nowrap;
|
|
2343
|
+
flex-shrink: 0;
|
|
2344
|
+
}
|
|
2345
|
+
.mcp-tool-title {
|
|
2346
|
+
color: var(--text-secondary);
|
|
2347
|
+
font-size: 0.75rem;
|
|
2348
|
+
font-weight: 500;
|
|
2349
|
+
}
|
|
2350
|
+
.mcp-tool-desc {
|
|
2351
|
+
color: var(--text-muted);
|
|
2352
|
+
font-size: 0.8125rem;
|
|
2353
|
+
}
|
|
2354
|
+
.mcp-tool-example {
|
|
2355
|
+
margin-top: 3px;
|
|
2356
|
+
color: var(--text-secondary);
|
|
2357
|
+
font-size: 0.75rem;
|
|
2358
|
+
font-style: italic;
|
|
2359
|
+
}
|
|
2360
|
+
.mcp-empty {
|
|
2361
|
+
text-align: center;
|
|
2362
|
+
padding: 56px 24px 48px;
|
|
2363
|
+
}
|
|
2364
|
+
.mcp-empty-icon {
|
|
2365
|
+
display: inline-flex;
|
|
2366
|
+
align-items: center;
|
|
2367
|
+
justify-content: center;
|
|
2368
|
+
width: 64px;
|
|
2369
|
+
height: 64px;
|
|
2370
|
+
margin-bottom: 20px;
|
|
2371
|
+
background: var(--bg-secondary);
|
|
2372
|
+
border-radius: 16px;
|
|
2373
|
+
color: var(--text-muted);
|
|
2374
|
+
}
|
|
2375
|
+
.mcp-empty-icon svg { width: 28px; height: 28px; }
|
|
2376
|
+
.mcp-empty-title {
|
|
2377
|
+
font-size: 0.9375rem;
|
|
2378
|
+
font-weight: 600;
|
|
2379
|
+
color: var(--text-primary);
|
|
2380
|
+
margin-bottom: 6px;
|
|
2381
|
+
}
|
|
2382
|
+
.mcp-empty-subtitle {
|
|
2383
|
+
font-size: 0.8125rem;
|
|
2384
|
+
color: var(--text-muted);
|
|
2385
|
+
line-height: 1.5;
|
|
2386
|
+
max-width: 380px;
|
|
2387
|
+
margin: 0 auto;
|
|
2388
|
+
}
|
|
2389
|
+
.mcp-badge {
|
|
2390
|
+
display: inline-block;
|
|
2391
|
+
background: var(--accent);
|
|
2392
|
+
color: #fff;
|
|
2393
|
+
font-size: 11px;
|
|
2394
|
+
font-weight: 700;
|
|
2395
|
+
padding: 1px 6px;
|
|
2396
|
+
border-radius: 9px;
|
|
2397
|
+
margin-left: 4px;
|
|
2398
|
+
min-width: 16px;
|
|
2399
|
+
text-align: center;
|
|
2400
|
+
}
|
|
1486
2401
|
</style>
|
|
1487
2402
|
</head>
|
|
1488
2403
|
<body>
|
|
@@ -1495,184 +2410,210 @@
|
|
|
1495
2410
|
<div class="container">
|
|
1496
2411
|
<div class="tabs">
|
|
1497
2412
|
<button class="tab active" data-tab="sessions">Dashboard</button>
|
|
1498
|
-
<button class="tab" data-tab="runtime">Runtime</button>
|
|
1499
|
-
<button class="tab" data-tab="network">Network</button>
|
|
1500
|
-
<button class="tab" data-tab="filesystem">Filesystem</button>
|
|
1501
2413
|
<button class="tab" data-tab="datasets">Datasets</button>
|
|
1502
|
-
<button class="tab" data-tab="
|
|
1503
|
-
<button class="tab" data-tab="
|
|
1504
|
-
<button class="tab" data-tab="
|
|
1505
|
-
<button class="tab" data-tab="slurm" id="slurmTab" style="display:none">SLURM Jobs <span class="slurm-badge" id="slurmBadge" style="display:none"></span></button>
|
|
2414
|
+
<button class="tab" data-tab="results">Results</button>
|
|
2415
|
+
<button class="tab" data-tab="jobs">Jobs <span class="slurm-badge" id="slurmBadge" style="display:none"></span></button>
|
|
2416
|
+
<button class="tab" data-tab="settings">Settings <span class="mcp-badge" id="mcpBadge" style="display:none"></span></button>
|
|
1506
2417
|
</div>
|
|
1507
2418
|
|
|
1508
|
-
<!--
|
|
1509
|
-
<div class="tab-panel" id="panel-
|
|
1510
|
-
<div class="
|
|
1511
|
-
<
|
|
1512
|
-
<
|
|
1513
|
-
<
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
2419
|
+
<!-- Settings (sub-tabs) -->
|
|
2420
|
+
<div class="tab-panel" id="panel-settings">
|
|
2421
|
+
<div class="sub-tabs">
|
|
2422
|
+
<button class="sub-tab active" data-subtab="runtime">Runtime</button>
|
|
2423
|
+
<button class="sub-tab" data-subtab="network">Network</button>
|
|
2424
|
+
<button class="sub-tab" data-subtab="filesystem">Filesystem</button>
|
|
2425
|
+
<button class="sub-tab" data-subtab="commands">Commands</button>
|
|
2426
|
+
<button class="sub-tab" data-subtab="audit">Audit & Logs</button>
|
|
2427
|
+
<button class="sub-tab" data-subtab="mcp">MCP Servers</button>
|
|
2428
|
+
</div>
|
|
2429
|
+
|
|
2430
|
+
<!-- Runtime sub-panel -->
|
|
2431
|
+
<div class="sub-panel active" id="subpanel-runtime">
|
|
2432
|
+
<div class="card">
|
|
2433
|
+
<h3>Runtime & Image</h3>
|
|
2434
|
+
<p class="card-description">Apptainer (primary) or Podman runtime and base image for sandbox sessions.</p>
|
|
2435
|
+
<div class="field-row">
|
|
2436
|
+
<div class="field">
|
|
2437
|
+
<label for="runtime">Runtime</label>
|
|
2438
|
+
<select id="runtime">
|
|
2439
|
+
<option value="auto">auto (prefer apptainer, then podman)</option>
|
|
2440
|
+
<option value="apptainer">apptainer</option>
|
|
2441
|
+
<option value="podman">podman</option>
|
|
2442
|
+
</select>
|
|
2443
|
+
</div>
|
|
2444
|
+
<div class="field">
|
|
2445
|
+
<label for="timeout">Session Timeout (hours)</label>
|
|
2446
|
+
<input type="number" id="timeout" min="0" step="1" value="8">
|
|
2447
|
+
</div>
|
|
1521
2448
|
</div>
|
|
1522
2449
|
<div class="field">
|
|
1523
|
-
<label for="
|
|
1524
|
-
<input type="
|
|
2450
|
+
<label for="image">Container Image</label>
|
|
2451
|
+
<input type="text" id="image" placeholder="docker.io/library/node:20-slim">
|
|
1525
2452
|
</div>
|
|
1526
2453
|
</div>
|
|
1527
|
-
<div class="field">
|
|
1528
|
-
<label for="image">Container Image</label>
|
|
1529
|
-
<input type="text" id="image" placeholder="docker.io/library/node:20-slim">
|
|
1530
|
-
</div>
|
|
1531
2454
|
</div>
|
|
1532
|
-
</div>
|
|
1533
2455
|
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
</
|
|
1546
|
-
</div>
|
|
1547
|
-
</div>
|
|
1548
|
-
<div class="card">
|
|
1549
|
-
<h3>Allowed Domains</h3>
|
|
1550
|
-
<p class="card-description">Domains agents can reach in filtered mode.</p>
|
|
1551
|
-
<div class="list-editor" id="domainsList"></div>
|
|
1552
|
-
<div class="list-add">
|
|
1553
|
-
<input type="text" id="domainInput" placeholder="api.example.com" onkeydown="if(event.key==='Enter'){addDomain()}">
|
|
1554
|
-
<button class="btn-add" onclick="addDomain()">Add</button>
|
|
1555
|
-
</div>
|
|
1556
|
-
</div>
|
|
1557
|
-
</div>
|
|
1558
|
-
|
|
1559
|
-
<!-- Filesystem -->
|
|
1560
|
-
<div class="tab-panel" id="panel-filesystem">
|
|
1561
|
-
<div class="card">
|
|
1562
|
-
<h3>Blocked Patterns</h3>
|
|
1563
|
-
<p class="card-description">Glob patterns hidden from the sandbox via empty overlays.</p>
|
|
1564
|
-
<div class="list-editor" id="blockedList"></div>
|
|
1565
|
-
<div class="list-add">
|
|
1566
|
-
<input type="text" id="blockedInput" placeholder="**/.ssh" onkeydown="if(event.key==='Enter'){addBlocked()}">
|
|
1567
|
-
<button class="btn-add" onclick="addBlocked()">Add</button>
|
|
2456
|
+
<!-- Network sub-panel -->
|
|
2457
|
+
<div class="sub-panel" id="subpanel-network">
|
|
2458
|
+
<div class="card">
|
|
2459
|
+
<h3>Network Policy</h3>
|
|
2460
|
+
<p class="card-description">Control how sandboxed agents access the network.</p>
|
|
2461
|
+
<div class="field">
|
|
2462
|
+
<label for="networkMode">Mode</label>
|
|
2463
|
+
<select id="networkMode">
|
|
2464
|
+
<option value="host">host (full network access)</option>
|
|
2465
|
+
<option value="filtered">filtered (proxy + domain allowlist)</option>
|
|
2466
|
+
</select>
|
|
2467
|
+
</div>
|
|
1568
2468
|
</div>
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
<option value="ro">ro</option>
|
|
1578
|
-
<option value="rw">rw</option>
|
|
1579
|
-
</select>
|
|
1580
|
-
<button class="btn-add" onclick="addMount()">Add</button>
|
|
2469
|
+
<div class="card">
|
|
2470
|
+
<h3>Allowed Domains</h3>
|
|
2471
|
+
<p class="card-description">Domains agents can reach in filtered mode.</p>
|
|
2472
|
+
<div class="list-editor" id="domainsList"></div>
|
|
2473
|
+
<div class="list-add">
|
|
2474
|
+
<input type="text" id="domainInput" placeholder="api.example.com" onkeydown="if(event.key==='Enter'){addDomain()}">
|
|
2475
|
+
<button class="btn-add" onclick="addDomain()">Add</button>
|
|
2476
|
+
</div>
|
|
1581
2477
|
</div>
|
|
1582
2478
|
</div>
|
|
1583
|
-
</div>
|
|
1584
2479
|
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
<
|
|
1594
|
-
<input type="text" id="datasetNameInput" placeholder="Name (auto)" style="flex:1">
|
|
2480
|
+
<!-- Filesystem sub-panel -->
|
|
2481
|
+
<div class="sub-panel" id="subpanel-filesystem">
|
|
2482
|
+
<div class="card">
|
|
2483
|
+
<h3>Blocked Patterns</h3>
|
|
2484
|
+
<p class="card-description">Glob patterns hidden from the sandbox via empty overlays.</p>
|
|
2485
|
+
<div class="list-editor" id="blockedList"></div>
|
|
2486
|
+
<div class="list-add">
|
|
2487
|
+
<input type="text" id="blockedInput" placeholder="**/.ssh" onkeydown="if(event.key==='Enter'){addBlocked()}">
|
|
2488
|
+
<button class="btn-add" onclick="addBlocked()">Add</button>
|
|
1595
2489
|
</div>
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
2490
|
+
</div>
|
|
2491
|
+
<div class="card">
|
|
2492
|
+
<h3>Extra Mount Paths</h3>
|
|
2493
|
+
<p class="card-description">Additional host paths to mount into the sandbox.</p>
|
|
2494
|
+
<div class="list-editor" id="mountsList"></div>
|
|
2495
|
+
<div class="list-add">
|
|
2496
|
+
<input type="text" id="mountPathInput" placeholder="/data/shared" onkeydown="if(event.key==='Enter'){addMount()}">
|
|
2497
|
+
<select id="mountModeInput">
|
|
2498
|
+
<option value="ro">ro</option>
|
|
1600
2499
|
<option value="rw">rw</option>
|
|
1601
2500
|
</select>
|
|
1602
|
-
<button class="btn-add" onclick="
|
|
2501
|
+
<button class="btn-add" onclick="addMount()">Add</button>
|
|
1603
2502
|
</div>
|
|
1604
2503
|
</div>
|
|
1605
2504
|
</div>
|
|
1606
|
-
</div>
|
|
1607
2505
|
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
2506
|
+
<!-- Commands sub-panel -->
|
|
2507
|
+
<div class="sub-panel" id="subpanel-commands">
|
|
2508
|
+
<div class="card">
|
|
2509
|
+
<h3>Command Blacklist</h3>
|
|
2510
|
+
<p class="card-description">Commands blocked inside the sandbox. Agents get a clear error instead of silent failure.</p>
|
|
2511
|
+
<div class="list-editor" id="blacklistList"></div>
|
|
2512
|
+
<div class="list-add">
|
|
2513
|
+
<input type="text" id="blacklistInput" placeholder="ssh" onkeydown="if(event.key==='Enter'){addBlacklist()}">
|
|
2514
|
+
<button class="btn-add" onclick="addBlacklist()">Add</button>
|
|
2515
|
+
</div>
|
|
1617
2516
|
</div>
|
|
1618
2517
|
</div>
|
|
1619
|
-
</div>
|
|
1620
2518
|
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
2519
|
+
<!-- Audit & Logs sub-panel (merged) -->
|
|
2520
|
+
<div class="sub-panel" id="subpanel-audit">
|
|
2521
|
+
<div class="card">
|
|
2522
|
+
<h3>Audit Logging</h3>
|
|
2523
|
+
<p class="card-description">Record session events to structured JSONL files.</p>
|
|
2524
|
+
<div class="field">
|
|
2525
|
+
<div class="toggle-wrap">
|
|
2526
|
+
<input type="checkbox" class="toggle" id="auditEnabled">
|
|
2527
|
+
<span class="toggle-label">Enable audit logging</span>
|
|
2528
|
+
</div>
|
|
2529
|
+
</div>
|
|
2530
|
+
<div class="field" style="margin-top: 16px">
|
|
2531
|
+
<label for="logDir">Log Directory</label>
|
|
2532
|
+
<input type="text" id="logDir" placeholder="~/.labgate/logs">
|
|
1630
2533
|
</div>
|
|
1631
2534
|
</div>
|
|
1632
|
-
<div class="
|
|
1633
|
-
<
|
|
1634
|
-
<
|
|
2535
|
+
<div class="card">
|
|
2536
|
+
<h3>Recent Audit Logs <button class="refresh-btn" onclick="loadLogs()">Refresh</button></h3>
|
|
2537
|
+
<div id="logsContent"><div class="empty-state">Loading...</div></div>
|
|
1635
2538
|
</div>
|
|
1636
2539
|
</div>
|
|
1637
|
-
</div>
|
|
1638
2540
|
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
<div class="net-mode-pills">
|
|
1645
|
-
<button class="net-mode-pill" data-mode="none" onclick="switchNetMode('none')">None</button>
|
|
1646
|
-
<button class="net-mode-pill" data-mode="filtered" onclick="switchNetMode('filtered')">Filtered</button>
|
|
1647
|
-
<button class="net-mode-pill" data-mode="host" onclick="switchNetMode('host')">Host</button>
|
|
2541
|
+
<!-- MCP Servers sub-panel -->
|
|
2542
|
+
<div class="sub-panel" id="subpanel-mcp">
|
|
2543
|
+
<div style="display:flex;align-items:baseline;justify-content:space-between;margin-bottom:16px">
|
|
2544
|
+
<p class="card-description" style="margin:0">MCP servers registered for the AI agent. These provide specialized tools the agent can use during sessions.</p>
|
|
2545
|
+
<button class="refresh-btn" onclick="loadMcpServers()">Refresh</button>
|
|
1648
2546
|
</div>
|
|
2547
|
+
<div id="mcpContent"><div class="empty-state">Loading...</div></div>
|
|
1649
2548
|
</div>
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
2549
|
+
|
|
2550
|
+
</div><!-- /panel-settings -->
|
|
2551
|
+
|
|
2552
|
+
<!-- Datasets -->
|
|
2553
|
+
<div class="tab-panel" id="panel-datasets">
|
|
2554
|
+
<div style="display:flex;align-items:baseline;justify-content:space-between;margin-bottom:16px">
|
|
2555
|
+
<p class="card-description" style="margin:0">Named datasets mounted under <code>/datasets/{name}</code> in the sandbox. The AI agent is told about each dataset by name and description.</p>
|
|
2556
|
+
<span class="dataset-count" id="datasetCount" style="display:none"></span>
|
|
2557
|
+
</div>
|
|
2558
|
+
<div class="dataset-notice" id="datasetNotice">
|
|
2559
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
|
|
2560
|
+
<span class="dataset-notice-text">Save config, then restart active sessions from this page (or run: labgate restart <session-id>).</span>
|
|
2561
|
+
<button class="dataset-notice-btn" id="datasetRestartBtn" style="display:none" onclick="restartOutdatedSessions()">Restart Sessions Now</button>
|
|
2562
|
+
</div>
|
|
2563
|
+
<div id="datasetsList"></div>
|
|
2564
|
+
<div class="dataset-add-form">
|
|
2565
|
+
<div class="form-title">
|
|
2566
|
+
<span class="form-title-icon">
|
|
2567
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
|
2568
|
+
</span>
|
|
2569
|
+
Add a dataset
|
|
1656
2570
|
</div>
|
|
1657
|
-
<div class="
|
|
1658
|
-
<div class="
|
|
1659
|
-
<
|
|
1660
|
-
<
|
|
2571
|
+
<div class="field-grid">
|
|
2572
|
+
<div class="field">
|
|
2573
|
+
<label for="datasetPathInput">Host Path</label>
|
|
2574
|
+
<input type="text" id="datasetPathInput" placeholder="/data/genomes" onkeydown="if(event.key==='Enter'){addDataset()}">
|
|
1661
2575
|
</div>
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
2576
|
+
<div class="field">
|
|
2577
|
+
<label for="datasetNameInput">Name <span style="font-weight:400;color:var(--text-muted)">(auto)</span></label>
|
|
2578
|
+
<input type="text" id="datasetNameInput" placeholder="genomes">
|
|
2579
|
+
</div>
|
|
2580
|
+
<div class="field field-full">
|
|
2581
|
+
<label for="datasetDescInput">Description <span style="font-weight:400;color:var(--text-muted)">(optional)</span></label>
|
|
2582
|
+
<input type="text" id="datasetDescInput" placeholder="Human reference genomes collection">
|
|
1667
2583
|
</div>
|
|
1668
2584
|
</div>
|
|
1669
|
-
<div class="
|
|
1670
|
-
<div class="
|
|
1671
|
-
<
|
|
1672
|
-
<
|
|
2585
|
+
<div class="form-footer">
|
|
2586
|
+
<div class="field" style="margin-bottom:0">
|
|
2587
|
+
<label style="margin-bottom:4px">Access Mode</label>
|
|
2588
|
+
<div class="mode-toggle">
|
|
2589
|
+
<input type="radio" name="datasetMode" id="datasetModeRO" value="ro" checked>
|
|
2590
|
+
<label for="datasetModeRO" class="mode-ro">Read-only</label>
|
|
2591
|
+
<input type="radio" name="datasetMode" id="datasetModeRW" value="rw">
|
|
2592
|
+
<label for="datasetModeRW" class="mode-rw">Read-write</label>
|
|
2593
|
+
</div>
|
|
1673
2594
|
</div>
|
|
2595
|
+
<button class="btn-add-dataset" onclick="addDataset()">Add Dataset</button>
|
|
1674
2596
|
</div>
|
|
1675
2597
|
</div>
|
|
2598
|
+
</div>
|
|
2599
|
+
|
|
2600
|
+
<!-- Results -->
|
|
2601
|
+
<div class="tab-panel" id="panel-results">
|
|
2602
|
+
<div style="display:flex;align-items:baseline;justify-content:space-between;margin-bottom:16px">
|
|
2603
|
+
<p class="card-description" style="margin:0">Structured findings saved by Claude/Codex via LabGate results tools.</p>
|
|
2604
|
+
<button class="refresh-btn" onclick="loadResults()">Refresh</button>
|
|
2605
|
+
</div>
|
|
2606
|
+
<div class="results-toolbar">
|
|
2607
|
+
<input type="text" class="results-search" id="resultsSearchInput" placeholder="Search title, summary, details, tags..." oninput="debounceResultsSearch()">
|
|
2608
|
+
<input type="text" class="results-tag" id="resultsTagInput" placeholder="tag filter (exact)" oninput="debounceResultsSearch()">
|
|
2609
|
+
<input type="text" class="results-tag" id="resultsSourceInput" placeholder="source filter (claude/codex)" oninput="debounceResultsSearch()">
|
|
2610
|
+
<button class="btn-add-result" onclick="openResultEditor()">+ Add Result</button>
|
|
2611
|
+
</div>
|
|
2612
|
+
<div id="resultsContent"><div class="empty-state">Loading...</div></div>
|
|
2613
|
+
</div>
|
|
2614
|
+
|
|
2615
|
+
<!-- Sessions / Dashboard -->
|
|
2616
|
+
<div class="tab-panel active" id="panel-sessions">
|
|
1676
2617
|
<div class="blocked-events-list" id="blockedEventsList"></div>
|
|
1677
2618
|
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:16px">
|
|
1678
2619
|
<div class="sessions-summary" id="sessionsSummary"></div>
|
|
@@ -1681,16 +2622,15 @@
|
|
|
1681
2622
|
<div id="sessionsContent"><div class="empty-state">Loading...</div></div>
|
|
1682
2623
|
</div>
|
|
1683
2624
|
|
|
1684
|
-
<!--
|
|
1685
|
-
<div class="tab-panel" id="panel-
|
|
1686
|
-
<div class="
|
|
1687
|
-
<
|
|
1688
|
-
<div
|
|
2625
|
+
<!-- Jobs panel (SLURM) -->
|
|
2626
|
+
<div class="tab-panel" id="panel-jobs">
|
|
2627
|
+
<div id="jobsEmptyState" class="empty-state" style="padding: 56px 24px">
|
|
2628
|
+
<div style="font-weight: 600; margin-bottom: 6px;">No Job Scheduler</div>
|
|
2629
|
+
<div style="font-size: 0.8125rem; color: var(--text-muted); max-width: 340px; margin: 0 auto;">
|
|
2630
|
+
Enable SLURM integration in Settings to monitor cluster jobs here.
|
|
2631
|
+
</div>
|
|
1689
2632
|
</div>
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
<!-- SLURM Jobs panel -->
|
|
1693
|
-
<div class="tab-panel" id="panel-slurm">
|
|
2633
|
+
<div id="jobsSlurmContent" style="display:none">
|
|
1694
2634
|
<div class="slurm-summary" id="slurmSummary">
|
|
1695
2635
|
<div class="slurm-stat slurm-stat-pending"><span class="slurm-stat-value" id="slurmPending">0</span><span class="slurm-stat-label">Pending</span></div>
|
|
1696
2636
|
<div class="slurm-stat slurm-stat-running"><span class="slurm-stat-value" id="slurmRunning">0</span><span class="slurm-stat-label">Running</span></div>
|
|
@@ -1714,10 +2654,11 @@
|
|
|
1714
2654
|
<div class="card">
|
|
1715
2655
|
<div id="slurmJobsContent"><div class="empty-state">Loading SLURM jobs...</div></div>
|
|
1716
2656
|
</div>
|
|
1717
|
-
|
|
2657
|
+
</div><!-- /jobsSlurmContent -->
|
|
2658
|
+
</div><!-- /panel-jobs -->
|
|
1718
2659
|
|
|
1719
2660
|
<!-- SLURM output modal -->
|
|
1720
|
-
<div class="modal-backdrop" id="slurmOutputModal" onclick="if(event.target===this){closeSlurmOutput()}"
|
|
2661
|
+
<div class="modal-backdrop" id="slurmOutputModal" onclick="if(event.target===this){closeSlurmOutput()}">
|
|
1721
2662
|
<div class="instructions-modal">
|
|
1722
2663
|
<div class="instructions-header">
|
|
1723
2664
|
<div>
|
|
@@ -1738,7 +2679,7 @@
|
|
|
1738
2679
|
</div>
|
|
1739
2680
|
|
|
1740
2681
|
<!-- SLURM notes modal -->
|
|
1741
|
-
<div class="modal-backdrop" id="slurmNotesModal" onclick="if(event.target===this){closeSlurmNotes()}"
|
|
2682
|
+
<div class="modal-backdrop" id="slurmNotesModal" onclick="if(event.target===this){closeSlurmNotes()}">
|
|
1742
2683
|
<div class="instructions-modal" style="max-width:600px">
|
|
1743
2684
|
<div class="instructions-header">
|
|
1744
2685
|
<div>
|
|
@@ -1756,6 +2697,55 @@
|
|
|
1756
2697
|
</div>
|
|
1757
2698
|
</div>
|
|
1758
2699
|
|
|
2700
|
+
<!-- Results editor modal -->
|
|
2701
|
+
<div class="modal-backdrop" id="resultsModal" onclick="if(event.target===this){closeResultEditor()}">
|
|
2702
|
+
<div class="instructions-modal" style="max-width:920px;width:min(920px,94vw)">
|
|
2703
|
+
<div class="instructions-header">
|
|
2704
|
+
<div>
|
|
2705
|
+
<div class="instructions-title" id="resultEditorTitle">Add Result</div>
|
|
2706
|
+
<div class="instructions-subtitle" id="resultEditorSubtitle"></div>
|
|
2707
|
+
</div>
|
|
2708
|
+
<span class="spacer"></span>
|
|
2709
|
+
<button class="instructions-btn-secondary" onclick="closeResultEditor()">Cancel</button>
|
|
2710
|
+
<button class="instructions-btn-primary" onclick="saveResultEditor()">Save</button>
|
|
2711
|
+
</div>
|
|
2712
|
+
<div class="result-form-grid">
|
|
2713
|
+
<div>
|
|
2714
|
+
<label class="result-label" for="resultTitleInput">Title</label>
|
|
2715
|
+
<input class="result-input" id="resultTitleInput" type="text" placeholder="What was achieved?">
|
|
2716
|
+
</div>
|
|
2717
|
+
<div>
|
|
2718
|
+
<label class="result-label" for="resultSourceInputModal">Source</label>
|
|
2719
|
+
<input class="result-input" id="resultSourceInputModal" type="text" placeholder="claude">
|
|
2720
|
+
</div>
|
|
2721
|
+
<div class="result-field-full">
|
|
2722
|
+
<label class="result-label" for="resultSummaryInput">Summary</label>
|
|
2723
|
+
<input class="result-input" id="resultSummaryInput" type="text" placeholder="One-line summary">
|
|
2724
|
+
</div>
|
|
2725
|
+
<div class="result-field-full">
|
|
2726
|
+
<label class="result-label" for="resultDetailsInput">Details</label>
|
|
2727
|
+
<textarea class="result-textarea" id="resultDetailsInput" placeholder="Longer details (optional)"></textarea>
|
|
2728
|
+
</div>
|
|
2729
|
+
<div>
|
|
2730
|
+
<label class="result-label" for="resultTagsInput">Tags (comma-separated)</label>
|
|
2731
|
+
<input class="result-input" id="resultTagsInput" type="text" placeholder="slurm, gpu, benchmark">
|
|
2732
|
+
</div>
|
|
2733
|
+
<div>
|
|
2734
|
+
<label class="result-label" for="resultArtifactsInput">Artifacts (comma-separated paths)</label>
|
|
2735
|
+
<input class="result-input" id="resultArtifactsInput" type="text" placeholder="/work/out/report.md, /work/out/plot.png">
|
|
2736
|
+
</div>
|
|
2737
|
+
<div>
|
|
2738
|
+
<label class="result-label" for="resultSessionIdInput">Session ID</label>
|
|
2739
|
+
<input class="result-input" id="resultSessionIdInput" type="text" placeholder="optional">
|
|
2740
|
+
</div>
|
|
2741
|
+
<div>
|
|
2742
|
+
<label class="result-label" for="resultWorkdirInput">Workdir</label>
|
|
2743
|
+
<input class="result-input" id="resultWorkdirInput" type="text" placeholder="/work">
|
|
2744
|
+
</div>
|
|
2745
|
+
</div>
|
|
2746
|
+
</div>
|
|
2747
|
+
</div>
|
|
2748
|
+
|
|
1759
2749
|
<!-- Admin: Policy (hidden by default, shown for admins) -->
|
|
1760
2750
|
<div class="tab-panel" id="panel-admin-policy" style="display:none">
|
|
1761
2751
|
<div class="card admin-card">
|
|
@@ -1799,6 +2789,10 @@
|
|
|
1799
2789
|
<div class="toast" id="toast"></div>
|
|
1800
2790
|
<div class="modal-backdrop" id="instructionsModal" onclick="if(event.target===this){closeInstructions()}">
|
|
1801
2791
|
<div class="instructions-modal">
|
|
2792
|
+
<div class="sidebar-resize-handle" id="sidebarResizeHandle"></div>
|
|
2793
|
+
<button class="sidebar-collapse-btn" onclick="closeInstructions()" title="Close sidebar">
|
|
2794
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/></svg>
|
|
2795
|
+
</button>
|
|
1802
2796
|
<div class="instructions-header">
|
|
1803
2797
|
<div>
|
|
1804
2798
|
<div class="instructions-title">Session Instructions</div>
|
|
@@ -1843,6 +2837,12 @@ var instructionsState = {
|
|
|
1843
2837
|
template: '',
|
|
1844
2838
|
loading: false
|
|
1845
2839
|
};
|
|
2840
|
+
var mcpAutoRefreshTimer = null;
|
|
2841
|
+
var mcpAutoRegisterInFlight = false;
|
|
2842
|
+
var MCP_AUTO_REFRESH_MS = 12000;
|
|
2843
|
+
var resultsSearchTimeout = null;
|
|
2844
|
+
var resultsCache = [];
|
|
2845
|
+
var resultEditorId = null;
|
|
1846
2846
|
|
|
1847
2847
|
// ── Activity sparkline timeline buffer ───────
|
|
1848
2848
|
var activityTimeline = new Map(); // sessionId → [{status, ts}]
|
|
@@ -1914,12 +2914,37 @@ function renderResourceRow(stats) {
|
|
|
1914
2914
|
// ── Tab navigation ───────────────────────────
|
|
1915
2915
|
document.querySelectorAll('.tab').forEach(function(tab) {
|
|
1916
2916
|
tab.addEventListener('click', function() {
|
|
2917
|
+
var tabName = tab.dataset.tab;
|
|
1917
2918
|
document.querySelectorAll('.tab').forEach(function(t) { t.classList.remove('active'); });
|
|
1918
2919
|
document.querySelectorAll('.tab-panel').forEach(function(p) { p.classList.remove('active'); });
|
|
1919
2920
|
tab.classList.add('active');
|
|
1920
|
-
document.getElementById('panel-' +
|
|
1921
|
-
if (
|
|
1922
|
-
if (
|
|
2921
|
+
document.getElementById('panel-' + tabName).classList.add('active');
|
|
2922
|
+
if (tabName === 'sessions') loadSessions();
|
|
2923
|
+
if (tabName === 'datasets') checkDatasetRestartNeeded();
|
|
2924
|
+
if (tabName === 'results') loadResults();
|
|
2925
|
+
if (tabName === 'jobs' && slurmEnabled) loadSlurmJobs();
|
|
2926
|
+
if (tabName === 'settings') {
|
|
2927
|
+
var activeSubTab = document.querySelector('.sub-tab.active');
|
|
2928
|
+
if (activeSubTab && activeSubTab.dataset.subtab === 'mcp') { setMcpTabActive(true); loadMcpServers(); }
|
|
2929
|
+
else if (activeSubTab && activeSubTab.dataset.subtab === 'audit') { loadLogs(); }
|
|
2930
|
+
else { setMcpTabActive(false); }
|
|
2931
|
+
} else {
|
|
2932
|
+
setMcpTabActive(false);
|
|
2933
|
+
}
|
|
2934
|
+
});
|
|
2935
|
+
});
|
|
2936
|
+
|
|
2937
|
+
// ── Sub-tab navigation (within Settings) ─────
|
|
2938
|
+
document.querySelectorAll('.sub-tab').forEach(function(tab) {
|
|
2939
|
+
tab.addEventListener('click', function() {
|
|
2940
|
+
var subtabName = tab.dataset.subtab;
|
|
2941
|
+
document.querySelectorAll('.sub-tab').forEach(function(t) { t.classList.remove('active'); });
|
|
2942
|
+
document.querySelectorAll('.sub-panel').forEach(function(p) { p.classList.remove('active'); });
|
|
2943
|
+
tab.classList.add('active');
|
|
2944
|
+
document.getElementById('subpanel-' + subtabName).classList.add('active');
|
|
2945
|
+
setMcpTabActive(subtabName === 'mcp');
|
|
2946
|
+
if (subtabName === 'mcp') loadMcpServers();
|
|
2947
|
+
if (subtabName === 'audit') loadLogs();
|
|
1923
2948
|
});
|
|
1924
2949
|
});
|
|
1925
2950
|
|
|
@@ -1960,6 +2985,10 @@ function markDirty() {
|
|
|
1960
2985
|
document.getElementById('saveBar').classList.toggle('visible', dirty);
|
|
1961
2986
|
}
|
|
1962
2987
|
|
|
2988
|
+
function normalizeUiNetworkMode(mode) {
|
|
2989
|
+
return mode === 'filtered' ? 'filtered' : 'host';
|
|
2990
|
+
}
|
|
2991
|
+
|
|
1963
2992
|
// ── Populate UI from config ──────────────────
|
|
1964
2993
|
function populateUI() {
|
|
1965
2994
|
document.getElementById('runtime').value = config.runtime || 'auto';
|
|
@@ -1968,7 +2997,9 @@ function populateUI() {
|
|
|
1968
2997
|
? config.session_timeout_hours
|
|
1969
2998
|
: 8;
|
|
1970
2999
|
document.getElementById('timeout').value = String(timeout);
|
|
1971
|
-
|
|
3000
|
+
if (!config.network) config.network = {};
|
|
3001
|
+
config.network.mode = normalizeUiNetworkMode(config.network.mode);
|
|
3002
|
+
document.getElementById('networkMode').value = config.network.mode;
|
|
1972
3003
|
document.getElementById('auditEnabled').checked = config.audit ? config.audit.enabled : true;
|
|
1973
3004
|
document.getElementById('logDir').value = config.audit ? config.audit.log_dir : '~/.labgate/logs';
|
|
1974
3005
|
|
|
@@ -1986,7 +3017,7 @@ function collectConfig() {
|
|
|
1986
3017
|
var parsedTimeout = parseFloat(document.getElementById('timeout').value);
|
|
1987
3018
|
config.session_timeout_hours = Number.isFinite(parsedTimeout) ? parsedTimeout : 8;
|
|
1988
3019
|
if (!config.network) config.network = {};
|
|
1989
|
-
config.network.mode = document.getElementById('networkMode').value;
|
|
3020
|
+
config.network.mode = normalizeUiNetworkMode(document.getElementById('networkMode').value);
|
|
1990
3021
|
if (!config.audit) config.audit = {};
|
|
1991
3022
|
config.audit.enabled = document.getElementById('auditEnabled').checked;
|
|
1992
3023
|
config.audit.log_dir = document.getElementById('logDir').value;
|
|
@@ -2112,22 +3143,60 @@ function removeMount(i) {
|
|
|
2112
3143
|
function renderDatasets() {
|
|
2113
3144
|
var container = document.getElementById('datasetsList');
|
|
2114
3145
|
var datasets = config.datasets || [];
|
|
3146
|
+
var dbIcon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">'
|
|
3147
|
+
+ '<ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>'
|
|
3148
|
+
+ '<path d="M21 12c0 1.66-4.03 3-9 3s-9-1.34-9-3"></path>'
|
|
3149
|
+
+ '<path d="M3 5v14c0 1.66 4.03 3 9 3s9-1.34 9-3V5"></path>'
|
|
3150
|
+
+ '</svg>';
|
|
3151
|
+
var arrowIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>';
|
|
3152
|
+
// Toggle notice visibility and count badge
|
|
3153
|
+
var notice = document.getElementById('datasetNotice');
|
|
3154
|
+
if (notice) notice.style.display = datasets.length > 0 ? '' : 'none';
|
|
3155
|
+
var countEl = document.getElementById('datasetCount');
|
|
3156
|
+
if (countEl) {
|
|
3157
|
+
if (datasets.length > 0) {
|
|
3158
|
+
countEl.textContent = datasets.length + ' dataset' + (datasets.length > 1 ? 's' : '');
|
|
3159
|
+
countEl.style.display = '';
|
|
3160
|
+
} else {
|
|
3161
|
+
countEl.style.display = 'none';
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
2115
3164
|
if (datasets.length === 0) {
|
|
2116
|
-
container.innerHTML = '<div class="empty
|
|
3165
|
+
container.innerHTML = '<div class="dataset-empty">'
|
|
3166
|
+
+ '<div class="dataset-empty-icon">' + dbIcon + '</div>'
|
|
3167
|
+
+ '<div class="dataset-empty-title">No datasets configured</div>'
|
|
3168
|
+
+ '<div class="dataset-empty-subtitle">Add a host directory below to make it available as a named dataset in the sandbox.</div>'
|
|
3169
|
+
+ '</div>';
|
|
2117
3170
|
return;
|
|
2118
3171
|
}
|
|
2119
|
-
container.innerHTML = datasets.map(function(ds, i) {
|
|
2120
|
-
var
|
|
2121
|
-
|
|
3172
|
+
container.innerHTML = '<div class="dataset-grid">' + datasets.map(function(ds, i) {
|
|
3173
|
+
var descHtml = ds.description
|
|
3174
|
+
? '<div class="dataset-card-desc">' + escapeHtml(ds.description) + '</div>'
|
|
3175
|
+
: '';
|
|
3176
|
+
return '<div class="dataset-card">'
|
|
3177
|
+
+ '<div class="dataset-card-header">'
|
|
3178
|
+
+ '<div class="dataset-card-icon">' + dbIcon + '</div>'
|
|
3179
|
+
+ '<div class="dataset-card-title">'
|
|
2122
3180
|
+ '<span class="dataset-name">' + escapeHtml(ds.name) + '</span>'
|
|
2123
|
-
+ '<span class="dataset-path">' + escapeHtml(ds.path) + '</span>'
|
|
2124
|
-
+ desc
|
|
2125
3181
|
+ '<span class="mount-mode ' + ds.mode + '">' + ds.mode + '</span>'
|
|
2126
|
-
+ '
|
|
3182
|
+
+ '</div>'
|
|
3183
|
+
+ '<button class="remove-btn" data-index="' + i + '" title="Remove dataset">×</button>'
|
|
3184
|
+
+ '</div>'
|
|
3185
|
+
+ descHtml
|
|
3186
|
+
+ '<div class="dataset-card-paths">'
|
|
3187
|
+
+ '<span class="dataset-path">' + escapeHtml(ds.path) + '</span>'
|
|
3188
|
+
+ '<span class="dataset-path-arrow">' + arrowIcon + '</span>'
|
|
3189
|
+
+ '<span class="dataset-path dataset-path-container">/datasets/' + escapeHtml(ds.name) + '</span>'
|
|
3190
|
+
+ '</div>'
|
|
2127
3191
|
+ '</div>';
|
|
2128
|
-
}).join('');
|
|
3192
|
+
}).join('') + '</div>';
|
|
2129
3193
|
container.querySelectorAll('.remove-btn').forEach(function(btn) {
|
|
2130
|
-
btn.addEventListener('click', function() {
|
|
3194
|
+
btn.addEventListener('click', function() {
|
|
3195
|
+
var idx = parseInt(btn.dataset.index);
|
|
3196
|
+
var ds = config.datasets[idx];
|
|
3197
|
+
if (!confirm('Remove dataset "' + ds.name + '"?')) return;
|
|
3198
|
+
removeDataset(idx);
|
|
3199
|
+
});
|
|
2131
3200
|
});
|
|
2132
3201
|
}
|
|
2133
3202
|
|
|
@@ -2135,7 +3204,6 @@ function addDataset() {
|
|
|
2135
3204
|
var pathInput = document.getElementById('datasetPathInput');
|
|
2136
3205
|
var nameInput = document.getElementById('datasetNameInput');
|
|
2137
3206
|
var descInput = document.getElementById('datasetDescInput');
|
|
2138
|
-
var modeInput = document.getElementById('datasetModeInput');
|
|
2139
3207
|
|
|
2140
3208
|
var path = pathInput.value.trim();
|
|
2141
3209
|
if (!path) { showToast('Dataset path is required', 'error'); return; }
|
|
@@ -2152,7 +3220,8 @@ function addDataset() {
|
|
|
2152
3220
|
return;
|
|
2153
3221
|
}
|
|
2154
3222
|
|
|
2155
|
-
var
|
|
3223
|
+
var modeEl = document.querySelector('input[name="datasetMode"]:checked');
|
|
3224
|
+
var mode = modeEl ? modeEl.value : 'ro';
|
|
2156
3225
|
var desc = descInput.value.trim();
|
|
2157
3226
|
|
|
2158
3227
|
// Preflight path validation (advisory — still adds even if path not found)
|
|
@@ -2189,8 +3258,10 @@ function doAddDataset(path, name, desc, mode) {
|
|
|
2189
3258
|
document.getElementById('datasetPathInput').value = '';
|
|
2190
3259
|
document.getElementById('datasetNameInput').value = '';
|
|
2191
3260
|
document.getElementById('datasetDescInput').value = '';
|
|
3261
|
+
document.getElementById('datasetModeRO').checked = true;
|
|
2192
3262
|
renderDatasets();
|
|
2193
3263
|
markDirty();
|
|
3264
|
+
showToast('Dataset "' + name + '" added', 'success');
|
|
2194
3265
|
}
|
|
2195
3266
|
|
|
2196
3267
|
function removeDataset(i) {
|
|
@@ -2200,48 +3271,399 @@ function removeDataset(i) {
|
|
|
2200
3271
|
markDirty();
|
|
2201
3272
|
}
|
|
2202
3273
|
|
|
3274
|
+
// ── Results ─────────────────────────────
|
|
3275
|
+
function parseCsvList(value) {
|
|
3276
|
+
if (!value) return [];
|
|
3277
|
+
return value.split(',').map(function(s) { return s.trim(); }).filter(Boolean);
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
function formatResultTime(iso) {
|
|
3281
|
+
if (!iso) return '-';
|
|
3282
|
+
try {
|
|
3283
|
+
return new Date(iso).toLocaleString();
|
|
3284
|
+
} catch {
|
|
3285
|
+
return iso;
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
|
|
3289
|
+
function getResultById(id) {
|
|
3290
|
+
if (!id) return null;
|
|
3291
|
+
return resultsCache.find(function(r) { return r.id === id; }) || null;
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
// Result card icons
|
|
3295
|
+
var _resultIcon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="10"/></svg>';
|
|
3296
|
+
var _clockIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>';
|
|
3297
|
+
var _folderIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>';
|
|
3298
|
+
var _linkIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="20" height="14" rx="2" ry="2"/><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/></svg>';
|
|
3299
|
+
var _fileIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>';
|
|
3300
|
+
var _editIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>';
|
|
3301
|
+
var _trashIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>';
|
|
3302
|
+
|
|
3303
|
+
function resultSourceClass(source) {
|
|
3304
|
+
var s = (source || '').toLowerCase();
|
|
3305
|
+
if (s === 'claude') return 'source-claude';
|
|
3306
|
+
if (s === 'codex') return 'source-codex';
|
|
3307
|
+
return 'source-default';
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3310
|
+
function formatRelativeTime(iso) {
|
|
3311
|
+
if (!iso) return '';
|
|
3312
|
+
try {
|
|
3313
|
+
var diff = Date.now() - new Date(iso).getTime();
|
|
3314
|
+
var mins = Math.floor(diff / 60000);
|
|
3315
|
+
if (mins < 1) return 'just now';
|
|
3316
|
+
if (mins < 60) return mins + 'm ago';
|
|
3317
|
+
var hours = Math.floor(mins / 60);
|
|
3318
|
+
if (hours < 24) return hours + 'h ago';
|
|
3319
|
+
var days = Math.floor(hours / 24);
|
|
3320
|
+
if (days < 30) return days + 'd ago';
|
|
3321
|
+
return new Date(iso).toLocaleDateString();
|
|
3322
|
+
} catch(e) {
|
|
3323
|
+
return iso;
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
function toggleResultDetails(toggleEl, contentId) {
|
|
3328
|
+
var content = document.getElementById(contentId);
|
|
3329
|
+
if (!content) return;
|
|
3330
|
+
var isVisible = content.classList.contains('visible');
|
|
3331
|
+
if (isVisible) {
|
|
3332
|
+
content.classList.remove('visible');
|
|
3333
|
+
toggleEl.classList.remove('expanded');
|
|
3334
|
+
toggleEl.querySelector('span:last-child').textContent = 'Show details';
|
|
3335
|
+
} else {
|
|
3336
|
+
content.classList.add('visible');
|
|
3337
|
+
toggleEl.classList.add('expanded');
|
|
3338
|
+
toggleEl.querySelector('span:last-child').textContent = 'Hide details';
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
function renderResults(data) {
|
|
3343
|
+
var container = document.getElementById('resultsContent');
|
|
3344
|
+
var rows = (data && Array.isArray(data.results)) ? data.results : [];
|
|
3345
|
+
resultsCache = rows.slice();
|
|
3346
|
+
|
|
3347
|
+
if (rows.length === 0) {
|
|
3348
|
+
container.innerHTML = '<div class="results-empty">'
|
|
3349
|
+
+ '<div class="results-empty-icon">' + _resultIcon + '</div>'
|
|
3350
|
+
+ '<div class="results-empty-title">No results recorded yet</div>'
|
|
3351
|
+
+ '<div class="results-empty-subtitle">Use the MCP <code>register_result</code> tool or click "+ Add Result" to create a structured result entry.</div>'
|
|
3352
|
+
+ '</div>';
|
|
3353
|
+
return;
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
var html = '<div class="results-grid">';
|
|
3357
|
+
rows.forEach(function(r) {
|
|
3358
|
+
var tags = Array.isArray(r.tags) ? r.tags : [];
|
|
3359
|
+
var artifacts = Array.isArray(r.artifacts) ? r.artifacts : [];
|
|
3360
|
+
var hasDetails = !!(r.details && r.details.trim());
|
|
3361
|
+
|
|
3362
|
+
html += '<div class="result-card">';
|
|
3363
|
+
|
|
3364
|
+
// Header: icon + title + source badge + actions
|
|
3365
|
+
html += '<div class="result-card-header">';
|
|
3366
|
+
html += '<div class="result-card-icon">' + _resultIcon + '</div>';
|
|
3367
|
+
html += '<div class="result-card-title">';
|
|
3368
|
+
html += '<span class="result-name">' + escapeHtml(r.title || '(untitled)') + '</span>';
|
|
3369
|
+
html += '<span class="result-source-badge ' + resultSourceClass(r.source) + '">' + escapeHtml(r.source || 'unknown') + '</span>';
|
|
3370
|
+
html += '</div>';
|
|
3371
|
+
html += '<div class="result-card-actions">';
|
|
3372
|
+
html += '<button title="Edit" onclick="openResultEditor(\'' + escapeHtml(r.id) + '\')">' + _editIcon + '</button>';
|
|
3373
|
+
html += '<button class="danger" title="Delete" onclick="deleteResult(\'' + escapeHtml(r.id) + '\')">' + _trashIcon + '</button>';
|
|
3374
|
+
html += '</div>';
|
|
3375
|
+
html += '</div>';
|
|
3376
|
+
|
|
3377
|
+
// Summary
|
|
3378
|
+
if (r.summary) {
|
|
3379
|
+
html += '<div class="result-card-summary">' + escapeHtml(r.summary) + '</div>';
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
// Tags
|
|
3383
|
+
if (tags.length > 0) {
|
|
3384
|
+
html += '<div class="result-card-tags">';
|
|
3385
|
+
tags.forEach(function(tag) {
|
|
3386
|
+
html += '<span class="result-tag-pill">' + escapeHtml(tag) + '</span>';
|
|
3387
|
+
});
|
|
3388
|
+
html += '</div>';
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
// Metadata row
|
|
3392
|
+
var metaItems = [];
|
|
3393
|
+
metaItems.push('<span class="result-meta-item">' + _clockIcon + '<span title="' + escapeHtml(r.updated_at || '') + '">' + escapeHtml(formatRelativeTime(r.updated_at)) + '</span></span>');
|
|
3394
|
+
if (r.session_id) {
|
|
3395
|
+
metaItems.push('<span class="result-meta-item">' + _linkIcon + '<span>' + escapeHtml(r.session_id.slice(0, 8)) + '</span></span>');
|
|
3396
|
+
}
|
|
3397
|
+
if (r.workdir) {
|
|
3398
|
+
metaItems.push('<span class="result-meta-item">' + _folderIcon + '<span title="' + escapeHtml(r.workdir) + '">' + escapeHtml(r.workdir) + '</span></span>');
|
|
3399
|
+
}
|
|
3400
|
+
if (artifacts.length > 0) {
|
|
3401
|
+
metaItems.push('<span class="result-meta-item">' + _fileIcon + '<span>' + artifacts.length + ' artifact' + (artifacts.length !== 1 ? 's' : '') + '</span></span>');
|
|
3402
|
+
}
|
|
3403
|
+
html += '<div class="result-card-meta">' + metaItems.join('') + '</div>';
|
|
3404
|
+
|
|
3405
|
+
// Details toggle
|
|
3406
|
+
if (hasDetails) {
|
|
3407
|
+
var detailsId = 'result-details-' + r.id.replace(/[^a-zA-Z0-9-]/g, '');
|
|
3408
|
+
html += '<div class="result-card-details-toggle" onclick="toggleResultDetails(this, \'' + detailsId + '\')">';
|
|
3409
|
+
html += '<span class="chevron">▶</span>';
|
|
3410
|
+
html += '<span>Show details</span>';
|
|
3411
|
+
html += '</div>';
|
|
3412
|
+
html += '<div class="result-card-details-content" id="' + detailsId + '">' + escapeHtml(r.details) + '</div>';
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
html += '</div>';
|
|
3416
|
+
});
|
|
3417
|
+
html += '</div>';
|
|
3418
|
+
container.innerHTML = html;
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
function loadResults() {
|
|
3422
|
+
var container = document.getElementById('resultsContent');
|
|
3423
|
+
if (container) container.innerHTML = '<div class="empty-state">Loading...</div>';
|
|
3424
|
+
|
|
3425
|
+
var search = (document.getElementById('resultsSearchInput').value || '').trim();
|
|
3426
|
+
var tag = (document.getElementById('resultsTagInput').value || '').trim();
|
|
3427
|
+
var source = (document.getElementById('resultsSourceInput').value || '').trim();
|
|
3428
|
+
|
|
3429
|
+
var url = '/api/results?limit=200';
|
|
3430
|
+
if (search) url += '&search=' + encodeURIComponent(search);
|
|
3431
|
+
if (tag) url += '&tag=' + encodeURIComponent(tag);
|
|
3432
|
+
if (source) url += '&source=' + encodeURIComponent(source);
|
|
3433
|
+
|
|
3434
|
+
fetch(url).then(function(r) { return r.json(); }).then(function(data) {
|
|
3435
|
+
if (!data.ok) {
|
|
3436
|
+
container.innerHTML = '<div class="empty-state">Could not load results</div>';
|
|
3437
|
+
return;
|
|
3438
|
+
}
|
|
3439
|
+
renderResults(data);
|
|
3440
|
+
}).catch(function(err) {
|
|
3441
|
+
container.innerHTML = '<div class="empty-state">Error loading results: ' + escapeHtml(err.message) + '</div>';
|
|
3442
|
+
});
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3445
|
+
function debounceResultsSearch() {
|
|
3446
|
+
if (resultsSearchTimeout) clearTimeout(resultsSearchTimeout);
|
|
3447
|
+
resultsSearchTimeout = setTimeout(loadResults, 250);
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3450
|
+
function fillResultEditor(result) {
|
|
3451
|
+
document.getElementById('resultTitleInput').value = (result && result.title) || '';
|
|
3452
|
+
document.getElementById('resultSummaryInput').value = (result && result.summary) || '';
|
|
3453
|
+
document.getElementById('resultDetailsInput').value = (result && result.details) || '';
|
|
3454
|
+
document.getElementById('resultSourceInputModal').value = (result && result.source) || 'claude';
|
|
3455
|
+
document.getElementById('resultSessionIdInput').value = (result && result.session_id) || '';
|
|
3456
|
+
document.getElementById('resultWorkdirInput').value = (result && result.workdir) || '';
|
|
3457
|
+
document.getElementById('resultTagsInput').value = (result && Array.isArray(result.tags)) ? result.tags.join(', ') : '';
|
|
3458
|
+
document.getElementById('resultArtifactsInput').value = (result && Array.isArray(result.artifacts)) ? result.artifacts.join(', ') : '';
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
function openResultEditor(id) {
|
|
3462
|
+
resultEditorId = id || null;
|
|
3463
|
+
var modal = document.getElementById('resultsModal');
|
|
3464
|
+
if (!modal) return;
|
|
3465
|
+
|
|
3466
|
+
var titleEl = document.getElementById('resultEditorTitle');
|
|
3467
|
+
var subtitleEl = document.getElementById('resultEditorSubtitle');
|
|
3468
|
+
if (resultEditorId) {
|
|
3469
|
+
var result = getResultById(resultEditorId);
|
|
3470
|
+
fillResultEditor(result);
|
|
3471
|
+
titleEl.textContent = 'Edit Result';
|
|
3472
|
+
subtitleEl.textContent = result ? ('Result ' + result.id) : ('Result ' + resultEditorId);
|
|
3473
|
+
} else {
|
|
3474
|
+
fillResultEditor(null);
|
|
3475
|
+
titleEl.textContent = 'Add Result';
|
|
3476
|
+
subtitleEl.textContent = 'Create a new structured result entry';
|
|
3477
|
+
}
|
|
3478
|
+
|
|
3479
|
+
modal.classList.add('visible');
|
|
3480
|
+
document.body.style.overflow = 'hidden';
|
|
3481
|
+
document.getElementById('resultTitleInput').focus();
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
function closeResultEditor() {
|
|
3485
|
+
var modal = document.getElementById('resultsModal');
|
|
3486
|
+
if (!modal) return;
|
|
3487
|
+
modal.classList.remove('visible');
|
|
3488
|
+
document.body.style.overflow = '';
|
|
3489
|
+
resultEditorId = null;
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
function buildResultEditorPayload() {
|
|
3493
|
+
var title = (document.getElementById('resultTitleInput').value || '').trim();
|
|
3494
|
+
if (!title) {
|
|
3495
|
+
showToast('Result title is required.', 'error');
|
|
3496
|
+
return null;
|
|
3497
|
+
}
|
|
3498
|
+
var summary = (document.getElementById('resultSummaryInput').value || '').trim();
|
|
3499
|
+
var details = (document.getElementById('resultDetailsInput').value || '').trim();
|
|
3500
|
+
var source = (document.getElementById('resultSourceInputModal').value || '').trim() || 'claude';
|
|
3501
|
+
var sessionId = (document.getElementById('resultSessionIdInput').value || '').trim();
|
|
3502
|
+
var workdir = (document.getElementById('resultWorkdirInput').value || '').trim();
|
|
3503
|
+
var tags = parseCsvList(document.getElementById('resultTagsInput').value || '');
|
|
3504
|
+
var artifacts = parseCsvList(document.getElementById('resultArtifactsInput').value || '');
|
|
3505
|
+
|
|
3506
|
+
return {
|
|
3507
|
+
title: title,
|
|
3508
|
+
summary: summary,
|
|
3509
|
+
details: details || null,
|
|
3510
|
+
source: source,
|
|
3511
|
+
session_id: sessionId || null,
|
|
3512
|
+
workdir: workdir || null,
|
|
3513
|
+
tags: tags,
|
|
3514
|
+
artifacts: artifacts,
|
|
3515
|
+
};
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
function saveResultEditor() {
|
|
3519
|
+
var payload = buildResultEditorPayload();
|
|
3520
|
+
if (!payload) return;
|
|
3521
|
+
|
|
3522
|
+
var isUpdate = !!resultEditorId;
|
|
3523
|
+
var endpoint = isUpdate ? '/api/results/' + encodeURIComponent(resultEditorId) : '/api/results';
|
|
3524
|
+
var method = isUpdate ? 'PUT' : 'POST';
|
|
3525
|
+
|
|
3526
|
+
fetch(endpoint, {
|
|
3527
|
+
method: method,
|
|
3528
|
+
headers: apiWriteHeaders(),
|
|
3529
|
+
body: JSON.stringify(payload)
|
|
3530
|
+
}).then(parseApiResponse).then(function(resp) {
|
|
3531
|
+
if (!resp.ok || !resp.data || !resp.data.ok) {
|
|
3532
|
+
throw new Error((resp.data && resp.data.error) || ('HTTP ' + resp.status));
|
|
3533
|
+
}
|
|
3534
|
+
showToast(isUpdate ? 'Result updated' : 'Result created', 'success');
|
|
3535
|
+
closeResultEditor();
|
|
3536
|
+
loadResults();
|
|
3537
|
+
}).catch(function(err) {
|
|
3538
|
+
showToast('Save failed: ' + err.message, 'error');
|
|
3539
|
+
});
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
function deleteResult(id) {
|
|
3543
|
+
if (!id) return;
|
|
3544
|
+
if (!confirm('Delete result ' + id + '?')) return;
|
|
3545
|
+
fetch('/api/results/' + encodeURIComponent(id), {
|
|
3546
|
+
method: 'DELETE',
|
|
3547
|
+
headers: apiWriteHeaders(),
|
|
3548
|
+
}).then(parseApiResponse).then(function(resp) {
|
|
3549
|
+
if (!resp.ok || !resp.data || !resp.data.ok) {
|
|
3550
|
+
throw new Error((resp.data && resp.data.error) || ('HTTP ' + resp.status));
|
|
3551
|
+
}
|
|
3552
|
+
showToast('Result deleted', 'success');
|
|
3553
|
+
loadResults();
|
|
3554
|
+
}).catch(function(err) {
|
|
3555
|
+
showToast('Delete failed: ' + err.message, 'error');
|
|
3556
|
+
});
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3559
|
+
// ── Dataset restart helpers ─────────────────
|
|
3560
|
+
function checkDatasetRestartNeeded() {
|
|
3561
|
+
fetch('/api/sessions').then(function(r) { return r.json(); }).then(function(data) {
|
|
3562
|
+
var sessions = data.sessions || [];
|
|
3563
|
+
var needsRestart = sessions.filter(function(s) { return s.restartRequired; });
|
|
3564
|
+
var btn = document.getElementById('datasetRestartBtn');
|
|
3565
|
+
var notice = document.getElementById('datasetNotice');
|
|
3566
|
+
var text = notice ? notice.querySelector('.dataset-notice-text') : null;
|
|
3567
|
+
if (needsRestart.length > 0) {
|
|
3568
|
+
if (text) text.textContent = needsRestart.length + ' active session' + (needsRestart.length > 1 ? 's' : '') + ' running with outdated config. Restart them now to apply dataset changes.';
|
|
3569
|
+
if (btn) { btn.style.display = ''; btn.disabled = false; btn.textContent = 'Restart Sessions Now'; }
|
|
3570
|
+
} else if (sessions.length > 0) {
|
|
3571
|
+
if (text) text.textContent = 'Dataset changes are saved. Active sessions keep old mounts until you restart them.';
|
|
3572
|
+
if (btn) btn.style.display = 'none';
|
|
3573
|
+
} else {
|
|
3574
|
+
if (text) text.textContent = 'No active sessions. Dataset changes will be used the next time you start a session.';
|
|
3575
|
+
if (btn) btn.style.display = 'none';
|
|
3576
|
+
}
|
|
3577
|
+
}).catch(function() {});
|
|
3578
|
+
}
|
|
3579
|
+
|
|
3580
|
+
function restartOutdatedSessions() {
|
|
3581
|
+
var btn = document.getElementById('datasetRestartBtn');
|
|
3582
|
+
if (btn) { btn.disabled = true; btn.textContent = 'Checking...'; }
|
|
3583
|
+
|
|
3584
|
+
fetch('/api/sessions').then(function(r) { return r.json(); }).then(function(data) {
|
|
3585
|
+
var sessions = data.sessions || [];
|
|
3586
|
+
var needsRestart = sessions.filter(function(s) { return s.restartRequired; });
|
|
3587
|
+
if (needsRestart.length === 0) {
|
|
3588
|
+
showToast('No sessions need restarting', 'success');
|
|
3589
|
+
if (btn) { btn.style.display = 'none'; }
|
|
3590
|
+
return;
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3593
|
+
var msg = 'Restarting will stop and relaunch ' + needsRestart.length + ' active session' + (needsRestart.length > 1 ? 's' : '')
|
|
3594
|
+
+ ' with the latest config. Unsaved work inside those sessions may be lost. Continue?';
|
|
3595
|
+
if (!confirm(msg)) {
|
|
3596
|
+
if (btn) {
|
|
3597
|
+
btn.disabled = false;
|
|
3598
|
+
btn.textContent = 'Restart Sessions Now';
|
|
3599
|
+
}
|
|
3600
|
+
return;
|
|
3601
|
+
}
|
|
3602
|
+
if (btn) { btn.disabled = true; btn.textContent = 'Restarting...'; }
|
|
3603
|
+
|
|
3604
|
+
var restartPromises = needsRestart.map(function(s) {
|
|
3605
|
+
return fetch('/api/sessions/restart', {
|
|
3606
|
+
method: 'POST',
|
|
3607
|
+
headers: apiWriteHeaders(),
|
|
3608
|
+
body: JSON.stringify({ id: s.id })
|
|
3609
|
+
}).then(parseApiResponse).then(function(resp) {
|
|
3610
|
+
var data = resp.data || {};
|
|
3611
|
+
return { ok: !!(resp.ok && data.ok), error: data.error || ('HTTP ' + resp.status) };
|
|
3612
|
+
});
|
|
3613
|
+
});
|
|
3614
|
+
|
|
3615
|
+
Promise.all(restartPromises).then(function(results) {
|
|
3616
|
+
var restarted = results.filter(function(r) { return r.ok; }).length;
|
|
3617
|
+
var failed = results.length - restarted;
|
|
3618
|
+
if (failed > 0) {
|
|
3619
|
+
showToast(
|
|
3620
|
+
restarted + ' session' + (restarted !== 1 ? 's' : '') + ' restarted, '
|
|
3621
|
+
+ failed + ' failed.',
|
|
3622
|
+
'error'
|
|
3623
|
+
);
|
|
3624
|
+
} else {
|
|
3625
|
+
showToast(restarted + ' session' + (restarted !== 1 ? 's' : '') + ' restarted with fresh config.', 'success');
|
|
3626
|
+
}
|
|
3627
|
+
if (btn) { btn.textContent = failed > 0 ? 'Retry Restart' : 'Restarted'; }
|
|
3628
|
+
loadSessions();
|
|
3629
|
+
setTimeout(function() { checkDatasetRestartNeeded(); }, 1200);
|
|
3630
|
+
}).catch(function(err) {
|
|
3631
|
+
showToast('Failed to restart sessions: ' + err.message, 'error');
|
|
3632
|
+
if (btn) { btn.disabled = false; btn.textContent = 'Restart Sessions Now'; }
|
|
3633
|
+
});
|
|
3634
|
+
}).catch(function(err) {
|
|
3635
|
+
showToast('Failed to fetch sessions: ' + err.message, 'error');
|
|
3636
|
+
if (btn) { btn.disabled = false; btn.textContent = 'Restart Sessions Now'; }
|
|
3637
|
+
});
|
|
3638
|
+
}
|
|
3639
|
+
|
|
2203
3640
|
// ── Restart from UI ─────────────────────────
|
|
2204
|
-
function restartFromUI(id,
|
|
2205
|
-
if (!confirm('
|
|
3641
|
+
function restartFromUI(id, btn) {
|
|
3642
|
+
if (!confirm('Restart session ' + id.slice(0, 8) + ' now? Unsaved work in that session may be lost.')) return;
|
|
2206
3643
|
btn.disabled = true;
|
|
2207
|
-
btn.textContent = '
|
|
2208
|
-
fetch('/api/sessions/
|
|
3644
|
+
btn.textContent = 'Restarting...';
|
|
3645
|
+
fetch('/api/sessions/restart', {
|
|
2209
3646
|
method: 'POST',
|
|
2210
3647
|
headers: apiWriteHeaders(),
|
|
2211
3648
|
body: JSON.stringify({ id: id })
|
|
2212
|
-
}).then(
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
3649
|
+
}).then(parseApiResponse).then(function(resp) {
|
|
3650
|
+
var data = resp.data || {};
|
|
3651
|
+
if (resp.ok && data.ok) {
|
|
3652
|
+
showToast('Session restarted with fresh config', 'success');
|
|
3653
|
+
loadSessions();
|
|
3654
|
+
checkDatasetRestartNeeded();
|
|
2217
3655
|
} else {
|
|
2218
|
-
showToast('
|
|
3656
|
+
showToast('Restart failed: ' + (data.error || ('HTTP ' + resp.status)), 'error');
|
|
2219
3657
|
btn.disabled = false;
|
|
2220
|
-
btn.textContent = '
|
|
3658
|
+
btn.textContent = 'Restart Now';
|
|
2221
3659
|
}
|
|
2222
3660
|
}).catch(function(err) {
|
|
2223
|
-
showToast('
|
|
3661
|
+
showToast('Restart failed: ' + err.message, 'error');
|
|
2224
3662
|
btn.disabled = false;
|
|
2225
|
-
btn.textContent = '
|
|
3663
|
+
btn.textContent = 'Restart Now';
|
|
2226
3664
|
});
|
|
2227
3665
|
}
|
|
2228
3666
|
|
|
2229
|
-
function copyToClipboard(text) {
|
|
2230
|
-
try {
|
|
2231
|
-
navigator.clipboard.writeText(text);
|
|
2232
|
-
} catch(e) {
|
|
2233
|
-
// Fallback for non-HTTPS or older browsers
|
|
2234
|
-
var ta = document.createElement('textarea');
|
|
2235
|
-
ta.value = text;
|
|
2236
|
-
ta.style.position = 'fixed';
|
|
2237
|
-
ta.style.left = '-9999px';
|
|
2238
|
-
document.body.appendChild(ta);
|
|
2239
|
-
ta.select();
|
|
2240
|
-
try { document.execCommand('copy'); } catch(e2) {}
|
|
2241
|
-
document.body.removeChild(ta);
|
|
2242
|
-
}
|
|
2243
|
-
}
|
|
2244
|
-
|
|
2245
3667
|
// ── Change listeners on simple fields ────────
|
|
2246
3668
|
['runtime', 'image', 'timeout', 'networkMode', 'auditEnabled', 'logDir'].forEach(function(id) {
|
|
2247
3669
|
var el = document.getElementById(id);
|
|
@@ -2255,7 +3677,9 @@ function copyToClipboard(text) {
|
|
|
2255
3677
|
function loadConfig() {
|
|
2256
3678
|
fetch('/api/config').then(function(r) { return r.json(); }).then(function(data) {
|
|
2257
3679
|
config = data;
|
|
2258
|
-
|
|
3680
|
+
if (!config.network) config.network = {};
|
|
3681
|
+
config.network.mode = normalizeUiNetworkMode(config.network.mode);
|
|
3682
|
+
originalConfig = JSON.stringify(config);
|
|
2259
3683
|
populateUI();
|
|
2260
3684
|
updateNetSwitcher();
|
|
2261
3685
|
}).catch(function(err) {
|
|
@@ -2276,6 +3700,7 @@ function saveConfig() {
|
|
|
2276
3700
|
dirty = false;
|
|
2277
3701
|
document.getElementById('saveBar').classList.remove('visible');
|
|
2278
3702
|
showToast('Config saved', 'success');
|
|
3703
|
+
checkDatasetRestartNeeded();
|
|
2279
3704
|
} else {
|
|
2280
3705
|
var msg = data.error || (data.errors || []).join(', ') || 'unknown error';
|
|
2281
3706
|
showToast('Save failed: ' + msg, 'error');
|
|
@@ -2300,7 +3725,7 @@ function loadConfigPath() {
|
|
|
2300
3725
|
|
|
2301
3726
|
// ── Network mode switcher ────────────────────
|
|
2302
3727
|
function updateNetSwitcher() {
|
|
2303
|
-
var mode = config.network ? config.network.mode : '
|
|
3728
|
+
var mode = normalizeUiNetworkMode(config.network ? config.network.mode : 'host');
|
|
2304
3729
|
document.querySelectorAll('.net-mode-pill').forEach(function(pill) {
|
|
2305
3730
|
var m = pill.dataset.mode;
|
|
2306
3731
|
pill.className = 'net-mode-pill' + (m === mode ? ' active-' + m : '');
|
|
@@ -2308,6 +3733,7 @@ function updateNetSwitcher() {
|
|
|
2308
3733
|
}
|
|
2309
3734
|
|
|
2310
3735
|
function switchNetMode(mode) {
|
|
3736
|
+
mode = normalizeUiNetworkMode(mode);
|
|
2311
3737
|
collectConfig();
|
|
2312
3738
|
config.network.mode = mode;
|
|
2313
3739
|
updateNetSwitcher();
|
|
@@ -2427,7 +3853,7 @@ function renderSessions(data) {
|
|
|
2427
3853
|
var dotClass = actStatus !== 'unknown' ? actStatus : (s.status || 'running');
|
|
2428
3854
|
var dotLabel = activity.label || s.status || 'running';
|
|
2429
3855
|
var cardClass = 'session-card' + (actStatus === 'waiting' ? ' waiting' : '');
|
|
2430
|
-
html += '<div class="' + cardClass + '">';
|
|
3856
|
+
html += '<div class="' + cardClass + '" onclick="onSessionCardClick(event, \'' + escapeHtml(s.id || '') + '\')" style="cursor:pointer">';
|
|
2431
3857
|
html += '<div class="session-card-header">';
|
|
2432
3858
|
html += '<span class="badge ' + agentBadgeClass(agent) + '">' + escapeHtml(agent) + '</span>';
|
|
2433
3859
|
html += '<span class="session-id">' + escapeHtml(shortId) + '</span>';
|
|
@@ -2446,7 +3872,7 @@ function renderSessions(data) {
|
|
|
2446
3872
|
html += '<span class="restart-banner-icon">⚠</span>';
|
|
2447
3873
|
html += '<span class="restart-banner-text">' + escapeHtml(reasons) + '. Restart to apply. ';
|
|
2448
3874
|
html += '<code>labgate restart ' + escapeHtml(shortId) + '</code></span>';
|
|
2449
|
-
html += '<button class="btn-restart-stop" onclick="restartFromUI(\'' + escapeHtml(s.id || '') + '\',
|
|
3875
|
+
html += '<button class="btn-restart-stop" onclick="restartFromUI(\'' + escapeHtml(s.id || '') + '\', this)">Restart Now</button>';
|
|
2450
3876
|
html += '</div>';
|
|
2451
3877
|
}
|
|
2452
3878
|
// Sparkline timeline + resource usage
|
|
@@ -2668,19 +4094,6 @@ function stopSession(id, btn) {
|
|
|
2668
4094
|
|
|
2669
4095
|
// ── Security stats rendering ─────────────────
|
|
2670
4096
|
function renderSecurity(data) {
|
|
2671
|
-
document.getElementById('blockedCountValue').textContent = data.blockedCount || '0';
|
|
2672
|
-
document.getElementById('blacklistCountValue').textContent = data.protection ? data.protection.blacklistedCommands : '-';
|
|
2673
|
-
document.getElementById('patternsCountValue').textContent = data.protection ? data.protection.blockedPatterns : '-';
|
|
2674
|
-
document.getElementById('netModeValue').textContent = data.protection ? data.protection.networkMode : '-';
|
|
2675
|
-
|
|
2676
|
-
var statEl = document.getElementById('secStatBlocked');
|
|
2677
|
-
if (data.blockedCount > 0) {
|
|
2678
|
-
statEl.classList.add('has-blocked');
|
|
2679
|
-
} else {
|
|
2680
|
-
statEl.classList.remove('has-blocked');
|
|
2681
|
-
}
|
|
2682
|
-
|
|
2683
|
-
// Render blocked events list
|
|
2684
4097
|
var listEl = document.getElementById('blockedEventsList');
|
|
2685
4098
|
var cmds = data.blockedCommands || [];
|
|
2686
4099
|
if (cmds.length === 0) {
|
|
@@ -2725,14 +4138,16 @@ function connectSSE() {
|
|
|
2725
4138
|
try {
|
|
2726
4139
|
var d = JSON.parse(e.data);
|
|
2727
4140
|
if (d.stats) updateSlurmStats(d.stats);
|
|
2728
|
-
// Show
|
|
4141
|
+
// Show SLURM content if not yet visible
|
|
2729
4142
|
if (!slurmEnabled && d.stats && d.stats.total > 0) {
|
|
2730
4143
|
slurmEnabled = true;
|
|
2731
|
-
var
|
|
2732
|
-
|
|
4144
|
+
var emptyEl = document.getElementById('jobsEmptyState');
|
|
4145
|
+
var contentEl = document.getElementById('jobsSlurmContent');
|
|
4146
|
+
if (emptyEl) emptyEl.style.display = 'none';
|
|
4147
|
+
if (contentEl) contentEl.style.display = '';
|
|
2733
4148
|
}
|
|
2734
4149
|
// Update table if panel is active
|
|
2735
|
-
var panel = document.getElementById('panel-
|
|
4150
|
+
var panel = document.getElementById('panel-jobs');
|
|
2736
4151
|
if (panel && panel.classList.contains('active') && d.jobs) {
|
|
2737
4152
|
var el = document.getElementById('slurmJobsContent');
|
|
2738
4153
|
if (d.jobs.length === 0) {
|
|
@@ -2819,6 +4234,76 @@ function getInstructionButtonLabel(agent) {
|
|
|
2819
4234
|
return getDefaultInstructionFile(agent);
|
|
2820
4235
|
}
|
|
2821
4236
|
|
|
4237
|
+
// ── Sidebar resize ─────────────────────────
|
|
4238
|
+
(function initSidebarResize() {
|
|
4239
|
+
var handle = document.getElementById('sidebarResizeHandle');
|
|
4240
|
+
if (!handle) return;
|
|
4241
|
+
var panel = handle.closest('.instructions-modal');
|
|
4242
|
+
var minWidth = 320;
|
|
4243
|
+
var maxWidthPct = 85;
|
|
4244
|
+
var dragging = false;
|
|
4245
|
+
|
|
4246
|
+
function onMouseDown(e) {
|
|
4247
|
+
e.preventDefault();
|
|
4248
|
+
e.stopPropagation();
|
|
4249
|
+
dragging = true;
|
|
4250
|
+
handle.classList.add('dragging');
|
|
4251
|
+
panel.classList.add('resizing');
|
|
4252
|
+
document.body.style.cursor = 'col-resize';
|
|
4253
|
+
document.body.style.userSelect = 'none';
|
|
4254
|
+
document.addEventListener('mousemove', onMouseMove);
|
|
4255
|
+
document.addEventListener('mouseup', onMouseUp);
|
|
4256
|
+
}
|
|
4257
|
+
|
|
4258
|
+
function onMouseMove(e) {
|
|
4259
|
+
if (!dragging) return;
|
|
4260
|
+
var vw = window.innerWidth;
|
|
4261
|
+
var newWidth = vw - e.clientX;
|
|
4262
|
+
if (newWidth < minWidth) newWidth = minWidth;
|
|
4263
|
+
var maxPx = vw * maxWidthPct / 100;
|
|
4264
|
+
if (newWidth > maxPx) newWidth = maxPx;
|
|
4265
|
+
panel.style.width = newWidth + 'px';
|
|
4266
|
+
}
|
|
4267
|
+
|
|
4268
|
+
function onMouseUp() {
|
|
4269
|
+
dragging = false;
|
|
4270
|
+
handle.classList.remove('dragging');
|
|
4271
|
+
panel.classList.remove('resizing');
|
|
4272
|
+
document.body.style.cursor = '';
|
|
4273
|
+
document.body.style.userSelect = '';
|
|
4274
|
+
document.removeEventListener('mousemove', onMouseMove);
|
|
4275
|
+
document.removeEventListener('mouseup', onMouseUp);
|
|
4276
|
+
}
|
|
4277
|
+
|
|
4278
|
+
handle.addEventListener('mousedown', onMouseDown);
|
|
4279
|
+
|
|
4280
|
+
// Touch support
|
|
4281
|
+
handle.addEventListener('touchstart', function(e) {
|
|
4282
|
+
e.preventDefault();
|
|
4283
|
+
dragging = true;
|
|
4284
|
+
handle.classList.add('dragging');
|
|
4285
|
+
panel.classList.add('resizing');
|
|
4286
|
+
}, { passive: false });
|
|
4287
|
+
|
|
4288
|
+
document.addEventListener('touchmove', function(e) {
|
|
4289
|
+
if (!dragging) return;
|
|
4290
|
+
var touch = e.touches[0];
|
|
4291
|
+
var vw = window.innerWidth;
|
|
4292
|
+
var newWidth = vw - touch.clientX;
|
|
4293
|
+
if (newWidth < minWidth) newWidth = minWidth;
|
|
4294
|
+
var maxPx = vw * maxWidthPct / 100;
|
|
4295
|
+
if (newWidth > maxPx) newWidth = maxPx;
|
|
4296
|
+
panel.style.width = newWidth + 'px';
|
|
4297
|
+
});
|
|
4298
|
+
|
|
4299
|
+
document.addEventListener('touchend', function() {
|
|
4300
|
+
if (!dragging) return;
|
|
4301
|
+
dragging = false;
|
|
4302
|
+
handle.classList.remove('dragging');
|
|
4303
|
+
panel.classList.remove('resizing');
|
|
4304
|
+
});
|
|
4305
|
+
})();
|
|
4306
|
+
|
|
2822
4307
|
function formatInstructionTime(ms) {
|
|
2823
4308
|
if (!ms || !Number.isFinite(ms)) return '';
|
|
2824
4309
|
try { return new Date(ms).toLocaleString(); } catch { return ''; }
|
|
@@ -2862,6 +4347,16 @@ function renderManagedInstruction() {
|
|
|
2862
4347
|
if (text) text.textContent = hasManaged ? instructionsState.managedText : '';
|
|
2863
4348
|
}
|
|
2864
4349
|
|
|
4350
|
+
function onSessionCardClick(event, sessionId) {
|
|
4351
|
+
// Don't open sidebar if user clicked a button or link inside the card
|
|
4352
|
+
var t = event.target;
|
|
4353
|
+
while (t && t !== event.currentTarget) {
|
|
4354
|
+
if (t.tagName === 'BUTTON' || t.tagName === 'A') return;
|
|
4355
|
+
t = t.parentElement;
|
|
4356
|
+
}
|
|
4357
|
+
openInstructions(sessionId);
|
|
4358
|
+
}
|
|
4359
|
+
|
|
2865
4360
|
function openInstructions(sessionId) {
|
|
2866
4361
|
if (!sessionId) return;
|
|
2867
4362
|
var modal = document.getElementById('instructionsModal');
|
|
@@ -3032,9 +4527,214 @@ function saveInstructionsFile() {
|
|
|
3032
4527
|
}
|
|
3033
4528
|
|
|
3034
4529
|
document.addEventListener('keydown', function(event) {
|
|
3035
|
-
if (event.key === 'Escape') { closeInstructions(); closeSlurmOutput(); closeSlurmNotes(); }
|
|
4530
|
+
if (event.key === 'Escape') { closeInstructions(); closeSlurmOutput(); closeSlurmNotes(); closeResultEditor(); }
|
|
3036
4531
|
});
|
|
3037
4532
|
|
|
4533
|
+
// ── MCP Servers ──────────────────────────────
|
|
4534
|
+
|
|
4535
|
+
function setMcpTabActive(active) {
|
|
4536
|
+
if (active) {
|
|
4537
|
+
if (!mcpAutoRefreshTimer) {
|
|
4538
|
+
mcpAutoRefreshTimer = setInterval(function() {
|
|
4539
|
+
loadMcpServers({ background: true, autoRepair: false });
|
|
4540
|
+
}, MCP_AUTO_REFRESH_MS);
|
|
4541
|
+
}
|
|
4542
|
+
} else if (mcpAutoRefreshTimer) {
|
|
4543
|
+
clearInterval(mcpAutoRefreshTimer);
|
|
4544
|
+
mcpAutoRefreshTimer = null;
|
|
4545
|
+
}
|
|
4546
|
+
}
|
|
4547
|
+
|
|
4548
|
+
function triggerMcpAutoRegister() {
|
|
4549
|
+
if (mcpAutoRegisterInFlight) return;
|
|
4550
|
+
mcpAutoRegisterInFlight = true;
|
|
4551
|
+
fetch('/api/mcp/reregister', {
|
|
4552
|
+
method: 'POST',
|
|
4553
|
+
headers: apiWriteHeaders(),
|
|
4554
|
+
body: '{}',
|
|
4555
|
+
}).then(parseApiResponse).then(function(resp) {
|
|
4556
|
+
if (!resp.ok || !resp.data || !resp.data.ok) {
|
|
4557
|
+
var msg = (resp.data && resp.data.error) || ('HTTP ' + resp.status);
|
|
4558
|
+
showToast('MCP auto-setup failed: ' + msg, 'error');
|
|
4559
|
+
return;
|
|
4560
|
+
}
|
|
4561
|
+
loadMcpServers({ background: true, autoRepair: false });
|
|
4562
|
+
}).catch(function(err) {
|
|
4563
|
+
showToast('MCP auto-setup failed: ' + err.message, 'error');
|
|
4564
|
+
}).finally(function() {
|
|
4565
|
+
mcpAutoRegisterInFlight = false;
|
|
4566
|
+
});
|
|
4567
|
+
}
|
|
4568
|
+
|
|
4569
|
+
function loadMcpServers(options) {
|
|
4570
|
+
var opts = options || {};
|
|
4571
|
+
var background = !!opts.background;
|
|
4572
|
+
var autoRepair = opts.autoRepair !== false;
|
|
4573
|
+
var el = document.getElementById('mcpContent');
|
|
4574
|
+
if (!background) {
|
|
4575
|
+
el.innerHTML = '<div class="empty-state">Loading...</div>';
|
|
4576
|
+
}
|
|
4577
|
+
|
|
4578
|
+
fetch('/api/mcp').then(function(r) { return r.json(); }).then(function(data) {
|
|
4579
|
+
if (!data.ok || !data.servers) {
|
|
4580
|
+
el.innerHTML = '<div class="empty-state">Could not load MCP server data</div>';
|
|
4581
|
+
return;
|
|
4582
|
+
}
|
|
4583
|
+
|
|
4584
|
+
// Update badge
|
|
4585
|
+
var badge = document.getElementById('mcpBadge');
|
|
4586
|
+
if (data.activeCount > 0) {
|
|
4587
|
+
badge.textContent = data.activeCount;
|
|
4588
|
+
badge.style.display = '';
|
|
4589
|
+
} else {
|
|
4590
|
+
badge.style.display = 'none';
|
|
4591
|
+
}
|
|
4592
|
+
|
|
4593
|
+
var activeServers = data.servers.filter(function(s) { return s.active; });
|
|
4594
|
+
var inactiveServers = data.servers.filter(function(s) { return !s.active; });
|
|
4595
|
+
|
|
4596
|
+
if (activeServers.length === 0 && inactiveServers.length === 0) {
|
|
4597
|
+
el.innerHTML = renderMcpEmptyState();
|
|
4598
|
+
return;
|
|
4599
|
+
}
|
|
4600
|
+
|
|
4601
|
+
var html = '';
|
|
4602
|
+
activeServers.forEach(function(s) { html += renderMcpServerCard(s); });
|
|
4603
|
+
inactiveServers.forEach(function(s) { html += renderMcpServerCard(s); });
|
|
4604
|
+
el.innerHTML = html;
|
|
4605
|
+
|
|
4606
|
+
if (autoRepair) {
|
|
4607
|
+
var needsRegistration = data.servers.some(function(s) {
|
|
4608
|
+
return !!s.configured && !s.registered;
|
|
4609
|
+
});
|
|
4610
|
+
if (needsRegistration) {
|
|
4611
|
+
triggerMcpAutoRegister();
|
|
4612
|
+
}
|
|
4613
|
+
}
|
|
4614
|
+
}).catch(function() {
|
|
4615
|
+
if (!background) {
|
|
4616
|
+
el.innerHTML = '<div class="empty-state">Error loading MCP servers</div>';
|
|
4617
|
+
}
|
|
4618
|
+
});
|
|
4619
|
+
}
|
|
4620
|
+
|
|
4621
|
+
function renderMcpEmptyState() {
|
|
4622
|
+
var iconSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>';
|
|
4623
|
+
return '<div class="mcp-empty">' +
|
|
4624
|
+
'<div class="mcp-empty-icon">' + iconSvg + '</div>' +
|
|
4625
|
+
'<div class="mcp-empty-title">No MCP Servers</div>' +
|
|
4626
|
+
'<div class="mcp-empty-subtitle">MCP servers are automatically registered when you enable features like SLURM tracking or configure datasets. Start a session to activate them.</div>' +
|
|
4627
|
+
'</div>';
|
|
4628
|
+
}
|
|
4629
|
+
|
|
4630
|
+
function renderMcpServerCard(server) {
|
|
4631
|
+
var ready = !!server.ready;
|
|
4632
|
+
var configured = !!server.configured;
|
|
4633
|
+
var registered = !!server.registered;
|
|
4634
|
+
var statusClass = ready ? 'active' : (configured ? 'partial' : 'inactive');
|
|
4635
|
+
var statusLabel = ready ? 'Ready' : (configured ? 'Needs Setup' : 'Disabled');
|
|
4636
|
+
|
|
4637
|
+
var iconSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"></polyline><line x1="12" y1="19" x2="20" y2="19"></line></svg>';
|
|
4638
|
+
|
|
4639
|
+
var html = '<div class="mcp-server-card">';
|
|
4640
|
+
html += '<div class="mcp-server-header">';
|
|
4641
|
+
html += '<div class="mcp-server-icon">' + iconSvg + '</div>';
|
|
4642
|
+
html += '<span class="mcp-server-name">' + escapeHtml(server.name) + '</span>';
|
|
4643
|
+
html += '<span class="mcp-server-status ' + statusClass + '">' + statusLabel + '</span>';
|
|
4644
|
+
html += '</div>';
|
|
4645
|
+
html += '<div class="mcp-server-states">'
|
|
4646
|
+
+ '<span class="mcp-state-pill ' + (configured ? 'on' : 'off') + '">Configured: ' + (configured ? 'yes' : 'no') + '</span>'
|
|
4647
|
+
+ '<span class="mcp-state-pill ' + (registered ? 'on' : 'off') + '">Registered: ' + (registered ? 'yes' : 'no') + '</span>'
|
|
4648
|
+
+ '<span class="mcp-state-pill ' + (ready ? 'on' : 'off') + '">Ready: ' + (ready ? 'yes' : 'no') + '</span>'
|
|
4649
|
+
+ '</div>';
|
|
4650
|
+
var reason = mcpReasonText(server.reason);
|
|
4651
|
+
if (reason) {
|
|
4652
|
+
html += '<div class="mcp-server-reason">' + escapeHtml(reason) + '</div>';
|
|
4653
|
+
}
|
|
4654
|
+
html += '<div class="mcp-server-desc">' + escapeHtml(server.description) + '</div>';
|
|
4655
|
+
|
|
4656
|
+
if (server.command) {
|
|
4657
|
+
var cmdStr = server.command;
|
|
4658
|
+
if (server.args && server.args.length > 0) {
|
|
4659
|
+
cmdStr += ' ' + server.args.join(' ');
|
|
4660
|
+
}
|
|
4661
|
+
html += '<div class="mcp-server-command">' + escapeHtml(cmdStr) + '</div>';
|
|
4662
|
+
}
|
|
4663
|
+
|
|
4664
|
+
if (server.tools && server.tools.length > 0) {
|
|
4665
|
+
html += '<div class="mcp-tools-label">Commands (' + server.tools.length + ')</div>';
|
|
4666
|
+
html += '<div class="mcp-tool-list">';
|
|
4667
|
+
server.tools.forEach(function(tool) {
|
|
4668
|
+
var title = tool.title || tool.name;
|
|
4669
|
+
var example = mcpToolExample(tool.name);
|
|
4670
|
+
html += '<div class="mcp-tool-item">';
|
|
4671
|
+
html += '<div class="mcp-tool-main">';
|
|
4672
|
+
html += '<span class="mcp-tool-name">' + escapeHtml(tool.name) + '</span>';
|
|
4673
|
+
html += '<span class="mcp-tool-title">' + escapeHtml(title) + '</span>';
|
|
4674
|
+
html += '</div>';
|
|
4675
|
+
html += '<div class="mcp-tool-desc">' + escapeHtml(tool.description) + '</div>';
|
|
4676
|
+
if (example) {
|
|
4677
|
+
html += '<div class="mcp-tool-example">Try: ' + escapeHtml(example) + '</div>';
|
|
4678
|
+
}
|
|
4679
|
+
html += '</div>';
|
|
4680
|
+
});
|
|
4681
|
+
html += '</div>';
|
|
4682
|
+
}
|
|
4683
|
+
|
|
4684
|
+
html += '</div>';
|
|
4685
|
+
return html;
|
|
4686
|
+
}
|
|
4687
|
+
|
|
4688
|
+
function mcpToolExample(name) {
|
|
4689
|
+
var prompts = {
|
|
4690
|
+
get_cluster_partitions: 'Show cluster partitions and their limits.',
|
|
4691
|
+
get_partition_limits: 'Show detailed limits for partition gpu.',
|
|
4692
|
+
get_queue_pressure: 'Show queue pressure by partition.',
|
|
4693
|
+
find_module: 'Find available modules matching openmpi.',
|
|
4694
|
+
list_slurm_jobs: 'Show my running SLURM jobs.',
|
|
4695
|
+
get_slurm_job: 'Show details for SLURM job 12345.',
|
|
4696
|
+
get_slurm_output: 'Show the latest stdout for SLURM job 12345.',
|
|
4697
|
+
cancel_slurm_job: 'Cancel SLURM job 12345.',
|
|
4698
|
+
set_slurm_job_notes: 'Add a note to SLURM job 12345: GPU queue is congested.',
|
|
4699
|
+
list_datasets: 'List all datasets I can access.',
|
|
4700
|
+
inspect_dataset: 'Inspect the genomes dataset.',
|
|
4701
|
+
search_dataset: 'Search genomes for files matching "*.fa".',
|
|
4702
|
+
get_dataset_summary: 'Summarize the genomes dataset.',
|
|
4703
|
+
read_dataset_file: 'Read /README.md from the genomes dataset.',
|
|
4704
|
+
validate_dataset: 'Validate dataset path /data/genomes with name genomes in read-only mode.',
|
|
4705
|
+
register_dataset: 'Register /data/genomes as dataset genomes (read-only).',
|
|
4706
|
+
update_dataset: 'Update dataset genomes description to "Human reference genomes".',
|
|
4707
|
+
unregister_dataset: 'Remove dataset genomes.',
|
|
4708
|
+
list_results: 'List results tagged gpu from source claude.',
|
|
4709
|
+
register_result: 'Register result: GPU tuning reduced runtime by 22%.',
|
|
4710
|
+
get_result: 'Get result by id 123e4567-e89b-12d3-a456-426614174000.',
|
|
4711
|
+
update_result: 'Update result 123e... with a clearer summary.',
|
|
4712
|
+
delete_result: 'Delete result 123e...'
|
|
4713
|
+
};
|
|
4714
|
+
return prompts[name] || '';
|
|
4715
|
+
}
|
|
4716
|
+
|
|
4717
|
+
function mcpReasonText(reason) {
|
|
4718
|
+
if (!reason || reason === 'ready') return '';
|
|
4719
|
+
if (reason === 'disabled_in_config') return 'Disabled in config: enable the feature first.';
|
|
4720
|
+
if (reason === 'not_registered_yet') return 'Preparing registration. Open a session if this does not clear automatically.';
|
|
4721
|
+
if (reason === 'missing_command') return 'Invalid MCP entry: missing command.';
|
|
4722
|
+
if (reason === 'missing_bundle_or_script') return 'Registered command path is missing on disk.';
|
|
4723
|
+
if (reason === 'missing_dependency_better_sqlite3') return 'Missing runtime dependency: better-sqlite3 for SLURM MCP.';
|
|
4724
|
+
return 'Not ready: ' + reason;
|
|
4725
|
+
}
|
|
4726
|
+
|
|
4727
|
+
function initMcpBadge() {
|
|
4728
|
+
fetch('/api/mcp').then(function(r) { return r.json(); }).then(function(data) {
|
|
4729
|
+
if (!data.ok) return;
|
|
4730
|
+
var badge = document.getElementById('mcpBadge');
|
|
4731
|
+
if (data.activeCount > 0) {
|
|
4732
|
+
badge.textContent = data.activeCount;
|
|
4733
|
+
badge.style.display = '';
|
|
4734
|
+
}
|
|
4735
|
+
}).catch(function() {});
|
|
4736
|
+
}
|
|
4737
|
+
|
|
3038
4738
|
// ── SLURM Jobs ───────────────────────────────
|
|
3039
4739
|
var slurmEnabled = false;
|
|
3040
4740
|
var slurmSearchTimeout = null;
|
|
@@ -3046,8 +4746,10 @@ function initSlurmTab() {
|
|
|
3046
4746
|
fetch('/api/config').then(function(r) { return r.json(); }).then(function(cfg) {
|
|
3047
4747
|
if (cfg && cfg.slurm && cfg.slurm.enabled) {
|
|
3048
4748
|
slurmEnabled = true;
|
|
3049
|
-
var
|
|
3050
|
-
|
|
4749
|
+
var emptyEl = document.getElementById('jobsEmptyState');
|
|
4750
|
+
var contentEl = document.getElementById('jobsSlurmContent');
|
|
4751
|
+
if (emptyEl) emptyEl.style.display = 'none';
|
|
4752
|
+
if (contentEl) contentEl.style.display = '';
|
|
3051
4753
|
loadSlurmJobs();
|
|
3052
4754
|
}
|
|
3053
4755
|
}).catch(function() {});
|
|
@@ -3229,12 +4931,14 @@ function editSlurmNotes(jobId) {
|
|
|
3229
4931
|
var job = _slurmJobsCache.find(function(j){ return j.job_id === jobId; });
|
|
3230
4932
|
document.getElementById('slurmNotesSubtitle').textContent = 'Job ' + jobId + (job && job.name ? ' (' + job.name + ')' : '');
|
|
3231
4933
|
document.getElementById('slurmNotesTextarea').value = (job && job.notes) || '';
|
|
3232
|
-
document.getElementById('slurmNotesModal').
|
|
4934
|
+
document.getElementById('slurmNotesModal').classList.add('visible');
|
|
4935
|
+
document.body.style.overflow = 'hidden';
|
|
3233
4936
|
document.getElementById('slurmNotesTextarea').focus();
|
|
3234
4937
|
}
|
|
3235
4938
|
|
|
3236
4939
|
function closeSlurmNotes() {
|
|
3237
|
-
document.getElementById('slurmNotesModal').
|
|
4940
|
+
document.getElementById('slurmNotesModal').classList.remove('visible');
|
|
4941
|
+
document.body.style.overflow = '';
|
|
3238
4942
|
_notesJobId = null;
|
|
3239
4943
|
}
|
|
3240
4944
|
|
|
@@ -3262,7 +4966,8 @@ function openSlurmOutput(jobId, stream) {
|
|
|
3262
4966
|
document.getElementById('slurmOutStdout').classList.toggle('active', currentSlurmOutputStream === 'stdout');
|
|
3263
4967
|
document.getElementById('slurmOutStderr').classList.toggle('active', currentSlurmOutputStream === 'stderr');
|
|
3264
4968
|
|
|
3265
|
-
document.getElementById('slurmOutputModal').
|
|
4969
|
+
document.getElementById('slurmOutputModal').classList.add('visible');
|
|
4970
|
+
document.body.style.overflow = 'hidden';
|
|
3266
4971
|
refreshSlurmOutput();
|
|
3267
4972
|
}
|
|
3268
4973
|
|
|
@@ -3298,7 +5003,8 @@ function refreshSlurmOutput() {
|
|
|
3298
5003
|
}
|
|
3299
5004
|
|
|
3300
5005
|
function closeSlurmOutput() {
|
|
3301
|
-
document.getElementById('slurmOutputModal').
|
|
5006
|
+
document.getElementById('slurmOutputModal').classList.remove('visible');
|
|
5007
|
+
document.body.style.overflow = '';
|
|
3302
5008
|
currentSlurmOutputJobId = null;
|
|
3303
5009
|
}
|
|
3304
5010
|
|
|
@@ -3370,7 +5076,16 @@ function applyFieldLocks(data) {
|
|
|
3370
5076
|
var field = el.closest('.field');
|
|
3371
5077
|
if (field) field.classList.add('field-locked');
|
|
3372
5078
|
el.disabled = true;
|
|
3373
|
-
el.title = '
|
|
5079
|
+
el.title = 'Set by admin';
|
|
5080
|
+
if (field) {
|
|
5081
|
+
var label = field.querySelector('label');
|
|
5082
|
+
if (label && !label.querySelector('.set-by-admin-note')) {
|
|
5083
|
+
var note = document.createElement('span');
|
|
5084
|
+
note.className = 'set-by-admin-note';
|
|
5085
|
+
note.textContent = 'Set by admin';
|
|
5086
|
+
label.appendChild(note);
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
3374
5089
|
});
|
|
3375
5090
|
|
|
3376
5091
|
// Lock network mode pills if network.mode is locked
|
|
@@ -3462,6 +5177,7 @@ function initAdminTabs(bootstrapOnly) {
|
|
|
3462
5177
|
document.querySelectorAll('.tab').forEach(function(t) { t.classList.remove('active'); });
|
|
3463
5178
|
document.querySelectorAll('.tab-panel').forEach(function(p) { p.classList.remove('active'); });
|
|
3464
5179
|
btn.classList.add('active');
|
|
5180
|
+
setMcpTabActive(false);
|
|
3465
5181
|
var panel = document.getElementById('panel-admin-' + name.toLowerCase());
|
|
3466
5182
|
if (panel) { panel.style.display = ''; panel.classList.add('active'); }
|
|
3467
5183
|
if (name === 'Policy') loadAdminPolicy();
|
|
@@ -3577,6 +5293,7 @@ loadConfig();
|
|
|
3577
5293
|
loadConfigPath();
|
|
3578
5294
|
loadSessions();
|
|
3579
5295
|
loadSecurity();
|
|
5296
|
+
initMcpBadge();
|
|
3580
5297
|
initSlurmTab();
|
|
3581
5298
|
initEnterprise();
|
|
3582
5299
|
</script>
|