aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-09 03:23:10 +0000
committertv <tv@krebsco.de>2026-11-09 04:27:48 +0100
commitb52d4b6181e7fcf806a655d218e74de71b54cbf8 (patch)
tree3cac1fb1adba43feb899159fdecb05e43cac3bff /src
parentacafad962b5eea4772479d7f44912038d71e528e (diff)
dashboard: the replay window and archive retention in the remake dialog
The remake dialog set the kind, its input, the settle time and the switch delay, but not the replay window or the archive retention. Now each has two fields, epochs and an age, their defaults as placeholders: both empty sends nothing (the node's defaults), one given sends that limit alone, as the API reads it; in the body, or for an import in the query (`replayWindow`, `archiveRetention`). Epochs that aren't a whole number are refused in the page.
Diffstat (limited to 'src')
-rw-r--r--src/api/dashboard/dashboard.html32
1 files changed, 32 insertions, 0 deletions
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html
index d173418..dbf5480 100644
--- a/src/api/dashboard/dashboard.html
+++ b/src/api/dashboard/dashboard.html
@@ -418,6 +418,12 @@
title="How long nodes wait after the vote succeeded before switching, so the commit reaches every node first: seconds, or e.g. 500ms, 1m; empty: one keepalive interval"></label>
<label title="Accept a switch delay below a keepalive interval (?force=switchDelay): slow nodes may miss the commit before the others switch, and have to rejoin"><input
type="checkbox" id="remake-force-switch-delay"> even below a keepalive interval</label>
+ <label title="How far behind a returning node may be and still have its writes carried over automatically: at most this many remakes behind, or the first it missed proposed at most this long ago (seconds, or e.g. 12h, 7d). Both empty: 1 epoch or 7 days; one given: that limit alone">replay window
+ <input id="remake-replay-epochs" size="3" inputmode="numeric" placeholder="1"> epochs or
+ <input id="remake-replay-age" size="5" placeholder="7d"></label>
+ <label title="How long nodes keep the old document: this many epochs, or this long (seconds, or e.g. 12h, 30d). Both empty: 2 epochs or 30 days; one given: that limit alone">archive retention
+ <input id="remake-archive-epochs" size="3" inputmode="numeric" placeholder="2"> epochs or
+ <input id="remake-archive-age" size="5" placeholder="30d"></label>
<p class="note">Writes made meanwhile that no rule covers are quarantined on the node
that made them.</p>
<div class="actions"><button type="submit" class="toggle">propose</button>
@@ -4160,6 +4166,20 @@ document.getElementById("remake-json-file").addEventListener("change", event =>
file.text().then(text => { document.getElementById("remake-json").value = text; })
.catch(error => remakeResult("Reading " + file.name + ": " + error, true));
});
+// A retention from its two fields (`epochs` and `age`), or null for the node's default
+// (both empty); throws where the epochs aren't a whole number.
+function remakeRetention(name, label) {
+ const epochs = document.getElementById("remake-" + name + "-epochs").value.trim();
+ const age = document.getElementById("remake-" + name + "-age").value.trim();
+ if (!epochs && !age) return null;
+ const retention = {};
+ if (epochs) {
+ if (!/^\d+$/.test(epochs)) throw new Error("The " + label + "'s epochs aren't a whole number.");
+ retention.epochs = Number(epochs);
+ }
+ if (age) retention.age = age;
+ return retention;
+}
remakeForm.addEventListener("submit", event => {
event.preventDefault();
const kind = document.getElementById("remake-kind").value;
@@ -4167,6 +4187,14 @@ remakeForm.addEventListener("submit", event => {
const switchDelay = document.getElementById("remake-switch-delay").value.trim();
const force = document.getElementById("remake-force-switch-delay").checked
? "force=switchDelay" : "";
+ let replayWindow, archiveRetention;
+ try {
+ replayWindow = remakeRetention("replay", "replay window");
+ archiveRetention = remakeRetention("archive", "archive retention");
+ } catch (error) {
+ remakeResult(error.message, true);
+ return;
+ }
let request;
if (kind === "import") {
// The document as it is (octet-stream): its options in the query.
@@ -4175,6 +4203,8 @@ remakeForm.addEventListener("submit", event => {
const options = new URLSearchParams(force);
if (settle) options.set("settle", settle);
if (switchDelay) options.set("switchDelay", switchDelay);
+ if (replayWindow) options.set("replayWindow", JSON.stringify(replayWindow));
+ if (archiveRetention) options.set("archiveRetention", JSON.stringify(archiveRetention));
const rules = remakeRules();
if (rules.length) options.set("rules", JSON.stringify(rules));
const query = options.size ? "?" + options : "";
@@ -4184,6 +4214,8 @@ remakeForm.addEventListener("submit", event => {
const body = { kind };
if (settle) body.settle = settle;
if (switchDelay) body.switchDelay = switchDelay;
+ if (replayWindow) body.replayWindow = replayWindow;
+ if (archiveRetention) body.archiveRetention = archiveRetention;
if (kind === "replace") {
let parsed;
try {