mirror of
https://github.com/blw1138/cross-py-builder.git
synced 2026-09-07 21:41:09 -05:00
Fix worker removal and improve web UI defaults
- Parse optional http:// scheme prefix on worker specs so a scheme-typed IP is stored cleanly and its remove button works. - Delete workers by their stored DB host/port instead of re-parsing the rendered URL, and surface add/remove errors in the UI. - Default new builds to git URL instead of zip upload. - Show worker hostname/IP from agent status in the address column. - Always include DB host/port in worker API output.
This commit is contained in:
+21
-9
@@ -63,7 +63,8 @@ async function refreshWorkers() {
|
|||||||
box.innerHTML = `<table>
|
box.innerHTML = `<table>
|
||||||
<tr><th>Address</th><th>Status</th><th>OS / CPU</th><th></th></tr>` +
|
<tr><th>Address</th><th>Status</th><th>OS / CPU</th><th></th></tr>` +
|
||||||
workers.map((w) => {
|
workers.map((w) => {
|
||||||
const addr = `${w.host || ""}:${w.port || ""}`;
|
const displayHost = w.online ? (w.hostname || w.ip || w.host) : w.host;
|
||||||
|
const addr = `${esc(displayHost)}:${esc(w.port || "")}`;
|
||||||
const status = w.online
|
const status = w.online
|
||||||
? `<span class="badge done">ready · ${esc(w.status || "ok")}</span>`
|
? `<span class="badge done">ready · ${esc(w.status || "ok")}</span>`
|
||||||
: `<span class="badge failed">down</span>`;
|
: `<span class="badge failed">down</span>`;
|
||||||
@@ -72,7 +73,7 @@ async function refreshWorkers() {
|
|||||||
<td><code>${esc(addr)}</code></td>
|
<td><code>${esc(addr)}</code></td>
|
||||||
<td>${status}</td>
|
<td>${status}</td>
|
||||||
<td>${osCpu}</td>
|
<td>${osCpu}</td>
|
||||||
<td><button class="remove-worker" data-url="${esc(w.url)}">Remove</button></td>
|
<td><button class="remove-worker" data-host="${esc(w.host)}" data-port="${esc(w.port)}">Remove</button></td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
}).join("") + `</table>`;
|
}).join("") + `</table>`;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
@@ -85,6 +86,7 @@ $("#worker-form").addEventListener("submit", async (e) => {
|
|||||||
const input = $("#worker-spec");
|
const input = $("#worker-spec");
|
||||||
const spec = input.value.trim();
|
const spec = input.value.trim();
|
||||||
if (!spec) return;
|
if (!spec) return;
|
||||||
|
const msg = $("#worker-msg");
|
||||||
try {
|
try {
|
||||||
const resp = await fetch("/api/workers", {
|
const resp = await fetch("/api/workers", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -94,25 +96,35 @@ $("#worker-form").addEventListener("submit", async (e) => {
|
|||||||
const data = await resp.json().catch(() => ({}));
|
const data = await resp.json().catch(() => ({}));
|
||||||
if (!resp.ok) throw new Error(data.error || resp.statusText);
|
if (!resp.ok) throw new Error(data.error || resp.statusText);
|
||||||
input.value = "";
|
input.value = "";
|
||||||
|
msg.textContent = `Added worker ${spec}`;
|
||||||
|
msg.className = "msg";
|
||||||
refreshWorkers();
|
refreshWorkers();
|
||||||
refreshCapabilities();
|
refreshCapabilities();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$("#worker-spec").value = "";
|
input.value = "";
|
||||||
$("#worker-spec").placeholder = `Error: ${err.message}`;
|
msg.textContent = `Error: ${err.message}`;
|
||||||
|
msg.className = "msg error";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#workers").addEventListener("click", async (e) => {
|
$("#workers").addEventListener("click", async (e) => {
|
||||||
const btn = e.target.closest("button.remove-worker");
|
const btn = e.target.closest("button.remove-worker");
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
const url = btn.dataset.url;
|
const msg = $("#worker-msg");
|
||||||
const host = url.replace(/^https?:\/\//, "").split(":")[0];
|
const host = btn.dataset.host;
|
||||||
const port = url.replace(/^https?:\/\//, "").split(":")[1];
|
const port = btn.dataset.port;
|
||||||
try {
|
try {
|
||||||
await fetch(`/api/workers/${encodeURIComponent(port)}/${encodeURIComponent(host)}`, { method: "DELETE" });
|
const resp = await fetch(`/api/workers/${encodeURIComponent(port)}/${encodeURIComponent(host)}`, { method: "DELETE" });
|
||||||
|
const data = await resp.json().catch(() => ({}));
|
||||||
|
if (!resp.ok) throw new Error(data.error || resp.statusText);
|
||||||
|
msg.textContent = `Removed ${host}:${port}`;
|
||||||
|
msg.className = "msg";
|
||||||
refreshWorkers();
|
refreshWorkers();
|
||||||
refreshCapabilities();
|
refreshCapabilities();
|
||||||
} catch (_) {}
|
} catch (err) {
|
||||||
|
msg.textContent = `Remove failed: ${err.message}`;
|
||||||
|
msg.className = "msg error";
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function refreshJobs() {
|
async function refreshJobs() {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
<input type="text" id="worker-spec" placeholder="host:port (e.g. 192.168.1.40:9001)">
|
<input type="text" id="worker-spec" placeholder="host:port (e.g. 192.168.1.40:9001)">
|
||||||
<button type="submit">Add worker</button>
|
<button type="submit">Add worker</button>
|
||||||
</form>
|
</form>
|
||||||
|
<p id="worker-msg" class="msg"></p>
|
||||||
<div id="workers"></div>
|
<div id="workers"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -28,18 +29,18 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="mode">Source</label>
|
<label for="mode">Source</label>
|
||||||
<select id="mode">
|
<select id="mode">
|
||||||
<option value="upload">Upload zip</option>
|
|
||||||
<option value="git">Git repo</option>
|
<option value="git">Git repo</option>
|
||||||
|
<option value="upload">Upload zip</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="field" id="upload-field">
|
<div class="field" id="url-field">
|
||||||
<label for="file">Project zip</label>
|
|
||||||
<input type="file" id="file" accept=".zip">
|
|
||||||
</div>
|
|
||||||
<div class="field" id="url-field" hidden>
|
|
||||||
<label for="url">Git URL</label>
|
<label for="url">Git URL</label>
|
||||||
<input type="text" id="url" placeholder="https://…">
|
<input type="text" id="url" placeholder="https://…">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field" id="upload-field" hidden>
|
||||||
|
<label for="file">Project zip</label>
|
||||||
|
<input type="file" id="file" accept=".zip">
|
||||||
|
</div>
|
||||||
<div class="fields-row">
|
<div class="fields-row">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="os">OS</label>
|
<label for="os">OS</label>
|
||||||
|
|||||||
+16
-4
@@ -4,8 +4,16 @@ import requests
|
|||||||
|
|
||||||
|
|
||||||
def parse_worker(spec):
|
def parse_worker(spec):
|
||||||
"""'host:port' -> {'url': 'http://host:port', 'host': host, 'port': port}."""
|
"""'host:port' -> {'url': 'http://host:port', 'host': host, 'port': port}.
|
||||||
host, _, port = spec.strip().rpartition(":")
|
|
||||||
|
Tolerates an optional http:// or https:// scheme prefix.
|
||||||
|
"""
|
||||||
|
s = spec.strip()
|
||||||
|
for scheme in ("http://", "https://"):
|
||||||
|
if s.lower().startswith(scheme):
|
||||||
|
s = s[len(scheme):]
|
||||||
|
break
|
||||||
|
host, _, port = s.rpartition(":")
|
||||||
if not host or not port.isdigit():
|
if not host or not port.isdigit():
|
||||||
raise ValueError(f"Invalid worker spec: {spec!r} (expected host:port)")
|
raise ValueError(f"Invalid worker spec: {spec!r} (expected host:port)")
|
||||||
return {"url": f"http://{host}:{port}", "host": host, "port": int(port)}
|
return {"url": f"http://{host}:{port}", "host": host, "port": int(port)}
|
||||||
@@ -22,7 +30,7 @@ def configured_workers():
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def probe_worker(url, timeout=None):
|
def probe_worker(url, host=None, port=None, timeout=None):
|
||||||
"""Return a normalized worker status dict, or None if unreachable."""
|
"""Return a normalized worker status dict, or None if unreachable."""
|
||||||
timeout = timeout or settings.WORKER_STATUS_TIMEOUT
|
timeout = timeout or settings.WORKER_STATUS_TIMEOUT
|
||||||
try:
|
try:
|
||||||
@@ -34,13 +42,17 @@ def probe_worker(url, timeout=None):
|
|||||||
|
|
||||||
info["url"] = url
|
info["url"] = url
|
||||||
info["online"] = True
|
info["online"] = True
|
||||||
|
if host is not None:
|
||||||
|
info["host"] = host
|
||||||
|
if port is not None:
|
||||||
|
info["port"] = port
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
def probe_all():
|
def probe_all():
|
||||||
workers = []
|
workers = []
|
||||||
for w in configured_workers():
|
for w in configured_workers():
|
||||||
status = probe_worker(w["url"])
|
status = probe_worker(w["url"], host=w["host"], port=w["port"])
|
||||||
if status:
|
if status:
|
||||||
workers.append(status)
|
workers.append(status)
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user