diff --git a/assets/sai.css b/assets/sai.css
index 5ba27f8..286761f 100644
--- a/assets/sai.css
+++ b/assets/sai.css
@@ -636,6 +636,9 @@ div.ibuil {
color:#000000;
padding:3px;
border-radius:10px;
+ user-select: none;
+ -webkit-user-select: none;
+ cursor: pointer;
}
div.ibuilctr {
@@ -830,3 +833,26 @@ img.branch {
.activity-3 {
animation: flash-fast 1s infinite;
}
+
+.context-menu {
+ position: absolute;
+ background-color: #f9f9f9;
+ border: 1px solid #ccc;
+ padding: 5px;
+ z-index: 3000;
+}
+
+.context-menu ul {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.context-menu li {
+ padding: 8px 12px;
+ cursor: pointer;
+}
+
+.context-menu li:hover {
+ background-color: #f2f2f2;
+}
diff --git a/assets/sai.js b/assets/sai.js
index 32167e0..734d886 100644
--- a/assets/sai.js
+++ b/assets/sai.js
@@ -880,6 +880,46 @@ function after_delete() {
location.reload();
}
+function createContextMenu(event, menuItems) {
+ event.preventDefault();
+
+ // Remove any existing context menu
+ const existingMenus = document.querySelectorAll(".context-menu");
+ existingMenus.forEach(menu => {
+ document.body.removeChild(menu);
+ });
+
+ const menu = document.createElement("div");
+ menu.className = "context-menu";
+ menu.style.top = event.pageY + "px";
+ menu.style.left = event.pageX + "px";
+
+ const ul = document.createElement("ul");
+ menu.appendChild(ul);
+
+ menuItems.forEach(item => {
+ const li = document.createElement("li");
+ li.innerHTML = item.label;
+ if (item.callback) {
+ li.addEventListener("click", item.callback);
+ }
+ ul.appendChild(li);
+ });
+
+ document.body.appendChild(menu);
+
+ const closeMenu = () => {
+ if (document.body.contains(menu)) {
+ document.body.removeChild(menu);
+ }
+ document.removeEventListener("click", closeMenu);
+ };
+
+ setTimeout(() => {
+ document.addEventListener("click", closeMenu);
+ }, 0);
+}
+
function createBuilderDiv(plat) {
const platDiv = document.createElement("div");
platDiv.className = "ibuil bdr";
@@ -916,6 +956,61 @@ function createBuilderDiv(plat) {
innerHTML += `</div></td></tr></tbody></table>`;
platDiv.innerHTML = innerHTML;
+
+ const menuItems = [
+ { label: `<b>SAI Hash:</b> ${plat.sai_hash}` },
+ { label: `<b>LWS Hash:</b> ${plat.lws_hash}` },
+ {
+ label: "Update SAI",
+ callback: () => {
+ const rebuildMsg = {
+ schema: "com.warmcat.sai.rebuild",
+ builder_name: plat.name
+ };
+ sai.send(JSON.stringify(rebuildMsg));
+ }
+ }
+ ];
+
+ platDiv.addEventListener("contextmenu", function(event) {
+ createContextMenu(event, menuItems);
+ });
+
+ let touchStartTime = 0;
+ let touchStartPos = { x: 0, y: 0 };
+
+ platDiv.addEventListener("touchstart", function(event) {
+ if (event.touches.length > 1) {
+ return;
+ }
+ touchStartTime = Date.now();
+ const touch = event.touches[0];
+ touchStartPos = { x: touch.pageX, y: touch.pageY };
+ });
+
+ platDiv.addEventListener("touchend", function(event) {
+ const touchEndTime = Date.now();
+ const touch = event.changedTouches[0];
+ const touchEndPos = { x: touch.pageX, y: touch.pageY };
+ const pressDuration = touchEndTime - touchStartTime;
+ const distance = Math.sqrt(
+ Math.pow(touchEndPos.x - touchStartPos.x, 2) +
+ Math.pow(touchEndPos.y - touchStartPos.y, 2)
+ );
+
+ if (pressDuration >= 500 && distance < 10) {
+ event.preventDefault();
+
+ const mockEvent = {
+ preventDefault: () => {},
+ pageX: touchStartPos.x,
+ pageY: touchStartPos.y
+ };
+ createContextMenu(mockEvent, menuItems);
+ }
+ touchStartTime = 0;
+ });
+
return platDiv;
}
diff --git a/etc-sai-EXAMPLE/builder/conf b/etc-sai-EXAMPLE/builder/conf
index aae370e..fd03488 100644
--- a/etc-sai-EXAMPLE/builder/conf
+++ b/etc-sai-EXAMPLE/builder/conf
@@ -2,6 +2,8 @@
"perms": "sai:nobody",
"home": "/home/sai",
"host": "bionic-noi",
+ "rebuild_script_user": "cd ~/libwebsockets/build && git fetch origin +main:m && git reset --hard m && make -j12 && cd ../../sai/build && git fetch origin +main:m && git reset --hard m && make -j12",
+ "rebuild_script_root": "cd /home/agreen/libwebsockets/build && make -j12 install && cd /home/agreen/sai/build && make -j12 install && systemctl restart sai-builder",
# if you are using auto-power-on and off, the builder needs
# to point to a sai-power instance that can coordinate power-off
diff --git a/src/builder/CMakeLists.txt b/src/builder/CMakeLists.txt
index 82eaff7..f1259ee 100644
--- a/src/builder/CMakeLists.txt
+++ b/src/builder/CMakeLists.txt
@@ -12,6 +12,7 @@ set(SRCS
b-logproxy.c
b-refproxy.c
b-load.c
+ ../common/c-utils.c
)
set(requirements 1)
diff --git a/src/builder/b-conf.c b/src/builder/b-conf.c
index 49aae31..3d9bda7 100644
--- a/src/builder/b-conf.c
+++ b/src/builder/b-conf.c
@@ -41,7 +41,9 @@ static const char * const paths_global[] = {
"power-on.url",
"power-on.mac",
"power-off.type",
- "power-off.url"
+ "power-off.url",
+ "rebuild_script_user",
+ "rebuild_script_root"
};
enum enum_paths_global {
@@ -56,7 +58,9 @@ enum enum_paths_global {
LEJPM_POWER_ON_URL,
LEJPM_POWER_ON_MAC,
LEJPM_POWER_OFF_TYPE,
- LEJPM_POWER_OFF_URL
+ LEJPM_POWER_OFF_URL,
+ LEJPM_REBUILD_SCRIPT_USER,
+ LEJPM_REBUILD_SCRIPT_ROOT
};
/* platform-related part */
@@ -111,6 +115,11 @@ saib_conf_cb(struct lejp_ctx *ctx, char reason)
a->sai_plat->instances = 1; /* default */
+ lws_strncpy(a->sai_plat->sai_hash, BUILD_INFO,
+ sizeof(a->sai_plat->sai_hash));
+ lws_strncpy(a->sai_plat->lws_hash, LWS_BUILD_HASH,
+ sizeof(a->sai_plat->lws_hash));
+
lws_dll2_add_tail(&a->sai_plat->sai_plat_list,
&a->builder->sai_plat_owner);
break;
@@ -322,6 +331,14 @@ saib_conf_global_cb(struct lejp_ctx *ctx, char reason)
pp = &a->builder->power_off_url;
break;
+ case LEJPM_REBUILD_SCRIPT_USER:
+ pp = &a->builder->rebuild_script_user;
+ break;
+
+ case LEJPM_REBUILD_SCRIPT_ROOT:
+ pp = &a->builder->rebuild_script_root;
+ break;
+
default:
return 0;
}
diff --git a/src/builder/b-private.h b/src/builder/b-private.h
index 67d0823..e9a71ec 100644
--- a/src/builder/b-private.h
+++ b/src/builder/b-private.h
@@ -120,6 +120,8 @@ struct sai_builder {
const char *perms; /* user:group */
const char *host; /* prepended before hostname */
+ const char *rebuild_script_user;
+ const char *rebuild_script_root;
char path[256];
diff --git a/src/builder/b-sai.c b/src/builder/b-sai.c
index 626afcd..a04e7ac 100644
--- a/src/builder/b-sai.c
+++ b/src/builder/b-sai.c
@@ -64,7 +64,7 @@ int getpid(void) { return 0; }
static const char *config_dir = "/etc/sai/builder";
static int interrupted;
static lws_state_notify_link_t nl;
-static struct lws_spawn_piped *lsp_suspender;
+struct lws_spawn_piped *lsp_suspender;
struct sai_builder builder;
@@ -741,8 +741,6 @@ int main(int argc, const char **argv)
struct stat sb;
const char *p;
-#if !defined(WIN32)
-
if ((p = lws_cmdline_option(argc, argv, "-s"))) {
ssize_t n = 0;
@@ -772,6 +770,27 @@ int main(int argc, const char **argv)
break;
}
+#if defined(WIN32)
+ /*
+ * On Windows, we can use the shutdown command.
+ * For suspend, we can use rundll32.
+ */
+ switch(d) {
+ case 0:
+ system("shutdown /s /t 0");
+ break;
+ case 1:
+ system("rundll32.exe powrprof.dll,SetSuspendState 0,1,0");
+ break;
+ case 3:
+ if (builder.rebuild_script_user &&
+ builder.rebuild_script_root) {
+ if (system(builder.rebuild_script_user) == 0)
+ system(builder.rebuild_script_root);
+ }
+ break;
+ }
+#else
p = fork();
if (!p)
switch(d) {
@@ -785,16 +804,44 @@ int main(int argc, const char **argv)
case 1:
execl("/usr/bin/systemctl", "/usr/bin/systemctl", "suspend", NULL);
break;
+ case 3:
+ if (builder.rebuild_script_user &&
+ builder.rebuild_script_root) {
+ char cmd[8192];
+ char user_buf[64];
+ const char *user = "sai";
+ int ret;
+
+ if (builder.perms) {
+ lws_strncpy(user_buf, builder.perms, sizeof(user_buf));
+ if (strchr(user_buf, ':'))
+ *strchr(user_buf, ':') = '\0';
+ user = user_buf;
+ }
+
+ lws_snprintf(cmd, sizeof(cmd),
+ "su - %s -c '%s'",
+ user,
+ builder.rebuild_script_user);
+ ret = system(cmd);
+ if (ret == 0) {
+ lws_snprintf(cmd, sizeof(cmd),
+ "PATH=/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin %s",
+ builder.rebuild_script_root);
+ system(cmd);
+ }
+ }
+ break;
}
else
waitpid(p, &status, 0);
+#endif
}
lwsl_notice("%s: exiting suspend process\n", __func__);
return 0;
}
-#endif
if ((p = lws_cmdline_option(argc, argv, "-d")))
logs = atoi(p);
diff --git a/src/builder/b-task.c b/src/builder/b-task.c
index 85dd5f3..ea09f4e 100644
--- a/src/builder/b-task.c
+++ b/src/builder/b-task.c
@@ -42,14 +42,16 @@ const lws_struct_map_t lsm_schema_map_m_to_b[] = {
LSM_SCHEMA (sai_cancel_t, NULL, lsm_task_cancel, "com.warmcat.sai.taskcan"),
LSM_SCHEMA (sai_viewer_state_t, NULL, lsm_viewerstate_members,
"com.warmcat.sai.viewerstate"),
- LSM_SCHEMA (sai_resource_t, NULL, lsm_resource, "com-warmcat-sai-resource")
+ LSM_SCHEMA (sai_resource_t, NULL, lsm_resource, "com-warmcat-sai-resource"),
+ LSM_SCHEMA (sai_rebuild_t, NULL, lsm_rebuild, "com.warmcat.sai.rebuild")
};
enum {
SAIB_RX_TASK_ALLOCATION,
SAIB_RX_TASK_CANCEL,
SAIB_RX_VIEWERSTATE,
- SAIB_RX_RESOURCE_REPLY
+ SAIB_RX_RESOURCE_REPLY,
+ SAIB_RX_REBUILD
};
static const char * const nsstates[] = {
@@ -428,6 +430,8 @@ saib_sul_task_cancel(struct lws_sorted_usec_list *sul)
saib_sul_task_cancel, 500 * LWS_US_PER_MS);
}
+extern struct lws_spawn_piped *lsp_suspender;
+
int
saib_ws_json_rx_builder(struct sai_plat_server *spm, const void *in, size_t len)
{
@@ -437,6 +441,7 @@ saib_ws_json_rx_builder(struct sai_plat_server *spm, const void *in, size_t len)
struct lejp_ctx ctx;
lws_struct_args_t a;
sai_cancel_t *can;
+ sai_rebuild_t *reb;
char url[128], *p;
sai_task_t *task;
int n, m;
@@ -819,6 +824,19 @@ saib_ws_json_rx_builder(struct sai_plat_server *spm, const void *in, size_t len)
saib_handle_resource_result(spm, in, len);
break;
+ case SAIB_RX_REBUILD:
+ reb = (sai_rebuild_t *)a.dest;
+
+ lwsl_notice("%s: REBUILD: %s\n", __func__, reb->builder_name);
+
+ if (lsp_suspender) {
+ uint8_t b = 3;
+ if (write(lws_spawn_get_fd_stdxxx(lsp_suspender, 0), &b, 1) != 1)
+ lwsl_err("%s: Failed to write to suspender\n",
+ __func__);
+ }
+ break;
+
default:
break;
}
diff --git a/src/common/c-utils.c b/src/common/c-utils.c
new file mode 100644
index 0000000..7d815fa
--- /dev/null
+++ b/src/common/c-utils.c
@@ -0,0 +1,38 @@
+/*
+ * Sai common utils
+ *
+ * Copyright (C) 2025 Andy Green <andy@warmcat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation:
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+
+#include <libwebsockets.h>
+#include "include/private.h"
+
+int
+sai_uuid16_create(struct lws_context *context, char *dest33)
+{
+ uint8_t uuid[16];
+ int n;
+
+ if (lws_get_random(context, uuid, sizeof(uuid)) != sizeof(uuid))
+ return -1;
+
+ for (n = 0; n < 16; n++)
+ lws_snprintf(dest33 + (n * 2), 3, "%02X", uuid[n]);
+
+ return 0;
+}
diff --git a/src/common/include/private.h b/src/common/include/private.h
index 9ae4586..24d5325 100644
--- a/src/common/include/private.h
+++ b/src/common/include/private.h
@@ -221,6 +221,15 @@ typedef struct sai_cancel {
char task_uuid[65];
} sai_cancel_t;
+/*
+ * Browser is asking a builder to rebuild
+ */
+
+typedef struct sai_rebuild {
+ lws_dll2_t list;
+ char builder_name[96];
+} sai_rebuild_t;
+
struct sai_event;
@@ -405,6 +414,8 @@ typedef struct sai_plat {
struct lws *wsi; /* server side only */
lws_dll2_owner_t env_head;
+ char sai_hash[41];
+ char lws_hash[41];
uint64_t uid;
lws_dll2_owner_t loads;
int online; /* 1 = connected, 0 = offline */
@@ -474,10 +485,12 @@ extern const lws_struct_map_t
lsm_schema_json_map_task[1],
lsm_schema_json_map_event[1],
lsm_resource[4],
- lsm_power_state[3]
+ lsm_power_state[3],
+ lsm_rebuild[1],
+ lsm_schema_rebuild[1]
;
-extern const lws_struct_map_t lsm_plat[6];
-extern const lws_struct_map_t lsm_plat_for_json[10];
+extern const lws_struct_map_t lsm_plat[8];
+extern const lws_struct_map_t lsm_plat_for_json[12];
extern const lws_ss_info_t ssi_said_logproxy;
extern struct lws_ss_handle *ssh[3];
@@ -496,4 +509,6 @@ saicom_lp_callback_on_drain(saicom_drain_cb cb, void *opaque);
void
sul_idle_cb(lws_sorted_usec_list_t *sul);
+int
+sai_uuid16_create(struct lws_context *context, char *dest33);
diff --git a/src/common/struct-metadata.c b/src/common/struct-metadata.c
index 4899184..d13f3ec 100644
--- a/src/common/struct-metadata.c
+++ b/src/common/struct-metadata.c
@@ -47,6 +47,8 @@ const lws_struct_map_t lsm_plat[] = { /* !!! keep extern length in common/includ
LSM_STRING_PTR (sai_plat_t, platform, "platform"),
LSM_UNSIGNED (sai_plat_t, last_seen, "last_seen"),
LSM_CARRAY (sai_plat_t, peer_ip, "peer_ip"),
+ LSM_CARRAY (sai_plat_t, sai_hash, "sai_hash"),
+ LSM_CARRAY (sai_plat_t, lws_hash, "lws_hash"),
};
// This is the map for serializing to JSON
@@ -61,6 +63,8 @@ const lws_struct_map_t lsm_plat_for_json[] = {
LSM_SIGNED(sai_plat_t, powering_up, "powering_up"),
LSM_SIGNED(sai_plat_t, powering_down, "powering_down"),
LSM_CARRAY(sai_plat_t, peer_ip, "peer_ip"),
+ LSM_CARRAY(sai_plat_t, sai_hash, "sai_hash"),
+ LSM_CARRAY(sai_plat_t, lws_hash, "lws_hash"),
};
const lws_struct_map_t lsm_schema_map_plat_simple[] = {
@@ -163,6 +167,15 @@ const lws_struct_map_t lsm_task_cancel[] = {
LSM_CARRAY (sai_cancel_t, task_uuid, "task_uuid"),
};
+const lws_struct_map_t lsm_rebuild[] = {
+ LSM_CARRAY (sai_rebuild_t, builder_name, "builder_name"),
+};
+
+const lws_struct_map_t lsm_schema_rebuild[] = {
+ LSM_SCHEMA (sai_rebuild_t, NULL, lsm_rebuild,
+ "com.warmcat.sai.rebuild")
+};
+
const lws_struct_map_t lsm_schema_json_map_can[] = {
LSM_SCHEMA (sai_cancel_t, NULL, lsm_task_cancel,
"com.warmcat.sai.taskcan")
diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt
index 2e29af7..9b5eaba 100644
--- a/src/server/CMakeLists.txt
+++ b/src/server/CMakeLists.txt
@@ -12,6 +12,7 @@ set(SRCS
s-central.c
s-websrv.c
s-resource.c
+ ../common/c-utils.c
)
set(requirements 1)
diff --git a/src/server/s-comms.c b/src/server/s-comms.c
index 5b669ab..6dc669d 100644
--- a/src/server/s-comms.c
+++ b/src/server/s-comms.c
@@ -70,12 +70,6 @@ extern const lws_ss_info_t ssi_server;
*/
int
-sai_uuid16_create(struct lws_context *context, char *dest33)
-{
- return lws_hex_random(context, dest33, 33);
-}
-
-int
sai_sqlite3_statement(sqlite3 *pdb, const char *cmd, const char *desc)
{
sqlite3_stmt *sm;
diff --git a/src/server/s-private.h b/src/server/s-private.h
index 5850bab..4b3366b 100644
--- a/src/server/s-private.h
+++ b/src/server/s-private.h
@@ -105,6 +105,7 @@ struct pss {
lws_dll2_owner_t platform_owner; /* sai_platform_t builder offers */
lws_dll2_owner_t task_cancel_owner; /* sai_platform_t builder offers */
+ lws_dll2_owner_t rebuild_owner;
lws_dll2_owner_t aft_owner; /* for statefully spooling artifact info */
lws_dll2_owner_t res_owner; /* sai_resource_requisition_t
* owner of resource objects related
@@ -240,9 +241,6 @@ int
sai_sqlite3_statement(sqlite3 *pdb, const char *cmd, const char *desc);
int
-sai_uuid16_create(struct lws_context *context, char *dest33);
-
-int
sais_event_db_ensure_open(struct vhd *vhd, const char *event_uuid, char can_create, sqlite3 **ppdb);
void
diff --git a/src/server/s-websrv.c b/src/server/s-websrv.c
index d67086a..f12c519 100644
--- a/src/server/s-websrv.c
+++ b/src/server/s-websrv.c
@@ -69,6 +69,8 @@ static const lws_struct_map_t lsm_schema_json_map[] = {
"com.warmcat.sai.taskcan"),
LSM_SCHEMA (sai_viewer_state_t, NULL, lsm_viewercount_members,
"com.warmcat.sai.viewercount"),
+ LSM_SCHEMA (sai_rebuild_t, NULL, lsm_rebuild,
+ "com.warmcat.sai.rebuild"),
};
enum {
@@ -77,6 +79,7 @@ enum {
SAIS_WS_WEBSRV_RX_EVENTDELETE,
SAIS_WS_WEBSRV_RX_TASKCANCEL,
SAIS_WS_WEBSRV_RX_VIEWERCOUNT,
+ SAIS_WS_WEBSRV_RX_REBUILD,
};
void
@@ -117,6 +120,28 @@ reject:
return 1;
}
+static int
+sais_validate_builder_name(const char *id)
+{
+ const char *idin = id;
+
+ while (*id) {
+ if (!((*id >= '0' && *id <= '9') ||
+ (*id >= 'a' && *id <= 'z') ||
+ (*id >= 'A' && *id <= 'Z') ||
+ *id == '.' || *id == '/' || *id == '-' || *id == '_'))
+ goto reject;
+ id++;
+ }
+
+ return 0;
+reject:
+
+ lwsl_notice("%s: Invalid builder name '%s'\n", __func__, idin);
+
+ return 1;
+}
+
int
sais_websrv_queue_tx(struct lws_ss_handle *h, void *buf, size_t len)
{
@@ -567,6 +592,41 @@ websrvss_ws_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
}
break;
}
+ case SAIS_WS_WEBSRV_RX_REBUILD:
+ {
+ sai_rebuild_t *reb = (sai_rebuild_t *)a.dest;
+ sai_plat_t *cb;
+
+ if (sais_validate_builder_name(reb->builder_name))
+ goto soft_error;
+
+ cb = sais_builder_from_uuid(m->vhd, reb->builder_name,
+ __FILE__, __LINE__);
+ if (!cb) {
+ lwsl_info("%s: unknown builder %s for rebuild\n",
+ __func__, reb->builder_name);
+ lwsac_free(&a.ac);
+ break;
+ }
+
+ /* cb->wsi is the builder connection */
+ lws_start_foreach_dll(struct lws_dll2 *, p,
+ m->vhd->builders.head) {
+ struct pss *pss = lws_container_of(p, struct pss, same);
+
+ if (pss->wsi == cb->wsi) {
+ sai_rebuild_t *r = malloc(sizeof(*r));
+ if (!r)
+ break;
+ *r = *reb;
+ lws_dll2_add_tail(&r->list,
+ &pss->rebuild_owner);
+ lws_callback_on_writable(pss->wsi);
+ break;
+ }
+ } lws_end_foreach_dll(p);
+ }
+ break;
}
return 0;
diff --git a/src/server/s-ws-builder.c b/src/server/s-ws-builder.c
index 43d23db..73b07fd 100644
--- a/src/server/s-ws-builder.c
+++ b/src/server/s-ws-builder.c
@@ -566,6 +566,10 @@ handle:
live_cb->wsi = pss->wsi;
live_cb->ongoing = 0; /* Reset ongoing task count on connect */
lws_strncpy(live_cb->peer_ip, pss->peer_ip, sizeof(live_cb->peer_ip));
+ lws_strncpy(live_cb->sai_hash, build->sai_hash,
+ sizeof(live_cb->sai_hash));
+ lws_strncpy(live_cb->lws_hash, build->lws_hash,
+ sizeof(live_cb->lws_hash));
live_cb->online = 1;
} else {
/* New builder, create a deep-copied, malloc'd object */
@@ -583,6 +587,10 @@ handle:
live_cb->platform = p_str + nlen;
memcpy(p_str + nlen, build->platform, plen);
live_cb->instances = build->instances;
+ lws_strncpy(live_cb->sai_hash, build->sai_hash,
+ sizeof(live_cb->sai_hash));
+ lws_strncpy(live_cb->lws_hash, build->lws_hash,
+ sizeof(live_cb->lws_hash));
live_cb->wsi = pss->wsi;
live_cb->online = 1;
lws_strncpy(live_cb->peer_ip, pss->peer_ip, sizeof(live_cb->peer_ip));
@@ -629,6 +637,8 @@ handle:
* callback_on_writable handler to empty it
*/
+ sais_list_builders(vhd);
+
break;
bail:
@@ -1077,6 +1087,27 @@ sais_ws_json_tx_builder(struct vhd *vhd, struct pss *pss, uint8_t *buf,
goto send_json;
}
+ if (pss->rebuild_owner.head) {
+ /*
+ * Pending rebuild message to send
+ */
+ sai_rebuild_t *r = lws_container_of(pss->rebuild_owner.head,
+ sai_rebuild_t, list);
+
+ js = lws_struct_json_serialize_create(lsm_schema_rebuild,
+ LWS_ARRAY_SIZE(lsm_schema_rebuild), 0, r);
+ if (!js)
+ return 1;
+
+ n = (int)lws_struct_json_serialize(js, p, lws_ptr_diff_size_t(end, p), &w);
+ lws_struct_json_serialize_destroy(&js);
+
+ lws_dll2_remove(&r->list);
+ free(r);
+
+ goto send_json;
+ }
+
if (pss->task_cancel_owner.head) {
/*
* Pending cancel message to send
diff --git a/src/web/w-comms.c b/src/web/w-comms.c
index fd6b06b..8ce280f 100644
--- a/src/web/w-comms.c
+++ b/src/web/w-comms.c
@@ -130,12 +130,6 @@ saiw_update_viewer_count(struct vhd *vhd)
*/
int
-sai_uuid16_create(struct lws_context *context, char *dest33)
-{
- return lws_hex_random(context, dest33, 33);
-}
-
-int
sai_sqlite3_statement(sqlite3 *pdb, const char *cmd, const char *desc)
{
sqlite3_stmt *sm;
diff --git a/src/web/w-private.h b/src/web/w-private.h
index 0e06094..2cbc1c7 100644
--- a/src/web/w-private.h
+++ b/src/web/w-private.h
@@ -251,9 +251,6 @@ int
sai_sqlite3_statement(sqlite3 *pdb, const char *cmd, const char *desc);
int
-sai_uuid16_create(struct lws_context *context, char *dest33);
-
-int
sais_event_db_ensure_open(struct vhd *vhd, const char *event_uuid, char can_create, sqlite3 **ppdb);
void
diff --git a/src/web/w-ws-browser.c b/src/web/w-ws-browser.c
index e67fb6f..955a22d 100644
--- a/src/web/w-ws-browser.c
+++ b/src/web/w-ws-browser.c
@@ -97,6 +97,8 @@ static const lws_struct_map_t lsm_schema_json_map_bwsrx[] = {
"com.warmcat.sai.taskcan"),
LSM_SCHEMA (sai_load_report_t, NULL, lsm_load_report_members,
"com.warmcat.sai.loadreport"),
+ LSM_SCHEMA (sai_rebuild_t, NULL, lsm_rebuild,
+ "com.warmcat.sai.rebuild"),
};
enum {
@@ -107,6 +109,7 @@ enum {
SAIM_WS_BROWSER_RX_EVENTDELETE,
SAIM_WS_BROWSER_RX_TASKCANCEL,
SAIM_WS_BROWSER_RX_JS_HELLO,
+ SAIM_WS_BROWSER_RX_REBUILD,
};
@@ -569,6 +572,17 @@ saiw_ws_json_rx_browser(struct vhd *vhd, struct pss *pss, uint8_t *buf,
saiw_task_cancel(vhd, can->task_uuid);
break;
+ case SAIM_WS_BROWSER_RX_REBUILD:
+ if (!sais_conn_auth(pss))
+ goto soft_error;
+
+ /*
+ * User is asking us to rebuild a builder
+ */
+
+ saiw_websrv_queue_tx(vhd->h_ss_websrv, buf, bl);
+ break;
+
default:
assert(0);
break;