ui: F1 — populate OpenAlerts in baseView so nav badge updates everywhere
Flagged in review of 35dee98: the Alerts tab badge should show the
open count from any page, not just /alerts. baseView now takes the
request and queries store.ListAlerts(Status: "open") to fill
view.OpenAlerts on every page render. All call sites updated.
This commit is contained in:
@@ -59,8 +59,7 @@ func (s *Server) handleUIAlerts(w stdhttp.ResponseWriter, r *stdhttp.Request) {
|
||||
}
|
||||
page.Counts = computeAlertCounts(s, r)
|
||||
|
||||
view := s.baseView(u)
|
||||
view.OpenAlerts = page.Counts.Open
|
||||
view := s.baseView(r, u)
|
||||
view.Title = "Alerts · restic-manager"
|
||||
view.Active = "alerts"
|
||||
view.Page = page
|
||||
|
||||
@@ -93,12 +93,20 @@ func (s *Server) requireUIUser(w stdhttp.ResponseWriter, r *stdhttp.Request) *ui
|
||||
// OpenAlerts is populated via a quick store count so the nav badge
|
||||
// stays current on every page load without requiring a page-specific
|
||||
// store call.
|
||||
func (s *Server) baseView(u *ui.User) ui.ViewData {
|
||||
return ui.ViewData{
|
||||
func (s *Server) baseView(r *stdhttp.Request, u *ui.User) ui.ViewData {
|
||||
view := ui.ViewData{
|
||||
User: u,
|
||||
Active: "dashboard",
|
||||
Version: s.version(),
|
||||
}
|
||||
|
||||
// Populate OpenAlerts from the store so the nav badge shows the
|
||||
// current count on every page.
|
||||
if open, err := s.deps.Store.ListAlerts(r.Context(), store.AlertFilter{Status: "open"}); err == nil {
|
||||
view.OpenAlerts = len(open)
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
// version returns the binary's build version — passed in via Deps so
|
||||
@@ -231,8 +239,7 @@ func (s *Server) handleUIDashboard(w stdhttp.ResponseWriter, r *stdhttp.Request)
|
||||
slog.Warn("ui dashboard: list pending hosts", "err", perr)
|
||||
}
|
||||
|
||||
view := s.baseView(u)
|
||||
view.OpenAlerts = summary.OpenAlerts
|
||||
view := s.baseView(r, u)
|
||||
view.Page = dashboardPage{
|
||||
Hosts: rows,
|
||||
HostCount: len(hosts),
|
||||
@@ -299,7 +306,7 @@ func (s *Server) handleUIAddHostGet(w stdhttp.ResponseWriter, r *stdhttp.Request
|
||||
if u == nil {
|
||||
return
|
||||
}
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Title = "Add host · restic-manager"
|
||||
view.Page = addHostPage{ServerURL: s.publicURL(r)}
|
||||
if err := s.deps.UI.Render(w, "add_host", view); err != nil {
|
||||
@@ -371,7 +378,7 @@ func (s *Server) handleUIAddHostPost(w stdhttp.ResponseWriter, r *stdhttp.Reques
|
||||
}
|
||||
}
|
||||
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Title = "Add host · restic-manager"
|
||||
view.Page = page
|
||||
w.WriteHeader(stdhttp.StatusUnprocessableEntity)
|
||||
@@ -438,7 +445,7 @@ func (s *Server) handleUIPendingHost(w stdhttp.ResponseWriter, r *stdhttp.Reques
|
||||
}
|
||||
}
|
||||
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Title = "Pending host · restic-manager"
|
||||
view.Page = page
|
||||
if err := s.deps.UI.Render(w, "pending_host", view); err != nil {
|
||||
@@ -616,7 +623,7 @@ func (s *Server) handleUIHostDetail(w stdhttp.ResponseWriter, r *stdhttp.Request
|
||||
shown = shown[:cap]
|
||||
}
|
||||
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Title = host.Name + " · restic-manager"
|
||||
view.Page = hostDetailPage{
|
||||
hostChromeData: s.loadHostChrome(r, *host, "snapshots", "snapshots"),
|
||||
@@ -716,7 +723,7 @@ func (s *Server) handleUIJobDetail(w stdhttp.ResponseWriter, r *stdhttp.Request)
|
||||
nextSeq = logs[n-1].Seq
|
||||
}
|
||||
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Title = job.Kind + " · " + host.Name + " · restic-manager"
|
||||
view.Page = jobDetailPage{
|
||||
Job: *job,
|
||||
|
||||
@@ -108,8 +108,8 @@ func (s *Server) loadSettingsPage(r *stdhttp.Request) (*settingsPage, error) {
|
||||
|
||||
// renderSettingsPage renders the settings shell, setting HTTP 422 on
|
||||
// validation failure (pass status=0 for the normal 200).
|
||||
func (s *Server) renderSettingsPage(w stdhttp.ResponseWriter, _ *stdhttp.Request, u *ui.User, page *settingsPage, status int) {
|
||||
view := s.baseView(u)
|
||||
func (s *Server) renderSettingsPage(w stdhttp.ResponseWriter, r *stdhttp.Request, u *ui.User, page *settingsPage, status int) {
|
||||
view := s.baseView(r, u)
|
||||
view.Title = "Settings · restic-manager"
|
||||
view.Active = "settings"
|
||||
view.Page = *page
|
||||
|
||||
@@ -244,7 +244,7 @@ func (s *Server) handleUIHostRepo(w stdhttp.ResponseWriter, r *stdhttp.Request)
|
||||
return
|
||||
}
|
||||
page.SavedSection = r.URL.Query().Get("saved")
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Title = host.Name + " repo · restic-manager"
|
||||
view.Page = *page
|
||||
if err := s.deps.UI.Render(w, "host_repo", view); err != nil {
|
||||
@@ -268,7 +268,7 @@ func (s *Server) renderRepoPage(w stdhttp.ResponseWriter, r *stdhttp.Request, u
|
||||
page.AdminCredsError = adminErr
|
||||
page.BandwidthError = bwErr
|
||||
page.MaintenanceError = mntErr
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Title = host.Name + " repo · restic-manager"
|
||||
view.Page = *page
|
||||
w.WriteHeader(stdhttp.StatusUnprocessableEntity)
|
||||
|
||||
@@ -105,7 +105,7 @@ func (s *Server) handleUIRestoreGet(w stdhttp.ResponseWriter, r *stdhttp.Request
|
||||
}
|
||||
}
|
||||
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Title = "Restore · " + host.Name
|
||||
view.Page = page
|
||||
if err := s.deps.UI.Render(w, "host_restore", view); err != nil {
|
||||
@@ -161,7 +161,7 @@ func (s *Server) handleUIRestorePost(w stdhttp.ResponseWriter, r *stdhttp.Reques
|
||||
break
|
||||
}
|
||||
}
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Title = "Restore · " + host.Name
|
||||
view.Page = page
|
||||
w.WriteHeader(status)
|
||||
@@ -329,7 +329,7 @@ func (s *Server) handleUIRestoreTree(w stdhttp.ResponseWriter, r *stdhttp.Reques
|
||||
HostID: host.ID, SnapshotID: snapshotID, Path: pathArg,
|
||||
Error: "agent offline",
|
||||
}
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Page = page
|
||||
_ = s.deps.UI.RenderPartial(w, "tree_node", view)
|
||||
return
|
||||
@@ -345,7 +345,7 @@ func (s *Server) handleUIRestoreTree(w stdhttp.ResponseWriter, r *stdhttp.Reques
|
||||
HostID: host.ID, SnapshotID: snapshotID, Path: pathArg,
|
||||
Error: err.Error(),
|
||||
}
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Page = page
|
||||
_ = s.deps.UI.RenderPartial(w, "tree_node", view)
|
||||
return
|
||||
@@ -355,7 +355,7 @@ func (s *Server) handleUIRestoreTree(w stdhttp.ResponseWriter, r *stdhttp.Reques
|
||||
HostID: host.ID, SnapshotID: snapshotID, Path: pathArg,
|
||||
Error: result.Error,
|
||||
}
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Page = page
|
||||
_ = s.deps.UI.RenderPartial(w, "tree_node", view)
|
||||
return
|
||||
@@ -382,7 +382,7 @@ func (s *Server) handleUIRestoreTree(w stdhttp.ResponseWriter, r *stdhttp.Reques
|
||||
HostID: host.ID, SnapshotID: snapshotID, Path: pathArg,
|
||||
Children: children,
|
||||
}
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Page = page
|
||||
if err := s.deps.UI.RenderPartial(w, "tree_node", view); err != nil {
|
||||
slog.Warn("ui restore tree: render partial", "err", err)
|
||||
|
||||
@@ -112,7 +112,7 @@ func (s *Server) handleUISchedulesList(w stdhttp.ResponseWriter, r *stdhttp.Requ
|
||||
chrome.ScheduleCount = len(scheds)
|
||||
chrome.SourceGroupCount = len(groups)
|
||||
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Title = host.Name + " schedules · restic-manager"
|
||||
view.Page = hostSchedulesPage{
|
||||
hostChromeData: chrome,
|
||||
@@ -140,7 +140,7 @@ func (s *Server) handleUIScheduleNewGet(w stdhttp.ResponseWriter, r *stdhttp.Req
|
||||
stdhttp.Error(w, "internal", stdhttp.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Title = "New schedule · " + host.Name + " · restic-manager"
|
||||
view.Page = scheduleEditPage{
|
||||
hostChromeData: s.loadHostChrome(r, *host, "schedules", "new schedule"),
|
||||
@@ -186,7 +186,7 @@ func (s *Server) handleUIScheduleEditGet(w stdhttp.ResponseWriter, r *stdhttp.Re
|
||||
for _, gid := range sc.SourceGroupIDs {
|
||||
selected[gid] = true
|
||||
}
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Title = "Edit schedule · " + host.Name + " · restic-manager"
|
||||
view.Page = scheduleEditPage{
|
||||
hostChromeData: s.loadHostChrome(r, *host, "schedules", "edit schedule"),
|
||||
@@ -415,7 +415,7 @@ func (s *Server) renderScheduleFormError(w stdhttp.ResponseWriter, r *stdhttp.Re
|
||||
saveAction = "/hosts/" + host.ID + "/schedules/" + sid + "/edit"
|
||||
crumb = "edit schedule"
|
||||
}
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Title = "Schedule · " + host.Name + " · restic-manager"
|
||||
view.Page = scheduleEditPage{
|
||||
hostChromeData: s.loadHostChrome(r, *host, "schedules", crumb),
|
||||
|
||||
@@ -121,7 +121,7 @@ func (s *Server) handleUIHostSources(w stdhttp.ResponseWriter, r *stdhttp.Reques
|
||||
// loadHostChrome already counted groups; reuse count we just got.
|
||||
chrome.SourceGroupCount = len(groups)
|
||||
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Title = host.Name + " sources · restic-manager"
|
||||
view.Page = hostSourcesPage{hostChromeData: chrome, Groups: rows}
|
||||
if err := s.deps.UI.Render(w, "host_sources", view); err != nil {
|
||||
@@ -139,7 +139,7 @@ func (s *Server) handleUISourceGroupNewGet(w stdhttp.ResponseWriter, r *stdhttp.
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Title = "New source group · " + host.Name + " · restic-manager"
|
||||
view.Page = sourceGroupEditPage{
|
||||
hostChromeData: s.loadHostChrome(r, *host, "sources", "new source group"),
|
||||
@@ -173,7 +173,7 @@ func (s *Server) handleUISourceGroupEditGet(w stdhttp.ResponseWriter, r *stdhttp
|
||||
stdhttp.Error(w, "internal", stdhttp.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Title = g.Name + " · " + host.Name + " · restic-manager"
|
||||
form := formFromGroup(*g)
|
||||
form.PreHook = s.decryptHookOrFallback(g.PreHook, "", host.ID, "pre")
|
||||
@@ -362,7 +362,7 @@ func (s *Server) handleUISourceGroupDelete(w stdhttp.ResponseWriter, r *stdhttp.
|
||||
// typed input intact + an error banner. Returns 422 to signal "form
|
||||
// rejected" while still returning HTML (mirrors handleUIAddHostPost).
|
||||
func (s *Server) renderSourceFormError(w stdhttp.ResponseWriter, r *stdhttp.Request, u *ui.User, host *store.Host, gid string, isNew bool, form sourceFormData, msg string) {
|
||||
view := s.baseView(u)
|
||||
view := s.baseView(r, u)
|
||||
view.Title = "Source group · " + host.Name + " · restic-manager"
|
||||
saveAction := "/hosts/" + host.ID + "/sources/new"
|
||||
crumb := "new source group"
|
||||
|
||||
Reference in New Issue
Block a user