Author: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat Aug 16 15:39:22 2025 +0100 feat: Add task activity visualization This change introduces a new feature to visualize the activity of ongoing tasks in the sai-web interface. - `sai-server` now tracks the last log timestamp for each ongoing task. - A new periodic process in `sai-server` calculates an activity category (1, 2, or 3) for each task based on the time since the last log. - This activity data is broadcast to `sai-web` via the existing WebSocket connection. - `sai-web` forwards this data to the browser. - The browser-side JavaScript (`sai.js`) uses this data to dynamically apply CSS classes to the task icons. - New CSS animations provide a "flashing" effect to indicate the activity level of each task. diff --git a/assets/sai.css b/assets/sai.css index c44e92f..5ba27f8 100644 --- a/assets/sai.css +++ b/assets/sai.css @@ -811,3 +811,22 @@ img.branch { transition: opacity 0.3s; } +@keyframes flash-slow { + 50% { opacity: 0.8; } +} +@keyframes flash-medium { + 50% { opacity: 0.8; } +} +@keyframes flash-fast { + 50% { opacity: 0.8; } +} + +.activity-1 { + animation: flash-slow 4s infinite; +} +.activity-2 { + animation: flash-medium 2s infinite; +} +.activity-3 { + animation: flash-fast 1s infinite; +} diff --git a/assets/sai.js b/assets/sai.js index b658494..a2084e8 100644 --- a/assets/sai.js +++ b/assets/sai.js @@ -394,7 +394,21 @@ var lang_zhs = "{" + "\"%{pf}前创建, 创作时间: %{ct}ms \"" + "}}"; -var logs = "", redpend = 0, gitohashi_integ = 0, authd = 0, exptimer, auth_user = ""; +var logs = "", redpend = 0, gitohashi_integ = 0, authd = 0, exptimer, auth_user = "", + ongoing_task_activities = {}; + +function update_task_activities() { + for (const uuid in ongoing_task_activities) { + const el = document.getElementById("taskstate_" + uuid); + if (el) { + const cat = ongoing_task_activities[uuid]; + el.classList.remove("activity-1", "activity-2", "activity-3"); + if (cat > 0) { + el.classList.add("activity-" + cat); + } + } + } +} function expiry() { @@ -1517,6 +1531,16 @@ function ws_open_sai() break; + case "com.warmcat.sai.taskactivity": + ongoing_task_activities = {}; + if (jso.activity) { + for (var i = 0; i < jso.activity.length; i++) { + var act = jso.activity[i]; + ongoing_task_activities[act.uuid] = act.cat; + } + } + break; + case "com-warmcat-sai-logs": var s1 = atob(jso.log), s = hsanitize(s1), li, en = "", yo, dh, ce, tn = ""; @@ -1681,6 +1705,7 @@ window.addEventListener("load", function() { } setInterval(function() { + update_task_activities(); var locked = document.body.scrollHeight - document.body.clientHeight <= document.body.scrollTop + 1; diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..b61cda0 --- /dev/null +++ b/build.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -e +git clone https://github.com/warmcat/libwebsockets.git +cd libwebsockets +mkdir build +cd build +cmake .. -DLWS_WITH_JOSE=1 +sudo make install +cd ../.. +mkdir build +cd build +cmake .. +make -j8 diff --git a/src/server/s-comms.c b/src/server/s-comms.c index 9c9daf0..5b669ab 100644 --- a/src/server/s-comms.c +++ b/src/server/s-comms.c @@ -528,6 +528,9 @@ callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, lws_sul_schedule(vhd->context, 0, &vhd->sul_central, sais_central_cb, 500 * LWS_US_PER_MS); + lws_sul_schedule(vhd->context, 0, &vhd->sul_activity, + sais_activity_cb, 1 * LWS_US_PER_SEC); + break; case LWS_CALLBACK_PROTOCOL_DESTROY: diff --git a/src/server/s-private.h b/src/server/s-private.h index 8aa84ca..5850bab 100644 --- a/src/server/s-private.h +++ b/src/server/s-private.h @@ -173,6 +173,12 @@ typedef struct sais_sqlite_cache { int refcount; } sais_sqlite_cache_t; +typedef struct sai_ongoing_task { + lws_dll2_t list; + char uuid[65]; + lws_usec_t last_log_timestamp; +} sai_ongoing_task_t; + typedef struct sais_plat { lws_dll2_t list; @@ -192,6 +198,7 @@ struct vhd { struct lws_dll2_owner sai_powers; struct lws_dll2_owner pending_plats; lws_dll2_owner_t powering_up_list; /* sai_powering_up_plat_t */ + lws_dll2_owner_t ongoing_tasks; /* sai_ongoing_task_t */ struct lwsac *ac_plats; @@ -201,6 +208,7 @@ struct vhd { lws_dll2_owner_t tasklog_cache; lws_sorted_usec_list_t sul_logcache; lws_sorted_usec_list_t sul_central; /* background task allocation sul */ + lws_sorted_usec_list_t sul_activity; /* activity broadcast sul */ lws_usec_t last_check_abandoned_tasks; @@ -282,6 +290,9 @@ sais_subs_request_writeable(struct vhd *vhd, const char *task_uuid); void sais_central_cb(lws_sorted_usec_list_t *sul); +void +sais_activity_cb(lws_sorted_usec_list_t *sul); + int sais_task_reset(struct vhd *vhd, const char *task_uuid); diff --git a/src/server/s-task.c b/src/server/s-task.c index e5a4332..72b6575 100644 --- a/src/server/s-task.c +++ b/src/server/s-task.c @@ -267,6 +267,19 @@ sais_set_task_state(struct vhd *vhd, const char *builder_name, sais_event_db_close(vhd, (sqlite3 **)&e->pdb); lwsac_free(&ac); + if (state == SAIES_SUCCESS || state == SAIES_FAIL || state == SAIES_CANCELLED) { + sai_ongoing_task_t *ot = NULL; + lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, vhd->ongoing_tasks.head) { + ot = lws_container_of(p, sai_ongoing_task_t, list); + + if (!strcmp(ot->uuid, task_uuid)) { + lws_dll2_remove(&ot->list); + free(ot); + break; + } + } lws_end_foreach_dll_safe(p, p1); + } + return 0; bail: @@ -858,3 +871,55 @@ bail: return -1; } + +void +sais_activity_cb(lws_sorted_usec_list_t *sul) +{ + struct vhd *vhd = lws_container_of(sul, struct vhd, sul_activity); + char *p, *start, *end; + sai_ongoing_task_t *ot; + lws_usec_t now; + int cat, first = 1; + + p = start = malloc(8192); + if (!p) + return; + end = start + 8192; + + p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), + "{\"schema\":\"com.warmcat.sai.taskactivity\"," + "\"activity\":["); + + now = lws_now_usecs(); + + lws_start_foreach_dll(struct lws_dll2 *, d, vhd->ongoing_tasks.head) { + ot = lws_container_of(d, sai_ongoing_task_t, list); + + if (now - ot->last_log_timestamp > 10 * LWS_USEC_PER_SEC) + cat = 1; + else if (now - ot->last_log_timestamp > 3 * LWS_US_PER_SEC) + cat = 2; + else + cat = 3; + + if (!first) + *p++ = ','; + + p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), + "{\"uuid\":\"%s\",\"cat\":%d}", + ot->uuid, cat); + first = 0; + + } lws_end_foreach_dll(d); + + *p++ = ']'; + *p++ = '}'; + + if (lws_ptr_diff(p, start) > 48) + sais_websrv_broadcast(vhd->h_ss_websrv, start, lws_ptr_diff_size_t(p, start)); + + free(start); + + lws_sul_schedule(vhd->context, 0, &vhd->sul_activity, + sais_activity_cb, 1 * LWS_US_PER_SEC); +} diff --git a/src/server/s-ws-builder.c b/src/server/s-ws-builder.c index 729a8a8..43d23db 100644 --- a/src/server/s-ws-builder.c +++ b/src/server/s-ws-builder.c @@ -192,6 +192,7 @@ static void sais_log_to_db(struct vhd *vhd, sai_log_t *log) { sais_logcache_pertask_t *lcpt = NULL; + sai_ongoing_task_t *ot; sai_log_t *hlog; /* @@ -212,6 +213,8 @@ sais_log_to_db(struct vhd *vhd, sai_log_t *log) * Create a pertask and add it to the vhd list of them */ lcpt = malloc(sizeof(*lcpt)); + if (!lcpt) + return; memset(lcpt, 0, sizeof(*lcpt)); lws_strncpy(lcpt->uuid, log->task_uuid, sizeof(lcpt->uuid)); lws_dll2_add_tail(&lcpt->list, &vhd->tasklog_cache); @@ -237,6 +240,31 @@ sais_log_to_db(struct vhd *vhd, sai_log_t *log) lws_sul_schedule(vhd->context, 0, &vhd->sul_logcache, sais_dump_logs_to_db, 250 * LWS_US_PER_MS); + + /* + * Update ongoing task activity + */ + + ot = NULL; + lws_start_foreach_dll(struct lws_dll2 *, p, vhd->ongoing_tasks.head) { + sai_ongoing_task_t *fot = lws_container_of(p, sai_ongoing_task_t, list); + + if (!strcmp(fot->uuid, log->task_uuid)) { + ot = fot; + break; + } + } lws_end_foreach_dll(p); + + if (!ot) { + ot = malloc(sizeof(*ot)); + if (!ot) + return; + memset(ot, 0, sizeof(*ot)); + lws_strncpy(ot->uuid, log->task_uuid, sizeof(ot->uuid)); + lws_dll2_add_tail(&ot->list, &vhd->ongoing_tasks); + } + + ot->last_log_timestamp = lws_now_usecs(); } sai_plat_t * diff --git a/src/web/w-comms.c b/src/web/w-comms.c index d21ad1b..095ac6c 100644 --- a/src/web/w-comms.c +++ b/src/web/w-comms.c @@ -99,6 +99,8 @@ const lws_struct_map_t lsm_schema_json_map[] = { /* shares struct */ "sai-tasklogs"), LSM_SCHEMA (sai_load_report_t, NULL, lsm_load_report_members, "com.warmcat.sai.loadreport"), + LSM_SCHEMA (sai_browse_rx_evinfo_t, NULL, lsm_websrv_evinfo, + "com.warmcat.sai.taskactivity"), }; size_t lsm_schema_json_map_array_size = LWS_ARRAY_SIZE(lsm_schema_json_map); diff --git a/src/web/w-websrv.c b/src/web/w-websrv.c index ccc96c1..5601a4e 100644 --- a/src/web/w-websrv.c +++ b/src/web/w-websrv.c @@ -47,6 +47,7 @@ enum { SAIS_WS_WEBSRV_RX_OVERVIEW, /* deleted or added event */ SAIS_WS_WEBSRV_RX_TASKLOGS, /* new logs for task (ratelimited) */ SAIS_WS_WEBSRV_RX_LOADREPORT, /* builder's cpu load report */ + SAIS_WS_WEBSRV_RX_TASKACTIVITY, }; /* @@ -176,6 +177,10 @@ saiw_lp_rx(void *userobj, const uint8_t *buf, size_t len, int flags) saiw_ws_broadcast_raw(vhd, buf, len, 2, lws_write_ws_flags(LWS_WRITE_TEXT, flags & LWSSS_FLAG_SOM, flags & LWSSS_FLAG_EOM)); break; + case SAIS_WS_WEBSRV_RX_TASKACTIVITY: + saiw_ws_broadcast_raw(vhd, buf, len, 0, + lws_write_ws_flags(LWS_WRITE_TEXT, flags & LWSSS_FLAG_SOM, flags & LWSSS_FLAG_EOM)); + break; } cleanup_parse_allocs: