Dashboard: a per-address quota's bar counts all its addresses

One address that used up its allowance filled the bar (100%). The bar
is now all the addresses together, and the dashboard and the rule's
quota line say how many used theirs up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-22 18:52:51 +01:00
1 parent 500bfc7bdd
commit 4af4a5908b
3 files changed
+18 -6

No files matched your search

+11 -4
View File
@@ -72,19 +72,26 @@ export async function render(el) {
}
// quotaPanel lists every rule's quota with what it used: the total, or
// for a per-address quota the busiest address (the rest are on the rule).
// for a per-address quota how many addresses used theirs up and the
// busiest (the rest are on the rule). A per-address quota's bar is all
// its addresses together, so one address alone can't fill it.
function quotaPanel(quotas, names) {
const perAddress = (q) => q.quota.per_source || q.quota.per_destination;
const used = (q) => perAddress(q) ? ((q.addresses || [])[0] || { used: 0 }).used : q.used;
const addrs = (q) => q.addresses || [];
const usedUp = (q) => addrs(q).filter((a) => a.used >= q.quota.bytes).length;
const bar = (q) => perAddress(q)
? usageBar(addrs(q).reduce((n, a) => n + Math.min(a.used, q.quota.bytes), 0), addrs(q).length * q.quota.bytes)
: usageBar(q.used, q.quota.bytes);
return h("div", { class: "panel" },
h("div", { class: "panel-head" }, h("h2", null, "Quotas"), h("a", { class: "button small", href: "#/stats" }, "Statistics")),
table([
{ label: "Rule", render: (q) => h("a", { href: "#/rules?rule=" + q.rule_id }, q.rule) },
{ label: "Allowance", render: (q) => fmtBytes(q.quota.bytes) + (q.quota.per_source ? " each source" : q.quota.per_destination ? " each destination" : "") },
{ label: "Used", render: (q) => perAddress(q)
? [plural((q.addresses || []).length, "address", "addresses"), q.addresses && q.addresses.length ? [", busiest ", named(names, q.addresses[0].address), " " + fmtBytes(used(q)) + " (" + pct(used(q), q.quota.bytes) + ")"] : ""]
? [plural(addrs(q).length, "address", "addresses"), usedUp(q) ? ", " + usedUp(q) + " used up" : "",
addrs(q).length ? [", busiest ", named(names, addrs(q)[0].address), " " + fmtBytes(addrs(q)[0].used) + " (" + pct(addrs(q)[0].used, q.quota.bytes) + ")"] : ""]
: fmtBytes(q.used) + " (" + pct(q.used, q.quota.bytes) + ")" },
{ label: "", render: (q) => usageBar(used(q), q.quota.bytes) },
{ label: "", render: (q) => addrs(q).length || !perAddress(q) ? bar(q) : "" },
{ label: "Next reset", render: (q) => q.next_reset ? time(q.next_reset) : h("span", { class: "muted" }, "by hand") },
], quotas));
}