mirror of
https://github.com/blw1138/cross-py-builder.git
synced 2026-09-07 21:41:09 -05:00
Manage workers from the web UI, persisted in SQLite
Replace the CROSS_PY_WORKERS env list with a DB-backed worker store so agents can be added/removed (and their online status verified) from the browser. - db.py: add workers table + list_workers/add_worker/remove_worker. - workers.py: configured_workers() reads the DB instead of settings. - app.py: POST /api/workers (add, probes reachability) and DELETE /api/workers/<port>/<host>; startup log uses DB worker count. - UI: Workers card with add form, per-worker live status + OS/CPU, remove. - settings.py: drop CROSS_PY_WORKERS/_get_json_list; DB is single source. - README/DESIGN updated to describe UI-managed workers.
This commit is contained in:
+58
-2
@@ -50,15 +50,71 @@ async function refreshWorkers() {
|
||||
const workers = await api("/api/workers");
|
||||
const online = workers.filter((w) => w.online).length;
|
||||
const parts = workers.map((w) =>
|
||||
`${w.host || w.url}${w.online ? "" : " (down)"}`
|
||||
`${w.url}${w.online ? "" : " (down)"}`
|
||||
);
|
||||
$("#worker-summary").textContent =
|
||||
`${online}/${workers.length} workers online — ${parts.join(" · ")}`;
|
||||
`${online}/${workers.length} workers online` + (parts.length ? ` — ${parts.join(" · ")}` : "");
|
||||
|
||||
const box = $("#workers");
|
||||
if (!workers.length) {
|
||||
box.innerHTML = `<p class="muted">No workers configured. Add one below.</p>`;
|
||||
return;
|
||||
}
|
||||
box.innerHTML = `<table>
|
||||
<tr><th>Address</th><th>Status</th><th>OS / CPU</th><th></th></tr>` +
|
||||
workers.map((w) => {
|
||||
const addr = `${w.host || ""}:${w.port || ""}`;
|
||||
const status = w.online
|
||||
? `<span class="badge done">ready · ${esc(w.status || "ok")}</span>`
|
||||
: `<span class="badge failed">down</span>`;
|
||||
const osCpu = w.online ? `${esc(w.os || "?")} / ${esc(w.cpu || "?")}` : "—";
|
||||
return `<tr>
|
||||
<td><code>${esc(addr)}</code></td>
|
||||
<td>${status}</td>
|
||||
<td>${osCpu}</td>
|
||||
<td><button class="remove-worker" data-url="${esc(w.url)}">Remove</button></td>
|
||||
</tr>`;
|
||||
}).join("") + `</table>`;
|
||||
} catch (_) {
|
||||
$("#worker-summary").textContent = "Worker summary unavailable";
|
||||
}
|
||||
}
|
||||
|
||||
$("#worker-form").addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const input = $("#worker-spec");
|
||||
const spec = input.value.trim();
|
||||
if (!spec) return;
|
||||
try {
|
||||
const resp = await fetch("/api/workers", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ spec }),
|
||||
});
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
if (!resp.ok) throw new Error(data.error || resp.statusText);
|
||||
input.value = "";
|
||||
refreshWorkers();
|
||||
refreshCapabilities();
|
||||
} catch (err) {
|
||||
$("#worker-spec").value = "";
|
||||
$("#worker-spec").placeholder = `Error: ${err.message}`;
|
||||
}
|
||||
});
|
||||
|
||||
$("#workers").addEventListener("click", async (e) => {
|
||||
const btn = e.target.closest("button.remove-worker");
|
||||
if (!btn) return;
|
||||
const url = btn.dataset.url;
|
||||
const host = url.replace(/^https?:\/\//, "").split(":")[0];
|
||||
const port = url.replace(/^https?:\/\//, "").split(":")[1];
|
||||
try {
|
||||
await fetch(`/api/workers/${encodeURIComponent(port)}/${encodeURIComponent(host)}`, { method: "DELETE" });
|
||||
refreshWorkers();
|
||||
refreshCapabilities();
|
||||
} catch (_) {}
|
||||
});
|
||||
|
||||
async function refreshJobs() {
|
||||
let jobs;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user