Rules: a quota is one line, the rest in its dialog

A rule's quota shows on its row as one line (what it used, how it
resets) that opens a dialog with the rest: the allowance, last and next
reset, usage (each address's, with Reset and Give back), and Reset and
Give back for everyone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-21 11:50:04 +01:00
1 parent 53cd43187e
commit ff03e76925
3 files changed
+41 -28

No files matched your search

+34 -23
View File
@@ -158,42 +158,53 @@ function extras(r, q) {
return out;
}
// quotaLine shows a quota, and what it used: in total, or per address
// (the busiest, and the rest in a dialog).
// quotaLine sums up a quota on the rule's row: what it used (in total,
// or how many addresses and the busiest); the rest is in its dialog.
function quotaLine(r, q) {
const qt = r.quota;
const each = qt.per_source ? " each source" : qt.per_destination ? " each destination" : "";
const resets = qt.reset === "monthly" ? "monthly on day " + (qt.day || 1) : qt.reset || "by hand";
let used = null;
if (q && !qt.per_source && !qt.per_destination) {
used = [h("strong", null, fmtBytes(q.used)), " of " + fmtBytes(qt.bytes) + " used"];
} else if (q) {
const resets = qt.reset === "monthly" ? "monthly" : qt.reset || "reset by hand";
let used = fmtBytes(qt.bytes) + each;
if (q && !each) used = fmtBytes(q.used) + " of " + fmtBytes(qt.bytes) + " used";
else if (q) {
const n = (q.addresses || []).length;
used = [n === 1 ? "1 address" : n + " addresses", n ? [", busiest ", h("strong", null, fmtBytes(q.addresses[0].used))] : null, " ",
n ? h("button", { class: "small", onclick: () => usageDialog(r, q) }, "Usage…") : null];
used += " · " + (n === 1 ? "1 address" : n + " addresses") + (n ? ", busiest " + fmtBytes(q.addresses[0].used) : "");
}
return h("div", { class: "muted small" }, (qt.over ? "quota over " : "quota ") + fmtBytes(qt.bytes) + each + ", resets " + resets,
used ? [" · ", used] : null,
q && q.next_reset ? " · next reset " + new Date(q.next_reset).toLocaleString() : null,
can("config", true) && q && r.pending !== "new" ? [" ", h("button", { class: "small", onclick: () => resetQuota(r) }, "Reset"),
" ", h("button", { class: "small", onclick: () => giveBack(r) }, "Give back…")] : null);
const text = "quota " + used + " · " + resets;
return h("div", { class: "muted small" }, q && r.pending !== "new"
? h("button", { class: "link small", title: "Usage, reset and give back", onclick: () => quotaDialog(r, q) }, text) : text);
}
async function usageDialog(r, q) {
const names = await lookupNames(q.addresses.map((a) => a.address));
// A row's action closes this dialog first: it opens one of its own.
const then = (f) => (e) => { e.target.closest(".modal").querySelector(".buttons button").click(); f(); };
// quotaDialog shows a quota in full: what it used (each address's, for a
// per-address one), its resets, and resetting or giving back.
async function quotaDialog(r, q) {
const qt = r.quota;
const perAddress = qt.per_source || qt.per_destination;
const write = can("config", true);
await modal("Quota usage of " + (r.comment || "rule #" + r.id), h("div", null,
h("p", { class: "muted" }, "Each " + (r.quota.per_source ? "source" : "destination") + " has " + fmtBytes(r.quota.bytes) + "."),
table([
const resets = qt.reset === "monthly" ? "monthly on day " + (qt.day || 1) : qt.reset || "only by hand";
const when = (t) => t ? new Date(t).toLocaleString() : "–";
const names = perAddress ? await lookupNames((q.addresses || []).map((a) => a.address)) : {};
// A row's action closes this dialog first: it opens one of its own.
const then = (f) => (e) => { e.target.closest(".modal").querySelector(".buttons button:last-child").click(); f(); }; // Close
const v = await modal("Quota of " + (r.comment || "rule #" + r.id), h("div", null,
h("p", null, fmtBytes(qt.bytes) + (perAddress ? (qt.per_source ? " for each source" : " for each destination") : " in all") + "; the rule matches " +
(qt.over ? "once it is used up" : "while it lasts") + ". Resets " + resets + "."),
h("p", { class: "muted small" }, "Last reset " + when(q.last_reset) + " · next reset " + when(q.next_reset)),
perAddress ? table([
{ label: "Address", render: (a) => h("span", { class: "mono" }, named(names, a.address)) },
{ label: "Used", render: (a) => fmtBytes(a.used) },
{ label: "", render: (a) => usageBar(a.used, r.quota.bytes) },
{ label: "", render: (a) => usageBar(a.used, qt.bytes) },
write ? { label: "", render: (a) => h("span", { class: "nowrap" },
h("button", { class: "small", onclick: then(() => resetQuota(r, a.address)) }, "Reset"), " ",
h("button", { class: "small", onclick: then(() => giveBack(r, a.address)) }, "Give back…")) } : null,
].filter(Boolean), q.addresses)), [{ label: "Close", value: null }], { wide: true });
].filter(Boolean), q.addresses || [], { empty: "No address has used it since the last reset." })
: h("div", null, h("strong", null, fmtBytes(q.used)), " of " + fmtBytes(qt.bytes) + " used", usageBar(q.used, qt.bytes))),
[
...(write ? [{ label: perAddress ? "Reset all" : "Reset", value: "reset" }, { label: perAddress ? "Give back to all…" : "Give back…", value: "give" }] : []),
{ label: "Close", value: null },
], { wide: perAddress });
if (v === "reset") resetQuota(r);
if (v === "give") giveBack(r);
}
// giveBack takes an amount off what a quota used (one address's, or