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 / freertos-espidf.svg
Author[]Andy Green <andy@warmcat.com> 2026-08-23 17:58 UTC
Committer[]Andy Green <andy@warmcat.com> 2026-08-23 17:58 UTC
Tree28364a3c40a03d1d66aa9500e5c60f5865bcc0b0   Raw Patch
 
js-weburl
js-weburl
diff --git a/assets/sai.css b/assets/sai.css index f274d74..6f498c6 100644 --- a/assets/sai.css +++ b/assets/sai.css @@ -2073,6 +2073,27 @@ div.ibuil.vm-builder { color: #ffffff; } +/* + * Links into the repo's gitweb (event weburl): keep the surrounding text + * styling (so pass/fail backgrounds still read), hint with hover underline + */ +.event-tasks-title a, +.sb-event-tag a, +span.e1 a, +span.e2 a, +td.e6 a { + color: inherit; + text-decoration: none; +} + +.event-tasks-title a:hover, +.sb-event-tag a:hover, +span.e1 a:hover, +span.e2 a:hover, +td.e6 a:hover { + text-decoration: underline; +} + .tasks-table-display { width: 100%; border-collapse: collapse; diff --git a/assets/sai.js b/assets/sai.js index 9ec4891..8dc8b4e 100644 --- a/assets/sai.js +++ b/assets/sai.js @@ -995,6 +995,22 @@ function sai_event_hash_display(hash) { return "sai-" + hash.substring(0, 8); } +/* + * If the event was notified with a repository weburl (the base URL of the + * repo's gitweb, eg gitohashi), render `text` as a link into it; otherwise + * return the plain sanitized text. suffix is appended to the weburl: + * "" -> the repo summary page + * "/log?h=<ref>" -> the log of that branch / tag + * "/log?id=<commit hash>" -> that commit + */ +function sai_weburl_link(e, suffix, text) { + if (!e || !e.weburl) + return san(text); + + return "<a href=\"" + san(e.weburl + (suffix || "")) + "\">" + + san(text) + "</a>"; +} + function get_appropriate_ws_url() { var pcol; @@ -1473,29 +1489,35 @@ function sai_event_summary_render(o, now_ut, reset_all_icon) s += "<td><table class=\"nomar\">" + "<tr><td class=\"nomar\" colspan=2>" + - "<span class=\"e1\">" + san(e.repo_name); + "<span class=\"e1\">" + sai_weburl_link(e, "", e.repo_name); if (e.sec) s += " <img class=\"bico\" src=\"/sai/locked.svg\">"; s += "</span></td></tr><tr><td class=\"nomar\" colspan=2><span class=\"e2\">"; if (e.ref.substr(0, 11) === "refs/heads/") { s += "<img class=\"branch\">" + - san(e.ref.substr(11)); + sai_weburl_link(e, "/log?h=" + encodeURIComponent(e.ref.substr(11)), + e.ref.substr(11)); } else if (e.ref.substr(0, 10) === "refs/tags/") { s += "<img class=\"tag\">" + - san(e.ref.substr(10)); + sai_weburl_link(e, "/log?h=" + encodeURIComponent(e.ref.substr(10)), + e.ref.substr(10)); } else s += san(e.ref); s += "</span></td></tr><tr><td class=\"nomar e6\">" + - san(sai_event_hash_display(e.hash)) + + sai_weburl_link(e, "/log?id=" + encodeURIComponent(e.hash), + sai_event_hash_display(e.hash)) + "</td><td class=\"e6 nomar\">" + agify(now_ut, e.created) + "</td></tr>"; s += "</table>" + "</td>"; } else { - s +="<td><table><tr><td class=\"e6 nomar\">" + san(sai_event_hash_display(e.hash)) + " " + agify(now_ut, e.created) + + s +="<td><table><tr><td class=\"e6 nomar\">" + + sai_weburl_link(e, "/log?id=" + encodeURIComponent(e.hash), + sai_event_hash_display(e.hash)) + " " + + agify(now_ut, e.created) + "</td></tr><tr><td class=\"nomar e6\" id=\"sumbs-" + e.uuid + "\"></td></tr>" + "</table></td>"; } @@ -1713,7 +1735,9 @@ function render_sb_events() s += "<span class=\"sb-event-when\">" + san(sai_sb_fmt_when(e.created)) + " <span class='age-0' ut='" + e.created + "'>" + agify(now_ut, e.created) + "</span></span>"; - s += "<span class=\"sb-event-tag\">" + san(sai_event_hash_display(e.hash)) + "</span>"; + s += "<span class=\"sb-event-tag\">" + + sai_weburl_link(e, "/log?id=" + encodeURIComponent(e.hash), + sai_event_hash_display(e.hash)) + "</span>"; s += "<span class=\"sb-event-status\" id=\"sbsum-" + san(e.uuid) + "\"></span>"; /* progress bar slot (inline, takes remaining width), filled by sai_sb_render_event_summary() */ s += "<span class=\"sb-event-bar\" id=\"sbbar-" + san(e.uuid) + "\"></span>"; @@ -1722,7 +1746,10 @@ function render_sb_events() } c.innerHTML = s; c.querySelectorAll(".sb-event-row").forEach(function(row) { - row.addEventListener("click", function() { + row.addEventListener("click", function(ev) { + /* embedded gitweb links navigate on their own */ + if (ev.target && ev.target.closest && ev.target.closest("a")) + return; selectEvent(row.getAttribute("data-uuid")); }); }); @@ -1859,7 +1886,13 @@ function render_selected_event_tasks(o) { if (e.state == 4 || e.state == 6) s += " comp_fail"; s += "\">"; var refName = e.ref.replace("refs/heads/", "").replace("refs/tags/", ""); - s += "<span class=\"event-tasks-title\">" + san(e.repo_name) + " (" + san(refName) + ") - " + san(sai_event_hash_display(e.hash)) + "</span>"; + s += "<span class=\"event-tasks-title\">" + + sai_weburl_link(e, "", e.repo_name) + + " (" + sai_weburl_link(e, "/log?h=" + encodeURIComponent(refName), refName) + ")" + + " - " + + sai_weburl_link(e, "/log?id=" + encodeURIComponent(e.hash), + sai_event_hash_display(e.hash)) + + "</span>"; /* admin-only restart-all / delete-event controls live here now */ if (!gitohashi_integ && auth_state === SaiAuthState.LOGGED_IN_GRANT_ADMIN) { s += "<img class=\"rebuild\" alt=\"rebuild all\" src=\"/sai/rebuild.png\" " + diff --git a/src/common/include/private.h b/src/common/include/private.h index 4edefc3..ccec1a6 100644 --- a/src/common/include/private.h +++ b/src/common/include/private.h @@ -403,6 +403,8 @@ typedef struct sai_event { struct lws_dll2 list; char repo_name[65]; char repo_fetchurl[96]; + /* optional http(s) url for browsing the repo in a web ui */ + char repo_weburl[128]; char ref[65]; char hash[65]; char uuid[65]; diff --git a/src/common/struct-metadata.c b/src/common/struct-metadata.c index f4c840d..6746e45 100644 --- a/src/common/struct-metadata.c +++ b/src/common/struct-metadata.c @@ -132,6 +132,7 @@ const lws_struct_map_t lsm_event[] = { LSM_UNSIGNED (sai_event_t, uid, "uid"), LSM_CARRAY (sai_event_t, repo_name, "repo_name"), LSM_CARRAY (sai_event_t, repo_fetchurl, "repo_fetchurl"), + LSM_CARRAY (sai_event_t, repo_weburl, "weburl"), LSM_CARRAY (sai_event_t, ref, "ref"), LSM_CARRAY (sai_event_t, hash, "hash"), LSM_CARRAY (sai_event_t, uuid, "uuid"), diff --git a/src/server/s-comms.c b/src/server/s-comms.c index d3bc117..6092c05 100644 --- a/src/server/s-comms.c +++ b/src/server/s-comms.c @@ -215,6 +215,21 @@ s_callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, return -1; } + /* + * create_table() is "if not exists", so existing event tables + * lack later columns like weburl... try to add them (fails + * harmlessly if the column is already there) + */ + { + char *err = NULL; + + sqlite3_exec(vhd->server.pdb, + "ALTER TABLE events ADD COLUMN weburl varchar;", + NULL, NULL, &err); + if (err) + sqlite3_free(err); + } + sai_sqlite3_statement(vhd->server.pdb, "CREATE UNIQUE INDEX IF NOT EXISTS idx_event_uuid ON events(uuid);", "create event index"); if (lws_struct_sq3_create_table(vhd->server.pdb, diff --git a/src/server/s-notification.c b/src/server/s-notification.c index 8807adc..1eac80a 100644 --- a/src/server/s-notification.c +++ b/src/server/s-notification.c @@ -36,6 +36,7 @@ static const char * const paths[] = { "action", "repository.name", "repository.fetchurl", + "repository.weburl", "ref", "hash", "nonce", @@ -49,6 +50,7 @@ enum enum_paths { LEJPN_ACTION, LEJPN_REPOSITORY_NAME, LEJPN_REPOSITORY_FETCHURL, + LEJPN_REPOSITORY_WEBURL, LEJPN_REF, LEJPN_HASH, LEJPN_NONCE, @@ -879,6 +881,27 @@ sai_notification_lejp_cb(struct lejp_ctx *ctx, char reason) sizeof(sn->e.repo_fetchurl)); break; + case LEJPN_REPOSITORY_WEBURL: + /* + * weburl is optional, and only used to build links to the + * repo's web ui (eg, gitohashi) in the browser. Since repo + * data is attacker-influenced and this becomes an href, only + * accept a clean http(s) URL... anything else is dropped and + * the event stored without a weburl rather than rejecting the + * whole notification over a UI nicety. + */ + sn->e.repo_weburl[0] = '\0'; + + if ((!strncasecmp(ctx->buf, "https://", 8) || + !strncasecmp(ctx->buf, "http://", 7)) && + !sai_str_has_shell_metachars(ctx->buf)) + lws_strncpy(sn->e.repo_weburl, ctx->buf, + sizeof(sn->e.repo_weburl)); + else + lwsl_notice("%s: ignoring weburl '%s'\n", + __func__, ctx->buf); + break; + case LEJPN_REF: lws_strncpy(sn->e.ref, ctx->buf, sizeof(sn->e.ref)); /* diff --git a/src/web/w-comms.c b/src/web/w-comms.c index 465de59..9fd99d2 100644 --- a/src/web/w-comms.c +++ b/src/web/w-comms.c @@ -195,6 +195,21 @@ w_callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, return -1; } + /* + * create_table() is "if not exists", so existing event tables + * lack later columns like weburl... try to add them (fails + * harmlessly if the column is already there) + */ + { + char *err = NULL; + + sqlite3_exec(vhd->pdb, + "ALTER TABLE events ADD COLUMN weburl varchar;", + NULL, NULL, &err); + if (err) + sqlite3_free(err); + } + sai_sqlite3_statement(vhd->pdb, "CREATE UNIQUE INDEX IF NOT EXISTS idx_event_uuid ON events(uuid);", "create event index"); sai_sqlite3_statement(vhd->pdb,
Page fetched 0s ago, creation time: 27ms (vhost etag hits: 0%, cache hits: 0%)