panel: clearer L1 rate-limit display and status badges
test / test (push) Has been cancelled
release / prepare (push) Has been cancelled
release / build (amd64, ubuntu-latest) (push) Has been cancelled
release / build (arm64, ubuntu-24.04-arm) (push) Has been cancelled
release / merge (push) Has been cancelled

Show the level-1 backstop as its own line and code-row, use st badges for
active/inactive domain and app limits, and fold the polish into 1.2.4.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-12 23:37:12 +03:00
parent 6671bd3393
commit 584f8cbc1c
4 changed files with 35 additions and 25 deletions
+6 -2
View File
@@ -7,8 +7,8 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
## [1.2.4] - 2026-08-12 ## [1.2.4] - 2026-08-12
Level-2 rate-limit semantics inverted after 1.2.3, plus a small DNS field Level-2 rate-limit semantics inverted after 1.2.3, with clearer L1 display and
height fix. Upgrading is a tag bump; no migration. status badges. Upgrading is a tag bump; no migration.
### Changed ### Changed
@@ -20,6 +20,10 @@ height fix. Upgrading is a tag bump; no migration.
level-1 backstop on domain/application forms and Settings, rejects maxima level-1 backstop on domain/application forms and Settings, rejects maxima
above level 1, and requires an application override to exceed the domain above level 1, and requires an application override to exceed the domain
maximum. Operator guide and architecture updated. maximum. Operator guide and architecture updated.
- panel: level-1 backstop is shown as a dedicated line (`N messages / Ws`) and
in message-limit labels; Settings uses a code-row for the L1 value. Domain
and application rate-limit status uses the shared `st` badge (`active` /
`inactive — level 1 only`). Trusted-IP override copy is a single paragraph.
### Fixed ### Fixed
+20 -18
View File
@@ -316,7 +316,7 @@
.panel-toggle in panel.css. --> .panel-toggle in panel.css. -->
<div class="actions"> <div class="actions">
<input class="panel-toggle t-edit" id="edit-{{.ID}}" type="checkbox"> <input class="panel-toggle t-edit" id="edit-{{.ID}}" type="checkbox">
<label class="toggle for-edit" for="edit-{{.ID}}">Edit{{if .HasLimit}} (limit active){{end}}</label> <label class="toggle for-edit" for="edit-{{.ID}}">Edit{{if .HasLimit}} <span class="st st-ok">limit</span>{{end}}</label>
<form class="inline" method="post" action="/applications/{{.ID}}/password" <form class="inline" method="post" action="/applications/{{.ID}}/password"
data-confirm="Regenerate the password for {{.Login}}? The current password stops working immediately."> data-confirm="Regenerate the password for {{.Login}}? The current password stops working immediately.">
<button type="submit">New password</button> <button type="submit">New password</button>
@@ -344,21 +344,21 @@
</form> </form>
</div> </div>
<div class="check-col"> <div class="check-col">
<p class="check-col-title">Optional trusted-IP override</p> <p class="check-col-title">Optional trusted-IP override
<p class="muted">Listed client IPs get a higher ceiling than the {{if .HasLimit}}<span class="st st-ok">active</span>{{else}}<span class="st st-unknown">inactive</span>{{end}}</p>
domain limit (still capped by level&nbsp;1: <p class="muted">These IPs get a higher ceiling than the domain
{{$.L1Messages}} / {{$.L1Window}}s — (≤ level&nbsp;1) and skip the domain check; everyone else uses
<a href="/settings">Settings</a>). {{if $.DomainHasRL}}the domain ceiling ({{$.DomainRLMaxNum}}){{else}}level&nbsp;1 only{{end}}.</p>
{{if $.DomainHasRL}}Domain ceiling: {{$.DomainRLMaxNum}}.{{else}}No domain ceiling (level&nbsp;1 only for other IPs).{{end}}</p> <p>Level&nbsp;1 backstop:
<strong>{{$.L1Messages}}</strong> messages /
<strong>{{$.L1Window}}</strong>s
<a href="/settings#rate-limits">Settings</a>.</p>
<form id="rl-{{.ID}}" method="post" action="/applications/{{.ID}}/ratelimit"> <form id="rl-{{.ID}}" method="post" action="/applications/{{.ID}}/ratelimit">
<label>Trusted client IPs (required; one per line or comma-separated)</label> <label>Trusted client IPs (required; one per line or comma-separated)</label>
<textarea name="allowed_ips" rows="2" placeholder="203.0.113.10">{{.IPsText}}</textarea> <textarea name="allowed_ips" rows="2" placeholder="203.0.113.10">{{.IPsText}}</textarea>
<p class="muted">Only these IPs use the application ceiling and
skip the domain limit. Other IPs stay under the domain (or
level&nbsp;1).</p>
<div class="field-pair"> <div class="field-pair">
<div> <div>
<label>Message limit</label> <label>Message limit (max {{$.L1Messages}})</label>
<input name="max_messages" type="number" min="1" max="{{$.L1Messages}}" <input name="max_messages" type="number" min="1" max="{{$.L1Messages}}"
value="{{.MaxText}}" placeholder="{{$.L1Messages}}"> value="{{.MaxText}}" placeholder="{{$.L1Messages}}">
</div> </div>
@@ -420,17 +420,19 @@
</div> </div>
<div class="check-col"> <div class="check-col">
<p class="check-col-title">Optional level-2 sending rate limit</p> <p class="check-col-title">Optional level-2 sending rate limit
<p class="muted">Applies to every client IP on this domain. Status: {{if .DomainHasRL}}<span class="st st-ok">active</span>{{else}}<span class="st st-unknown">inactive — level 1 only</span>{{end}}</p>
{{if .DomainHasRL}}<strong>active</strong>{{else}}inactive (level&nbsp;1 only){{end}}. <p class="muted">Applies to every client IP on this domain. Leave the
Level&nbsp;1 backstop: {{.L1Messages}} messages / {{.L1Window}}s message limit empty to use level&nbsp;1 only.</p>
(<a href="/settings">Settings</a>). Leave the message limit empty to <p>Level&nbsp;1 backstop (from <code>.env</code>):
use level&nbsp;1 only.</p> <strong>{{.L1Messages}}</strong> messages /
<strong>{{.L1Window}}</strong>s
— see <a href="/settings#rate-limits">Settings</a>.</p>
<form method="post" action="/domains/{{.Domain.ID}}/ratelimit"> <form method="post" action="/domains/{{.Domain.ID}}/ratelimit">
<div class="field-pair"> <div class="field-pair">
<div> <div>
<label for="d_max">Message limit</label> <label for="d_max">Message limit (max {{.L1Messages}})</label>
<input id="d_max" name="max_messages" type="number" min="1" max="{{.L1Messages}}" <input id="d_max" name="max_messages" type="number" min="1" max="{{.L1Messages}}"
value="{{.DomainRLMax}}" placeholder="{{.L1Messages}}"> value="{{.DomainRLMax}}" placeholder="{{.L1Messages}}">
</div> </div>
+3 -3
View File
@@ -110,9 +110,9 @@ this one stays signed in.</p>
each domain's page.</p> each domain's page.</p>
<label>Level 1 — per client IP (Postfix)</label> <label>Level 1 — per client IP (Postfix)</label>
<p><strong>{{.L1Messages}}</strong> messages per <strong>{{.L1Window}}</strong> <p class="code-row"><span class="code">{{.L1Messages}} messages / {{.L1Window}} seconds</span></p>
seconds (<code>RATE_LIMIT_MESSAGES_PER_IP</code> / <p class="muted"><code>RATE_LIMIT_MESSAGES_PER_IP</code> /
<code>RATE_LIMIT_WINDOW_SECONDS</code>). Hard ceiling for every connecting IP; <code>RATE_LIMIT_WINDOW_SECONDS</code>. Hard ceiling for every connecting IP;
the panel cannot raise a domain or application limit above this.</p> the panel cannot raise a domain or application limit above this.</p>
<label>Level 2 — domain</label> <label>Level 2 — domain</label>
+6 -2
View File
@@ -244,8 +244,11 @@ func TestDomainDetailPageHasPairedCards(t *testing.T) {
if strings.Contains(src, `id="d_ips"`) { if strings.Contains(src, `id="d_ips"`) {
t.Error("domain rate limit must not ask for client IPs") t.Error("domain rate limit must not ask for client IPs")
} }
if !strings.Contains(src, "level&nbsp;1") { if !strings.Contains(src, "{{.L1Messages}}") && !strings.Contains(src, "{{$.L1Messages}}") {
t.Error("domain rate limit should mention the level-1 backstop") t.Error("domain rate limit should show the L1 message count")
}
if !strings.Contains(src, "Level&nbsp;1 backstop") {
t.Error("domain rate limit should show a Level 1 backstop line")
} }
if !strings.Contains(src, "Trusted client IPs") { if !strings.Contains(src, "Trusted client IPs") {
t.Error("application override should ask for trusted client IPs") t.Error("application override should ask for trusted client IPs")
@@ -268,6 +271,7 @@ func TestSettingsPageDocumentsRateLimits(t *testing.T) {
"RATE_LIMIT_MESSAGES_PER_IP", "RATE_LIMIT_MESSAGES_PER_IP",
"Level 2 — domain", "Level 2 — domain",
"trusted IPs", "trusted IPs",
"{{.L1Messages}} messages / {{.L1Window}} seconds",
} { } {
if !strings.Contains(src, want) { if !strings.Contains(src, want) {
t.Errorf("settings rate limits card missing %q", want) t.Errorf("settings rate limits card missing %q", want)