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 / terminal-core.js
Author[]Andy Green <andy@warmcat.com> 2026-07-11 09:18 UTC
Committer[]Andy Green <andy@warmcat.com> 2026-07-11 17:50 UTC
Treed5ca8c8b8777de46dd7b45bb35bfdb85405100ee   Raw Patch
 
rationalize-layout
rationalize-layout
diff --git a/assets/index.html b/assets/index.html index 14e9102..c9cf83e 100644 --- a/assets/index.html +++ b/assets/index.html @@ -31,12 +31,17 @@ </div> <div class="resizer" id="resizer"></div> <div class="right-pane"> - <div id="sai_sticky" class="sticky"></div> - <div id="p2" class="undernailed"> - <div class="summary" id="summary"> - <noscript><span id="noscript" class="noscript">Please enable Javascript</span></noscript> - <div id="sai_overview"></div> - <div id="sai_task"></div> + <div class="event-decals-section" id="sai_event_decals"></div> + <div class="tasks-section" id="sai_event_tasks"></div> + <div class="resizer-h" id="resizer_h"></div> + <div class="logs-section" id="sai_task_logs_container"> + <div id="sai_sticky" class="sticky"></div> + <div id="p2" class="undernailed"> + <div class="summary" id="summary"> + <noscript><span id="noscript" class="noscript">Please enable Javascript</span></noscript> + <div id="sai_overview"></div> + <div id="sai_task"></div> + </div> </div> </div> </div> diff --git a/assets/sai.css b/assets/sai.css index 564f857..da1beda 100644 --- a/assets/sai.css +++ b/assets/sai.css @@ -1736,3 +1736,257 @@ div.ibuil.vm-builder { fill: #e74c3c !important; } +/* Three-pane layout styles */ +.right-pane { + display: flex; + flex-direction: column; + height: 100vh; + overflow: hidden; +} + +.event-decals-section { + flex: 0 0 auto; + height: 110px; + box-sizing: border-box; + border-bottom: 1px solid rgba(0, 0, 0, 0.15); + background: #f8f9fa; + display: flex; + flex-direction: row; + overflow-x: auto; + align-items: center; + padding: 8px 12px; + gap: 12px; + scrollbar-width: thin; +} + +/* Event Decal Card styles */ +.event-decal-card { + display: inline-block; + flex: 0 0 auto; + background: #f1f5f9; + border: 1px solid #cbd5e1; + border-radius: 6px; + padding: 6px 10px; + cursor: pointer; + transition: all 0.2s ease-in-out; + user-select: none; + max-height: 85px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); +} + +.event-decal-card:hover { + border-color: #94a3b8; + transform: translateY(-1px); + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.08); +} + +.event-decal-card.comp_pass { + background: #01ff70; +} + +.event-decal-card.comp_fail { + background: #ff4136; + color: #ffffff; +} + +.event-decal-card.comp_fail span, +.event-decal-card.comp_fail a { + color: #ffffff !important; +} + +.event-decal-card.selected { + border: 2px solid #000000; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.event-decal-card table.comp { + background: transparent !important; + border: none !important; + margin: 0 !important; + padding: 0 !important; +} + +.event-decal-card table.comp td { + padding: 0 4px !important; +} + +.tasks-section { + flex: 0 0 200px; + box-sizing: border-box; + border-bottom: 1px solid rgba(0, 0, 0, 0.15); + overflow-y: auto; + padding: 12px; + background: #ffffff; + min-height: 100px; +} + +.resizer-h { + height: 5px; + cursor: ns-resize; + background-color: #ccc; + flex-shrink: 0; +} + +.event-tasks-header { + margin-bottom: 8px; + padding-bottom: 4px; + border-bottom: 1px solid #f1f5f9; +} + +.event-tasks-title { + font-weight: bold; + font-size: 11pt; + color: #1e293b; +} + +.tasks-table-display { + width: 100%; + border-collapse: collapse; +} + +.tasks-table-display td.tasks { + border: none !important; + padding: 0 !important; +} + +.logs-section { + flex: 1 1 0%; + min-height: 0; + display: flex; + flex-direction: column; + overflow: hidden; + background: #ffffff; + color: #000000; + position: relative; +} + +#sai_task_logs_container .undernailed { + margin-left: 0; + top: 0; + flex-grow: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +#sai_task_logs_container #summary { + flex-grow: 1; + display: flex; + flex-direction: column; + overflow: hidden; + background: transparent; + margin: 0; + padding: 0; +} + +#sai_task_logs_container #sai_overview { + flex-grow: 1; + overflow-y: auto; + min-height: 0; + padding: 10px; + scrollbar-width: thin; +} + +#sai_task_logs_container #sai_sticky { + position: relative; + float: none; + background: #ffffff; + width: 100%; + z-index: 10; + border-bottom: 1px solid #e2e8f0; +} + +/* Override taskinfo to be relative when inside Section 3 */ +#sai_task_logs_container div.taskinfo { + position: relative; + background: #ffffff; + color: #000000; + border-style: none; + border-bottom: 1px solid #e2e8f0; + margin: 0; + padding: 8px 12px; + width: auto; + z-index: 10; + flex-shrink: 0; +} + +#sai_task_logs_container div.taskinfo table { + color: #000000; +} + +#sai_task_logs_container div.taskinfo span.ti1 { + color: #000000; + font-weight: bold; +} + +#sai_task_logs_container div.taskinfo span.ti5 { + color: #64748b; +} + +#sai_task_logs_container div.taskinfo table.comp { + display: none; /* Hide the duplicate event decal in the logs header since we have Section 1 */ +} + +/* Scrolllogs light styling */ +#sai_task_logs_container table.scrollogs { + margin-top: 0px; + background: #ffffff; + width: 100%; +} + +#sai_task_logs_container table.scrollogs td.atop { + vertical-align: top; +} + +#sai_task_logs_container div.dlogs { + background: #ffffff; + color: #000000; +} + +#sai_task_logs_container div.dlogsn a { + color: #64748b; + text-decoration: none; +} + +#sai_task_logs_container div.dlogsn a:hover { + color: #334155; +} + +#sai_task_logs_container div.dlogst { + color: #64748b; +} + +.no-tasks, .no-events, .loading-logs { + padding: 20px; + text-align: center; + color: #64748b; + font-style: italic; +} + +.loading-logs { + color: #888888; +} + +/* Pagination card inside horizontal scroll */ +.pagination-card { + display: inline-block; + flex: 0 0 auto; + background: #e2e8f0; + border: 1px solid #cbd5e1; + border-radius: 6px; + padding: 6px 10px; + cursor: pointer; + align-self: center; + font-weight: bold; + height: fit-content; + transition: all 0.2s; + color: #334155; + user-select: none; +} + +.pagination-card:hover { + background: #cbd5e1; + color: #0f172a; +} + + diff --git a/assets/sai.js b/assets/sai.js index 4e3071c..60079f4 100644 --- a/assets/sai.js +++ b/assets/sai.js @@ -950,6 +950,7 @@ function updateSpreadsheetDOM(container, tasks) { var pos = 0, lli = 1, lines = "", times = "", locked = 1, tfirst = 0, cont = [ 0, 0, 0, 0, 0]; var deleted_events_cache = new Set(); +var loaded_events = [], selected_event_uuid = null, selected_task_uuid = null, total_events = 0, current_offset = 0; function get_appropriate_ws_url() { @@ -1085,14 +1086,14 @@ function sai_taskinfo_render(t, now_ut) s = "<table><tr class=\"nomar\"><td class=\"atop\"><table>" + sai_event_render(t, now_ut, 0) + "</table></td><td class=\"ti\">" + "<span class=\"ti1\">" + sai_plat_icon(t.t.platform, 2) + - san(t.t.platform) + "</span>&nbsp;"; + san(t.t.platform) + "</span>&nbsp;" + + sai_stateful_taskname(t.t.state, t.t.taskname, 1) + "&nbsp;&nbsp;"; if (auth_state === SaiAuthState.LOGGED_IN_GRANT_ADMIN && t.t.state != 0 && t.t.state != 3 && t.t.state != 4 && t.t.state != 5) s += "<img class=\"rebuild\" alt=\"stop build\" src=\"stop.svg\" " + "id=\"stop-" + san(t.t.uuid) + "\">&nbsp;"; if (auth_state === SaiAuthState.LOGGED_IN_GRANT_ADMIN) s += "<img class=\"rebuild\" alt=\"rebuild\" src=\"rebuild.png\" " + "id=\"rebuild-" + san(t.t.uuid) + "\">&nbsp;"; - s += sai_stateful_taskname(t.t.state, t.t.taskname, 1); if (t.t.builder_name) { var now_ut = Math.round((new Date().getTime() / 1000)); @@ -1109,7 +1110,7 @@ function sai_taskinfo_render(t, now_ut) sai_arts = ""; } - if (t.runs && t.runs.length > 1) { + if (t.runs && t.runs.length > 0) { var r1 = "", r2 = ""; s += "<div class=\"runs-header-container\"><table class=\"runs-table\"><tr>"; for (var n = t.runs.length - 1; n >= 0; n--) { @@ -1117,7 +1118,7 @@ function sai_taskinfo_render(t, now_ut) var ridx = typeof r.run !== 'undefined' ? r.run : 0; var current = (ridx == (typeof t.t.run !== 'undefined' ? t.t.run : 0)); var dcl = current ? "run-current-decal" : "run-decal"; - var decal = "<div class=\"taskstate taskstate" + r.state + " " + dcl + "\"><a href=\"?task=" + t.t.uuid + "&run=" + ridx + "\">" + sai_plat_icon(r.platform, 0) + "</a></div>"; + var decal = "<div class=\"taskstate taskstate" + r.state + " " + dcl + "\"><a href=\"index.html?task=" + t.t.uuid + "&run=" + ridx + "\">" + sai_plat_icon(r.platform, 0) + "</a></div>"; var timeStr = r.started ? agify(now_ut, r.started) + " ago" : "pending"; r1 += "<td>" + decal + "</td>"; r2 += "<td><span class=\"ti5\">" + timeStr + "</span></td>"; @@ -1188,37 +1189,64 @@ function update_summary_and_progress(event_uuid) { function summarize_build_situation(event_uuid) { - var good = 0, bad = 0, total = 0, ongoing = 0, pending = 0, - roo = document.getElementById("taskcont-" + event_uuid), - same; - - if (!roo) - return { text: "" }; - - same = roo.querySelectorAll(".taskstate:not(.run-decal)"); - if (same) - total = same.length; - same = roo.querySelectorAll(".taskstate0:not(.run-decal)"); - if (same) - pending = same.length; - same = roo.querySelectorAll(".taskstate1:not(.run-decal)"); - if (same) - ongoing += same.length; - same = roo.querySelectorAll(".taskstate2:not(.run-decal)"); - if (same) - ongoing += same.length; - same = roo.querySelectorAll(".taskstate3:not(.run-decal)"); - if (same) - good = same.length; - same = roo.querySelectorAll(".taskstate4:not(.run-decal)"); - if (same) - bad += same.length; - same = roo.querySelectorAll(".taskstate5:not(.run-decal)"); - if (same) - bad += same.length; // treat cancelled as bad - same = roo.querySelectorAll(".taskstate6:not(.run-decal)"); - if (same) - ongoing += same.length; + var good = 0, bad = 0, total = 0, ongoing = 0, pending = 0; + var ev_obj = null; + if (typeof loaded_events !== 'undefined' && loaded_events) { + ev_obj = loaded_events.find(o => o.e.uuid === event_uuid); + } + + if (ev_obj && ev_obj.t) { + var run_max = {}; + for (var q = 0; q < ev_obj.t.length; q++) { + var tx = ev_obj.t[q]; + var ru = typeof tx.run !== 'undefined' ? tx.run : 0; + if (typeof run_max[tx.uuid] === 'undefined' || ru > (typeof run_max[tx.uuid].run !== 'undefined' ? run_max[tx.uuid].run : 0)) + run_max[tx.uuid] = tx; + } + + for (var uid in run_max) { + var t = run_max[uid]; + total++; + switch (t.state) { + case 0: pending++; break; + case 1: + case 2: + case 6: ongoing++; break; + case 3: good++; break; + case 4: + case 5: bad++; break; + } + } + } else { + var roo = document.getElementById("taskcont-" + event_uuid); + if (!roo) + return { text: "" }; + + var same = roo.querySelectorAll(".taskstate:not(.run-decal)"); + if (same) + total = same.length; + same = roo.querySelectorAll(".taskstate0:not(.run-decal)"); + if (same) + pending = same.length; + same = roo.querySelectorAll(".taskstate1:not(.run-decal)"); + if (same) + ongoing += same.length; + same = roo.querySelectorAll(".taskstate2:not(.run-decal)"); + if (same) + ongoing += same.length; + same = roo.querySelectorAll(".taskstate3:not(.run-decal)"); + if (same) + good = same.length; + same = roo.querySelectorAll(".taskstate4:not(.run-decal)"); + if (same) + bad += same.length; + same = roo.querySelectorAll(".taskstate5:not(.run-decal)"); + if (same) + bad += same.length; + same = roo.querySelectorAll(".taskstate6:not(.run-decal)"); + if (same) + ongoing += same.length; + } var text; if (good == total && total > 0) @@ -1368,6 +1396,286 @@ function sai_event_summary_render(o, now_ut, reset_all_icon) return s; } +function find_event_by_task_uuid(taskUuid) { + if (!loaded_events) return null; + for (var i = 0; i < loaded_events.length; i++) { + var o = loaded_events[i]; + if (o.t) { + for (var j = 0; j < o.t.length; j++) { + if (o.t[j].uuid === taskUuid) { + return o.e.uuid; + } + } + } + } + return null; +} + +function render_event_decals() { + var now_ut = Math.round((new Date().getTime() / 1000)); + var s = ""; + + // Add "Newer" pagination button if applicable + if (!gitohashi_integ && total_events > 6 && current_offset > 0) { + s += "<div class=\"btn sai-pagination-btn pagination-card\" data-offset=\"" + Math.max(0, current_offset - 6) + "\">&lt; Newer</div>"; + } + + if (loaded_events && loaded_events.length) { + for (var n = loaded_events.length - 1; n >= 0; n--) { + var o = loaded_events[n]; + var isSelected = (o.e.uuid === selected_event_uuid); + var stateClass = ""; + if (o.e && o.e.state == 3) stateClass = "comp_pass"; + if (o.e && (o.e.state == 4 || o.e.state == 6)) stateClass = "comp_fail"; + s += "<div class=\"event-decal-card " + stateClass + (isSelected ? " selected" : "") + "\" data-uuid=\"" + san(o.e.uuid) + "\">"; + s += sai_event_summary_render(o, now_ut, 1); + s += "</div>"; + } + } else { + s += "<div class=\"no-events\">No events found</div>"; + } + + // Add "Older" pagination button if applicable + if (!gitohashi_integ && total_events > 6 && current_offset + 6 < total_events) { + s += "<div class=\"btn sai-pagination-btn pagination-card\" data-offset=\"" + (current_offset + 6) + "\">Older &gt;</div>"; + } + + var container = document.getElementById("sai_event_decals"); + if (container) { + container.innerHTML = s; + + container.querySelectorAll(".event-decal-card").forEach(function(card) { + card.addEventListener("click", function() { + var uuid = card.getAttribute("data-uuid"); + selectEvent(uuid); + }); + }); + + // Scroll the selected card into view + var selectedCard = container.querySelector(".event-decal-card.selected"); + if (selectedCard) { + selectedCard.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" }); + } + } + + // Refresh summaries/progress bars + if (loaded_events) { + loaded_events.forEach(o => { + update_summary_and_progress(o.e.uuid); + }); + } +} + +function render_selected_event_tasks(o) { + var now_ut = Math.round((new Date().getTime() / 1000)); + var s = ""; + var e = o.e; + if (o.t && o.t.length) { + s += "<div class=\"event-tasks-header\">"; + var refName = e.ref.replace("refs/heads/", "").replace("refs/tags/", ""); + s += "<span class=\"event-tasks-title\">" + san(e.repo_name) + " (" + san(refName) + ") - " + san(e.hash.substr(0, 8)) + "</span>"; + s += "</div>"; + s += "<table class=\"tasks-table-display\"><tr><td class=\"tasks\" id=\"taskcont-" + san(e.uuid) + "\">"; + + var run_max = {}, run_list = {}; + for (var q = 0; q < o.t.length; q++) { + var tx = o.t[q]; + var ru = typeof tx.run !== 'undefined' ? tx.run : 0; + if (!run_list[tx.uuid]) run_list[tx.uuid] = []; + run_list[tx.uuid].push(tx); + if (typeof run_max[tx.uuid] === 'undefined' || ru > (typeof run_max[tx.uuid].run !== 'undefined' ? run_max[tx.uuid].run : 0)) + run_max[tx.uuid] = tx; + } + for (var uid in run_list) { + run_list[uid].sort(function(a, b) { var ar = typeof a.run !== 'undefined' ? a.run : 0; var br = typeof b.run !== 'undefined' ? b.run : 0; return ar - br; }); + } + + var ctn = ""; + var s1 = ""; + for (var q = 0; q < o.t.length; q++) { + var t = o.t[q]; + + if (t !== run_max[t.uuid]) + continue; + + if (t.taskname !== ctn) { + if (ctn !== "") { + s += "<div class=\"ib\"><table class=\"nomar\">" + + "<tr><td class=\"tn\">" + ctn + + "</td><td class=\"keepline\">" + s1 + + "</td></tr></table></div>"; + s1 = ""; + } + ctn = t.taskname; + } + + s1 += "<div id=\"taskstate_" + t.uuid + "\" class=\"taskstate taskstate" + t.state + + (run_list[t.uuid].length > 1 ? " has_runs" : "") + + "\" data-event-uuid=\"" + san(e.uuid) + "\" data-platform=\"" + san(t.platform) + + "\" data-rebuildable=\"" + t.rebuildable + "\">"; + s1 += "<a href=\"index.html?task=" + t.uuid + "\">" + + sai_plat_icon(t.platform, 0) + "</a>"; + if (run_list[t.uuid].length > 1) { + s1 += "<div class=\"runs-popup\"><table>"; + for (var w = 0; w < run_list[t.uuid].length; w++) { + var rt = run_list[t.uuid][w]; + var rr = typeof rt.run !== 'undefined' ? rt.run : 0; + var decal = "<div class=\"taskstate taskstate" + rt.state + " run-decal\"><a href=\"index.html?task=" + t.uuid + "&run=" + rr + "\">" + sai_plat_icon(rt.platform, 0) + "</a></div>"; + var timeStr = rt.started ? agify(now_ut, rt.started) + " ago" : "pending"; + s1 += "<tr><td>" + decal + "</td><td class=\"runs-time-cell\"><span class=\"ti5\">" + timeStr + "</span></td></tr>"; + } + s1 += "</table></div>"; + } + s1 += "</div>"; + } + + if (ctn !== "") { + s += "<div class=\"ib\"><table class=\"nomar\">" + + "<tr><td class=\"tn\">" + ctn + + "<td class=\"keepline\">" + s1 + + "</td></tr></table></div>"; + } + + s += "</td></tr></table>"; + } else { + s += "<div class=\"no-tasks\">No tasks for this event</div>"; + } + + var container = document.getElementById("sai_event_tasks"); + if (container) { + container.innerHTML = s; + + // Refresh progress bars for these tasks + if (o.t) { + for (var q = 0; q < o.t.length; q++) { + refresh_state(o.t[q]); + } + } + update_summary_and_progress(e.uuid); + } +} + +function selectEvent(uuid) { + selected_event_uuid = uuid; + + // Check if selected task belongs to this event + var ev_obj = loaded_events.find(o => o.e.uuid === uuid); + var hasTask = false; + if (ev_obj && ev_obj.t && selected_task_uuid) { + hasTask = ev_obj.t.some(t => t.uuid === selected_task_uuid); + } + if (!hasTask) { + selected_task_uuid = null; + window.current_task_run = null; + var stickyEl = document.getElementById("sai_sticky"); + var overviewEl = document.getElementById("sai_overview"); + if (stickyEl) stickyEl.innerHTML = ""; + if (overviewEl) overviewEl.innerHTML = ""; + + lines = times = logs = ""; + lines_pending = times_pending = logs_pending = ""; + segment_stack = []; + seg_counter = 0; + window.held_start_line = null; + logAnsiState = {}; + tfirst = 0; + lli = 1; + last_log_timestamp = 0; + + var par = new URLSearchParams(window.location.search); + par.delete("task"); + par.delete("run"); + var qs = par.toString(); + var path = window.location.pathname; + if (!path.endsWith('/') && !path.endsWith('index.html')) { + path += '/'; + } + window.history.pushState({}, "", path + (qs ? ("?" + qs) : "")); + } + + // Highlight card + var container = document.getElementById("sai_event_decals"); + if (container) { + container.querySelectorAll(".event-decal-card").forEach(function(card) { + if (card.getAttribute("data-uuid") === uuid) { + card.classList.add("selected"); + } else { + card.classList.remove("selected"); + } + }); + } + + if (ev_obj) { + render_selected_event_tasks(ev_obj); + } +} + +function init_task_logs_dom() { + var s = "<table><td colspan=\"3\"><pre><table class=\"scrollogs\"><tr>" + + "<td class=\"atop\">" + + "<div id=\"dlogsn\" class=\"dlogsn\">" + lines + "</div></td>" + + "<td class=\"atop\">" + + "<div id=\"dlogst\" class=\"dlogst\">" + times + "</div></td>" + + "<td class=\"atop\"><div id=\"dlogs\" class=\"dlogs\">" + + "<span id=\"logs\" class=\"nowrap\">" + logs + + "</span>"+ + "</div></td></tr></table></pre>"; + var overviewEl = document.getElementById("sai_overview"); + if (overviewEl) { + overviewEl.innerHTML = s; + } +} + +function selectTask(taskUuid, runVal) { + selected_task_uuid = taskUuid; + window.current_task_run = runVal; + + // Update URL query parameters dynamically (fully relative) + var par = new URLSearchParams(window.location.search); + par.set("task", taskUuid); + if (runVal && runVal !== "-1") { + par.set("run", runVal); + } else { + par.delete("run"); + } + var path = window.location.pathname; + if (!path.endsWith('/') && !path.endsWith('index.html')) { + path += '/'; + } + window.history.pushState({}, "", path + "?" + par.toString()); + + // Setup loading state and clear logs (without the loading text overlay) + var stickyEl = document.getElementById("sai_sticky"); + if (stickyEl) { + stickyEl.innerHTML = "<div class=\"taskinfo\" id=\"taskinfo-" + san(taskUuid) + "\"></div>"; + } + + lines = times = logs = ""; + lines_pending = times_pending = logs_pending = ""; + segment_stack = []; + seg_counter = 0; + window.held_start_line = null; + logAnsiState = {}; + tfirst = 0; + lli = 1; + last_log_timestamp = 0; + + init_task_logs_dom(); + + // Request logs from websocket + var req = "{\"schema\":" + + "\"com.warmcat.sai.taskinfo\"," + + "\"js_api_version\": " + SAI_JS_API_VERSION + "," + + "\"logs\": 1," + + "\"last_log_ts\":" + last_log_timestamp + ","; + if (runVal && runVal !== "-1") + req += "\"run\":" + runVal + ","; + else + req += "\"run\": -1,"; + req += "\"task_hash\":" + JSON.stringify(taskUuid) + "}"; + sai.send(req); +} + function sai_event_render(o, now_ut, reset_all_icon) { var s, q, ctn = "", wai, s1 = "", n, e = o.e; @@ -2211,13 +2519,9 @@ function ws_open_sai() run_idx = par.get('run'); if (tid) { - /* - * We're being the page monitoring / reporting - * on what happened with a specific task... ask - * about the specific task on the ws link - */ - console.log("tid " + tid); + selected_task_uuid = tid; + if (run_idx) window.current_task_run = run_idx; var req = "{\"schema\":" + "\"com.warmcat.sai.taskinfo\"," + @@ -2231,17 +2535,16 @@ function ws_open_sai() req += "\"task_hash\":" + JSON.stringify(tid) + "}"; sai.send(req); + // Also request the overview + sai.send("{\"schema\":" + + "\"com.warmcat.sai.taskinfo\", \"js_api_version\": " + SAI_JS_API_VERSION + + ", \"offset\": " + current_overview_offset + "}"); return; } if (eid) { - /* - * We're being the page monitoring / reporting - * on what happened with a specific event... ask - * about the specific event on the ws link - */ - console.log("eid " + eid); + selected_event_uuid = eid; sai.send("{\"schema\":" + "\"com.warmcat.sai.eventinfo\"," + @@ -2249,6 +2552,10 @@ function ws_open_sai() "\"event_hash\":" + JSON.stringify(eid) + "}"); + // Also request the overview + sai.send("{\"schema\":" + + "\"com.warmcat.sai.taskinfo\", \"js_api_version\": " + SAI_JS_API_VERSION + + ", \"offset\": " + current_overview_offset + "}"); return; } @@ -2528,148 +2835,43 @@ function ws_open_sai() } } - s = "<table class=\"events-table\">"; - - /* - * Update existing? - */ - - // console.log("jso.overview.length " + jso.overview.length); - - if (jso.overview.length == 1 && - document.getElementById("ev-group-" + jso.overview[0].e.uuid)) { - /* completely replace the event HTML to capture new runs/tasks */ - document.getElementById("ev-group-" + jso.overview[0].e.uuid).outerHTML = - sai_event_render(jso.overview[0], now_ut, 1); - - /* restore progress bars via refresh_state */ - if (jso.overview[0].t) { - for (n = jso.overview[0].t.length - 1; n >= 0; n--) - refresh_state(jso.overview[0].t[n]); + if (jso.overview) { + jso.overview.forEach(function(new_ev) { + var idx = loaded_events.findIndex(o => o.e.uuid === new_ev.e.uuid); + if (idx !== -1) { + loaded_events[idx] = new_ev; + } else { + loaded_events.push(new_ev); + } + }); + if (jso.overview.length > 1) { + loaded_events = jso.overview; + if (typeof jso.total_events !== 'undefined') total_events = jso.total_events; + if (typeof jso.offset !== 'undefined') current_offset = jso.offset; } + } - update_summary_and_progress(jso.overview[0].e.uuid); - - aging(); - } else - { - /* - * display events wholesale - */ - if (jso.overview.length) { - var pagination_html_top = ""; - var pagination_html_bottom = ""; - if (!gitohashi_integ && jso.total_events > 6) { - pagination_html_top += "<div class=\"sai-pagination\">"; - if (jso.offset > 0) { - pagination_html_top += "<button class=\"btn sai-pagination-btn\" data-offset=\"" + Math.max(0, jso.offset - 6) + "\">&lt; Newer</button>"; - } - pagination_html_top += "</div>"; - - pagination_html_bottom += "<div class=\"sai-pagination\">"; - if (jso.offset + 6 < jso.total_events) { - pagination_html_bottom += "<button class=\"btn sai-pagination-btn\" data-offset=\"" + (jso.offset + 6) + "\">Older &gt;</button>"; - } - pagination_html_bottom += "</div>"; + if (loaded_events.length > 0) { + if (!selected_event_uuid) { + if (selected_task_uuid) { + var evUuid = find_event_by_task_uuid(selected_task_uuid); + if (evUuid) selected_event_uuid = evUuid; } - - s = pagination_html_top + s; - - for (n = jso.overview.length - 1; n >= 0; n--) - s += sai_event_render(jso.overview[n], now_ut, 1); - - s = s + "</table>" + pagination_html_bottom; - - if (document.getElementById("sai_sticky")) - document.getElementById("sai_sticky").innerHTML = s; - - for (n = jso.overview.length - 1; n >= 0; n--) { - var esr_el = document.getElementById("esr-" + jso.overview[n].e.uuid); - if (esr_el) - esr_el.innerHTML = - sai_event_summary_render(jso.overview[n], now_ut, 1); - - if (jso.overview[n].t) { - for (var q = 0; q < jso.overview[n].t.length; q++) { - refresh_state(jso.overview[n].t[q]); - } - } - - update_summary_and_progress(jso.overview[n].e.uuid); + if (!selected_event_uuid) { + selected_event_uuid = loaded_events[loaded_events.length - 1].e.uuid; } - aging(); } + } + render_event_decals(); - if (gitohashi_integ && document.getElementById("gitohashi_sai_icon")) { - var integ_state = 0; - document.getElementById("gitohashi_sai_icon").addEventListener("mouseenter", function( event ) { - document.getElementById("gitohashi_sai_icon").style.zIndex = 1999; - document.getElementById("gitohashi_sai_details").style.zIndex = 2000; - document.getElementById("gitohashi_sai_details").style.opacity = 1.0; - integ_state = 1; - }, false); - - document.getElementById("gitohashi_sai_details").addEventListener("mouseout", function( event ) { - var e = event.toElement || event.relatedTarget; - while (e && e.parentNode && e.parentNode != window) { - if (e.parentNode == this || e == this) { - if (e.preventDefault) - e.preventDefault(); - return false; - } - e = e.parentNode; - } - document.getElementById("gitohashi_sai_details").style.opacity = 0.0; - document.getElementById("gitohashi_sai_details").style.zIndex = -1; - document.getElementById("gitohashi_sai_icon").style.zIndex = 2001; - }, true); - - aging(); + if (selected_event_uuid) { + var ev_obj = loaded_events.find(o => o.e.uuid === selected_event_uuid); + if (ev_obj) { + render_selected_event_tasks(ev_obj); } } - if (jso.overview.length) - for (n = jso.overview.length - 1; n >= 0; n--) { - if (document.getElementById("rebuild-ev-" + san(jso.overview[n].e.uuid))) - document.getElementById("rebuild-ev-" + san(jso.overview[n].e.uuid)). - addEventListener("click", function(e) { - console.log(e); - var rs= "{\"schema\":" + - "\"com.warmcat.sai.eventreset\"," + - "\"uuid\": " + - JSON.stringify(san(e.srcElement.id.substring(11))) + "}"; - - console.log(rs); - sai.send(rs); - }); - if (document.getElementById("delete-ev-" + san(jso.overview[n].e.uuid))) - document.getElementById("delete-ev-" + san(jso.overview[n].e.uuid)). - addEventListener("click", function(e) { - console.log(e); - var uuid = san(e.srcElement.id.substring(10)); - deleted_events_cache.add(uuid); - var rs= "{\"schema\":" + - "\"com.warmcat.sai.eventdelete\"," + - "\"uuid\": " + - JSON.stringify(uuid) + "}"; - - console.log(rs); - sai.send(rs); - // Remove the event row immediately - var td = e.srcElement.closest(".waiting"); - if (td && td.parentNode) { - var tr = td.parentNode; - tr.style.transition = 'opacity 0.3s'; - tr.style.opacity = '0'; - setTimeout(function() { - var tbody = document.getElementById("ev-group-" + uuid); - if (tbody && tbody.parentNode) { - tbody.parentNode.removeChild(tbody); - } - }, 300); - } - }); - } + aging(); break; case "com.warmcat.sai.taskinfo": @@ -2681,158 +2883,145 @@ function ws_open_sai() window.current_viewed_task_state = jso.t.state; } - /* - * We get told about changes to any task state, - * it's up to us to figure out if the page we - * showed should display the update and in what - * form. - * - * We make sure the div containing the task info - * has a special ID depending on if it's shown - * as a tuple or as extended info - * - * First see if it appears as a tuple, and if - * so, let's just update that - */ - if (document.getElementById("taskstate_" + jso.t.uuid)) { console.log("found taskstate_" + jso.t.uuid); refresh_state(jso.t); - update_summary_and_progress(jso.t.uuid.substring(0, 32)); + } - } else - - /* update task summary if shown anywhere */ - - if (document.getElementById("taskinfo-" + jso.t.uuid)) { - if (typeof window.current_task_run !== 'undefined' && window.current_task_run !== jso.t.run) { - if (document.getElementById("sai-task-logs")) - document.getElementById("sai-task-logs").innerHTML = ""; - /* update the URL without reloading so sharing works */ - const newUrl = new URL(window.location.href); - newUrl.searchParams.set('run', jso.t.run); - window.history.replaceState({}, '', newUrl); - } - window.current_task_run = jso.t.run; - document.getElementById("taskinfo-" + jso.t.uuid).innerHTML = sai_taskinfo_render(jso); - if (jso.e) { - if (document.getElementById("esr-" + jso.e.uuid)) - document.getElementById("esr-" + jso.e.uuid).innerHTML = - sai_event_summary_render(jso, now_ut, 1); - update_summary_and_progress(jso.e.uuid); + /* update task summary if shown anywhere */ + if (document.getElementById("taskinfo-" + jso.t.uuid)) { + if (typeof window.current_task_run !== 'undefined' && window.current_task_run !== jso.t.run) { + if (document.getElementById("sai-task-logs")) + document.getElementById("sai-task-logs").innerHTML = ""; + /* update the URL without reloading so sharing works */ + var par = new URLSearchParams(window.location.search); + par.set('run', jso.t.run); + var path = window.location.pathname; + if (!path.endsWith('/') && !path.endsWith('index.html')) { + path += '/'; } + window.history.replaceState({}, '', path + '?' + par.toString()); + } + window.current_task_run = jso.t.run; + document.getElementById("taskinfo-" + jso.t.uuid).innerHTML = sai_taskinfo_render(jso); + if (jso.e) { + if (document.getElementById("esr-" + jso.e.uuid)) + document.getElementById("esr-" + jso.e.uuid).innerHTML = + sai_event_summary_render(jso, now_ut, 1); + update_summary_and_progress(jso.e.uuid); + } + } - } else { - - console.log("NO taskinfo- or taskstate_" + jso.t.uuid); - - /* - * Last chance if we might be - * on a task-specific page, and - * want to show the task info - * at the top - */ - - - const urlParams = new URLSearchParams(window.location.search); - const url_task_uuid = urlParams.get('task'); - - if (url_task_uuid === jso.t.uuid && - document.getElementById("sai_sticky")) { - window.current_task_run = jso.t.run; - document.getElementById("sai_sticky").innerHTML = - "<div class=\"taskinfo\" id=\"taskinfo-" + - san(jso.t.uuid) + "\">" + - sai_taskinfo_render(jso) + - "</div>"; + if (!document.getElementById("taskstate_" + jso.t.uuid) && + !document.getElementById("taskinfo-" + jso.t.uuid)) { + console.log("NO taskinfo- or taskstate_" + jso.t.uuid); - s = "<table><td colspan=\"3\"><pre><table class=\"scrollogs\"><tr>" + - "<td class=\"atop\">" + - "<div id=\"dlogsn\" class=\"dlogsn\">" + lines + "</div></td>" + - "<td class=\"atop\">" + - "<div id=\"dlogst\" class=\"dlogst\">" + times + "</div></td>" + - "<td class=\"atop\"><div id=\"dlogs\" class=\"dlogs\">" + - "<span id=\"logs\" class=\"nowrap\">" + logs + - "</span>"+ - "</div></td></tr></table></pre>"; + /* + * Last chance if we might be + * on a task-specific page, and + * want to show the task info + * at the top + */ - if (document.getElementById("sai_overview")) { - document.getElementById("sai_overview").innerHTML = s; - logs_pending = times_pending = lines_pending = ""; + const urlParams = new URLSearchParams(window.location.search); + const url_task_uuid = urlParams.get('task'); - if (jso.e && document.getElementById("esr-" + jso.e.uuid)) - document.getElementById("esr-" + jso.e.uuid).innerHTML = - sai_event_summary_render(jso, now_ut, 1); + if (url_task_uuid === jso.t.uuid && + document.getElementById("sai_sticky")) { + window.current_task_run = jso.t.run; + document.getElementById("sai_sticky").innerHTML = + "<div class=\"taskinfo\" id=\"taskinfo-" + + san(jso.t.uuid) + "\">" + + sai_taskinfo_render(jso) + + "</div>"; + + s = "<table><td colspan=\"3\"><pre><table class=\"scrollogs\"><tr>" + + "<td class=\"atop\">" + + "<div id=\"dlogsn\" class=\"dlogsn\">" + lines + "</div></td>" + + "<td class=\"atop\">" + + "<div id=\"dlogst\" class=\"dlogst\">" + times + "</div></td>" + + "<td class=\"atop\"><div id=\"dlogs\" class=\"dlogs\">" + + "<span id=\"logs\" class=\"nowrap\">" + logs + + "</span>"+ + "</div></td></tr></table></pre>"; + + if (document.getElementById("sai_overview")) { + document.getElementById("sai_overview").innerHTML = s; + logs_pending = times_pending = lines_pending = ""; + + if (jso.e && document.getElementById("esr-" + jso.e.uuid)) + document.getElementById("esr-" + jso.e.uuid).innerHTML = + sai_event_summary_render(jso, now_ut, 1); - } } - if (jso.e) - update_summary_and_progress(jso.e.uuid); - - if (document.getElementById("rebuild-" + san(jso.t.uuid))) { - document.getElementById("rebuild-" + san(jso.t.uuid)). - addEventListener("click", function(e) { - var rs= "{\"schema\":" + - "\"com.warmcat.sai.taskreset\"," + - "\"uuid\": " + - JSON.stringify(san(e.srcElement.id.substring(8))) + "}"; - - console.log(rs); - sai.send(rs); - - var tid = san(e.srcElement.id.substring(8)); - if (new URLSearchParams(window.location.search).get('run')) { - window.location.search = '?task=' + tid; - return; - } - - /* - * and immediately re-request the task info, so we can get - * the new logs - */ - var rq = "{\"schema\":" + - "\"com.warmcat.sai.taskinfo\"," + - "\"js_api_version\": " + SAI_JS_API_VERSION + "," + - "\"logs\": 1," + - "\"run\": -1," + - "\"last_log_ts\":" + last_log_timestamp + "," + - "\"task_hash\":" + - JSON.stringify(tid) + "}"; - - console.log(rq); - sai.send(rq); - - document.getElementById("dlogsn").innerHTML = ""; - document.getElementById("dlogst").innerHTML = ""; - document.getElementById("dlogs").innerHTML = "<span id=\"logs\" class=\"nowrap\"></span>"; - lines = times = logs = ""; - lines_pending = times_pending = logs_pending = ""; - segment_stack = []; - seg_counter = 0; - window.held_start_line = null; - logAnsiState = {}; - tfirst = 0; - lli = 1; - last_log_timestamp = 0; - }); } + } + if (jso.e) + update_summary_and_progress(jso.e.uuid); + + if (document.getElementById("rebuild-" + san(jso.t.uuid))) { + document.getElementById("rebuild-" + san(jso.t.uuid)). + addEventListener("click", function(e) { + var rs= "{\"schema\":" + + "\"com.warmcat.sai.taskreset\"," + + "\"uuid\": " + + JSON.stringify(san(e.srcElement.id.substring(8))) + "}"; + + console.log(rs); + sai.send(rs); + + var tid = san(e.srcElement.id.substring(8)); + if (new URLSearchParams(window.location.search).get('run')) { + window.location.search = '?task=' + tid; + return; + } - if (document.getElementById("stop-" + san(jso.t.uuid))) { - document.getElementById("stop-" + san(jso.t.uuid)). - addEventListener("click", function(e) { - var rs= "{\"schema\":" + - "\"com.warmcat.sai.taskcan\"," + - "\"task_uuid\": " + - JSON.stringify(san(e.srcElement.id.substring(5))) + "}"; - console.log(rs); - sai.send(rs); - }); - } + /* + * and immediately re-request the task info, so we can get + * the new logs + */ + var rq = "{\"schema\":" + + "\"com.warmcat.sai.taskinfo\"," + + "\"js_api_version\": " + SAI_JS_API_VERSION + "," + + "\"logs\": 1," + + "\"run\": -1," + + "\"last_log_ts\":" + last_log_timestamp + "," + + "\"task_hash\":" + + JSON.stringify(tid) + "}"; + + console.log(rq); + sai.send(rq); + + document.getElementById("dlogsn").innerHTML = ""; + document.getElementById("dlogst").innerHTML = ""; + document.getElementById("dlogs").innerHTML = "<span id=\"logs\" class=\"nowrap\"></span>"; + lines = times = logs = ""; + lines_pending = times_pending = logs_pending = ""; + segment_stack = []; + seg_counter = 0; + window.held_start_line = null; + logAnsiState = {}; + tfirst = 0; + lli = 1; + last_log_timestamp = 0; + }); + } - aging(); + if (document.getElementById("stop-" + san(jso.t.uuid))) { + document.getElementById("stop-" + san(jso.t.uuid)). + addEventListener("click", function(e) { + var rs= "{\"schema\":" + + "\"com.warmcat.sai.taskcan\"," + + "\"task_uuid\": " + + JSON.stringify(san(e.srcElement.id.substring(5))) + "}"; + console.log(rs); + sai.send(rs); + }); } + + aging(); break; case "com.warmcat.sai.loadreport": @@ -3157,7 +3346,7 @@ function ws_open_sai() if (!redpend) { redpend = 1; setTimeout(function() { - const rightPane = document.querySelector('.right-pane'); + const rightPane = document.getElementById('sai_overview') || document.querySelector('.right-pane'); redpend = 0; if (rightPane) locked = rightPane.scrollHeight - @@ -3234,6 +3423,84 @@ window.addEventListener("load", function() { window.change_page(parseInt(pbtn.getAttribute('data-offset'))); } } + + var a = e.target.closest('a'); + if (a) { + var href = a.getAttribute('href'); + if (href && (href.indexOf('?task=') !== -1 || href.indexOf('index.html?task=') !== -1)) { + e.preventDefault(); + var urlParams = new URLSearchParams(href.substring(href.indexOf('?'))); + var taskUuid = urlParams.get('task'); + var runVal = urlParams.get('run') || '-1'; + selectTask(taskUuid, runVal); + } + } + + var rebuildBtn = e.target.closest("[id^='rebuild-']"); + if (rebuildBtn && !rebuildBtn.id.startsWith("rebuild-ev-")) { + var tid = rebuildBtn.id.substring(8); + var rs = "{\"schema\":\"com.warmcat.sai.taskreset\",\"uuid\":" + JSON.stringify(san(tid)) + "}"; + console.log(rs); + sai.send(rs); + + // Clear logs and re-request taskinfo + var dlogsn = document.getElementById("dlogsn"); + var dlogst = document.getElementById("dlogst"); + var dlogs = document.getElementById("dlogs"); + if (dlogsn) dlogsn.innerHTML = ""; + if (dlogst) dlogst.innerHTML = ""; + if (dlogs) dlogs.innerHTML = "<span id=\"logs\" class=\"nowrap\"></span>"; + lines = times = logs = ""; + lines_pending = times_pending = logs_pending = ""; + segment_stack = []; + seg_counter = 0; + window.held_start_line = null; + logAnsiState = {}; + tfirst = 0; + lli = 1; + last_log_timestamp = 0; + + var rq = "{\"schema\":\"com.warmcat.sai.taskinfo\",\"js_api_version\":" + SAI_JS_API_VERSION + ",\"logs\":1,\"run\":-1,\"last_log_ts\":" + last_log_timestamp + ",\"task_hash\":" + JSON.stringify(tid) + "}"; + console.log(rq); + sai.send(rq); + } + + var stopBtn = e.target.closest("[id^='stop-']"); + if (stopBtn) { + var tid = stopBtn.id.substring(5); + var rs = "{\"schema\":\"com.warmcat.sai.taskcan\",\"task_uuid\":" + JSON.stringify(san(tid)) + "}"; + console.log(rs); + sai.send(rs); + } + + var rebuildEvBtn = e.target.closest("[id^='rebuild-ev-']"); + if (rebuildEvBtn) { + var uuid = rebuildEvBtn.id.substring(11); + var rs = "{\"schema\":\"com.warmcat.sai.eventreset\",\"uuid\":" + JSON.stringify(san(uuid)) + "}"; + console.log(rs); + sai.send(rs); + } + + var deleteEvBtn = e.target.closest("[id^='delete-ev-']"); + if (deleteEvBtn) { + var uuid = deleteEvBtn.id.substring(10); + deleted_events_cache.add(uuid); + var rs = "{\"schema\":\"com.warmcat.sai.eventdelete\",\"uuid\":" + JSON.stringify(uuid) + "}"; + console.log(rs); + sai.send(rs); + + // Remove / hide the decal card + var card = document.querySelector(".event-decal-card[data-uuid='" + uuid + "']"); + if (card) { + card.style.transition = 'opacity 0.3s'; + card.style.opacity = '0'; + setTimeout(function() { + if (card.parentNode) { + card.parentNode.removeChild(card); + } + }, 300); + } + } }); const savedFlex = localStorage.getItem('sai-left-pane-flex'); @@ -3324,100 +3591,97 @@ window.addEventListener("load", function() { }, 500) - const stickyEl = document.getElementById("sai_sticky"); - if (stickyEl) { - stickyEl.addEventListener("contextmenu", function(event) { - let target = event.target; - let taskDiv = null; - - // find the taskstate div parent - while (target && target.id !== "sai_sticky") { - if (target.classList && target.classList.contains("taskstate")) { - taskDiv = target; - break; - } - target = target.parentElement; + document.addEventListener("contextmenu", function(event) { + let target = event.target; + let taskDiv = null; + + // find the taskstate div parent + while (target && target !== document.body) { + if (target.classList && target.classList.contains("taskstate")) { + taskDiv = target; + break; } + target = target.parentElement; + } + + if (taskDiv && auth_state === SaiAuthState.LOGGED_IN_GRANT_ADMIN) { + event.preventDefault(); - if (taskDiv && auth_state === SaiAuthState.LOGGED_IN_GRANT_ADMIN) { - event.preventDefault(); + const taskUuid = taskDiv.id.substring(10); + const eventUuid = taskDiv.dataset.eventUuid; + const platform = taskDiv.dataset.platform; + + const menuItems = [ + { + label: "Rebuild this task", + callback: () => { + sai.send(JSON.stringify({ + schema: "com.warmcat.sai.taskreset", + uuid: taskUuid + })); + } + }, + { + label: "Remove all tries", + callback: () => { + sai.send(JSON.stringify({ + schema: "com.warmcat.sai.taskremovealltries", + uuid: taskUuid + })); + } + }, + { + label: `Rebuild all <b>${hsanitize(platform)}</b>`, + callback: () => { + sai.send(JSON.stringify({ + schema: "com.warmcat.sai.platreset", + event_uuid: eventUuid, + platform: platform + })); + } + } + ]; - const taskUuid = taskDiv.id.substring(10); - const eventUuid = taskDiv.dataset.eventUuid; - const platform = taskDiv.dataset.platform; + const isFinalState = ["taskstate3", "taskstate4", "taskstate5", "taskstate7"].some(s => taskDiv.classList.contains(s)); - const menuItems = [ - { - label: "Rebuild this task", + if (!isFinalState) { + if (taskDiv.classList.contains("taskstate10")) { + menuItems.push({ + label: "Continue task", callback: () => { sai.send(JSON.stringify({ - schema: "com.warmcat.sai.taskreset", + schema: "com.warmcat.sai.taskresume", uuid: taskUuid })); } - }, - { - label: "Remove all tries", + }); + } else { + menuItems.push({ + label: "Pause task", callback: () => { sai.send(JSON.stringify({ - schema: "com.warmcat.sai.taskremovealltries", + schema: "com.warmcat.sai.taskpause", uuid: taskUuid })); } - }, - { - label: `Rebuild all <b>${hsanitize(platform)}</b>`, - callback: () => { - sai.send(JSON.stringify({ - schema: "com.warmcat.sai.platreset", - event_uuid: eventUuid, - platform: platform - })); - } - } - ]; - - const isFinalState = ["taskstate3", "taskstate4", "taskstate5", "taskstate7"].some(s => taskDiv.classList.contains(s)); - - if (!isFinalState) { - if (taskDiv.classList.contains("taskstate10")) { - menuItems.push({ - label: "Continue task", - callback: () => { - sai.send(JSON.stringify({ - schema: "com.warmcat.sai.taskresume", - uuid: taskUuid - })); - } - }); - } else { - menuItems.push({ - label: "Pause task", - callback: () => { - sai.send(JSON.stringify({ - schema: "com.warmcat.sai.taskpause", - uuid: taskUuid - })); - } - }); - } + }); } + } - if (taskDiv.dataset.rebuildable === "1") - menuItems.splice(1, 0, { - label: "Rebuild last step", - callback: () => { - sai.send(JSON.stringify({ - schema: "com.warmcat.sai.taskrebuildlaststep", - uuid: taskUuid - })); - } - }); + if (taskDiv.dataset.rebuildable === "1") + menuItems.splice(1, 0, { + label: "Rebuild last step", + callback: () => { + sai.send(JSON.stringify({ + schema: "com.warmcat.sai.taskrebuildlaststep", + uuid: taskUuid + })); + } + }); - createContextMenu(event, menuItems); - } - }); - } + createContextMenu(event, menuItems); + } + }); const resizer = document.getElementById('resizer'); if (resizer) { const leftPane = resizer.previousElementSibling; @@ -3446,6 +3710,43 @@ window.addEventListener("load", function() { resizer.addEventListener('mousedown', onMouseDown); } + + const savedTasksHeight = localStorage.getItem('sai-tasks-height'); + if (savedTasksHeight) { + const tasksSection = document.getElementById('sai_event_tasks'); + if (tasksSection) { + tasksSection.style.flex = savedTasksHeight; + } + } + + const resizerH = document.getElementById('resizer_h'); + if (resizerH) { + const tasksSection = resizerH.previousElementSibling; + + let y = 0; + let tasksHeight = 0; + + const onMouseMoveH = (e) => { + const dy = e.clientY - y; + const newHeight = tasksHeight + dy; + tasksSection.style.flex = `0 0 ${newHeight}px`; + }; + + const onMouseUpH = () => { + document.removeEventListener('mousemove', onMouseMoveH); + document.removeEventListener('mouseup', onMouseUpH); + localStorage.setItem('sai-tasks-height', tasksSection.style.flex); + }; + + const onMouseDownH = (e) => { + y = e.clientY; + tasksHeight = tasksSection.getBoundingClientRect().height; + document.addEventListener('mousemove', onMouseMoveH); + document.addEventListener('mouseup', onMouseUpH); + }; + + resizerH.addEventListener('mousedown', onMouseDownH); + } }, false); }()); diff --git a/src/web/w-comms.c b/src/web/w-comms.c index 82fab82..e08150e 100644 --- a/src/web/w-comms.c +++ b/src/web/w-comms.c @@ -589,6 +589,8 @@ http_resp: } } } + if (pss->specificity == SAIM_SPECIFIC_TASK) + pss->specific_project[0] = '\0'; if (!pss->specificity) { pss->specificity = SAIM_SPECIFIC_H; diff --git a/src/web/w-private.h b/src/web/w-private.h index b9e9ecf..38470a9 100644 --- a/src/web/w-private.h +++ b/src/web/w-private.h @@ -132,6 +132,7 @@ struct pss { unsigned int announced:1; unsigned int bulk_binary_data:1; unsigned int toggle_favour_sch:1; + unsigned int resolved_task_offset:1; sai_auth_state_t auth_state; }; diff --git a/src/web/w-ws-browser.c b/src/web/w-ws-browser.c index fdc352b..7652b62 100644 --- a/src/web/w-ws-browser.c +++ b/src/web/w-ws-browser.c @@ -479,12 +479,22 @@ saiw_pss_schedule_taskinfo(struct pss *pss, const char *task_uuid, int logsub, i lws_write_ws_flags(LWS_WRITE_TEXT, fi, 1)); /* does he want to subscribe to logs? */ - if (logsub && !pss->subs_list.owner) { + if (logsub) { + int new_run = run_idx >= 0 ? run_idx : one_task->run; + int is_new_task = strcmp(pss->sub_task_uuid, one_task->uuid); + int is_new_run = pss->sub_run != new_run; + strcpy(pss->sub_task_uuid, one_task->uuid); - pss->sub_run = run_idx >= 0 ? run_idx : one_task->run; - lws_dll2_add_head(&pss->subs_list, &pss->vhd->subs_owner); - pss->sub_timestamp = pss->initial_log_timestamp; /* where we got up to */ - saiw_broadcast_logs_batch(pss->vhd, pss); + pss->sub_run = new_run; + + if (!pss->subs_list.owner) { + lws_dll2_add_head(&pss->subs_list, &pss->vhd->subs_owner); + } + + if (is_new_task || is_new_run || pss->initial_log_timestamp == 0) { + pss->sub_timestamp = pss->initial_log_timestamp; + saiw_broadcast_logs_batch(pss->vhd, pss); + } } else if (!strcmp(pss->sub_task_uuid, one_task->uuid)) { /* If already subscribed to this task, track new runs automatically */ int new_run = run_idx >= 0 ? run_idx : one_task->run; @@ -992,6 +1002,34 @@ saiw_browser_queue_overview(struct vhd *vhd, struct pss *pss) esc[0] = '\0'; n = -6; + if (pss->specific_task[0] && !pss->resolved_task_offset) { + char event_uuid[33]; + char q[256]; + sqlite3_stmt *stmt = NULL; + uint64_t ev_created = 0; + + sai_task_uuid_to_event_uuid(event_uuid, pss->specific_task); + lws_snprintf(q, sizeof(q), "SELECT created FROM events WHERE uuid='%s'", event_uuid); + if (sqlite3_prepare_v2(vhd->pdb, q, -1, &stmt, NULL) == SQLITE_OK) { + if (sqlite3_step(stmt) == SQLITE_ROW) { + ev_created = (uint64_t)sqlite3_column_int64(stmt, 0); + } + sqlite3_finalize(stmt); + } + if (ev_created > 0) { + unsigned int events_newer = 0; + lws_snprintf(q, sizeof(q), "SELECT COUNT(*) FROM events WHERE state != %d AND created > %llu", SAIES_DELETED, (unsigned long long)ev_created); + if (sqlite3_prepare_v2(vhd->pdb, q, -1, &stmt, NULL) == SQLITE_OK) { + if (sqlite3_step(stmt) == SQLITE_ROW) { + events_newer = (unsigned int)sqlite3_column_int(stmt, 0); + } + sqlite3_finalize(stmt); + } + pss->overview_offset = (events_newer / 6) * 6; + } + pss->resolved_task_offset = 1; + } + if (pss->specific_project[0]) { lws_sql_purify(esc, pss->specific_project, sizeof(esc) - 1); lws_snprintf(filt, sizeof(filt), " and state != %d and repo_name=\"%s\"", SAIES_DELETED, esc); @@ -1047,7 +1085,7 @@ saiw_browser_queue_overview(struct vhd *vhd, struct pss *pss) * Walk through events */ - if (pss->specificity) + if (pss->specificity && pss->specificity != SAIM_SPECIFIC_TASK) walk = lws_dll2_get_head(&owner); else walk = lws_dll2_get_tail(&owner); @@ -1060,7 +1098,7 @@ saiw_browser_queue_overview(struct vhd *vhd, struct pss *pss) while (walk) { sai_event_t *e = lws_container_of(walk, sai_event_t, list); - if (pss->specificity) { + if (pss->specificity && pss->specificity != SAIM_SPECIFIC_TASK) { if (!strcmp(pss->specific_ref, "refs/heads/master") && !strcmp(e->ref, "refs/heads/main")) ; // any = 1; @@ -1242,12 +1280,12 @@ saiw_browser_queue_overview(struct vhd *vhd, struct pss *pss) p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), "]}"); - if (pss->specificity) + if (pss->specificity && pss->specificity != SAIM_SPECIFIC_TASK) walk = walk->next; else walk = walk->prev; - if (walk && !pss->specificity) + if (walk && (!pss->specificity || pss->specificity == SAIM_SPECIFIC_TASK)) continue; }
Page fetched 0s ago, creation time: 38ms (vhost etag hits: 0%, cache hits: 0%)