|
51 | 51 | } |
52 | 52 |
|
53 | 53 | /* ─── Page layout ─── */ |
| 54 | + /* main is a viewport-tall flex column: the fixed-height blocks above the |
| 55 | + data-grid (header, stats, current-batch, search) take their natural |
| 56 | + height, then .data-grid flexes to fill whatever vertical space is left |
| 57 | + so the page fits the viewport with no scroll into empty space below the |
| 58 | + table. The table-wrap and log body scroll internally instead. */ |
54 | 59 | main { |
55 | 60 | max-width: 1600px; margin: 0 auto; |
56 | | - padding: 28px 24px 80px; |
| 61 | + padding: 28px 24px 24px; |
| 62 | + height: 100vh; |
| 63 | + display: flex; |
| 64 | + flex-direction: column; |
57 | 65 | } |
58 | 66 |
|
59 | 67 | /* Live-banner / status row (kept for status var references) */ |
|
288 | 296 | display: grid; |
289 | 297 | grid-template-columns: 1fr 380px; |
290 | 298 | gap: 16px; |
291 | | - height: 640px; |
| 299 | + /* Fill the remaining viewport height left by the blocks above instead of |
| 300 | + a fixed 640px. min-height keeps the grid usable on short viewports |
| 301 | + (the page may scroll only then); min-height:0 on the flex child lets |
| 302 | + the inner table-wrap / log body own the overflow. */ |
| 303 | + flex: 1 1 auto; |
| 304 | + min-height: 360px; |
292 | 305 | } |
293 | 306 |
|
294 | | - .table-container { display: flex; flex-direction: column; overflow: hidden; height: 640px; max-height: 640px; } |
| 307 | + .table-container { display: flex; flex-direction: column; overflow: hidden; height: 100%; min-height: 0; } |
295 | 308 |
|
296 | 309 | .panel-header { |
297 | 310 | padding: 16px 20px; |
|
354 | 367 | background: var(--bg-card-solid) !important; |
355 | 368 | /* Single header separator. The box-shadow draws the only divider line |
356 | 369 | (and fills the border-spacing gap above/below it) so it survives the |
357 | | - sticky-header scroll; a plain border-bottom here would double it. */ |
| 370 | + sticky-header scroll; a plain border-bottom here would double it. |
| 371 | + The solid fill (-6px / +5px) and the 1px border (+5px → +6px) share an |
| 372 | + edge at exactly 5px, so there is no 1px background sliver between the |
| 373 | + fill-end and the border — one clean line below the header row. The |
| 374 | + downward fill is capped at 5px (< the 4px+ row body offset would show) |
| 375 | + so it covers the border-spacing gap and meets the divider with no seam. */ |
358 | 376 | box-shadow: |
359 | 377 | 0 -6px 0 0 var(--bg-card-solid), |
360 | | - 0 6px 0 0 var(--bg-card-solid), |
361 | | - 0 7px 0 0 var(--border); |
| 378 | + 0 5px 0 0 var(--bg-card-solid), |
| 379 | + 0 6px 0 0 var(--border); |
362 | 380 | z-index: 10; |
363 | 381 | } |
364 | 382 |
|
|
397 | 415 | .row-copy-btn:hover { border-color: var(--text); color: var(--text); background: var(--bg-card); } |
398 | 416 | .row-copy-btn:disabled { opacity: 0.6; cursor: wait; } |
399 | 417 |
|
400 | | - /* ─── Log panel — fixed height, scroll inside ─── */ |
| 418 | + /* ─── Log panel — fills its grid cell, scroll inside ─── */ |
401 | 419 | .log-container { |
402 | 420 | display: flex; flex-direction: column; overflow: hidden; |
403 | | - height: 640px; max-height: 640px; |
404 | | - align-self: start; |
405 | | - position: sticky; top: 12px; |
| 421 | + height: 100%; min-height: 0; |
406 | 422 | } |
407 | 423 | .logs { |
408 | 424 | flex: 1 1 auto; min-height: 0; |
|
476 | 492 | padding: 2px 6px; border-radius: 3px; cursor: pointer; |
477 | 493 | } |
478 | 494 | .log-entry .log-copy:hover { border-color: var(--text); color: var(--text); background: var(--bg-card); } |
479 | | - .log-filters { |
480 | | - display: inline-flex; gap: 2px; |
481 | | - border: 1px solid var(--border); border-radius: 5px; |
482 | | - overflow: hidden; font-size: 10px; letter-spacing: 0.5px; |
483 | | - font-family: var(--font-display); |
484 | | - } |
485 | | - .log-filter-btn { |
486 | | - padding: 3px 8px; color: var(--text-muted); font-weight: 600; |
487 | | - cursor: pointer; user-select: none; |
488 | | - } |
489 | | - .log-filter-btn.active { background: var(--bg-input); color: var(--text); } |
490 | 495 | @keyframes slideIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } } |
491 | 496 |
|
492 | 497 | /* ─── Toast ─── */ |
@@ -822,15 +827,6 @@ <h1 class="live-page-title"> |
822 | 827 | <div class="glass-panel log-container"> |
823 | 828 | <div class="panel-header"> |
824 | 829 | <div class="panel-title">Live Log</div> |
825 | | - <div style="display:flex;align-items:center;gap:8px"> |
826 | | - <span class="log-filters" id="logFilters"> |
827 | | - <span class="log-filter-btn active" data-lf="all" onclick="setLogFilter('all')">ALL</span> |
828 | | - <span class="log-filter-btn" data-lf="node" onclick="setLogFilter('node')">NODE</span> |
829 | | - <span class="log-filter-btn" data-lf="fail" onclick="setLogFilter('fail')">FAIL</span> |
830 | | - <span class="log-filter-btn" data-lf="sys" onclick="setLogFilter('sys')">SYS</span> |
831 | | - </span> |
832 | | - <span style="font-size:11px;color:var(--text-dim)" id="logStatus">idle</span> |
833 | | - </div> |
834 | 830 | </div> |
835 | 831 | <div class="logs" id="logBody"></div> |
836 | 832 | </div> |
@@ -1363,29 +1359,9 @@ <h1 class="live-page-title"> |
1363 | 1359 | } |
1364 | 1360 |
|
1365 | 1361 | // ─── Logs ─── |
1366 | | - let _logFilter = 'all'; // 'all' | 'node' | 'fail' | 'sys' |
1367 | | - |
1368 | | - function setLogFilter(f) { |
1369 | | - _logFilter = f; |
1370 | | - document.querySelectorAll('.log-filter-btn').forEach(b => { |
1371 | | - b.classList.toggle('active', b.dataset.lf === f); |
1372 | | - }); |
1373 | | - const body = document.getElementById('logBody'); |
1374 | | - if (!body) return; |
1375 | | - for (const row of body.children) { |
1376 | | - row.style.display = _logEntryMatches(row, f) ? '' : 'none'; |
1377 | | - } |
1378 | | - body.scrollTop = body.scrollHeight; |
1379 | | - } |
1380 | | - |
1381 | | - function _logEntryMatches(row, f) { |
1382 | | - if (f === 'all') return true; |
1383 | | - const cat = row.dataset.cat || 'sys'; |
1384 | | - if (f === 'node') return cat === 'node' || cat === 'fail'; |
1385 | | - if (f === 'fail') return cat === 'fail'; |
1386 | | - if (f === 'sys') return cat === 'sys'; |
1387 | | - return true; |
1388 | | - } |
| 1362 | + // Unified feed: every line is appended; there is no per-line show/hide |
| 1363 | + // filter (the ALL/NODE/FAIL/SYS buttons were removed). The cat/status tags |
| 1364 | + // are kept solely for tag-pill styling. |
1389 | 1365 |
|
1390 | 1366 | // Structured log entry. Takes opts: |
1391 | 1367 | // { tag, cat, status ('pass'|'fail'|'warn'|'ok'|'sys'), |
@@ -1482,8 +1458,6 @@ <h1 class="live-page-title"> |
1482 | 1458 | // which froze the page when replaying thousands of buffered lines. |
1483 | 1459 | if (!_logSeeding) body.scrollTop = body.scrollHeight; |
1484 | 1460 | if (body.children.length > MAX_LOG) body.removeChild(body.firstChild); |
1485 | | - |
1486 | | - if (!_logEntryMatches(div, _logFilter)) div.style.display = 'none'; |
1487 | 1461 | } |
1488 | 1462 |
|
1489 | 1463 | // ─── Toast / status ─── |
|
0 commit comments