diff --git a/CHANGELOG.md b/CHANGELOG.md index 057b5b0..f67d290 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -114,6 +114,10 @@ All notable changes to Housecarl are documented here. The format follows ### Fixed +- The dashboard's bar for a per-address quota showed only the busiest + address, so one address that used up its allowance filled it (100%). + The bar is now all the addresses together, and the dashboard and the + rule's quota line say how many addresses used theirs up. - Changing how a quota counts lost or muddled what it used: per address to a total started at nothing, per source to per destination kept the sources' use as if it were the destinations', and other groups left diff --git a/internal/web/static/assets/pages/dashboard.js b/internal/web/static/assets/pages/dashboard.js index 91369ed..1f75601 100644 --- a/internal/web/static/assets/pages/dashboard.js +++ b/internal/web/static/assets/pages/dashboard.js @@ -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)); } diff --git a/internal/web/static/assets/pages/rules.js b/internal/web/static/assets/pages/rules.js index fdecde2..6062f00 100644 --- a/internal/web/static/assets/pages/rules.js +++ b/internal/web/static/assets/pages/rules.js @@ -167,8 +167,9 @@ function quotaLine(r, q) { let used = fmtBytes(qt.bytes) + each; if (q && !each) used = fmtBytes(q.used) + " of " + fmtBytes(qt.bytes) + " used (" + pct(q.used, qt.bytes) + ")"; else if (q) { - const n = (q.addresses || []).length; - used += " · " + (n === 1 ? "1 address" : n + " addresses") + (n ? ", busiest " + fmtBytes(q.addresses[0].used) + " (" + pct(q.addresses[0].used, qt.bytes) + ")" : ""); + const a = q.addresses || [], n = a.length; + const up = a.filter((x) => x.used >= qt.bytes).length; + used += " · " + (n === 1 ? "1 address" : n + " addresses") + (up ? ", " + up + " used up" : "") + (n ? ", busiest " + fmtBytes(a[0].used) + " (" + pct(a[0].used, qt.bytes) + ")" : ""); } const text = "quota " + used + " · " + resets; return h("div", { class: "muted small" }, q && r.pending !== "new"