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 / scripts / usr-local-svc-method-sai_builder-Solaris
Author[]google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.c...> 2025-08-22 15:16 UTC
Committer[]Andy Green <andy@warmcat.com> 2025-08-23 06:30 UTC
Tree3513caaea6dd39b7fb1e075da6d09cebe60123a0   Raw Patch
 
Fixes and features for Sai UI and task handling
Fixes and features for Sai UI and task handling

This commit addresses three issues:
1.  Prevents the event log from re-sending from the beginning on
    WebSocket reconnect by tracking the last seen log timestamp on the
    client and using it to resume the log stream.
2.  Fixes ignored UI actions (like "restart event") by sending an
    "unauthorized" message from the backend when the user's session has
    expired. The client handles this by reloading the page to force
    re-authentication.
3.  Adds a context menu to tasks on the main page, providing options
    to "rebuild this task" or "rebuild all" tasks for a given platform
    within an event. This includes a new WebSocket message and backend
    logic for the platform-wide rebuild.
diff --git a/assets/sai.js b/assets/sai.js index 734d886..325f3f1 100644 --- a/assets/sai.js +++ b/assets/sai.js @@ -395,7 +395,7 @@ var lang_zhs = "{" + "}}"; var logs = "", redpend = 0, gitohashi_integ = 0, authd = 0, exptimer, auth_user = "", - ongoing_task_activities = {}; + ongoing_task_activities = {}, last_log_timestamp = 0; function update_task_activities() { for (const uuid in ongoing_task_activities) { @@ -799,7 +799,8 @@ function sai_event_render(o, now_ut, reset_all_icon) if (t.state === 4 || t.state == 6) has_bad = 1; - s1 += "<div id=\"taskstate_" + t.uuid + "\" class=\"taskstate taskstate" + t.state + "\">"; + s1 += "<div id=\"taskstate_" + t.uuid + "\" class=\"taskstate taskstate" + t.state + + "\" data-event-uuid=\"" + san(e.uuid) + "\" data-platform=\"" + san(t.platform) + "\">"; if (t.state === 0) wai = 1; @@ -1080,6 +1081,7 @@ function ws_open_sai() "\"com.warmcat.sai.taskinfo\"," + "\"js_api_version\": " + SAI_JS_API_VERSION + "," + "\"logs\": 1," + + "\"last_log_ts\":" + last_log_timestamp + "," + "\"task_hash\":" + JSON.stringify(tid) + "}"); @@ -1523,6 +1525,7 @@ function ws_open_sai() lines = times = logs = ""; tfirst = 0; lli = 1; + last_log_timestamp = 0; }); } @@ -1639,12 +1642,18 @@ function ws_open_sai() } break; + case "com.warmcat.sai.unauthorized": + location.reload(); + break; + case "com-warmcat-sai-logs": var s1 = atob(jso.log), s = hsanitize(s1), li, en = "", yo, dh, ce, tn = ""; if (!tfirst) - tfirst = jso.timestamp; + tfirst = jso.timestamp; + + last_log_timestamp = jso.timestamp; li = (s1.match(/\n/g)||[]).length; @@ -1813,6 +1822,55 @@ window.addEventListener("load", function() { document.body.clientHeight; }, 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; + } + + if (taskDiv && authd) { + 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: `Rebuild all <b>${hsanitize(platform)}</b>`, + callback: () => { + sai.send(JSON.stringify({ + schema: "com.warmcat.sai.platreset", + event_uuid: eventUuid, + platform: platform + })); + } + } + ]; + + createContextMenu(event, menuItems); + } + }); + } }, false); diff --git a/src/common/include/private.h b/src/common/include/private.h index 24d5325..f497e31 100644 --- a/src/common/include/private.h +++ b/src/common/include/private.h @@ -230,6 +230,11 @@ typedef struct sai_rebuild { char builder_name[96]; } sai_rebuild_t; +typedef struct sai_platreset { + lws_dll2_t list; + char event_uuid[65]; + char platform[65]; +} sai_browse_rx_platreset_t; struct sai_event; @@ -451,6 +456,7 @@ typedef struct sai_browse_rx_evinfo { typedef struct sai_browse_rx_taskinfo { char task_hash[65]; + uint64_t last_log_ts; unsigned int log_start; unsigned int js_api_version; uint8_t logs; diff --git a/src/server/s-websrv.c b/src/server/s-websrv.c index f12c519..419c917 100644 --- a/src/server/s-websrv.c +++ b/src/server/s-websrv.c @@ -54,6 +54,11 @@ static lws_struct_map_t lsm_browser_taskreset[] = { LSM_CARRAY (sai_browse_rx_evinfo_t, event_hash, "uuid"), }; +static lws_struct_map_t lsm_browser_platreset[] = { + LSM_CARRAY (sai_browse_rx_platreset_t, event_uuid, "event_uuid"), + LSM_CARRAY (sai_browse_rx_platreset_t, platform, "platform"), +}; + static const lws_struct_map_t lsm_viewercount_members[] = { LSM_UNSIGNED(sai_viewer_state_t, viewers, "count"), }; @@ -71,6 +76,8 @@ static const lws_struct_map_t lsm_schema_json_map[] = { "com.warmcat.sai.viewercount"), LSM_SCHEMA (sai_rebuild_t, NULL, lsm_rebuild, "com.warmcat.sai.rebuild"), + LSM_SCHEMA (sai_browse_rx_platreset_t, NULL, lsm_browser_platreset, + "com.warmcat.sai.platreset"), }; enum { @@ -80,6 +87,7 @@ enum { SAIS_WS_WEBSRV_RX_TASKCANCEL, SAIS_WS_WEBSRV_RX_VIEWERCOUNT, SAIS_WS_WEBSRV_RX_REBUILD, + SAIS_WS_WEBSRV_RX_PLATRESET, }; void @@ -459,6 +467,56 @@ websrvss_ws_rx(void *userobj, const uint8_t *buf, size_t len, int flags) return 0; + case SAIS_WS_WEBSRV_RX_PLATRESET: { + sai_browse_rx_platreset_t *pr = (sai_browse_rx_platreset_t *)a.dest; + char filt[256]; + + if (sais_validate_id(pr->event_uuid, SAI_EVENTID_LEN)) + goto soft_error; + + /* open the event-specific database object */ + + if (sais_event_db_ensure_open(m->vhd, pr->event_uuid, 0, &pdb)) { + lwsl_err("%s: unable to open event-specific database\n", + __func__); + goto soft_error; + } + + /* + * Retreive all the related structs into a dll2 list + */ + lws_sql_purify(esc, pr->platform, sizeof(esc)); + lws_snprintf(filt, sizeof(filt), " and platform='%s'", esc); + + if (lws_struct_sq3_deserialize(pdb, filt, NULL, + lsm_schema_sq3_map_task, + &o, &a.ac, 0, 999) >= 0) { + + sqlite3_exec(pdb, "BEGIN TRANSACTION", + NULL, NULL, &err); + sqlite3_free(err); + + /* + * Walk the results list resetting all the tasks + */ + + lws_start_foreach_dll(struct lws_dll2 *, p, o.head) { + sai_task_t *t = lws_container_of(p, sai_task_t, + list); + sais_task_reset(m->vhd, t->uuid); + } lws_end_foreach_dll(p); + + sqlite3_exec(pdb, "END TRANSACTION", + NULL, NULL, &err); + sqlite3_free(err); + } + + sais_event_db_close(m->vhd, &pdb); + lwsac_free(&a.ac); + + return 0; + } + case SAIS_WS_WEBSRV_RX_EVENTDELETE: ei = (sai_browse_rx_evinfo_t *)a.dest; diff --git a/src/web/w-private.h b/src/web/w-private.h index 2cbc1c7..344396a 100644 --- a/src/web/w-private.h +++ b/src/web/w-private.h @@ -180,6 +180,7 @@ struct pss { char login_form; uint64_t first_log_timestamp; + uint64_t initial_log_timestamp; uint64_t artifact_offset; uint64_t artifact_length; diff --git a/src/web/w-ws-browser.c b/src/web/w-ws-browser.c index 955a22d..8767cb6 100644 --- a/src/web/w-ws-browser.c +++ b/src/web/w-ws-browser.c @@ -71,10 +71,16 @@ static lws_struct_map_t lsm_browser_taskreset[] = { LSM_CARRAY (sai_browse_rx_evinfo_t, event_hash, "uuid"), }; +static lws_struct_map_t lsm_browser_platreset[] = { + LSM_CARRAY (sai_browse_rx_platreset_t, event_uuid, "event_uuid"), + LSM_CARRAY (sai_browse_rx_platreset_t, platform, "platform"), +}; + static lws_struct_map_t lsm_browser_taskinfo[] = { LSM_CARRAY (sai_browse_rx_taskinfo_t, task_hash, "task_hash"), LSM_UNSIGNED (sai_browse_rx_taskinfo_t, logs, "logs"), LSM_UNSIGNED (sai_browse_rx_taskinfo_t, js_api_version, "js_api_version"), + LSM_UNSIGNED (sai_browse_rx_taskinfo_t, last_log_ts, "last_log_ts"), }; /* @@ -99,6 +105,8 @@ static const lws_struct_map_t lsm_schema_json_map_bwsrx[] = { "com.warmcat.sai.loadreport"), LSM_SCHEMA (sai_rebuild_t, NULL, lsm_rebuild, "com.warmcat.sai.rebuild"), + LSM_SCHEMA (sai_browse_rx_platreset_t, NULL, lsm_browser_platreset, + "com.warmcat.sai.platreset"), }; enum { @@ -110,6 +118,7 @@ enum { SAIM_WS_BROWSER_RX_TASKCANCEL, SAIM_WS_BROWSER_RX_JS_HELLO, SAIM_WS_BROWSER_RX_REBUILD, + SAIM_WS_BROWSER_RX_PLATRESET, }; @@ -492,6 +501,11 @@ saiw_ws_json_rx_browser(struct vhd *vhd, struct pss *pss, uint8_t *buf, * as long as needed to send it out */ + if (ti->logs) + pss->initial_log_timestamp = ti->last_log_ts; + else + pss->initial_log_timestamp = 0; + if (saiw_pss_schedule_taskinfo(pss, ti->task_hash, !!ti->logs)) goto soft_error; @@ -509,7 +523,7 @@ saiw_ws_json_rx_browser(struct vhd *vhd, struct pss *pss, uint8_t *buf, case SAIM_WS_BROWSER_RX_TASKRESET: if (!sais_conn_auth(pss)) - goto soft_error; + goto auth_error; /* * User is asking us to reset / rebuild this task @@ -523,7 +537,7 @@ saiw_ws_json_rx_browser(struct vhd *vhd, struct pss *pss, uint8_t *buf, case SAIM_WS_BROWSER_RX_EVENTRESET: if (!sais_conn_auth(pss)) - goto soft_error; + goto auth_error; /* * User is asking us to reset / rebuild every task in the event @@ -543,7 +557,7 @@ saiw_ws_json_rx_browser(struct vhd *vhd, struct pss *pss, uint8_t *buf, */ if (!sais_conn_auth(pss)) - goto soft_error; + goto auth_error; ei = (sai_browse_rx_evinfo_t *)a.dest; @@ -558,7 +572,7 @@ saiw_ws_json_rx_browser(struct vhd *vhd, struct pss *pss, uint8_t *buf, case SAIM_WS_BROWSER_RX_TASKCANCEL: if (!sais_conn_auth(pss)) - goto soft_error; + goto auth_error; /* * Browser is informing us of task's STOP button clicked, we @@ -574,7 +588,7 @@ saiw_ws_json_rx_browser(struct vhd *vhd, struct pss *pss, uint8_t *buf, case SAIM_WS_BROWSER_RX_REBUILD: if (!sais_conn_auth(pss)) - goto soft_error; + goto auth_error; /* * User is asking us to rebuild a builder @@ -583,6 +597,17 @@ saiw_ws_json_rx_browser(struct vhd *vhd, struct pss *pss, uint8_t *buf, saiw_websrv_queue_tx(vhd->h_ss_websrv, buf, bl); break; + case SAIM_WS_BROWSER_RX_PLATRESET: + if (!sais_conn_auth(pss)) + goto auth_error; + + /* + * User is asking us to reset / rebuild a whole platform + */ + + saiw_websrv_queue_tx(vhd->h_ss_websrv, buf, bl); + break; + default: assert(0); break; @@ -595,6 +620,16 @@ bail: return ret; +auth_error: + { + uint8_t buf[LWS_PRE + 128]; + int n; + + n = lws_snprintf((char *)buf + LWS_PRE, sizeof(buf) - LWS_PRE, + "{\"schema\":\"com.warmcat.sai.unauthorized\"}"); + lws_write(pss->wsi, buf + LWS_PRE, (size_t)n, LWS_WRITE_TEXT); + } + soft_error: lwsac_free(&a.ac); @@ -1287,7 +1322,7 @@ send_it: if (sch && sch->logsub && sch->one_task) { strcpy(pss->sub_task_uuid, sch->one_task->uuid); lws_dll2_add_head(&pss->subs_list, &pss->vhd->subs_owner); - pss->sub_timestamp = 0; /* where we got up to */ + pss->sub_timestamp = pss->initial_log_timestamp; /* where we got up to */ lws_callback_on_writable(pss->wsi); lwsl_info("%s: subscribed to logs for %s\n", __func__,
Page fetched 0s ago, creation time: 22ms (vhost etag hits: 0%, cache hits: 0%)