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> ";
+ san(t.t.platform) + "</span> " +
+ sai_stateful_taskname(t.t.state, t.t.taskname, 1) + " ";
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) + "\"> ";
if (auth_state === SaiAuthState.LOGGED_IN_GRANT_ADMIN)
s += "<img class=\"rebuild\" alt=\"rebuild\" src=\"rebuild.png\" " +
"id=\"rebuild-" + san(t.t.uuid) + "\"> ";
- 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) + "\">< 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 ></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) + "\">< 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 ></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;
}