Files
devplacepy/devplacepy/static/js/AdminNodes.js
T
retoorandClaude Sonnet 5.5 02ab747cd0
DevPlace CI / test (push) Failing after 44m45s
Code Farm: per-plot auto-replant pause and crop-lock check; community sync and node work
Continuous Delivery could never be turned off once a crop was planted. Add a
one-shot per-plot "stop after harvest" flag (POST /game/plot/stop, Devii tool
game_plot_stop, game_plots.stop_after_harvest), cleared by the next plant,
autoreplant claim or Refactor. Autoreplant now also respects crop locks through
the shared _crop_lock predicate. Plan and measurements in happyplanting.md.

Also includes the pending community sync, node network and related work in the
tree; the full suite passed (4999) with all of it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 02:23:43 +02:00

123 lines
4.9 KiB
JavaScript

// retoor <retoor@molodetz.nl>
import { Http } from "./Http.js";
import { Poller } from "./Poller.js";
const REFRESH_MS = 10000;
const PAGE_URL = "/admin/nodes";
class AdminNodes {
constructor() {
this.root = document.querySelector("[data-nodes-root]");
if (!this.root) return;
this.modal = document.getElementById("node-suspend-modal");
this.suspendForm = document.getElementById("node-suspend-form");
this.suspendTitle = document.getElementById("node-suspend-title");
this.killswitch = document.querySelector("[data-nodes-killswitch]");
this.settings = document.querySelector("[data-nodes-settings]");
this.bindActions();
this.bindSuspendForm();
this.bindKillswitch();
this.bindSettings();
this.poller = new Poller(() => this.refresh(), REFRESH_MS, { pauseHidden: true, immediate: false });
}
async refresh() {
const response = await fetch(PAGE_URL, { headers: { "Accept": "text/html" } });
if (!response.ok) return;
const fresh = new DOMParser().parseFromString(await response.text(), "text/html");
this.root.querySelectorAll("[data-live]").forEach((section) => {
const next = fresh.querySelector(`[data-live="${section.dataset.live}"]`);
if (next) section.innerHTML = next.innerHTML;
});
const state = fresh.querySelector("[data-nodes-killswitch]");
if (state && this.killswitch) this.killswitch.innerHTML = state.innerHTML;
}
bindActions() {
this.root.addEventListener("click", async (event) => {
const button = event.target.closest("[data-node-action]");
if (!button) return;
const uid = button.dataset.uid;
const action = button.dataset.nodeAction;
if (action === "suspend") {
this.openSuspend(uid, button.dataset.label);
return;
}
if (action === "override") {
await this.override(uid, button.dataset.value, button.dataset.label);
return;
}
const urls = {
"unsuspend": `/admin/nodes/devices/${uid}/unsuspend`,
"resolve-flag": `/admin/nodes/flags/${uid}/resolve`,
};
await this.post(urls[action], {}, action === "unsuspend" ? "Suspension lifted" : "Flag resolved");
});
}
async override(uid, value, label) {
const allowing = value === "allow";
const message = allowing
? `Let ${label} serve jobs although its address is not residential or mobile?`
: `Remove the address class override of ${label}?`;
const ok = await window.app.dialog.confirm({ message, danger: allowing });
if (!ok) return;
await this.post(`/admin/nodes/devices/${uid}/class`, { value }, allowing ? "Override set" : "Override cleared");
}
openSuspend(uid, label) {
this.suspendForm.action = `/admin/nodes/devices/${uid}/suspend`;
this.suspendTitle.textContent = `Suspend device: ${label}`;
this.modal.classList.add("visible");
}
bindSuspendForm() {
this.suspendForm.addEventListener("submit", async (event) => {
event.preventDefault();
const params = Object.fromEntries(new FormData(this.suspendForm).entries());
if (await this.post(this.suspendForm.action, params, "Device suspended")) {
this.modal.classList.remove("visible");
this.suspendForm.reset();
}
});
}
bindKillswitch() {
if (!this.killswitch) return;
this.killswitch.addEventListener("submit", async (event) => {
event.preventDefault();
const turningOn = this.killswitch.querySelector("[name='enabled']").value === "true";
const message = turningOn
? "Switch community donation on? Consenting donors can connect again."
: "Switch community donation off? Every connected node is disconnected within seconds.";
const ok = await window.app.dialog.confirm({ message, danger: !turningOn });
if (!ok) return;
await this.post(this.killswitch.action, { enabled: String(turningOn) }, "Kill switch updated");
});
}
bindSettings() {
if (!this.settings) return;
this.settings.addEventListener("submit", async (event) => {
event.preventDefault();
const params = Object.fromEntries(new FormData(this.settings).entries());
await this.post(this.settings.action, params, "Settings saved");
});
}
async post(url, params, message) {
try {
await Http.send(url, params);
window.app.toast.show(message, { type: "success" });
await this.refresh();
return true;
} catch {
return false;
}
}
}
window.AdminNodes = AdminNodes;
export { AdminNodes };