Project homepage Mailing List  Warmcat.com  API Docs  Github Mirror 
    npro  
 Modern all-safe Rust Network Protocol library supporting h1, h2, h3, ws, wt sans-IO and with socket IO + tls
git clone https://npro.rs/repo/npro
 
root / assets / arch-x86-amd.svg
Author[]Andy Green <andy@warmcat.com> 2026-08-03 03:11 UTC
Committer[]Andy Green <andy@warmcat.com> 2026-08-03 03:15 UTC
Tree2086a1c72e7116b1c5e11552cbaaf220ff5bbe82   Raw Patch
 
ui10
ui10
diff --git a/assets/index.html b/assets/index.html index 24776e8..5394a79 100644 --- a/assets/index.html +++ b/assets/index.html @@ -2,9 +2,9 @@ <html lang="en"> <head> <meta charset=utf-8 http-equiv="Content-Language" content="en"/> - <link rel="stylesheet" type="text/css" href="sai.css?v=7"/> + <link rel="stylesheet" type="text/css" href="sai.css?v=9"/> <link rel="icon" href="sai-icon.svg" sizes="any" type="image/svg+xml"/> - <script type='text/javascript' src='sai.js?v=7'></script> + <script type='text/javascript' src='sai.js?v=9'></script> <script type='text/javascript' src='terminal-core.js'></script> <script type='text/javascript' src='sai-terminal.js'></script> <script type='text/javascript' src='lws-login.js'></script> diff --git a/assets/sai.css b/assets/sai.css index 7b4565d..dc3345a 100644 --- a/assets/sai.css +++ b/assets/sai.css @@ -1891,12 +1891,18 @@ div.ibuil.vm-builder { } /* event rows in col 4 */ +/* + * Tabular event rows: CSS grid with fixed-width columns so the date/time, + * tag, status and progress-bar cells line up across all rows regardless of + * their content width (the date/time and tag are constant width; status is + * auto so it takes its natural size; the bar is a fixed 250px). + */ .sb-event-row { - display: flex; - flex-direction: row; + display: grid; + grid-template-columns: 150px 96px minmax(60px, auto) 250px; align-items: center; - gap: 6px; - padding: 2px 6px; + column-gap: 10px; + padding: 2px 10px; margin: 1px 0; border-radius: 4px; cursor: pointer; @@ -1934,7 +1940,8 @@ div.ibuil.vm-builder { font-size: 8pt; color: #64748b; white-space: nowrap; - flex: 0 0 auto; + overflow: hidden; + text-overflow: ellipsis; } .sb-event-row.comp_fail .sb-event-when { @@ -1945,30 +1952,30 @@ div.ibuil.vm-builder { font-family: monospace; font-size: 9pt; white-space: nowrap; - flex: 0 0 auto; + overflow: hidden; + text-overflow: ellipsis; } .sb-event-row .sb-event-status { font-size: 8.5pt; white-space: nowrap; - flex: 0 0 auto; + overflow: hidden; + text-overflow: ellipsis; } /* progress bar slot: fixed compact width, inline on the same row */ .sb-event-row .sb-event-bar { - flex: 0 0 250px; - width: 250px; - max-width: 250px; display: inline-flex; align-items: center; + min-width: 0; } .sb-event-row .sb-event-bar:empty { display: none; } .sb-event-row .progress-bar { - width: 250px; - max-width: 250px; + width: 100%; + min-width: 0; height: 8px; } @@ -2018,6 +2025,20 @@ div.ibuil.vm-builder { display: inline-block; } +/* reflect the event's completion state on the header background, like div4 */ +.event-tasks-header.comp_pass { + background: #01ff70; + border-bottom-color: #2ecc40; +} +.event-tasks-header.comp_fail { + background: #ff4136; + color: #ffffff; + border-bottom-color: #df3030; +} +.event-tasks-header.comp_fail .event-tasks-title { + color: #ffffff; +} + .tasks-table-display { width: 100%; border-collapse: collapse; diff --git a/assets/sai.js b/assets/sai.js index 685967b..bc86121 100644 --- a/assets/sai.js +++ b/assets/sai.js @@ -1688,6 +1688,7 @@ function selectSbProject(name) render_sb_projects(); /* refresh the branch list for this project; auto-selects newest branch */ sai_sb_request_branches(name); + sai_sb_update_url(); } function selectSbBranch(ref) @@ -1708,6 +1709,33 @@ function selectSbBranch(ref) } } render_sb_events(); + /* reflect the project + branch selection in the URL */ + sai_sb_update_url(); +} + +/* + * Keep the project + branch in the URL query string so the view is shareable + * and survives a reload. Event/task params are managed by selectEvent; we + * only touch project/branch here. + */ +function sai_sb_update_url() +{ + try { + var par = new URLSearchParams(window.location.search); + if (sb_selected_project) + par.set("project", sb_selected_project); + else + par.delete("project"); + if (sb_selected_ref) + par.set("branch", sb_selected_ref); + else + par.delete("branch"); + var qs = par.toString(); + var path = window.location.pathname; + if (!path.endsWith('/') && !path.endsWith('index.html')) + path += '/'; + window.history.replaceState({}, "", path + (qs ? ("?" + qs) : "")); + } catch (e) {} } function render_event_decals() { @@ -1749,7 +1777,10 @@ function render_selected_event_tasks(o) { * available yet (sidebar-scoped overview events arrive with t:[] and * fetch their tasks on demand via selectEvent -> eventinfo). */ - s += "<div class=\"event-tasks-header\">"; + s += "<div class=\"event-tasks-header"; + if (e.state == 3) s += " comp_pass"; + if (e.state == 4 || e.state == 6) s += " comp_fail"; + s += "\">"; var refName = e.ref.replace("refs/heads/", "").replace("refs/tags/", ""); s += "<span class=\"event-tasks-title\">" + san(e.repo_name) + " (" + san(refName) + ") - " + san(sai_event_hash_display(e.hash)) + "</span>"; /* admin-only restart-all / delete-event controls live here now */ @@ -2870,7 +2901,17 @@ function ws_open_sai() * the project list. Its handler auto-selects the first * project -> branchlist -> auto-select newest branch -> * scoped overview. + * + * If the URL carries ?project= / ?branch= (a previously + * saved sidebar selection), pre-seed them so the cascade + * restores that view instead of the newest project/branch. */ + { + var _pp = par.get('project'); + var _bp = par.get('branch'); + if (_pp) sb_selected_project = _pp; + if (_bp) sb_selected_ref = _bp; + } sai_sb_request_projects(); }; @@ -3139,23 +3180,41 @@ function ws_open_sai() */ sb_projects = (jso.projects && Array.isArray(jso.projects)) ? jso.projects : []; if (!sb_selected_project && !selected_event_uuid && - !selected_task_uuid && sb_projects.length) + !selected_task_uuid && sb_projects.length) { selectSbProject(sb_projects[0]); - else + } else { render_sb_projects(); + /* + * If a project was pre-selected (e.g. from ?project= + * in the URL), drive the rest of the cascade for it: + * fetch its branch list. When a branch is also + * pre-selected, the branchlist handler below will + * preserve it and fire the scoped overview. + */ + if (sb_selected_project && !sb_branches.length) + sai_sb_request_branches(sb_selected_project); + } break; case "com.warmcat.sai.branchlist": /* * Unique refs for the selected project, * newest-first. Auto-select the most recent - * branch so col 4 populates immediately. - */ + * branch so col 4 populates immediately. + */ sb_branches = (jso.branches && Array.isArray(jso.branches)) ? jso.branches : []; - if (!sb_selected_ref && sb_branches.length) + if (!sb_selected_ref && sb_branches.length) { selectSbBranch(sb_branches[0]); - else + } else { render_sb_branches(); + /* + * If a branch was pre-selected (e.g. from ?branch=), + * fire the scoped overview now that we know the + * branch list for this project. + */ + if (sb_selected_ref) + sai_sb_request_overview(0); + } break; case "sai.warmcat.com.overview": @@ -3219,6 +3278,35 @@ function ws_open_sai() selected_event_uuid = loaded_events[loaded_events.length - 1].e.uuid; } } + + /* + * Deep-link (?event= / ?task=) support: if the + * URL pinned a specific event/task, derive the + * sidebar project + branch from it so the + * project and branch lists highlight the right + * entries. We only do this once (until the + * user manually picks something else). + */ + var _deep = false; + try { + var _dp = new URLSearchParams(window.location.search); + _deep = !!(_dp.get('event') || _dp.get('task')); + } catch (e2) {} + if (_deep && selected_event_uuid && + !sb_selected_project && !sb_selected_ref) { + var _dev = loaded_events.find( + o => o.e.uuid === selected_event_uuid); + if (_dev && _dev.e) { + sb_selected_project = _dev.e.repo_name || null; + sb_selected_ref = _dev.e.ref || null; + render_sb_projects(); + render_sb_branches(); + /* refresh this project's branch list from + * the server so the highlight is correct */ + if (sb_selected_project) + sai_sb_request_branches(sb_selected_project); + } + } } /* @@ -4096,6 +4184,19 @@ window.addEventListener("load", function() { container.classList.add('grant-none'); } } + + /* + * Re-render the tasks pane header so the admin + * restart-all / delete-event buttons appear now that + * auth_state has resolved (the buttons are gated on + * LOGGED_IN_GRANT_ADMIN and are absent from the initial + * render while auth_state was still NOT_LOGGED_IN). + */ + if (selected_event_uuid) { + var _ev = loaded_events.find(o => o.e.uuid === selected_event_uuid); + if (_ev) + render_selected_event_tasks(_ev); + } } }) .catch(function(err) {
Page fetched 0s ago, creation time: 4ms (vhost etag hits: 0%, cache hits: 0%)