diff --git a/internal/auth/account.go b/internal/auth/account.go index ea214cf..cbca9c0 100644 --- a/internal/auth/account.go +++ b/internal/auth/account.go @@ -103,8 +103,6 @@ func (s *Handlers) renderAccount(w http.ResponseWriter, r *http.Request, uid int "MinPasswordLen": MinPasswordLen, "Passkeys": views, "NextRename": nextRename, // nil when a rename is allowed now - "Error": r.URL.Query().Get("error"), - "OK": r.URL.Query().Get("ok"), "PKMsg": r.URL.Query().Get("pk"), "PKErr": r.URL.Query().Get("pkerr"), "EmMsg": r.URL.Query().Get(flashEmail), diff --git a/internal/auth/delete_handlers.go b/internal/auth/delete_handlers.go index 42781c8..9e41214 100644 --- a/internal/auth/delete_handlers.go +++ b/internal/auth/delete_handlers.go @@ -42,7 +42,6 @@ func (s *Handlers) pageDeleteAccount(w http.ResponseWriter, r *http.Request) { "HasPasskeys": preview.Passkeys > 0, // Repeater transfer lives on the app host, so links out need its origin. "AppOrigin": s.Auth.AppOrigin(r), - "Error": r.URL.Query().Get("error"), }) } @@ -101,5 +100,5 @@ func (s *Handlers) handleDeleteAccount(w http.ResponseWriter, r *http.Request) { // holding a cookie for an account that no longer exists. func (s *Handlers) finishDeletion(w http.ResponseWriter, r *http.Request) { _ = s.Auth.Logout(r.Context()) - web.RedirectFlash(w, r, "/login", "ok", "Your account and everything in it have been deleted.") + web.RedirectOK(w, r, "/login", "Your account and everything in it have been deleted.") } diff --git a/internal/auth/reset_handlers.go b/internal/auth/reset_handlers.go index 4dbf252..f1df7e6 100644 --- a/internal/auth/reset_handlers.go +++ b/internal/auth/reset_handlers.go @@ -15,7 +15,6 @@ import ( func (s *Handlers) pageForgot(w http.ResponseWriter, r *http.Request) { s.Render(w, r, "forgot.html", map[string]any{ "Layout": "authbase", - "Error": r.URL.Query().Get("error"), // Sent switches the page to the confirmation panel. It's a query flag rather // than a distinct URL so the POST can redirect (no re-submit on refresh) and // still say the same thing regardless of what was submitted. @@ -48,7 +47,7 @@ func (s *Handlers) handleForgot(addrLimit web.KeyLimiter) http.HandlerFunc { // nothing about whether the account exists: we'd show it just the same for // an address with no account if the provider were down. web.LogError(r, "password reset send failed", err) - web.RedirectFlash(w, r, "/forgot", "error", + web.RedirectErr(w, r, "/forgot", "We couldn't send the email just now. Please try again in a few minutes.") return } @@ -121,7 +120,7 @@ func (s *Handlers) handleReset(w http.ResponseWriter, r *http.Request) { case errors.Is(err, ErrResetNotAllowed): // The account shed its password between the link being sent and used, so there // is nothing to reset and email must not create one. - web.RedirectFlash(w, r, "/login", "error", + web.RedirectErr(w, r, "/login", "That account no longer has a password — sign in with your passkey instead.") return case err != nil: @@ -133,7 +132,7 @@ func (s *Handlers) handleReset(w http.ResponseWriter, r *http.Request) { // Deliberately not signed in here: every login was just revoked, and landing on // the sign-in page proves the new password actually works while the user still has // it in mind. - web.RedirectFlash(w, r, "/login", "ok", + web.RedirectOK(w, r, "/login", "Password updated. Sign in with your new password.") } diff --git a/internal/auth/templates/account.html b/internal/auth/templates/account.html index e95ed8c..d776678 100644 --- a/internal/auth/templates/account.html +++ b/internal/auth/templates/account.html @@ -2,7 +2,6 @@ {{define "header"}}
-
Your account

Account settings

@@ -23,7 +22,6 @@ clutter, not information. */}} {{define "content"}} {{if .Error}}
{{.Error}}
{{end}} -{{if .OK}}
{{.OK}}
{{end}}
diff --git a/internal/auth/templates/delete_account.html b/internal/auth/templates/delete_account.html index ccdbc22..d8b56d4 100644 --- a/internal/auth/templates/delete_account.html +++ b/internal/auth/templates/delete_account.html @@ -5,7 +5,6 @@
{{end}} {{define "content"}} -{{if .Error}}
{{.Error}}
{{end}}
diff --git a/internal/auth/templates/forgot.html b/internal/auth/templates/forgot.html index 97db7b9..ee0a4e3 100644 --- a/internal/auth/templates/forgot.html +++ b/internal/auth/templates/forgot.html @@ -24,7 +24,6 @@ Enter your username or the email address on your account, and we'll send a link to set a new password.

- {{if .Error}}
{{.Error}}
{{end}}
diff --git a/internal/auth/templates/login.html b/internal/auth/templates/login.html index fbda163..9e03c22 100644 --- a/internal/auth/templates/login.html +++ b/internal/auth/templates/login.html @@ -3,11 +3,9 @@

Sign in to your account

- {{if .Error}}
{{.Error}}
{{end}} {{/* Success flash: a completed password reset and a confirmed email address both land here, and both need to say so — otherwise the visitor can't tell whether what they just did worked. */}} - {{if .OK}}
{{.OK}}
{{end}} {{if .Next}}{{end}} diff --git a/internal/auth/templates/signup.html b/internal/auth/templates/signup.html index a38d143..e4dd19f 100644 --- a/internal/auth/templates/signup.html +++ b/internal/auth/templates/signup.html @@ -3,7 +3,6 @@

Create your account

- {{if .Error}}
{{.Error}}
{{end}}

No email required. Passkeys are the easiest, most secure option — your device unlocks the account with a fingerprint, face, PIN, or a security key.

diff --git a/internal/auth/web.go b/internal/auth/web.go index 881d6e9..be6f7c4 100644 --- a/internal/auth/web.go +++ b/internal/auth/web.go @@ -175,8 +175,6 @@ func (s *Handlers) pageLogin(w http.ResponseWriter, r *http.Request) { } s.Render(w, r, "login.html", map[string]any{ "Layout": "authbase", - "Error": r.URL.Query().Get("error"), - "OK": r.URL.Query().Get("ok"), "Next": r.URL.Query().Get("next"), // Gates the "Forgot password?" link — there's no point offering recovery on a // deployment that can't send mail. @@ -194,7 +192,6 @@ func (s *Handlers) pageSignup(w http.ResponseWriter, r *http.Request) { } s.Render(w, r, "signup.html", map[string]any{ "Layout": "authbase", - "Error": r.URL.Query().Get("error"), "Next": r.URL.Query().Get("next"), // Stated in the form and used as minlength, so the client hint can't drift // from the server's floor. diff --git a/internal/core/admin.go b/internal/core/admin.go index 3888035..960c1e2 100644 --- a/internal/core/admin.go +++ b/internal/core/admin.go @@ -164,7 +164,6 @@ func (s *Handlers) pageUsers(w http.ResponseWriter, r *http.Request) { data := map[string]any{ "Users": users, "Self": s.Auth.CurrentUserID(r.Context()), - "Error": q.Get("error"), "Sort": string(sortKey), "Cap": string(capFilter), "Query": query, diff --git a/internal/core/admin_csp.go b/internal/core/admin_csp.go index c39a7e5..fa218a4 100644 --- a/internal/core/admin_csp.go +++ b/internal/core/admin_csp.go @@ -59,7 +59,6 @@ func (s *Handlers) pageCSPReports(w http.ResponseWriter, r *http.Request) { // Clearing is destructive, so the button only renders for the capability // that already governs granting capabilities (see the route table). "CanClear": u.CapManageUsers, - "Flash": r.URL.Query().Get("flash"), }) } @@ -93,5 +92,5 @@ func (s *Handlers) handleClearCSPReports(w http.ResponseWriter, r *http.Request) if n == 1 { noun = "violation" } - web.RedirectFlash(w, r, back, "flash", fmt.Sprintf("%d %s cleared.", n, noun)) + web.RedirectOK(w, r, back, fmt.Sprintf("%d %s cleared.", n, noun)) } diff --git a/internal/core/companions.go b/internal/core/companions.go index 8ad0aad..c06e106 100644 --- a/internal/core/companions.go +++ b/internal/core/companions.go @@ -33,7 +33,6 @@ func (s *Handlers) pageCompanions(w http.ResponseWriter, r *http.Request) { "Companions": companions, "Max": store.MaxCompanionsPerUser, "TTLHours": int(store.UnconfirmedCompanionTTL.Hours()), - "Error": r.URL.Query().Get("error"), }) } diff --git a/internal/core/config_radio.go b/internal/core/config_radio.go index 22d8b56..71f4cf5 100644 --- a/internal/core/config_radio.go +++ b/internal/core/config_radio.go @@ -38,7 +38,7 @@ func (s *Handlers) handleSetRadio(w http.ResponseWriter, r *http.Request) { s.ServerError(w, r, "could not clear radio settings", err) return } - web.RedirectFlash(w, r, back, "ok", "Radio settings cleared.") + web.RedirectOK(w, r, back, "Radio settings cleared.") return } radio, ok := parseRadio(r) @@ -53,5 +53,5 @@ func (s *Handlers) handleSetRadio(w http.ResponseWriter, r *http.Request) { s.ServerError(w, r, "could not save radio settings", err) return } - web.RedirectFlash(w, r, back, "ok", "Radio settings saved.") + web.RedirectOK(w, r, back, "Radio settings saved.") } diff --git a/internal/core/net_air.go b/internal/core/net_air.go index 6b272e3..10a917b 100644 --- a/internal/core/net_air.go +++ b/internal/core/net_air.go @@ -168,5 +168,5 @@ func (s *Handlers) handleUpdateNetAir(w http.ResponseWriter, r *http.Request) { s.ServerError(w, r, "Couldn't save the on-air settings.", err) return } - web.RedirectFlash(w, r, netSettingsURL(org.Slug, net.ID), "ok", "On-air settings saved.") + web.RedirectOK(w, r, netSettingsURL(org.Slug, net.ID), "On-air settings saved.") } diff --git a/internal/core/net_session.go b/internal/core/net_session.go index cb8ff71..64c6973 100644 --- a/internal/core/net_session.go +++ b/internal/core/net_session.go @@ -134,7 +134,6 @@ func (s *Handlers) pageNetSession(w http.ResponseWriter, r *http.Request) { "Members": members, "Observers": len(observers), "ObserversOnline": online, - "Error": r.URL.Query().Get("error"), } if c.netControl && inProgress { air, err := s.Store.GetNetAir(ctx, c.org.ID, c.net.ID) diff --git a/internal/core/nets.go b/internal/core/nets.go index bcc7cd7..6b1d339 100644 --- a/internal/core/nets.go +++ b/internal/core/nets.go @@ -102,7 +102,6 @@ func (s *Handlers) pageOrgNets(w http.ResponseWriter, r *http.Request) { "IsAdmin": isAdmin, "Nets": views, "Now": now, - "Error": r.URL.Query().Get("error"), }) } @@ -228,8 +227,6 @@ func (s *Handlers) pageOrgNet(w http.ResponseWriter, r *http.Request) { "Now": now, "Sessions": sessions, "SettingsURL": netSettingsURL(org.Slug, net.ID), - "Error": r.URL.Query().Get("error"), - "OK": r.URL.Query().Get("ok"), } if hasMore && len(sessions) > 0 { data["NextBefore"] = web.EncodeCursor(sessions[len(sessions)-1].StartsAt) @@ -384,7 +381,7 @@ func (s *Handlers) handleUpdateNet(w http.ResponseWriter, r *http.Request) { s.ServerError(w, r, "Couldn't save the net.", err) return } - web.RedirectFlash(w, r, netSettingsURL(org.Slug, net.ID), "ok", "Schedule saved. Sessions that have already started keep their original times.") + web.RedirectOK(w, r, netSettingsURL(org.Slug, net.ID), "Schedule saved. Sessions that have already started keep their original times.") } // handleSetNetAck saves how a net's check-ins are acknowledged. @@ -400,7 +397,7 @@ func (s *Handlers) handleSetNetAck(w http.ResponseWriter, r *http.Request) { s.ServerError(w, r, "Couldn't save the net.", err) return } - web.RedirectFlash(w, r, netSettingsURL(org.Slug, net.ID), "ok", "Check-in settings saved.") + web.RedirectOK(w, r, netSettingsURL(org.Slug, net.ID), "Check-in settings saved.") } func (s *Handlers) handleDeleteNet(w http.ResponseWriter, r *http.Request) { @@ -420,7 +417,7 @@ func (s *Handlers) handleDeleteNet(w http.ResponseWriter, r *http.Request) { s.ServerError(w, r, "Couldn't delete the net.", err) return } - web.RedirectFlash(w, r, settingsURL(orgParam(r), "nets"), "ok", "Net deleted.") + web.RedirectOK(w, r, settingsURL(orgParam(r), "nets"), "Net deleted.") } // netSessionContext is what every session page and action resolves first. diff --git a/internal/core/org_checkins.go b/internal/core/org_checkins.go index 836b682..8af0c74 100644 --- a/internal/core/org_checkins.go +++ b/internal/core/org_checkins.go @@ -109,7 +109,7 @@ func (s *Handlers) handleSaveOrgBroker(w http.ResponseWriter, r *http.Request) { s.ServerError(w, r, "Couldn't save the broker.", err) return } - web.RedirectFlash(w, r, mqttURL(r), "ok", "Broker saved. MeshTender connects within a minute.") + web.RedirectOK(w, r, mqttURL(r), "Broker saved. MeshTender connects within a minute.") } func (s *Handlers) handleDeleteOrgBroker(w http.ResponseWriter, r *http.Request) { @@ -121,7 +121,7 @@ func (s *Handlers) handleDeleteOrgBroker(w http.ResponseWriter, r *http.Request) s.ServerError(w, r, "Couldn't remove the broker.", err) return } - web.RedirectFlash(w, r, mqttURL(r), "ok", "Broker removed.") + web.RedirectOK(w, r, mqttURL(r), "Broker removed.") } // handleTestOrgBroker connects to the saved broker once (address guard and all) @@ -157,7 +157,7 @@ func (s *Handlers) handleTestOrgBroker(w http.ResponseWriter, r *http.Request) { return } client.Disconnect(250) - web.RedirectFlash(w, r, mqttURL(r), "ok", "Connected to the broker.") + web.RedirectOK(w, r, mqttURL(r), "Connected to the broker.") } func (s *Handlers) handleAddOrgObserver(w http.ResponseWriter, r *http.Request) { diff --git a/internal/core/org_home.go b/internal/core/org_home.go index 7cac403..4a38a2d 100644 --- a/internal/core/org_home.go +++ b/internal/core/org_home.go @@ -113,7 +113,6 @@ func (s *Handlers) renderOrgHome(w http.ResponseWriter, r *http.Request, org *st "Radio": radio, "Config": config, "MapPoints": reps.MapPoints, - "Error": r.URL.Query().Get("error"), } if radio != nil { data["Preset"] = presetName(*radio) diff --git a/internal/core/org_settings.go b/internal/core/org_settings.go index a8fa7d9..a5b061f 100644 --- a/internal/core/org_settings.go +++ b/internal/core/org_settings.go @@ -102,8 +102,6 @@ func (s *Handlers) settingsData(ctx context.Context, r *http.Request, org *store "Org": org, "Nav": s.orgNav(ctx, org, "settings-"+section, true), "Section": section, - "Error": r.URL.Query().Get("error"), - "OK": r.URL.Query().Get("ok"), } switch section { case "profile": diff --git a/internal/core/orgs.go b/internal/core/orgs.go index ae6bad4..a4943ff 100644 --- a/internal/core/orgs.go +++ b/internal/core/orgs.go @@ -36,16 +36,13 @@ func (s *Handlers) pageMyOrgs(w http.ResponseWriter, r *http.Request) { return } s.Render(w, r, "my_orgs.html", map[string]any{ - "Orgs": mine, - "Error": r.URL.Query().Get("error"), + "Orgs": mine, }) } // pageNewOrg renders the standalone "create an organization" form. func (s *Handlers) pageNewOrg(w http.ResponseWriter, r *http.Request) { - s.Render(w, r, "new_org.html", map[string]any{ - "Error": r.URL.Query().Get("error"), - }) + s.Render(w, r, "new_org.html", map[string]any{}) } // handleCreateOrg creates an org with the current user as its first admin. @@ -177,7 +174,6 @@ func (s *Handlers) pageOrgMembers(w http.ResponseWriter, r *http.Request) { "IsAdmin": role == "admin", "Members": members, "Self": uid, - "Error": r.URL.Query().Get("error"), }) } @@ -257,7 +253,7 @@ func (s *Handlers) handleEditOrg(w http.ResponseWriter, r *http.Request) { return } // The slug may have changed; land on the new canonical URL. - web.RedirectFlash(w, r, settingsURL(entered.Slug, "profile"), "ok", "Profile saved.") + web.RedirectOK(w, r, settingsURL(entered.Slug, "profile"), "Profile saved.") } // handleSetOrgLinks replaces an org's whole set of social/site links from the @@ -360,7 +356,7 @@ func (s *Handlers) handleSetOrgLinks(w http.ResponseWriter, r *http.Request) { fail("Could not save links.") return } - web.RedirectFlash(w, r, settingsURL(orgParam(r), "profile"), "ok", "Links saved.") + web.RedirectOK(w, r, settingsURL(orgParam(r), "profile"), "Links saved.") } // requireOrgAdmin resolves {id} and verifies the current user is an org admin. diff --git a/internal/core/registry.go b/internal/core/registry.go index d63e322..a01bc65 100644 --- a/internal/core/registry.go +++ b/internal/core/registry.go @@ -33,7 +33,6 @@ func (s *Handlers) pageRepeaterDocs(w http.ResponseWriter, r *http.Request) { "Repeater": rep, "Nav": web.RepeaterNav(rep.PublicID, rep.Name, rep.OwnerName(), isOwner, "docs"), "IsOwner": isOwner, - "Error": r.URL.Query().Get("error"), }) } @@ -71,7 +70,6 @@ func (s *Handlers) pageRepeaterMaintenance(w http.ResponseWriter, r *http.Reques "IsOwner": isOwner, "Entries": entries, "Today": time.Now().Format("2006-01-02"), - "Error": r.URL.Query().Get("error"), }) } diff --git a/internal/core/repeaters.go b/internal/core/repeaters.go index ee0a621..f5f1fb7 100644 --- a/internal/core/repeaters.go +++ b/internal/core/repeaters.go @@ -53,7 +53,6 @@ func (s *Handlers) pageAddRepeater(w http.ResponseWriter, r *http.Request) { "RevokeCommand": s.Identity.RevokePermCommand(), "Radio": newRadioFields(defaultRadio(), true), "RadioSerial": newRadioFields(defaultRadio(), false), // serial-setup.js reads the fields by id - "Error": r.URL.Query().Get("error"), } if step == "serial" { orgs := s.setupOrgOptions(r) @@ -102,8 +101,6 @@ func (s *Handlers) pageRepeater(w http.ResponseWriter, r *http.Request) { "Nav": web.RepeaterNav(rep.PublicID, rep.Name, rep.OwnerName(), isOwner, "overview"), "Radio": radio, "ContactURI": contactURI, - "Error": r.URL.Query().Get("error"), - "OK": r.URL.Query().Get("ok"), } // When the owner has published a public page, surface its link plus a QR code // for it. @@ -260,7 +257,6 @@ func (s *Handlers) pageEditRepeater(w http.ResponseWriter, r *http.Request) { "Repeater": rep, "Radio": newRadioFields(config.RadioDefaults{FreqHz: uint32(rep.RadioFreqHz), BwHz: uint32(rep.RadioBwHz), SF: uint8(rep.RadioSF), CR: uint8(rep.RadioCR)}, true), //nolint:gosec // G115: radio config value is bounded (preset-constrained) "RevokeCommand": s.Identity.RevokePermCommand(), - "Error": r.URL.Query().Get("error"), }) } diff --git a/internal/core/shares.go b/internal/core/shares.go index fcaf332..8f07cc5 100644 --- a/internal/core/shares.go +++ b/internal/core/shares.go @@ -65,7 +65,6 @@ func (s *Handlers) pageShare(w http.ResponseWriter, r *http.Request) { // Stated in the copy so an owner knows a link is time-boxed before handing it // out. Derived from the store's TTL so the two can't disagree. "InviteTTLDays": inviteTTLDays, - "Error": r.URL.Query().Get("error"), }) } diff --git a/internal/core/templates/add_repeater.html b/internal/core/templates/add_repeater.html index 37a38fc..aa85bfa 100644 --- a/internal/core/templates/add_repeater.html +++ b/internal/core/templates/add_repeater.html @@ -7,7 +7,6 @@ {{define "content"}}
-{{if .Error}}
{{.Error}}
{{end}}
  • Connection
  • diff --git a/internal/core/templates/admin_csp.html b/internal/core/templates/admin_csp.html index cb221ad..42a96b3 100644 --- a/internal/core/templates/admin_csp.html +++ b/internal/core/templates/admin_csp.html @@ -15,7 +15,6 @@
{{end}} {{define "content"}} -{{if .Flash}}{{end}}
diff --git a/internal/core/templates/admin_users.html b/internal/core/templates/admin_users.html index 12b7b1f..0ea6ad2 100644 --- a/internal/core/templates/admin_users.html +++ b/internal/core/templates/admin_users.html @@ -56,7 +56,6 @@ {{define "users-frag"}}{{template "user-rows" .}}{{if .NextCursor}}{{template "user-more" .}}{{end}}{{end}} {{define "content"}} -{{if .Error}}
{{.Error}}
{{end}}
{{/* Search/filters come FIRST in the DOM and move to the right of the list at lg (order-lg-last). Source order governs both the mobile stack and the tab order, diff --git a/internal/core/templates/companions.html b/internal/core/templates/companions.html index 52b8636..75e0a08 100644 --- a/internal/core/templates/companions.html +++ b/internal/core/templates/companions.html @@ -2,13 +2,11 @@ {{define "header"}}
-
Your radios

Companions

{{end}} {{define "content"}} -{{if .Error}}{{end}}
@@ -21,7 +19,7 @@ {{.Name}} {{if .Confirmed}}Confirmed{{else}}Waiting for an advert{{end}} - {{if .LastHeardAt}}Last heard {{ts .LastHeardAt "datetime"}}{{else}}Not heard yet{{end}} + {{if .LastHeardAt}}Last heard {{ts .LastHeardAt "relative"}}{{else}}Not heard yet{{end}}
{{slice .PublicKeyHex 0 16}}…
diff --git a/internal/core/templates/dashboard.html b/internal/core/templates/dashboard.html index e12d566..dbcb218 100644 --- a/internal/core/templates/dashboard.html +++ b/internal/core/templates/dashboard.html @@ -2,13 +2,11 @@ {{define "header"}}
-
Dashboard

Welcome back{{if .UserName}}, {{.UserName}}{{end}}

{{end}} {{define "content"}} -{{if .Error}}{{end}}
@@ -106,7 +104,7 @@ {{.RepeaterName}} {{.CommandText}} {{if .SenderUsername}}{{.SenderName}}{{else}}{{.SenderName}}{{end}} - {{ts .SentAt "datetime"}} + {{ts .SentAt "relative"}} {{if .AckReceived}}reply{{else}}no reply{{end}} {{end}} diff --git a/internal/core/templates/edit_repeater.html b/internal/core/templates/edit_repeater.html index 44d0902..4963813 100644 --- a/internal/core/templates/edit_repeater.html +++ b/internal/core/templates/edit_repeater.html @@ -8,7 +8,6 @@
{{end}} {{define "content"}} -{{if .Error}}
{{.Error}}
{{end}}

The public key identifies the repeater and can't be changed — delete and re-add to change it.

diff --git a/internal/core/templates/my_orgs.html b/internal/core/templates/my_orgs.html index e1b03c4..11e4a98 100644 --- a/internal/core/templates/my_orgs.html +++ b/internal/core/templates/my_orgs.html @@ -2,7 +2,6 @@ {{define "header"}}
-
Your network

Organizations

@@ -14,7 +13,6 @@
{{end}} {{define "content"}} -{{if .Error}}
{{.Error}}
{{end}} {{if .Orgs}}
{{/* Search/filters come FIRST in the DOM and move to the right of the list at lg diff --git a/internal/core/templates/net_session.html b/internal/core/templates/net_session.html index befa8fd..eeaae92 100644 --- a/internal/core/templates/net_session.html +++ b/internal/core/templates/net_session.html @@ -2,7 +2,6 @@ {{define "header"}}{{template "org-header" .}}{{end}} {{define "content"}} {{template "org-tabs" .Nav}} -{{if .Error}}{{end}}