diff options
| -rw-r--r-- | docs/dashboard.md | 4 | ||||
| -rw-r--r-- | src/api/dashboard/dashboard.html | 32 |
2 files changed, 35 insertions, 1 deletions
diff --git a/docs/dashboard.md b/docs/dashboard.md index 2bd126a..59ec847 100644 --- a/docs/dashboard.md +++ b/docs/dashboard.md @@ -371,7 +371,9 @@ dropped; what no rule covers is quarantined on the node that made it), and for a long votes are collected and how long nodes wait after a successful vote before switching (the switch delay, `switchDelay`; below a keepalive interval only with "even below a keepalive interval" ticked, the API's `?force=switchDelay`: slow nodes may then -miss the commit before the others switch, and rejoin). The answer shows below: the remake's ID, or why it was refused +miss the commit before the others switch, and rejoin), and the replay window and the +archive retention (`replayWindow`, `archiveRetention`: epochs, an age, or both; both +empty, the defaults). The answer shows below: the remake's ID, or why it was refused (and the API's override, if any). The map shows the votes as they come. ## Console 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 { |
