release: 1.2.0: Panel 1.2.0-1.2.5: layout, RBAC, tests, GUI and CI fixes
[1.2.0] The second MINOR after 1.0.0: domain administrators with per-domain scope, a panel visual refresh on the SelfPost palette, and refinements to navigation and the send log. Upgrading runs one SQLite migration (`0005_panel_users`); the single administrator becomes a global user. Added: - panel: **domain-admin role** — global administrators manage panel users and Fixed: - Docker build: `LICENSE` is no longer excluded by `.dockerignore`, so the - panel: on signed-in pages with a narrow card (**Settings**, the user form) Changed: - panel: navigation session icons are distinct — **Settings** uses a gear, - panel: `/` redirects domain administrators to `/domains`; global users still - panel: **Sign out** in the navigation column uses the same type size and - panel: **visual style** brought in line with the SelfPost mark — brick accent - panel: the send log's **status is a badge**, in the same ok/warn/error/unknown … (1 more in CHANGELOG) [1.2.1] Panel refinements after 1.2.0: navigation icons, status-badge centreing, drill-down back-link placement, and safeguards for the sole global administrator. Upgrading is a tag bump; no migration. Fixed: - panel: status badge text sat low in the box (and below the heading or label - panel: the **Users** navigation icon was two full silhouettes with staggered - panel: the **Settings** navigation icon was a sun-with-rays (circle plus - panel: the user create/edit form placed «Back to users» at the bottom of the Changed: - panel: the user edit form disables role change and delete for the only global - panel: the user create/edit form hides **Assigned domains** when the role is - panel: **Settings** shows panel credentials and DMARC aggregate reports side [1.2.2] Status page layout after 1.2.1: paired cards in a wide column, denser machine and check copy, and a user-form checkbox fix. Upgrading is a tag bump; no migration. Changed: - panel: **Status** is wide again so paired cards fill the column. Layout: Fixed: - panel: cards inside `.split` used `margin: 0 auto`, which in a CSS grid - panel: on the user create/edit form, **Assigned domains** checkboxes stacked [1.2.3] Domain detail layout and panel polish after 1.2.2. Upgrading is a tag bump; no migration. Changed: - panel: the domain detail page is wide with paired cards (DKIM+SPF ‖ DMARC; - panel: Domains list — **Add domain** sits beside the domain field; the - panel: page URLs, browser titles, and headings are aligned — **Settings** is Fixed: - image: `mail.log` rotation no longer silently stops when the build context [1.2.4] Level-2 rate-limit semantics inverted after 1.2.3, plus a small DNS field height fix. Upgrading is a tag bump; no migration. Changed: - rate limiting (level 2): domain ceilings apply to every client IP (no IP Fixed: - panel: on the domain DNS status grid, the Type (TXT) field height matches [1.2.5] Rate-limit form polish after 1.2.4. Upgrading is a tag bump; no migration. Changed: - panel: rate-limit forms refined — level-1 backstop as muted copy on the Squashed CHANGELOG sections: [1.2.0], [1.2.1], [1.2.2], [1.2.3], [1.2.4], [1.2.5]. Full detail: CHANGELOG.md (1.2.0, 1.2.1, 1.2.2, 1.2.3, 1.2.4, 1.2.5).
This commit is contained in:
+40
-12
@@ -70,6 +70,11 @@ type Config struct {
|
||||
// checks must not go through the system resolver — see dnscheck's
|
||||
// externalResolver — so this is how a closed network points them at its own.
|
||||
DNSResolvers []string
|
||||
// RateLimitMessagesPerIP and RateLimitWindowSeconds are the level-1
|
||||
// Postfix anvil backstop (env RATE_LIMIT_*), mirrored into the panel for
|
||||
// display and to cap domain/app level-2 ceilings (guide § Rate limiting).
|
||||
RateLimitMessagesPerIP int
|
||||
RateLimitWindowSeconds int
|
||||
}
|
||||
|
||||
// Server is the panel HTTP application.
|
||||
@@ -96,15 +101,17 @@ func New(st *store.Store, domains *domain.Service, apps *app.Service, cfg Config
|
||||
TrustedProxyCIDRs: cfg.TrustedProxyCIDRs,
|
||||
}, v, setupTokenPath)
|
||||
h := handlers.New(st, domains, apps, handlers.Config{
|
||||
Hostname: cfg.Hostname,
|
||||
SubmissionEnabled: cfg.SubmissionEnabled,
|
||||
MailLogPath: cfg.MailLogPath,
|
||||
DataDir: cfg.DataDir,
|
||||
DBPath: cfg.DBPath,
|
||||
Version: cfg.Version,
|
||||
TLSCertFile: cfg.TLSCertFile,
|
||||
OpenDKIMSocket: cfg.OpenDKIMSocket,
|
||||
JournalSocket: cfg.JournalSocket,
|
||||
Hostname: cfg.Hostname,
|
||||
SubmissionEnabled: cfg.SubmissionEnabled,
|
||||
MailLogPath: cfg.MailLogPath,
|
||||
DataDir: cfg.DataDir,
|
||||
DBPath: cfg.DBPath,
|
||||
Version: cfg.Version,
|
||||
TLSCertFile: cfg.TLSCertFile,
|
||||
OpenDKIMSocket: cfg.OpenDKIMSocket,
|
||||
JournalSocket: cfg.JournalSocket,
|
||||
RateLimitMessagesPerIP: cfg.RateLimitMessagesPerIP,
|
||||
RateLimitWindowSeconds: cfg.RateLimitWindowSeconds,
|
||||
}, v, dnscheck.New(cfg.DNSResolvers), &health.MachineSampler{}, a)
|
||||
return &Server{cfg: cfg, auth: a, handlers: h}, nil
|
||||
}
|
||||
@@ -129,7 +136,7 @@ func (s *Server) Handler() http.Handler {
|
||||
mux.HandleFunc("/logout", s.auth.HandleLogout)
|
||||
|
||||
authed := http.NewServeMux()
|
||||
authed.HandleFunc("GET /{$}", redirectToStatus)
|
||||
authed.HandleFunc("GET /{$}", redirectHome)
|
||||
authed.HandleFunc("GET /status", h.HandleStatus)
|
||||
authed.HandleFunc("GET /status/fragment", h.HandleStatusFragment)
|
||||
authed.HandleFunc("POST /status/recheck", h.HandleStatusRecheck)
|
||||
@@ -151,7 +158,14 @@ func (s *Server) Handler() http.Handler {
|
||||
authed.HandleFunc("POST /applications/{aid}/delete", h.HandleDeleteApplication)
|
||||
authed.HandleFunc("POST /reload", h.HandleReload)
|
||||
|
||||
authed.HandleFunc("/account", h.HandleAccount)
|
||||
authed.HandleFunc("/settings", h.HandleAccount)
|
||||
authed.HandleFunc("/account", redirectSettings)
|
||||
|
||||
authed.HandleFunc("GET /users", h.HandleUsers)
|
||||
authed.HandleFunc("GET /users/new", h.HandleUserNew)
|
||||
authed.HandleFunc("POST /users/new", h.HandleUserNew)
|
||||
authed.HandleFunc("GET /users/{uid}", h.HandleUserEdit)
|
||||
authed.HandleFunc("POST /users/{uid}", h.HandleUserEdit)
|
||||
|
||||
authed.HandleFunc("GET /backup", h.HandleBackupPage)
|
||||
authed.HandleFunc("POST /backup", h.HandleBackup)
|
||||
@@ -168,7 +182,21 @@ func (s *Server) Handler() http.Handler {
|
||||
return s.secure(mux)
|
||||
}
|
||||
|
||||
func redirectToStatus(w http.ResponseWriter, r *http.Request) {
|
||||
// redirectSettings sends legacy /account bookmarks to /settings (308 preserves POST).
|
||||
func redirectSettings(w http.ResponseWriter, r *http.Request) {
|
||||
target := "/settings"
|
||||
if r.URL.RawQuery != "" {
|
||||
target += "?" + r.URL.RawQuery
|
||||
}
|
||||
http.Redirect(w, r, target, http.StatusPermanentRedirect)
|
||||
}
|
||||
|
||||
func redirectHome(w http.ResponseWriter, r *http.Request) {
|
||||
p, ok := auth.PrincipalFromRequest(r)
|
||||
if ok && !p.IsGlobal() {
|
||||
http.Redirect(w, r, "/domains", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/status", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user