panel: stack Add an application above the list, drop the two-column row
The side-by-side .split row read badly: main is capped at 48rem, so the applications table had to live in roughly 27rem and its actions column squeezed four controls into it. Put the create form directly above the list instead — the order the domains page already uses for "Add a sending domain" above "Domains" — and delete .split, which nothing else used. The empty-state text follows the same page's wording. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+3
-3
@@ -21,9 +21,9 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
|
|||||||
They are all buttons now: filled for a card's own action, compact and
|
They are all buttons now: filled for a card's own action, compact and
|
||||||
outlined where actions cluster in a table row or the nav bar. An `<a>` is
|
outlined where actions cluster in a table row or the nav bar. An `<a>` is
|
||||||
once again only used for navigation.
|
once again only used for navigation.
|
||||||
- panel: on the domain page, **Applications** and **Add an application** sit
|
- panel: on the domain page **Add an application** now sits directly above the
|
||||||
side by side on a wide viewport instead of the form being stranded below the
|
**Applications** list — the same order the domains page uses for its own add
|
||||||
domain rate limit; they stack again on a narrow one.
|
form — instead of being stranded below the domain rate limit.
|
||||||
- ci: hermetic container e2e suite (`test/e2e`, a separate Go module) gates
|
- ci: hermetic container e2e suite (`test/e2e`, a separate Go module) gates
|
||||||
image publishing — `make e2e` locally, and `go test ./...` in `test/e2e` as
|
image publishing — `make e2e` locally, and `go test ./...` in `test/e2e` as
|
||||||
a required step in `release.yml` before a version tag's image is pushed.
|
a required step in `release.yml` before a version tag's image is pushed.
|
||||||
|
|||||||
@@ -49,13 +49,6 @@ button:hover { background: #1d4ed8; }
|
|||||||
form.inline { display: inline; margin: 0; }
|
form.inline { display: inline; margin: 0; }
|
||||||
main { max-width: 48rem; }
|
main { max-width: 48rem; }
|
||||||
.card + .card { margin-top: 1.2rem; }
|
.card + .card { margin-top: 1.2rem; }
|
||||||
/* Two cards side by side on a wide viewport; below 52rem the columns collapse
|
|
||||||
and the grid gap keeps the same vertical rhythm as .card + .card. The inner
|
|
||||||
margin-top is cleared because the gap already spaces the pair. */
|
|
||||||
.split { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); gap: 1.2rem; align-items: start; }
|
|
||||||
.split > .card + .card { margin-top: 0; }
|
|
||||||
.card + .split, .split + .card { margin-top: 1.2rem; }
|
|
||||||
@media (max-width: 52rem) { .split { grid-template-columns: minmax(0, 1fr); } }
|
|
||||||
.flash { background: #ecfdf3; border: 1px solid #abefc6; color: #067647; padding: 0.7rem 1rem; border-radius: 8px; margin-bottom: 1.2rem; }
|
.flash { background: #ecfdf3; border: 1px solid #abefc6; color: #067647; padding: 0.7rem 1rem; border-radius: 8px; margin-bottom: 1.2rem; }
|
||||||
@media (prefers-color-scheme: dark) { .flash { background: #0d2818 !important; border-color: #1a5336 !important; color: #75d99b !important; } }
|
@media (prefers-color-scheme: dark) { .flash { background: #0d2818 !important; border-color: #1a5336 !important; color: #75d99b !important; } }
|
||||||
table { width: 100%; border-collapse: collapse; }
|
table { width: 100%; border-collapse: collapse; }
|
||||||
|
|||||||
@@ -144,11 +144,35 @@
|
|||||||
lost, generate a new one.</p>
|
lost, generate a new one.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{/* Applications and its create form sit side by side on a wide viewport: the
|
{{/* Create form above the list, the same order the domains page uses for
|
||||||
table is read far more often than the form, and pairing them saves scrolling
|
"Add a sending domain" above "Domains". */}}
|
||||||
past the list to add one. On a narrow viewport the .split grid stacks them
|
<div class="card">
|
||||||
again, list first. */}}
|
<h2>Add an application</h2>
|
||||||
<div class="split">
|
<form method="post" action="/domains/{{.Domain.ID}}/applications">
|
||||||
|
<label for="login">Login</label>
|
||||||
|
<input id="login" name="login" type="text" placeholder="prod-server"
|
||||||
|
autocomplete="off" autocapitalize="none" spellcheck="false"
|
||||||
|
value="{{.FormLogin}}" required>
|
||||||
|
|
||||||
|
<label for="mode">Address mode</label>
|
||||||
|
<select id="mode" name="mode" data-list-mode="{{.List}}">
|
||||||
|
<option value="{{.Wildcard}}" {{if eq .FormMode .Wildcard}}selected{{end}}>Any address of the domain</option>
|
||||||
|
<option value="{{.List}}" {{if eq .FormMode .List}}selected{{end}}>Specific addresses (list)</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<div data-addresses>
|
||||||
|
<label for="addresses">Addresses (one per line or comma-separated)</label>
|
||||||
|
<textarea id="addresses" name="addresses" rows="3"
|
||||||
|
placeholder="alerts@{{.Domain.Name}}">{{.FormAddrs}}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{if .Error}}<p class="error">{{.Error}}</p>{{end}}
|
||||||
|
<button type="submit">Create application</button>
|
||||||
|
</form>
|
||||||
|
<p class="muted">A strong password is generated and shown once. The login must
|
||||||
|
be unique across all domains and may contain letters, digits, '.', '-' and '_'.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>Applications</h2>
|
<h2>Applications</h2>
|
||||||
<p class="muted">Each application is a SASL login/password an app or script
|
<p class="muted">Each application is a SASL login/password an app or script
|
||||||
@@ -220,39 +244,10 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{{else}}
|
{{else}}
|
||||||
<p class="muted">No applications yet. Create the first one with the
|
<p class="muted">No applications yet. Add one above to get started.</p>
|
||||||
<strong>Add an application</strong> form.</p>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<h2>Add an application</h2>
|
|
||||||
<form method="post" action="/domains/{{.Domain.ID}}/applications">
|
|
||||||
<label for="login">Login</label>
|
|
||||||
<input id="login" name="login" type="text" placeholder="prod-server"
|
|
||||||
autocomplete="off" autocapitalize="none" spellcheck="false"
|
|
||||||
value="{{.FormLogin}}" required>
|
|
||||||
|
|
||||||
<label for="mode">Address mode</label>
|
|
||||||
<select id="mode" name="mode" data-list-mode="{{.List}}">
|
|
||||||
<option value="{{.Wildcard}}" {{if eq .FormMode .Wildcard}}selected{{end}}>Any address of the domain</option>
|
|
||||||
<option value="{{.List}}" {{if eq .FormMode .List}}selected{{end}}>Specific addresses (list)</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<div data-addresses>
|
|
||||||
<label for="addresses">Addresses (one per line or comma-separated)</label>
|
|
||||||
<textarea id="addresses" name="addresses" rows="3"
|
|
||||||
placeholder="alerts@{{.Domain.Name}}">{{.FormAddrs}}</textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{if .Error}}<p class="error">{{.Error}}</p>{{end}}
|
|
||||||
<button type="submit">Create application</button>
|
|
||||||
</form>
|
|
||||||
<p class="muted">A strong password is generated and shown once. The login must
|
|
||||||
be unique across all domains and may contain letters, digits, '.', '-' and '_'.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>Sending rate limit (domain)</h2>
|
<h2>Sending rate limit (domain)</h2>
|
||||||
<p class="muted">Optional level-2 limit (spec 7.4): cap how many messages this
|
<p class="muted">Optional level-2 limit (spec 7.4): cap how many messages this
|
||||||
|
|||||||
Reference in New Issue
Block a user