labgate 0.5.33 → 0.5.35
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 +2 -0
- package/dist/cli.js +12 -0
- package/dist/cli.js.map +1 -1
- package/dist/lib/config.d.ts +9 -0
- package/dist/lib/config.js +22 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/container.js +3 -2
- package/dist/lib/container.js.map +1 -1
- package/dist/lib/explorer-claude.js +1 -0
- package/dist/lib/explorer-claude.js.map +1 -1
- package/dist/lib/init.js +6 -0
- package/dist/lib/init.js.map +1 -1
- package/dist/lib/test/integration-harness.js +16 -2
- package/dist/lib/test/integration-harness.js.map +1 -1
- package/dist/lib/ui.d.ts +1 -0
- package/dist/lib/ui.html +732 -224
- package/dist/lib/ui.js +377 -3
- package/dist/lib/ui.js.map +1 -1
- package/dist/lib/web-terminal.js +2 -1
- package/dist/lib/web-terminal.js.map +1 -1
- package/dist/mcp-bundles/dataset-mcp.bundle.mjs +14 -0
- package/dist/mcp-bundles/explorer-mcp.bundle.mjs +19 -0
- package/package.json +1 -1
package/dist/lib/ui.html
CHANGED
|
@@ -1462,6 +1462,31 @@
|
|
|
1462
1462
|
font-family: 'GeistMono', monospace;
|
|
1463
1463
|
}
|
|
1464
1464
|
|
|
1465
|
+
.ui-update-row-actions {
|
|
1466
|
+
display: flex;
|
|
1467
|
+
flex-wrap: wrap;
|
|
1468
|
+
align-items: center;
|
|
1469
|
+
gap: 8px;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
.ui-update-row-actions .settings-info-value {
|
|
1473
|
+
flex: 1 1 220px;
|
|
1474
|
+
min-width: 0;
|
|
1475
|
+
margin: 0;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
.ui-update-status.warning {
|
|
1479
|
+
color: #92400e;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
.ui-update-status.error {
|
|
1483
|
+
color: #b91c1c;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
.ui-update-status.success {
|
|
1487
|
+
color: #166534;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1465
1490
|
/* ── Terminal compact footer ──────────────────── */
|
|
1466
1491
|
.terminal-compact-footer {
|
|
1467
1492
|
display: flex;
|
|
@@ -4745,26 +4770,153 @@
|
|
|
4745
4770
|
flex-direction: column;
|
|
4746
4771
|
align-items: flex-start;
|
|
4747
4772
|
}
|
|
4748
|
-
|
|
4749
|
-
|
|
4773
|
+
/* ── Compact SLURM sidebar ── */
|
|
4774
|
+
.slurm-compact-toolbar {
|
|
4775
|
+
display: flex;
|
|
4776
|
+
align-items: center;
|
|
4777
|
+
justify-content: space-between;
|
|
4778
|
+
gap: 8px;
|
|
4779
|
+
margin-bottom: 8px;
|
|
4750
4780
|
}
|
|
4751
|
-
.
|
|
4752
|
-
|
|
4753
|
-
|
|
4781
|
+
.slurm-scope-toggle {
|
|
4782
|
+
display: inline-flex;
|
|
4783
|
+
border: 1px solid var(--border-color);
|
|
4784
|
+
border-radius: 6px;
|
|
4785
|
+
background: var(--bg-secondary);
|
|
4786
|
+
padding: 2px;
|
|
4787
|
+
gap: 1px;
|
|
4754
4788
|
}
|
|
4755
|
-
.
|
|
4756
|
-
|
|
4789
|
+
.slurm-scope-btn {
|
|
4790
|
+
border: none;
|
|
4791
|
+
border-radius: 4px;
|
|
4792
|
+
background: transparent;
|
|
4793
|
+
color: var(--text-muted);
|
|
4794
|
+
font-family: 'GeistMono', monospace;
|
|
4795
|
+
font-size: 0.625rem;
|
|
4796
|
+
font-weight: 600;
|
|
4797
|
+
padding: 3px 8px;
|
|
4798
|
+
cursor: pointer;
|
|
4799
|
+
transition: background 0.15s, color 0.15s;
|
|
4800
|
+
}
|
|
4801
|
+
.slurm-scope-btn:hover { color: var(--text-primary); }
|
|
4802
|
+
.slurm-scope-btn.active {
|
|
4803
|
+
background: var(--card-bg);
|
|
4804
|
+
color: var(--text-primary);
|
|
4805
|
+
box-shadow: 0 1px 2px rgba(0,0,0,0.06);
|
|
4806
|
+
}
|
|
4807
|
+
.slurm-compact-stats {
|
|
4808
|
+
display: flex;
|
|
4809
|
+
gap: 8px;
|
|
4757
4810
|
}
|
|
4758
|
-
.
|
|
4811
|
+
.slurm-mini-stat {
|
|
4812
|
+
font-family: 'GeistMono', monospace;
|
|
4813
|
+
font-size: 0.625rem;
|
|
4814
|
+
color: var(--text-muted);
|
|
4815
|
+
}
|
|
4816
|
+
.slurm-mini-stat span { font-weight: 600; }
|
|
4817
|
+
.slurm-mini-pending span { color: #f59e0b; }
|
|
4818
|
+
.slurm-mini-running span { color: #16a34a; }
|
|
4819
|
+
.slurm-compact-filters {
|
|
4820
|
+
display: flex;
|
|
4821
|
+
gap: 6px;
|
|
4822
|
+
margin-bottom: 8px;
|
|
4823
|
+
}
|
|
4824
|
+
.slurm-compact-filters select,
|
|
4825
|
+
.slurm-compact-filters input {
|
|
4826
|
+
flex: 1;
|
|
4827
|
+
padding: 4px 8px;
|
|
4828
|
+
border: 1px solid var(--border-color);
|
|
4829
|
+
border-radius: 6px;
|
|
4830
|
+
background: var(--bg-secondary);
|
|
4831
|
+
font-family: 'GeistMono', monospace;
|
|
4832
|
+
font-size: 0.6875rem;
|
|
4833
|
+
color: var(--text-primary);
|
|
4834
|
+
}
|
|
4835
|
+
.slurm-compact-filters select { flex: 0 0 auto; min-width: 90px; }
|
|
4836
|
+
|
|
4837
|
+
/* ── SLURM job cards ── */
|
|
4838
|
+
.slurm-job-grid {
|
|
4839
|
+
display: flex;
|
|
4759
4840
|
flex-direction: column;
|
|
4841
|
+
gap: 2px;
|
|
4842
|
+
}
|
|
4843
|
+
.slurm-job-card {
|
|
4844
|
+
padding: 7px 8px;
|
|
4845
|
+
border-radius: 6px;
|
|
4846
|
+
background: transparent;
|
|
4847
|
+
cursor: pointer;
|
|
4848
|
+
transition: background 0.15s;
|
|
4849
|
+
}
|
|
4850
|
+
.slurm-job-card:hover { background: var(--bg-secondary); }
|
|
4851
|
+
.slurm-job-card-row {
|
|
4852
|
+
display: flex;
|
|
4853
|
+
align-items: center;
|
|
4760
4854
|
gap: 6px;
|
|
4761
4855
|
}
|
|
4762
|
-
.
|
|
4763
|
-
|
|
4856
|
+
.slurm-job-id {
|
|
4857
|
+
font-family: 'GeistMono', monospace;
|
|
4858
|
+
font-size: 0.8125rem;
|
|
4859
|
+
font-weight: 600;
|
|
4860
|
+
color: var(--text-primary);
|
|
4861
|
+
white-space: nowrap;
|
|
4764
4862
|
}
|
|
4765
|
-
.
|
|
4766
|
-
|
|
4863
|
+
.slurm-job-name {
|
|
4864
|
+
font-size: 0.75rem;
|
|
4865
|
+
color: var(--text-secondary);
|
|
4866
|
+
overflow: hidden;
|
|
4867
|
+
text-overflow: ellipsis;
|
|
4868
|
+
white-space: nowrap;
|
|
4869
|
+
flex: 1;
|
|
4870
|
+
min-width: 0;
|
|
4767
4871
|
}
|
|
4872
|
+
.slurm-job-state {
|
|
4873
|
+
display: inline-block;
|
|
4874
|
+
padding: 1px 6px;
|
|
4875
|
+
border-radius: 3px;
|
|
4876
|
+
font-family: 'GeistMono', monospace;
|
|
4877
|
+
font-size: 0.5625rem;
|
|
4878
|
+
font-weight: 600;
|
|
4879
|
+
text-transform: uppercase;
|
|
4880
|
+
letter-spacing: 0.03em;
|
|
4881
|
+
white-space: nowrap;
|
|
4882
|
+
}
|
|
4883
|
+
.slurm-job-state-PENDING { background: rgba(245,158,11,0.12); color: #b45309; }
|
|
4884
|
+
.slurm-job-state-RUNNING { background: rgba(22,163,74,0.12); color: #15803d; }
|
|
4885
|
+
.slurm-job-state-COMPLETED { background: rgba(96,165,250,0.12); color: #2563eb; }
|
|
4886
|
+
.slurm-job-state-FAILED { background: rgba(220,38,38,0.12); color: #dc2626; }
|
|
4887
|
+
.slurm-job-state-CANCELLED { background: rgba(156,163,175,0.12); color: #6b7280; }
|
|
4888
|
+
.slurm-job-state-TIMEOUT { background: rgba(168,85,247,0.12); color: #7c3aed; }
|
|
4889
|
+
.slurm-job-meta {
|
|
4890
|
+
font-family: 'GeistMono', monospace;
|
|
4891
|
+
font-size: 0.625rem;
|
|
4892
|
+
color: var(--text-muted);
|
|
4893
|
+
margin-top: 2px;
|
|
4894
|
+
display: flex;
|
|
4895
|
+
gap: 8px;
|
|
4896
|
+
align-items: center;
|
|
4897
|
+
}
|
|
4898
|
+
.slurm-job-actions {
|
|
4899
|
+
display: flex;
|
|
4900
|
+
gap: 4px;
|
|
4901
|
+
margin-left: auto;
|
|
4902
|
+
}
|
|
4903
|
+
.slurm-job-action-btn {
|
|
4904
|
+
border: none;
|
|
4905
|
+
background: transparent;
|
|
4906
|
+
cursor: pointer;
|
|
4907
|
+
font-family: 'GeistMono', monospace;
|
|
4908
|
+
font-size: 0.5625rem;
|
|
4909
|
+
color: var(--text-muted);
|
|
4910
|
+
padding: 1px 4px;
|
|
4911
|
+
border-radius: 3px;
|
|
4912
|
+
transition: background 0.15s, color 0.15s;
|
|
4913
|
+
}
|
|
4914
|
+
.slurm-job-action-btn:hover { background: var(--bg-secondary); color: var(--text-primary); }
|
|
4915
|
+
.slurm-job-action-btn.danger { color: #dc2626; }
|
|
4916
|
+
.slurm-job-action-btn.danger:hover { background: rgba(220,38,38,0.08); }
|
|
4917
|
+
|
|
4918
|
+
.sidebar-section-body .slurm-summary { display: none; }
|
|
4919
|
+
.sidebar-section-body .card { overflow-x: auto; }
|
|
4768
4920
|
/* ── Enterprise: locked fields ─────────────── */
|
|
4769
4921
|
.field-locked label::before { content: '\1F512 '; font-size: 0.75em; }
|
|
4770
4922
|
.field-locked input, .field-locked select, .field-locked textarea {
|
|
@@ -5221,9 +5373,6 @@
|
|
|
5221
5373
|
background: var(--card-bg);
|
|
5222
5374
|
border-color: var(--border-color);
|
|
5223
5375
|
}
|
|
5224
|
-
.terminal-chat-msg.assistant {
|
|
5225
|
-
margin-right: auto;
|
|
5226
|
-
}
|
|
5227
5376
|
.terminal-chat-msg.error {
|
|
5228
5377
|
border-color: #fecaca;
|
|
5229
5378
|
background: #fef2f2;
|
|
@@ -5273,131 +5422,157 @@
|
|
|
5273
5422
|
font-size: inherit;
|
|
5274
5423
|
color: inherit;
|
|
5275
5424
|
}
|
|
5276
|
-
.terminal-chat-msg.pending .terminal-chat-msg-header::after {
|
|
5277
|
-
content: '';
|
|
5278
|
-
display: inline-block;
|
|
5279
|
-
width: 6px;
|
|
5280
|
-
height: 6px;
|
|
5281
|
-
margin-left: 6px;
|
|
5282
|
-
border-radius: 50%;
|
|
5283
|
-
background: var(--accent);
|
|
5284
|
-
animation: chatPulse 1.4s ease-in-out infinite;
|
|
5285
|
-
vertical-align: middle;
|
|
5286
|
-
}
|
|
5287
5425
|
@keyframes chatPulse {
|
|
5288
5426
|
0%, 100% { opacity: 0.25; transform: scale(0.85); }
|
|
5289
5427
|
50% { opacity: 1; transform: scale(1); }
|
|
5290
5428
|
}
|
|
5291
5429
|
|
|
5292
|
-
/* ──
|
|
5293
|
-
.
|
|
5294
|
-
|
|
5295
|
-
align-items: center;
|
|
5296
|
-
gap: 7px;
|
|
5297
|
-
max-width: min(600px, 90%);
|
|
5430
|
+
/* ── Claude Turn Container ── */
|
|
5431
|
+
.claude-turn {
|
|
5432
|
+
max-width: min(820px, 100%);
|
|
5298
5433
|
margin-right: auto;
|
|
5299
|
-
|
|
5300
|
-
border
|
|
5301
|
-
background:
|
|
5302
|
-
|
|
5303
|
-
font-size: 0.6875rem;
|
|
5304
|
-
color: var(--text-secondary);
|
|
5305
|
-
transition: opacity 0.2s;
|
|
5306
|
-
border-left: 2px solid var(--border-color);
|
|
5434
|
+
border-radius: 10px;
|
|
5435
|
+
border: 1px solid var(--border-color);
|
|
5436
|
+
background: var(--card-bg);
|
|
5437
|
+
overflow: hidden;
|
|
5307
5438
|
flex-shrink: 0;
|
|
5308
5439
|
}
|
|
5309
|
-
.
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
.terminal-chat-tool .tool-icon {
|
|
5313
|
-
flex-shrink: 0;
|
|
5314
|
-
font-size: 0.625rem;
|
|
5315
|
-
width: 14px;
|
|
5316
|
-
text-align: center;
|
|
5440
|
+
.claude-turn.error {
|
|
5441
|
+
border-color: #fecaca;
|
|
5442
|
+
background: #fef2f2;
|
|
5317
5443
|
}
|
|
5318
|
-
.
|
|
5444
|
+
.claude-turn-header {
|
|
5445
|
+
padding: 5px 10px;
|
|
5319
5446
|
font-family: 'GeistPixelSquare', monospace;
|
|
5320
5447
|
font-size: 0.5625rem;
|
|
5448
|
+
letter-spacing: 0.05em;
|
|
5321
5449
|
font-weight: 600;
|
|
5322
5450
|
text-transform: uppercase;
|
|
5323
|
-
letter-spacing: 0.04em;
|
|
5324
|
-
color: var(--text-secondary);
|
|
5325
|
-
white-space: nowrap;
|
|
5326
|
-
}
|
|
5327
|
-
.terminal-chat-tool .tool-detail {
|
|
5328
5451
|
color: var(--text-muted);
|
|
5329
|
-
overflow: hidden;
|
|
5330
|
-
text-overflow: ellipsis;
|
|
5331
|
-
white-space: nowrap;
|
|
5332
|
-
}
|
|
5333
|
-
.terminal-chat-tool.pending {
|
|
5334
|
-
border-left-color: var(--accent);
|
|
5335
5452
|
}
|
|
5336
|
-
.
|
|
5453
|
+
.claude-turn.pending .claude-turn-header::after {
|
|
5454
|
+
content: '';
|
|
5337
5455
|
display: inline-block;
|
|
5338
5456
|
width: 6px;
|
|
5339
5457
|
height: 6px;
|
|
5458
|
+
margin-left: 6px;
|
|
5340
5459
|
border-radius: 50%;
|
|
5341
5460
|
background: var(--accent);
|
|
5342
5461
|
animation: chatPulse 1.4s ease-in-out infinite;
|
|
5343
|
-
|
|
5344
|
-
line-height: 0;
|
|
5462
|
+
vertical-align: middle;
|
|
5345
5463
|
}
|
|
5346
|
-
.
|
|
5347
|
-
|
|
5464
|
+
.claude-turn-body {
|
|
5465
|
+
padding: 0 12px 10px;
|
|
5348
5466
|
}
|
|
5349
|
-
|
|
5350
|
-
|
|
5467
|
+
|
|
5468
|
+
/* ── Text segments inside a turn ── */
|
|
5469
|
+
.claude-turn-text {
|
|
5470
|
+
font-family: 'GeistMono', monospace;
|
|
5471
|
+
font-size: 0.8rem;
|
|
5472
|
+
line-height: 1.55;
|
|
5473
|
+
color: var(--text-primary);
|
|
5474
|
+
white-space: pre-wrap;
|
|
5475
|
+
word-break: break-word;
|
|
5351
5476
|
}
|
|
5352
|
-
.
|
|
5353
|
-
|
|
5477
|
+
.claude-turn-text + .claude-turn-text,
|
|
5478
|
+
.claude-turn-tools + .claude-turn-text {
|
|
5479
|
+
margin-top: 8px;
|
|
5354
5480
|
}
|
|
5355
|
-
.
|
|
5356
|
-
|
|
5481
|
+
.claude-turn-text code {
|
|
5482
|
+
background: var(--bg-secondary);
|
|
5483
|
+
border: 1px solid var(--border-color);
|
|
5484
|
+
border-radius: 4px;
|
|
5485
|
+
padding: 1px 4px;
|
|
5486
|
+
font-size: 0.75rem;
|
|
5487
|
+
}
|
|
5488
|
+
.claude-turn-text pre {
|
|
5489
|
+
background: #1e1e2e;
|
|
5490
|
+
color: #cdd6f4;
|
|
5491
|
+
border-radius: 6px;
|
|
5492
|
+
padding: 10px 12px;
|
|
5493
|
+
margin: 6px 0;
|
|
5494
|
+
overflow-x: auto;
|
|
5495
|
+
font-size: 0.75rem;
|
|
5496
|
+
line-height: 1.5;
|
|
5497
|
+
}
|
|
5498
|
+
.claude-turn-text pre code {
|
|
5499
|
+
background: none;
|
|
5500
|
+
border: none;
|
|
5501
|
+
padding: 0;
|
|
5502
|
+
font-size: inherit;
|
|
5503
|
+
color: inherit;
|
|
5357
5504
|
}
|
|
5358
5505
|
|
|
5359
|
-
/* ──
|
|
5360
|
-
.
|
|
5506
|
+
/* ── Tool chips (inline in turn) ── */
|
|
5507
|
+
.claude-turn-tools {
|
|
5361
5508
|
display: flex;
|
|
5362
|
-
flex-
|
|
5363
|
-
gap:
|
|
5364
|
-
|
|
5509
|
+
flex-wrap: wrap;
|
|
5510
|
+
gap: 6px;
|
|
5511
|
+
margin: 8px 0;
|
|
5512
|
+
align-items: center;
|
|
5513
|
+
}
|
|
5514
|
+
.tool-chip {
|
|
5515
|
+
display: inline-flex;
|
|
5516
|
+
align-items: center;
|
|
5517
|
+
gap: 4px;
|
|
5518
|
+
padding: 2px 8px;
|
|
5519
|
+
border-radius: 4px;
|
|
5520
|
+
background: var(--bg-secondary);
|
|
5521
|
+
font-family: 'GeistMono', monospace;
|
|
5522
|
+
font-size: 0.6875rem;
|
|
5523
|
+
color: var(--text-secondary);
|
|
5524
|
+
cursor: default;
|
|
5525
|
+
white-space: nowrap;
|
|
5526
|
+
border: 1px solid transparent;
|
|
5527
|
+
transition: background 0.15s, border-color 0.15s;
|
|
5365
5528
|
}
|
|
5366
|
-
.
|
|
5367
|
-
|
|
5529
|
+
.tool-chip:hover {
|
|
5530
|
+
background: var(--accent-light);
|
|
5531
|
+
border-color: var(--border-color);
|
|
5368
5532
|
}
|
|
5369
|
-
.
|
|
5370
|
-
|
|
5533
|
+
.tool-chip-icon {
|
|
5534
|
+
font-size: 0.625rem;
|
|
5535
|
+
flex-shrink: 0;
|
|
5536
|
+
width: 12px;
|
|
5537
|
+
text-align: center;
|
|
5371
5538
|
}
|
|
5372
|
-
.
|
|
5373
|
-
|
|
5539
|
+
.tool-chip.done .tool-chip-icon { color: #16a34a; }
|
|
5540
|
+
.tool-chip.error .tool-chip-icon { color: #dc2626; }
|
|
5541
|
+
.tool-chip.pending .tool-chip-icon {
|
|
5542
|
+
display: inline-block;
|
|
5543
|
+
width: 6px;
|
|
5544
|
+
height: 6px;
|
|
5545
|
+
border-radius: 50%;
|
|
5546
|
+
background: var(--accent);
|
|
5547
|
+
animation: chatPulse 1.4s ease-in-out infinite;
|
|
5548
|
+
font-size: 0;
|
|
5549
|
+
line-height: 0;
|
|
5374
5550
|
}
|
|
5375
|
-
.tool-
|
|
5376
|
-
|
|
5551
|
+
.tool-chip-label { font-weight: 500; }
|
|
5552
|
+
|
|
5553
|
+
/* Collapsed chips */
|
|
5554
|
+
.tool-chip.tool-chip-collapsed { display: none; }
|
|
5555
|
+
.claude-turn-tools.expanded .tool-chip.tool-chip-collapsed { display: inline-flex; }
|
|
5556
|
+
|
|
5557
|
+
/* Toggle button for collapsed chips */
|
|
5558
|
+
.tool-chips-toggle {
|
|
5559
|
+
display: inline-flex;
|
|
5377
5560
|
align-items: center;
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
border: none;
|
|
5561
|
+
padding: 2px 8px;
|
|
5562
|
+
border: 1px solid var(--border-color);
|
|
5563
|
+
border-radius: 4px;
|
|
5382
5564
|
background: transparent;
|
|
5383
5565
|
cursor: pointer;
|
|
5384
5566
|
font-family: 'GeistMono', monospace;
|
|
5385
5567
|
font-size: 0.625rem;
|
|
5386
5568
|
color: var(--text-muted);
|
|
5387
|
-
|
|
5388
|
-
transition: color 0.15s;
|
|
5569
|
+
transition: color 0.15s, background 0.15s;
|
|
5389
5570
|
}
|
|
5390
|
-
.tool-
|
|
5571
|
+
.tool-chips-toggle:hover {
|
|
5391
5572
|
color: var(--text-secondary);
|
|
5573
|
+
background: var(--accent-light);
|
|
5392
5574
|
}
|
|
5393
|
-
.
|
|
5394
|
-
display: inline-block;
|
|
5395
|
-
transition: transform 0.15s;
|
|
5396
|
-
font-size: 0.5rem;
|
|
5397
|
-
}
|
|
5398
|
-
.terminal-chat-tool-group.expanded .tool-group-toggle .toggle-caret {
|
|
5399
|
-
transform: rotate(90deg);
|
|
5400
|
-
}
|
|
5575
|
+
.claude-turn-tools.expanded .tool-chips-toggle { display: none; }
|
|
5401
5576
|
|
|
5402
5577
|
/* ── Rich content widgets (inline in chat transcript) ── */
|
|
5403
5578
|
.terminal-chat-widget {
|
|
@@ -5956,10 +6131,6 @@
|
|
|
5956
6131
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
|
5957
6132
|
<span>Settings</span>
|
|
5958
6133
|
</button>
|
|
5959
|
-
<button class="left-sidebar-footer-item" id="explorerModeToggleBtn" onclick="toggleExplorerDevMode()" title="Toggle Solution Explorer mode">
|
|
5960
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3v12"/><path d="M18 9v12"/><path d="M6 7c0 2.5 3 2.5 6 5s6 2.5 6 5"/><circle cx="6" cy="3" r="2"/><circle cx="18" cy="9" r="2"/><circle cx="18" cy="21" r="2"/></svg>
|
|
5961
|
-
<span id="explorerModeToggleLabel">Explorer: Off</span>
|
|
5962
|
-
</button>
|
|
5963
6134
|
<!-- Hidden buttons to preserve JS getElementById references -->
|
|
5964
6135
|
<button id="notifyToggleBtn" onclick="toggleNotify()" aria-label="Toggle desktop notifications" aria-pressed="false" style="display:none"></button>
|
|
5965
6136
|
<button id="sidebarToggleBtn" onclick="toggleSidebar()" style="display:none"></button>
|
|
@@ -6048,7 +6219,6 @@
|
|
|
6048
6219
|
<div class="terminal-input-toolbar">
|
|
6049
6220
|
<div class="terminal-input-mode-toggle">
|
|
6050
6221
|
<button class="terminal-input-mode-btn" id="terminalInputModeChatBtn" type="button" onclick="setTerminalInputMode('chat', { focus: true })">Chat</button>
|
|
6051
|
-
<button class="terminal-input-mode-btn active" id="terminalInputModeRawBtn" type="button" onclick="setTerminalInputMode('raw', { focus: true })">Raw</button>
|
|
6052
6222
|
</div>
|
|
6053
6223
|
</div>
|
|
6054
6224
|
<textarea id="terminalChatInput" class="terminal-input-chat" placeholder="Type command or prompt. Enter sends, Shift+Enter inserts newline." onkeydown="handleTerminalChatInputKeydown(event)" spellcheck="false"></textarea>
|
|
@@ -6114,14 +6284,17 @@
|
|
|
6114
6284
|
</div>
|
|
6115
6285
|
</div>
|
|
6116
6286
|
<div id="jobsSlurmContent" style="display:none">
|
|
6117
|
-
<div class="slurm-
|
|
6118
|
-
<div class="slurm-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
<div class="slurm-
|
|
6287
|
+
<div class="slurm-compact-toolbar">
|
|
6288
|
+
<div class="slurm-scope-toggle">
|
|
6289
|
+
<button class="slurm-scope-btn active" id="slurmScopeMine" type="button" onclick="setSlurmScope('mine')">My session</button>
|
|
6290
|
+
<button class="slurm-scope-btn" id="slurmScopeAll" type="button" onclick="setSlurmScope('all')">All jobs</button>
|
|
6291
|
+
</div>
|
|
6292
|
+
<div class="slurm-compact-stats">
|
|
6293
|
+
<span class="slurm-mini-stat slurm-mini-pending" title="Pending"><span id="slurmPending">0</span> pending</span>
|
|
6294
|
+
<span class="slurm-mini-stat slurm-mini-running" title="Running"><span id="slurmRunning">0</span> running</span>
|
|
6295
|
+
</div>
|
|
6123
6296
|
</div>
|
|
6124
|
-
<div class="slurm-filters">
|
|
6297
|
+
<div class="slurm-compact-filters">
|
|
6125
6298
|
<select id="slurmStateFilter" onchange="loadSlurmJobs()">
|
|
6126
6299
|
<option value="">All states</option>
|
|
6127
6300
|
<option value="PENDING">Pending</option>
|
|
@@ -6132,11 +6305,12 @@
|
|
|
6132
6305
|
<option value="TIMEOUT">Timeout</option>
|
|
6133
6306
|
</select>
|
|
6134
6307
|
<input type="text" id="slurmSearch" placeholder="Search..." oninput="debounceSlurmSearch()">
|
|
6135
|
-
<button class="refresh-btn" onclick="loadSlurmJobs()">Refresh</button>
|
|
6136
|
-
</div>
|
|
6137
|
-
<div class="card">
|
|
6138
|
-
<div id="slurmJobsContent"><div class="empty-state">Loading SLURM jobs...</div></div>
|
|
6139
6308
|
</div>
|
|
6309
|
+
<div id="slurmJobsContent"><div class="empty-state" style="padding:12px">Loading...</div></div>
|
|
6310
|
+
<!-- hidden stats for backward compat -->
|
|
6311
|
+
<span id="slurmCompleted" style="display:none">0</span>
|
|
6312
|
+
<span id="slurmFailed" style="display:none">0</span>
|
|
6313
|
+
<span id="slurmOther" style="display:none">0</span>
|
|
6140
6314
|
</div>
|
|
6141
6315
|
</div>
|
|
6142
6316
|
</div>
|
|
@@ -6360,6 +6534,18 @@
|
|
|
6360
6534
|
<h3>Session Info</h3>
|
|
6361
6535
|
<p class="card-description">Reference metadata for this LabGate UI session.</p>
|
|
6362
6536
|
<div class="settings-info-list">
|
|
6537
|
+
<div class="settings-info-row">
|
|
6538
|
+
<span class="settings-info-label">LabGate Version</span>
|
|
6539
|
+
<code class="settings-info-value" id="labgateVersionValue">Loading...</code>
|
|
6540
|
+
</div>
|
|
6541
|
+
<div class="settings-info-row">
|
|
6542
|
+
<span class="settings-info-label">UI Update</span>
|
|
6543
|
+
<div class="ui-update-row-actions">
|
|
6544
|
+
<code class="settings-info-value ui-update-status" id="uiUpdateStatusValue">Checking...</code>
|
|
6545
|
+
<button type="button" class="agent-update-btn" id="uiUpdateCheckBtn" onclick="checkUiVersion(true)">Search for Update</button>
|
|
6546
|
+
<button type="button" class="agent-update-btn" id="uiUpdateApplyBtn" style="display:none" onclick="startUiUpdate()">Update</button>
|
|
6547
|
+
</div>
|
|
6548
|
+
</div>
|
|
6363
6549
|
<div class="settings-info-row">
|
|
6364
6550
|
<span class="settings-info-label">Config JSON Path</span>
|
|
6365
6551
|
<code class="settings-info-value" id="configPathValue">Loading...</code>
|
|
@@ -6375,6 +6561,13 @@
|
|
|
6375
6561
|
<span>Enable headless chat mode</span>
|
|
6376
6562
|
</label>
|
|
6377
6563
|
</div>
|
|
6564
|
+
<div class="field" style="margin-top: 12px">
|
|
6565
|
+
<label class="toggle-row" style="display:flex;align-items:center;gap:10px;cursor:pointer">
|
|
6566
|
+
<input type="checkbox" id="headlessAllowedPermissions" onchange="collectConfig();markDirty();">
|
|
6567
|
+
<span>Run Claude headless with allowed permissions</span>
|
|
6568
|
+
</label>
|
|
6569
|
+
<p class="card-description" style="margin-top:6px">Adds <code>--dangerously-skip-permissions</code> to Claude headless runs so tools can execute without interactive approval prompts.</p>
|
|
6570
|
+
</div>
|
|
6378
6571
|
</div>
|
|
6379
6572
|
</div>
|
|
6380
6573
|
|
|
@@ -6713,6 +6906,8 @@ var LABGATE_WRITE_TOKEN = '__LABGATE_WRITE_TOKEN__';
|
|
|
6713
6906
|
var mcpAutoRefreshTimer = null;
|
|
6714
6907
|
var mcpAutoRegisterInFlight = false;
|
|
6715
6908
|
var MCP_AUTO_REFRESH_MS = 12000;
|
|
6909
|
+
var uiUpdateInProgress = false;
|
|
6910
|
+
var uiUpdateStatusPollTimer = null;
|
|
6716
6911
|
var resultsCache = [];
|
|
6717
6912
|
var resultEditorId = null;
|
|
6718
6913
|
var resultsHasPendingRefresh = false;
|
|
@@ -7012,8 +7207,13 @@ function getWebTerminalSessionRuntime(session) {
|
|
|
7012
7207
|
function toggleHeadlessEnabled(enabled) {
|
|
7013
7208
|
webTerm.headlessEnabled = !!enabled;
|
|
7014
7209
|
try { localStorage.setItem('labgate_headless_enabled', enabled ? '1' : '0'); } catch(e) {}
|
|
7015
|
-
if (
|
|
7210
|
+
if (enabled) {
|
|
7211
|
+
setTerminalInputMode('chat', { focus: false });
|
|
7212
|
+
return;
|
|
7213
|
+
}
|
|
7214
|
+
if (webTerm.inputMode === 'chat') {
|
|
7016
7215
|
setTerminalInputMode('raw', { focus: false });
|
|
7216
|
+
return;
|
|
7017
7217
|
}
|
|
7018
7218
|
updateTerminalInputModeUi();
|
|
7019
7219
|
updateTerminalInputAvailability();
|
|
@@ -7095,6 +7295,19 @@ function updateHeadlessTranscriptMessage(sessionId, messageId, patch) {
|
|
|
7095
7295
|
renderTerminalChatTranscript();
|
|
7096
7296
|
}
|
|
7097
7297
|
|
|
7298
|
+
function shortenToolName(raw) {
|
|
7299
|
+
var name = String(raw || 'tool');
|
|
7300
|
+
// Strip MCP prefix: "mcp__server-name__tool_name" or "MCP__SERVER__TOOL"
|
|
7301
|
+
name = name.replace(/^mcp__[^_]+(?:[-][^_]+)*__/i, '');
|
|
7302
|
+
// Strip remaining double-underscore prefixed segments
|
|
7303
|
+
name = name.replace(/^[A-Za-z0-9_-]+__/, '');
|
|
7304
|
+
// Replace underscores with spaces
|
|
7305
|
+
name = name.replace(/_/g, ' ');
|
|
7306
|
+
// Title case
|
|
7307
|
+
name = name.replace(/\b\w/g, function(c) { return c.toUpperCase(); });
|
|
7308
|
+
return name.trim() || 'Tool';
|
|
7309
|
+
}
|
|
7310
|
+
|
|
7098
7311
|
function renderTerminalChatTranscript() {
|
|
7099
7312
|
var el = document.getElementById('terminalChatTranscript');
|
|
7100
7313
|
if (!el) return;
|
|
@@ -7125,87 +7338,143 @@ function renderTerminalChatTranscript() {
|
|
|
7125
7338
|
el.innerHTML = '<div class="terminal-chat-empty"><strong style="color:var(--accent)">Beta</strong> — Claude headless chat is ready. This feature is experimental and under active development. Your prompts and responses will appear here.</div>';
|
|
7126
7339
|
return;
|
|
7127
7340
|
}
|
|
7128
|
-
//
|
|
7129
|
-
|
|
7130
|
-
var
|
|
7341
|
+
// Build turn-based grouping: merge consecutive assistant/tool/widget entries
|
|
7342
|
+
// into a single "Claude turn" between user messages
|
|
7343
|
+
var turns = [];
|
|
7344
|
+
var currentTurn = null;
|
|
7345
|
+
var currentToolBatch = [];
|
|
7346
|
+
|
|
7347
|
+
function _flushToolBatch() {
|
|
7348
|
+
if (currentToolBatch.length > 0) {
|
|
7349
|
+
if (!currentTurn) currentTurn = { type: 'claude-turn', segments: [] };
|
|
7350
|
+
currentTurn.segments.push({ kind: 'tools', items: currentToolBatch.slice() });
|
|
7351
|
+
currentToolBatch = [];
|
|
7352
|
+
}
|
|
7353
|
+
}
|
|
7354
|
+
function _flushTurn() {
|
|
7355
|
+
_flushToolBatch();
|
|
7356
|
+
if (currentTurn && currentTurn.segments.length > 0) {
|
|
7357
|
+
turns.push(currentTurn);
|
|
7358
|
+
}
|
|
7359
|
+
currentTurn = null;
|
|
7360
|
+
}
|
|
7361
|
+
|
|
7131
7362
|
for (var ti = 0; ti < transcript.length; ti++) {
|
|
7132
7363
|
var entry = transcript[ti];
|
|
7133
7364
|
var role = String(entry.role || 'assistant');
|
|
7134
|
-
if (role === '
|
|
7135
|
-
|
|
7365
|
+
if (role === 'user') {
|
|
7366
|
+
_flushTurn();
|
|
7367
|
+
turns.push({ type: 'user', entry: entry });
|
|
7368
|
+
} else if (role === 'tool') {
|
|
7369
|
+
if (!currentTurn) currentTurn = { type: 'claude-turn', segments: [] };
|
|
7370
|
+
currentToolBatch.push(entry);
|
|
7371
|
+
} else if (role === 'widget') {
|
|
7372
|
+
_flushToolBatch();
|
|
7373
|
+
if (!currentTurn) currentTurn = { type: 'claude-turn', segments: [] };
|
|
7374
|
+
currentTurn.segments.push({ kind: 'widget', entry: entry });
|
|
7136
7375
|
} else {
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
|
|
7376
|
+
// assistant or system
|
|
7377
|
+
_flushToolBatch();
|
|
7378
|
+
if (!currentTurn) currentTurn = { type: 'claude-turn', segments: [] };
|
|
7379
|
+
currentTurn.segments.push({ kind: 'text', entry: entry });
|
|
7380
|
+
}
|
|
7381
|
+
}
|
|
7382
|
+
_flushTurn();
|
|
7383
|
+
|
|
7384
|
+
var html = turns.map(function(turn, turnIdx) {
|
|
7385
|
+
// User message — unchanged
|
|
7386
|
+
if (turn.type === 'user') {
|
|
7387
|
+
var ue = turn.entry;
|
|
7388
|
+
var uClasses = ['terminal-chat-msg', 'user'];
|
|
7389
|
+
if (ue.pending) uClasses.push('pending');
|
|
7390
|
+
if (ue.error) uClasses.push('error');
|
|
7391
|
+
return '<div class="' + uClasses.join(' ') + '">'
|
|
7392
|
+
+ '<div class="terminal-chat-msg-header">You</div>'
|
|
7393
|
+
+ '<div class="terminal-chat-msg-body">' + renderChatMarkdown(String(ue.text || '')) + '</div>'
|
|
7394
|
+
+ '</div>';
|
|
7395
|
+
}
|
|
7396
|
+
|
|
7397
|
+
// Claude turn — merge all segments into one card
|
|
7398
|
+
var segments = turn.segments;
|
|
7399
|
+
var hasVisibleContent = false;
|
|
7400
|
+
var turnPending = false;
|
|
7401
|
+
var turnError = false;
|
|
7402
|
+
for (var si = 0; si < segments.length; si++) {
|
|
7403
|
+
var seg = segments[si];
|
|
7404
|
+
if (seg.kind === 'tools' && seg.items.length > 0) hasVisibleContent = true;
|
|
7405
|
+
if (seg.kind === 'widget') hasVisibleContent = true;
|
|
7406
|
+
if (seg.kind === 'text') {
|
|
7407
|
+
if (String(seg.entry.text || '').trim()) hasVisibleContent = true;
|
|
7408
|
+
if (seg.entry.pending) turnPending = true;
|
|
7409
|
+
if (seg.entry.error) turnError = true;
|
|
7140
7410
|
}
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
+ '<span class="tool-icon">' + icon + '</span>'
|
|
7161
|
-
+ '<span class="tool-name">' + escapeHtml(String(e.toolName || 'tool')) + '</span>'
|
|
7162
|
-
+ (e.toolDetail ? '<span class="tool-detail">' + escapeHtml(String(e.toolDetail)) + '</span>' : '')
|
|
7163
|
-
+ '</div>';
|
|
7164
|
-
};
|
|
7165
|
-
if (!needsCollapse) {
|
|
7166
|
-
return items.map(renderToolDiv).join('');
|
|
7411
|
+
}
|
|
7412
|
+
// Skip empty pending turns (no text, no tools, no widgets yet)
|
|
7413
|
+
if (!hasVisibleContent && turnPending) return '';
|
|
7414
|
+
|
|
7415
|
+
var turnClasses = ['claude-turn'];
|
|
7416
|
+
if (turnPending) turnClasses.push('pending');
|
|
7417
|
+
if (turnError) turnClasses.push('error');
|
|
7418
|
+
|
|
7419
|
+
var out = '<div class="' + turnClasses.join(' ') + '">';
|
|
7420
|
+
out += '<div class="claude-turn-header">Claude</div>';
|
|
7421
|
+
out += '<div class="claude-turn-body">';
|
|
7422
|
+
|
|
7423
|
+
for (var si2 = 0; si2 < segments.length; si2++) {
|
|
7424
|
+
var seg2 = segments[si2];
|
|
7425
|
+
|
|
7426
|
+
if (seg2.kind === 'text') {
|
|
7427
|
+
var text = String(seg2.entry.text || '').trim();
|
|
7428
|
+
if (!text) continue;
|
|
7429
|
+
out += '<div class="claude-turn-text">' + renderChatMarkdown(text) + '</div>';
|
|
7167
7430
|
}
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
out +=
|
|
7431
|
+
|
|
7432
|
+
else if (seg2.kind === 'widget') {
|
|
7433
|
+
var wEntry = seg2.entry;
|
|
7434
|
+
var widgetId = 'widget-' + wEntry.id;
|
|
7435
|
+
out += '<div class="terminal-chat-widget" id="' + widgetId + '" style="margin:8px 0">';
|
|
7436
|
+
if (wEntry.widgetTitle) {
|
|
7437
|
+
out += '<div class="widget-title">' + escapeHtml(String(wEntry.widgetTitle)) + '</div>';
|
|
7438
|
+
}
|
|
7439
|
+
out += '<div class="widget-body" data-widget-type="' + escapeHtml(String(wEntry.widget || 'markdown')) + '" data-entry-id="' + escapeHtml(wEntry.id) + '">';
|
|
7440
|
+
out += '<div class="widget-placeholder">Loading widget...</div>';
|
|
7441
|
+
out += '</div></div>';
|
|
7177
7442
|
}
|
|
7178
|
-
|
|
7179
|
-
|
|
7443
|
+
|
|
7444
|
+
else if (seg2.kind === 'tools') {
|
|
7445
|
+
var items = seg2.items;
|
|
7446
|
+
var VISIBLE = 1;
|
|
7447
|
+
var needsCollapse = items.length > VISIBLE + 1;
|
|
7448
|
+
var toolGroupId = 'tchips-' + turnIdx + '-' + si2;
|
|
7449
|
+
|
|
7450
|
+
out += '<div class="claude-turn-tools" id="' + toolGroupId + '">';
|
|
7451
|
+
// Toggle button first (before collapsed chips)
|
|
7452
|
+
if (needsCollapse) {
|
|
7453
|
+
var hiddenN = items.length - VISIBLE;
|
|
7454
|
+
out += '<button type="button" class="tool-chips-toggle" onclick="var g=document.getElementById(\'' + toolGroupId + '\');g.classList.toggle(\'expanded\')">+' + hiddenN + ' more</button>';
|
|
7455
|
+
}
|
|
7456
|
+
for (var tci = 0; tci < items.length; tci++) {
|
|
7457
|
+
var te = items[tci];
|
|
7458
|
+
var chipClass = 'tool-chip';
|
|
7459
|
+
if (te.toolDone) chipClass += ' done';
|
|
7460
|
+
else if (te.toolError) chipClass += ' error';
|
|
7461
|
+
else chipClass += ' pending';
|
|
7462
|
+
// Hide earlier chips, keep last VISIBLE always visible
|
|
7463
|
+
if (needsCollapse && tci < items.length - VISIBLE) chipClass += ' tool-chip-collapsed';
|
|
7464
|
+
var chipIcon = te.toolDone ? '\u2713' : te.toolError ? '\u2717' : '';
|
|
7465
|
+
var shortName = shortenToolName(te.toolName);
|
|
7466
|
+
var fullTitle = escapeHtml(String(te.toolName || '')) + (te.toolDetail ? ' \u2014 ' + escapeHtml(String(te.toolDetail)) : '');
|
|
7467
|
+
out += '<div class="' + chipClass + '" title="' + fullTitle + '">'
|
|
7468
|
+
+ '<span class="tool-chip-icon">' + chipIcon + '</span>'
|
|
7469
|
+
+ '<span class="tool-chip-label">' + escapeHtml(shortName) + '</span>'
|
|
7470
|
+
+ '</div>';
|
|
7471
|
+
}
|
|
7472
|
+
out += '</div>';
|
|
7180
7473
|
}
|
|
7181
|
-
out += '</div>';
|
|
7182
|
-
return out;
|
|
7183
7474
|
}
|
|
7184
7475
|
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
// Render widget entries as rich content cards
|
|
7189
|
-
if (role === 'widget') {
|
|
7190
|
-
var widgetId = 'widget-' + entry.id;
|
|
7191
|
-
var widgetHtml = '<div class="terminal-chat-widget" id="' + widgetId + '">';
|
|
7192
|
-
if (entry.widgetTitle) {
|
|
7193
|
-
widgetHtml += '<div class="widget-title">' + escapeHtml(String(entry.widgetTitle)) + '</div>';
|
|
7194
|
-
}
|
|
7195
|
-
widgetHtml += '<div class="widget-body" data-widget-type="' + escapeHtml(String(entry.widget || 'markdown')) + '" data-entry-id="' + escapeHtml(entry.id) + '">';
|
|
7196
|
-
widgetHtml += '<div class="widget-placeholder">Loading widget...</div>';
|
|
7197
|
-
widgetHtml += '</div></div>';
|
|
7198
|
-
return widgetHtml;
|
|
7199
|
-
}
|
|
7200
|
-
|
|
7201
|
-
var classes = ['terminal-chat-msg', role];
|
|
7202
|
-
if (entry.pending) classes.push('pending');
|
|
7203
|
-
if (entry.error) classes.push('error');
|
|
7204
|
-
var label = role === 'user' ? 'You' : role === 'system' ? 'System' : 'Claude';
|
|
7205
|
-
return '<div class="' + classes.join(' ') + '">'
|
|
7206
|
-
+ '<div class="terminal-chat-msg-header">' + escapeHtml(label) + '</div>'
|
|
7207
|
-
+ '<div class="terminal-chat-msg-body">' + renderChatMarkdown(String(entry.text || '')) + '</div>'
|
|
7208
|
-
+ '</div>';
|
|
7476
|
+
out += '</div></div>';
|
|
7477
|
+
return out;
|
|
7209
7478
|
}).join('');
|
|
7210
7479
|
el.innerHTML = html;
|
|
7211
7480
|
// Hydrate widget bodies
|
|
@@ -8291,6 +8560,10 @@ function updateTerminalInputModeUi() {
|
|
|
8291
8560
|
var attachedSession = getAttachedWebTerminalSession();
|
|
8292
8561
|
var isRaw = webTerm.inputMode === 'raw';
|
|
8293
8562
|
var headlessCapable = isHeadlessClaudeChatSupported();
|
|
8563
|
+
if (headlessCapable && isRaw) {
|
|
8564
|
+
webTerm.inputMode = 'chat';
|
|
8565
|
+
isRaw = false;
|
|
8566
|
+
}
|
|
8294
8567
|
if (attachedSession && !headlessCapable && !isRaw) {
|
|
8295
8568
|
webTerm.inputMode = 'raw';
|
|
8296
8569
|
isRaw = true;
|
|
@@ -8304,6 +8577,7 @@ function updateTerminalInputModeUi() {
|
|
|
8304
8577
|
}
|
|
8305
8578
|
// Hide the Chat/Raw toggle entirely when headless is not enabled
|
|
8306
8579
|
if (modeToggle) modeToggle.style.display = webTerm.headlessEnabled ? '' : 'none';
|
|
8580
|
+
if (rawBtn) rawBtn.style.display = webTerm.headlessEnabled ? 'none' : '';
|
|
8307
8581
|
if (chatBtn) chatBtn.classList.toggle('active', !isRaw && headlessCapable);
|
|
8308
8582
|
if (rawBtn) rawBtn.classList.toggle('active', isRaw);
|
|
8309
8583
|
if (hint) {
|
|
@@ -8376,7 +8650,11 @@ function updateTerminalInputAvailability() {
|
|
|
8376
8650
|
function setTerminalInputMode(mode, opts) {
|
|
8377
8651
|
var options = opts || {};
|
|
8378
8652
|
var requested = mode === 'raw' ? 'raw' : 'chat';
|
|
8379
|
-
|
|
8653
|
+
var attachedSession = getAttachedWebTerminalSession();
|
|
8654
|
+
var headlessCapable = isHeadlessClaudeChatSupported();
|
|
8655
|
+
if (headlessCapable) {
|
|
8656
|
+
requested = 'chat';
|
|
8657
|
+
} else if (requested === 'chat' && attachedSession && !headlessCapable) {
|
|
8380
8658
|
requested = 'raw';
|
|
8381
8659
|
}
|
|
8382
8660
|
webTerm.inputMode = requested;
|
|
@@ -9815,10 +10093,7 @@ function toggleExplorerDevMode() {
|
|
|
9815
10093
|
showToast('Explorer mode enabled.', 'success');
|
|
9816
10094
|
}
|
|
9817
10095
|
|
|
9818
|
-
|
|
9819
|
-
if (EXPLORER_DEV_UI_QUERY_DISABLE) EXPLORER_DEV_UI_ENABLED = false;
|
|
9820
|
-
else if (EXPLORER_DEV_UI_QUERY_ENABLE) EXPLORER_DEV_UI_ENABLED = true;
|
|
9821
|
-
else EXPLORER_DEV_UI_ENABLED = explorerPref === true;
|
|
10096
|
+
EXPLORER_DEV_UI_ENABLED = false;
|
|
9822
10097
|
|
|
9823
10098
|
// Restore sidebar state from localStorage
|
|
9824
10099
|
try {
|
|
@@ -11456,6 +11731,14 @@ function populateUI() {
|
|
|
11456
11731
|
if (!config.slurm) config.slurm = {};
|
|
11457
11732
|
if (typeof config.slurm.enabled !== 'boolean') config.slurm.enabled = true;
|
|
11458
11733
|
document.getElementById('slurmEnabled').checked = !!config.slurm.enabled;
|
|
11734
|
+
if (!config.headless) config.headless = {};
|
|
11735
|
+
if (typeof config.headless.claude_run_with_allowed_permissions !== 'boolean') {
|
|
11736
|
+
config.headless.claude_run_with_allowed_permissions = true;
|
|
11737
|
+
}
|
|
11738
|
+
var headlessAllowedPermissions = document.getElementById('headlessAllowedPermissions');
|
|
11739
|
+
if (headlessAllowedPermissions) {
|
|
11740
|
+
headlessAllowedPermissions.checked = !!config.headless.claude_run_with_allowed_permissions;
|
|
11741
|
+
}
|
|
11459
11742
|
|
|
11460
11743
|
renderList('domainsList', config.network ? config.network.allowed_domains : [], removeDomain);
|
|
11461
11744
|
renderList('blockedList', config.filesystem ? config.filesystem.blocked_patterns : [], removeBlocked);
|
|
@@ -11478,6 +11761,11 @@ function collectConfig() {
|
|
|
11478
11761
|
config.audit.log_dir = document.getElementById('logDir').value;
|
|
11479
11762
|
if (!config.slurm) config.slurm = {};
|
|
11480
11763
|
config.slurm.enabled = document.getElementById('slurmEnabled').checked;
|
|
11764
|
+
if (!config.headless) config.headless = {};
|
|
11765
|
+
var headlessAllowedPermissions = document.getElementById('headlessAllowedPermissions');
|
|
11766
|
+
config.headless.claude_run_with_allowed_permissions = headlessAllowedPermissions
|
|
11767
|
+
? !!headlessAllowedPermissions.checked
|
|
11768
|
+
: true;
|
|
11481
11769
|
updateSlurmUIState({ skipLoadJobs: true });
|
|
11482
11770
|
}
|
|
11483
11771
|
|
|
@@ -12538,6 +12826,203 @@ function loadConfigPath() {
|
|
|
12538
12826
|
});
|
|
12539
12827
|
}
|
|
12540
12828
|
|
|
12829
|
+
function formatUiVersion(version) {
|
|
12830
|
+
var cleaned = String(version || '').trim();
|
|
12831
|
+
if (!cleaned) return '';
|
|
12832
|
+
return cleaned.charAt(0).toLowerCase() === 'v' ? cleaned : ('v' + cleaned);
|
|
12833
|
+
}
|
|
12834
|
+
|
|
12835
|
+
function setUiUpdateStatus(message, tone) {
|
|
12836
|
+
var el = document.getElementById('uiUpdateStatusValue');
|
|
12837
|
+
if (!el) return;
|
|
12838
|
+
el.textContent = message || '';
|
|
12839
|
+
el.className = 'settings-info-value ui-update-status' + (tone ? (' ' + tone) : '');
|
|
12840
|
+
}
|
|
12841
|
+
|
|
12842
|
+
function setUiUpdateButtonsState(opts) {
|
|
12843
|
+
var options = opts || {};
|
|
12844
|
+
var showApply = !!options.showApply;
|
|
12845
|
+
var checkBtn = document.getElementById('uiUpdateCheckBtn');
|
|
12846
|
+
var applyBtn = document.getElementById('uiUpdateApplyBtn');
|
|
12847
|
+
if (checkBtn) checkBtn.disabled = !!options.disableCheck;
|
|
12848
|
+
if (applyBtn) {
|
|
12849
|
+
applyBtn.style.display = showApply ? '' : 'none';
|
|
12850
|
+
applyBtn.disabled = !!options.disableApply;
|
|
12851
|
+
}
|
|
12852
|
+
}
|
|
12853
|
+
|
|
12854
|
+
function renderUiVersionState(data, opts) {
|
|
12855
|
+
var options = opts || {};
|
|
12856
|
+
var runningVersion = typeof data.runningVersion === 'string' ? data.runningVersion : '';
|
|
12857
|
+
var latestVersion = typeof data.latestVersion === 'string' ? data.latestVersion : '';
|
|
12858
|
+
var checkError = typeof data.checkError === 'string' ? data.checkError : '';
|
|
12859
|
+
var updateAvailable = !!data.updateAvailable;
|
|
12860
|
+
var latestCheckedAt = typeof data.latestCheckedAt === 'string' ? data.latestCheckedAt : '';
|
|
12861
|
+
|
|
12862
|
+
var versionEl = document.getElementById('labgateVersionValue');
|
|
12863
|
+
if (versionEl) {
|
|
12864
|
+
versionEl.textContent = runningVersion ? formatUiVersion(runningVersion) : 'Unknown';
|
|
12865
|
+
}
|
|
12866
|
+
|
|
12867
|
+
setUiUpdateButtonsState({
|
|
12868
|
+
showApply: updateAvailable && !!latestVersion,
|
|
12869
|
+
disableCheck: uiUpdateInProgress,
|
|
12870
|
+
disableApply: uiUpdateInProgress
|
|
12871
|
+
});
|
|
12872
|
+
|
|
12873
|
+
if (updateAvailable && latestVersion) {
|
|
12874
|
+
var updateCommand = typeof data.updateCommand === 'string' ? data.updateCommand : 'npm install -g labgate@latest';
|
|
12875
|
+
setUiUpdateStatus(formatUiVersion(latestVersion) + ' is available. Click "Update". If it fails, run "' + updateCommand + '".', 'warning');
|
|
12876
|
+
return;
|
|
12877
|
+
}
|
|
12878
|
+
|
|
12879
|
+
if (checkError) {
|
|
12880
|
+
setUiUpdateButtonsState({
|
|
12881
|
+
showApply: false,
|
|
12882
|
+
disableCheck: uiUpdateInProgress,
|
|
12883
|
+
disableApply: uiUpdateInProgress
|
|
12884
|
+
});
|
|
12885
|
+
setUiUpdateStatus('Update check unavailable: ' + checkError, 'error');
|
|
12886
|
+
return;
|
|
12887
|
+
}
|
|
12888
|
+
|
|
12889
|
+
if (latestVersion) {
|
|
12890
|
+
var checkedSuffix = latestCheckedAt ? (' Last checked: ' + new Date(latestCheckedAt).toLocaleTimeString() + '.') : '';
|
|
12891
|
+
setUiUpdateStatus('Up to date (' + formatUiVersion(latestVersion) + ').' + checkedSuffix, 'success');
|
|
12892
|
+
return;
|
|
12893
|
+
}
|
|
12894
|
+
|
|
12895
|
+
if (options.force) {
|
|
12896
|
+
setUiUpdateStatus('Update check finished with no version data.', 'warning');
|
|
12897
|
+
} else {
|
|
12898
|
+
setUiUpdateStatus('Click "Search for Update" to check npm for newer versions.', '');
|
|
12899
|
+
}
|
|
12900
|
+
}
|
|
12901
|
+
|
|
12902
|
+
function checkUiVersion(force) {
|
|
12903
|
+
var shouldForce = !!force;
|
|
12904
|
+
if (uiUpdateInProgress) return Promise.resolve();
|
|
12905
|
+
var checkBtn = document.getElementById('uiUpdateCheckBtn');
|
|
12906
|
+
var originalLabel = checkBtn ? checkBtn.textContent : '';
|
|
12907
|
+
if (checkBtn) {
|
|
12908
|
+
checkBtn.disabled = true;
|
|
12909
|
+
checkBtn.textContent = 'Checking...';
|
|
12910
|
+
}
|
|
12911
|
+
var endpoint = '/api/ui/version' + (shouldForce ? '?refresh=1' : '');
|
|
12912
|
+
return fetch(endpoint).then(parseApiResponse).then(function(resp) {
|
|
12913
|
+
var data = resp.data || {};
|
|
12914
|
+
if (!resp.ok || !data.ok) {
|
|
12915
|
+
throw new Error(data.error || ('HTTP ' + resp.status));
|
|
12916
|
+
}
|
|
12917
|
+
renderUiVersionState(data, { force: shouldForce });
|
|
12918
|
+
}).catch(function(err) {
|
|
12919
|
+
var msg = err && err.message ? err.message : String(err || 'unknown error');
|
|
12920
|
+
setUiUpdateStatus('Could not check for updates: ' + msg, 'error');
|
|
12921
|
+
if (shouldForce) showToast('Update check failed: ' + msg, 'error');
|
|
12922
|
+
}).finally(function() {
|
|
12923
|
+
if (checkBtn) {
|
|
12924
|
+
checkBtn.disabled = false;
|
|
12925
|
+
checkBtn.textContent = originalLabel || 'Search for Update';
|
|
12926
|
+
}
|
|
12927
|
+
if (!uiUpdateInProgress) {
|
|
12928
|
+
var applyBtn = document.getElementById('uiUpdateApplyBtn');
|
|
12929
|
+
if (applyBtn) applyBtn.disabled = false;
|
|
12930
|
+
}
|
|
12931
|
+
});
|
|
12932
|
+
}
|
|
12933
|
+
|
|
12934
|
+
function stopUiUpdateStatusPolling() {
|
|
12935
|
+
if (!uiUpdateStatusPollTimer) return;
|
|
12936
|
+
clearTimeout(uiUpdateStatusPollTimer);
|
|
12937
|
+
uiUpdateStatusPollTimer = null;
|
|
12938
|
+
}
|
|
12939
|
+
|
|
12940
|
+
function pollUiUpdateStatus() {
|
|
12941
|
+
return fetch('/api/ui/update/status').then(parseApiResponse).then(function(resp) {
|
|
12942
|
+
var data = resp.data || {};
|
|
12943
|
+
if (!resp.ok || !data.ok) {
|
|
12944
|
+
throw new Error(data.error || ('HTTP ' + resp.status));
|
|
12945
|
+
}
|
|
12946
|
+
|
|
12947
|
+
var status = String(data.status || 'idle');
|
|
12948
|
+
var latestVersion = typeof data.latestVersion === 'string' ? data.latestVersion : '';
|
|
12949
|
+
var error = typeof data.error === 'string' ? data.error : '';
|
|
12950
|
+
|
|
12951
|
+
if (status === 'running') {
|
|
12952
|
+
uiUpdateInProgress = true;
|
|
12953
|
+
setUiUpdateButtonsState({ showApply: true, disableCheck: true, disableApply: true });
|
|
12954
|
+
setUiUpdateStatus('Updating LabGate... write actions are temporarily locked.', 'warning');
|
|
12955
|
+
stopUiUpdateStatusPolling();
|
|
12956
|
+
uiUpdateStatusPollTimer = setTimeout(function() {
|
|
12957
|
+
pollUiUpdateStatus();
|
|
12958
|
+
}, 1500);
|
|
12959
|
+
return;
|
|
12960
|
+
}
|
|
12961
|
+
|
|
12962
|
+
uiUpdateInProgress = false;
|
|
12963
|
+
stopUiUpdateStatusPolling();
|
|
12964
|
+
if (status === 'success') {
|
|
12965
|
+
var next = latestVersion ? formatUiVersion(latestVersion) : 'the latest version';
|
|
12966
|
+
setUiUpdateStatus('Update complete (' + next + '). Restart `labgate ui`, then reload this page.', 'success');
|
|
12967
|
+
showToast('Update complete. Restart `labgate ui`.', 'success');
|
|
12968
|
+
setUiUpdateButtonsState({ showApply: false, disableCheck: false, disableApply: false });
|
|
12969
|
+
return;
|
|
12970
|
+
}
|
|
12971
|
+
if (status === 'error') {
|
|
12972
|
+
setUiUpdateStatus('Update failed: ' + (error || 'unknown error'), 'error');
|
|
12973
|
+
showToast('Update failed: ' + (error || 'unknown error'), 'error');
|
|
12974
|
+
setUiUpdateButtonsState({ showApply: true, disableCheck: false, disableApply: false });
|
|
12975
|
+
return;
|
|
12976
|
+
}
|
|
12977
|
+
|
|
12978
|
+
setUiUpdateButtonsState({ showApply: false, disableCheck: false, disableApply: false });
|
|
12979
|
+
}).catch(function(err) {
|
|
12980
|
+
uiUpdateInProgress = false;
|
|
12981
|
+
stopUiUpdateStatusPolling();
|
|
12982
|
+
var msg = err && err.message ? err.message : String(err || 'unknown error');
|
|
12983
|
+
setUiUpdateStatus('Could not read update status: ' + msg, 'error');
|
|
12984
|
+
setUiUpdateButtonsState({ showApply: true, disableCheck: false, disableApply: false });
|
|
12985
|
+
});
|
|
12986
|
+
}
|
|
12987
|
+
|
|
12988
|
+
function startUiUpdate() {
|
|
12989
|
+
if (uiUpdateInProgress) return;
|
|
12990
|
+
showConfirm({
|
|
12991
|
+
title: 'Update LabGate',
|
|
12992
|
+
message: 'LabGate will only update when no active sessions are running. Continue?',
|
|
12993
|
+
okLabel: 'Update',
|
|
12994
|
+
destructive: false
|
|
12995
|
+
}).then(function(ok) {
|
|
12996
|
+
if (!ok) return;
|
|
12997
|
+
uiUpdateInProgress = true;
|
|
12998
|
+
setUiUpdateButtonsState({ showApply: true, disableCheck: true, disableApply: true });
|
|
12999
|
+
setUiUpdateStatus('Starting update...', 'warning');
|
|
13000
|
+
fetch('/api/ui/update', {
|
|
13001
|
+
method: 'POST',
|
|
13002
|
+
headers: apiWriteHeaders(),
|
|
13003
|
+
body: '{}'
|
|
13004
|
+
}).then(parseApiResponse).then(function(resp) {
|
|
13005
|
+
var data = resp.data || {};
|
|
13006
|
+
if (!resp.ok || !data.ok) {
|
|
13007
|
+
var detail = data.error || ('HTTP ' + resp.status);
|
|
13008
|
+
if (data.code === 'in_use' && data.activeUsage) {
|
|
13009
|
+
var usage = data.activeUsage;
|
|
13010
|
+
detail = 'Stop active sessions first (' + (usage.containerSessions || 0) + ' sessions, '
|
|
13011
|
+
+ (usage.webTerminalSessions || 0) + ' web terminals).';
|
|
13012
|
+
}
|
|
13013
|
+
throw new Error(detail);
|
|
13014
|
+
}
|
|
13015
|
+
pollUiUpdateStatus();
|
|
13016
|
+
}).catch(function(err) {
|
|
13017
|
+
uiUpdateInProgress = false;
|
|
13018
|
+
var msg = err && err.message ? err.message : String(err || 'unknown error');
|
|
13019
|
+
setUiUpdateStatus('Update blocked: ' + msg, 'error');
|
|
13020
|
+
setUiUpdateButtonsState({ showApply: true, disableCheck: false, disableApply: false });
|
|
13021
|
+
showToast('Update blocked: ' + msg, 'error');
|
|
13022
|
+
});
|
|
13023
|
+
});
|
|
13024
|
+
}
|
|
13025
|
+
|
|
12541
13026
|
// ── Network mode switcher ────────────────────
|
|
12542
13027
|
function updateNetSwitcher() {
|
|
12543
13028
|
var mode = normalizeUiNetworkMode(config.network ? config.network.mode : 'host');
|
|
@@ -15065,6 +15550,7 @@ var slurmSystemAvailable = false;
|
|
|
15065
15550
|
var slurmSidebarVisible = false;
|
|
15066
15551
|
var slurmMissingCommands = [];
|
|
15067
15552
|
var slurmSearchTimeout = null;
|
|
15553
|
+
var slurmScope = 'mine'; // 'mine' or 'all'
|
|
15068
15554
|
var currentSlurmOutputJobId = null;
|
|
15069
15555
|
var currentSlurmOutputStream = 'stdout';
|
|
15070
15556
|
var slurmOutputFollowTimer = null;
|
|
@@ -15149,6 +15635,15 @@ function updateSlurmUIState(opts) {
|
|
|
15149
15635
|
if (!(opts && opts.skipLoadJobs)) loadSlurmJobs();
|
|
15150
15636
|
}
|
|
15151
15637
|
|
|
15638
|
+
function setSlurmScope(scope) {
|
|
15639
|
+
slurmScope = scope === 'all' ? 'all' : 'mine';
|
|
15640
|
+
var mineBtn = document.getElementById('slurmScopeMine');
|
|
15641
|
+
var allBtn = document.getElementById('slurmScopeAll');
|
|
15642
|
+
if (mineBtn) mineBtn.classList.toggle('active', slurmScope === 'mine');
|
|
15643
|
+
if (allBtn) allBtn.classList.toggle('active', slurmScope === 'all');
|
|
15644
|
+
loadSlurmJobs();
|
|
15645
|
+
}
|
|
15646
|
+
|
|
15152
15647
|
function loadSlurmJobs() {
|
|
15153
15648
|
if (!slurmEnabled || !slurmSidebarVisible) return;
|
|
15154
15649
|
var el = document.getElementById('slurmJobsContent');
|
|
@@ -15157,58 +15652,69 @@ function loadSlurmJobs() {
|
|
|
15157
15652
|
var url = '/api/slurm/jobs?limit=100';
|
|
15158
15653
|
if (state) url += '&state=' + encodeURIComponent(state);
|
|
15159
15654
|
if (search) url += '&search=' + encodeURIComponent(search);
|
|
15655
|
+
// Session-aware filtering
|
|
15656
|
+
if (slurmScope === 'mine') {
|
|
15657
|
+
var sid = getAttachedWebTerminalId();
|
|
15658
|
+
if (!sid) {
|
|
15659
|
+
el.innerHTML = '<div class="empty-state" style="padding:12px">Attach to a terminal session to view jobs for this session.</div>';
|
|
15660
|
+
return;
|
|
15661
|
+
}
|
|
15662
|
+
url += '&session_id=' + encodeURIComponent(sid);
|
|
15663
|
+
}
|
|
15160
15664
|
|
|
15161
15665
|
fetch(url).then(function(r) { return r.json(); }).then(function(data) {
|
|
15162
15666
|
if (!data.ok) {
|
|
15163
|
-
el.innerHTML = '<div class="empty-state">Error
|
|
15667
|
+
el.innerHTML = '<div class="empty-state" style="padding:12px">Error: ' + escapeHtml(data.error || 'unknown') + '</div>';
|
|
15164
15668
|
return;
|
|
15165
15669
|
}
|
|
15166
15670
|
if (!data.jobs || data.jobs.length === 0) {
|
|
15167
|
-
|
|
15671
|
+
var msg = slurmScope === 'mine' ? 'No jobs from this session' : 'No SLURM jobs found';
|
|
15672
|
+
el.innerHTML = '<div class="empty-state" style="padding:12px">' + msg + '</div>';
|
|
15168
15673
|
return;
|
|
15169
15674
|
}
|
|
15170
|
-
|
|
15675
|
+
renderSlurmJobCards(data.jobs, el);
|
|
15171
15676
|
}).catch(function(err) {
|
|
15172
|
-
el.innerHTML = '<div class="empty-state">Error
|
|
15677
|
+
el.innerHTML = '<div class="empty-state" style="padding:12px">Error: ' + escapeHtml(err.message) + '</div>';
|
|
15173
15678
|
});
|
|
15174
15679
|
}
|
|
15175
15680
|
|
|
15176
15681
|
var _slurmJobsCache = [];
|
|
15177
15682
|
|
|
15178
|
-
function
|
|
15683
|
+
function renderSlurmJobCards(jobs, el) {
|
|
15179
15684
|
_slurmJobsCache = jobs;
|
|
15180
|
-
var
|
|
15181
|
-
html
|
|
15182
|
-
html += '<th>Partition</th><th>Nodes</th><th>Output</th><th>Notes</th><th>Actions</th>';
|
|
15183
|
-
html += '</tr></thead><tbody>';
|
|
15184
|
-
|
|
15685
|
+
var currentSessionId = getAttachedWebTerminalId();
|
|
15686
|
+
var html = '<div class="slurm-job-grid">';
|
|
15185
15687
|
for (var i = 0; i < jobs.length; i++) {
|
|
15186
15688
|
var j = jobs[i];
|
|
15187
15689
|
var runtime = computeRuntime(j);
|
|
15188
|
-
var stateClass = 'slurm-state slurm-state-' + (j.state || 'UNKNOWN');
|
|
15189
|
-
|
|
15190
|
-
|
|
15191
|
-
html += '<
|
|
15192
|
-
html += '<
|
|
15193
|
-
html += '<
|
|
15194
|
-
|
|
15195
|
-
html += '<span class="
|
|
15196
|
-
html += '</
|
|
15197
|
-
html += '<
|
|
15198
|
-
html += '<
|
|
15199
|
-
html += '<
|
|
15200
|
-
|
|
15201
|
-
|
|
15202
|
-
|
|
15203
|
-
|
|
15204
|
-
|
|
15690
|
+
var stateClass = 'slurm-job-state slurm-job-state-' + (j.state || 'UNKNOWN');
|
|
15691
|
+
var isMySession = currentSessionId && j.session_id === currentSessionId;
|
|
15692
|
+
|
|
15693
|
+
html += '<div class="slurm-job-card">';
|
|
15694
|
+
html += '<div class="slurm-job-card-row">';
|
|
15695
|
+
html += '<span class="slurm-job-id">' + escapeHtml(j.job_id) + '</span>';
|
|
15696
|
+
html += '<span class="' + stateClass + '">' + escapeHtml(j.state || '?') + '</span>';
|
|
15697
|
+
if (j.name) html += '<span class="slurm-job-name">' + escapeHtml(j.name) + '</span>';
|
|
15698
|
+
html += '</div>';
|
|
15699
|
+
html += '<div class="slurm-job-meta">';
|
|
15700
|
+
html += '<span>' + escapeHtml(runtime) + '</span>';
|
|
15701
|
+
if (j.partition) html += '<span>' + escapeHtml(j.partition) + '</span>';
|
|
15702
|
+
if (j.stdout_path) {
|
|
15703
|
+
html += '<button class="slurm-job-action-btn" onclick="event.stopPropagation();openSlurmOutput(\'' + escapeHtml(j.job_id) + '\',\'stdout\')" title="View stdout">out</button>';
|
|
15704
|
+
}
|
|
15705
|
+
if (j.stderr_path) {
|
|
15706
|
+
html += '<button class="slurm-job-action-btn" onclick="event.stopPropagation();openSlurmOutput(\'' + escapeHtml(j.job_id) + '\',\'stderr\')" title="View stderr">err</button>';
|
|
15707
|
+
}
|
|
15708
|
+
html += '<div class="slurm-job-actions">';
|
|
15709
|
+
html += '<button class="slurm-job-action-btn" onclick="event.stopPropagation();editSlurmNotes(\'' + escapeHtml(j.job_id) + '\')" title="Notes">' + (j.notes ? '\u270E' : '+note') + '</button>';
|
|
15205
15710
|
if (j.state === 'PENDING' || j.state === 'RUNNING') {
|
|
15206
|
-
html += '<button class="slurm-
|
|
15711
|
+
html += '<button class="slurm-job-action-btn danger" onclick="event.stopPropagation();cancelSlurmJob(\'' + escapeHtml(j.job_id) + '\')" title="Cancel job">\u2717</button>';
|
|
15207
15712
|
}
|
|
15208
|
-
html += '</
|
|
15209
|
-
html += '</
|
|
15713
|
+
html += '</div>';
|
|
15714
|
+
html += '</div>';
|
|
15715
|
+
html += '</div>';
|
|
15210
15716
|
}
|
|
15211
|
-
html += '</
|
|
15717
|
+
html += '</div>';
|
|
15212
15718
|
el.innerHTML = html;
|
|
15213
15719
|
}
|
|
15214
15720
|
|
|
@@ -15530,7 +16036,8 @@ function applyFieldLocks(data) {
|
|
|
15530
16036
|
'audit.enabled': 'auditEnabled',
|
|
15531
16037
|
'audit.log_dir': 'logDir',
|
|
15532
16038
|
'session_timeout_hours': 'timeout',
|
|
15533
|
-
'slurm.enabled': 'slurmEnabled'
|
|
16039
|
+
'slurm.enabled': 'slurmEnabled',
|
|
16040
|
+
'headless.claude_run_with_allowed_permissions': 'headlessAllowedPermissions'
|
|
15534
16041
|
};
|
|
15535
16042
|
locked.forEach(function(fieldPath) {
|
|
15536
16043
|
var elId = fieldMap[fieldPath];
|
|
@@ -15764,6 +16271,7 @@ function loadAdminLicense() {
|
|
|
15764
16271
|
// ── Init ─────────────────────────────────────
|
|
15765
16272
|
loadConfig();
|
|
15766
16273
|
loadConfigPath();
|
|
16274
|
+
checkUiVersion(false);
|
|
15767
16275
|
setViewMode('terminal_attached', '');
|
|
15768
16276
|
loadSessions();
|
|
15769
16277
|
updateNotifyButton();
|