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)
|
page.Counts = computeAlertCounts(s, r)
|
||||||
|
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.OpenAlerts = page.Counts.Open
|
|
||||||
view.Title = "Alerts · restic-manager"
|
view.Title = "Alerts · restic-manager"
|
||||||
view.Active = "alerts"
|
view.Active = "alerts"
|
||||||
view.Page = page
|
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
|
// OpenAlerts is populated via a quick store count so the nav badge
|
||||||
// stays current on every page load without requiring a page-specific
|
// stays current on every page load without requiring a page-specific
|
||||||
// store call.
|
// store call.
|
||||||
func (s *Server) baseView(u *ui.User) ui.ViewData {
|
func (s *Server) baseView(r *stdhttp.Request, u *ui.User) ui.ViewData {
|
||||||
return ui.ViewData{
|
view := ui.ViewData{
|
||||||
User: u,
|
User: u,
|
||||||
Active: "dashboard",
|
Active: "dashboard",
|
||||||
Version: s.version(),
|
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
|
// 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)
|
slog.Warn("ui dashboard: list pending hosts", "err", perr)
|
||||||
}
|
}
|
||||||
|
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.OpenAlerts = summary.OpenAlerts
|
|
||||||
view.Page = dashboardPage{
|
view.Page = dashboardPage{
|
||||||
Hosts: rows,
|
Hosts: rows,
|
||||||
HostCount: len(hosts),
|
HostCount: len(hosts),
|
||||||
@@ -299,7 +306,7 @@ func (s *Server) handleUIAddHostGet(w stdhttp.ResponseWriter, r *stdhttp.Request
|
|||||||
if u == nil {
|
if u == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.Title = "Add host · restic-manager"
|
view.Title = "Add host · restic-manager"
|
||||||
view.Page = addHostPage{ServerURL: s.publicURL(r)}
|
view.Page = addHostPage{ServerURL: s.publicURL(r)}
|
||||||
if err := s.deps.UI.Render(w, "add_host", view); err != nil {
|
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.Title = "Add host · restic-manager"
|
||||||
view.Page = page
|
view.Page = page
|
||||||
w.WriteHeader(stdhttp.StatusUnprocessableEntity)
|
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.Title = "Pending host · restic-manager"
|
||||||
view.Page = page
|
view.Page = page
|
||||||
if err := s.deps.UI.Render(w, "pending_host", view); err != nil {
|
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]
|
shown = shown[:cap]
|
||||||
}
|
}
|
||||||
|
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.Title = host.Name + " · restic-manager"
|
view.Title = host.Name + " · restic-manager"
|
||||||
view.Page = hostDetailPage{
|
view.Page = hostDetailPage{
|
||||||
hostChromeData: s.loadHostChrome(r, *host, "snapshots", "snapshots"),
|
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
|
nextSeq = logs[n-1].Seq
|
||||||
}
|
}
|
||||||
|
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.Title = job.Kind + " · " + host.Name + " · restic-manager"
|
view.Title = job.Kind + " · " + host.Name + " · restic-manager"
|
||||||
view.Page = jobDetailPage{
|
view.Page = jobDetailPage{
|
||||||
Job: *job,
|
Job: *job,
|
||||||
|
|||||||
@@ -108,8 +108,8 @@ func (s *Server) loadSettingsPage(r *stdhttp.Request) (*settingsPage, error) {
|
|||||||
|
|
||||||
// renderSettingsPage renders the settings shell, setting HTTP 422 on
|
// renderSettingsPage renders the settings shell, setting HTTP 422 on
|
||||||
// validation failure (pass status=0 for the normal 200).
|
// 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) {
|
func (s *Server) renderSettingsPage(w stdhttp.ResponseWriter, r *stdhttp.Request, u *ui.User, page *settingsPage, status int) {
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.Title = "Settings · restic-manager"
|
view.Title = "Settings · restic-manager"
|
||||||
view.Active = "settings"
|
view.Active = "settings"
|
||||||
view.Page = *page
|
view.Page = *page
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ func (s *Server) handleUIHostRepo(w stdhttp.ResponseWriter, r *stdhttp.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
page.SavedSection = r.URL.Query().Get("saved")
|
page.SavedSection = r.URL.Query().Get("saved")
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.Title = host.Name + " repo · restic-manager"
|
view.Title = host.Name + " repo · restic-manager"
|
||||||
view.Page = *page
|
view.Page = *page
|
||||||
if err := s.deps.UI.Render(w, "host_repo", view); err != nil {
|
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.AdminCredsError = adminErr
|
||||||
page.BandwidthError = bwErr
|
page.BandwidthError = bwErr
|
||||||
page.MaintenanceError = mntErr
|
page.MaintenanceError = mntErr
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.Title = host.Name + " repo · restic-manager"
|
view.Title = host.Name + " repo · restic-manager"
|
||||||
view.Page = *page
|
view.Page = *page
|
||||||
w.WriteHeader(stdhttp.StatusUnprocessableEntity)
|
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.Title = "Restore · " + host.Name
|
||||||
view.Page = page
|
view.Page = page
|
||||||
if err := s.deps.UI.Render(w, "host_restore", view); err != nil {
|
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
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.Title = "Restore · " + host.Name
|
view.Title = "Restore · " + host.Name
|
||||||
view.Page = page
|
view.Page = page
|
||||||
w.WriteHeader(status)
|
w.WriteHeader(status)
|
||||||
@@ -329,7 +329,7 @@ func (s *Server) handleUIRestoreTree(w stdhttp.ResponseWriter, r *stdhttp.Reques
|
|||||||
HostID: host.ID, SnapshotID: snapshotID, Path: pathArg,
|
HostID: host.ID, SnapshotID: snapshotID, Path: pathArg,
|
||||||
Error: "agent offline",
|
Error: "agent offline",
|
||||||
}
|
}
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.Page = page
|
view.Page = page
|
||||||
_ = s.deps.UI.RenderPartial(w, "tree_node", view)
|
_ = s.deps.UI.RenderPartial(w, "tree_node", view)
|
||||||
return
|
return
|
||||||
@@ -345,7 +345,7 @@ func (s *Server) handleUIRestoreTree(w stdhttp.ResponseWriter, r *stdhttp.Reques
|
|||||||
HostID: host.ID, SnapshotID: snapshotID, Path: pathArg,
|
HostID: host.ID, SnapshotID: snapshotID, Path: pathArg,
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
}
|
}
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.Page = page
|
view.Page = page
|
||||||
_ = s.deps.UI.RenderPartial(w, "tree_node", view)
|
_ = s.deps.UI.RenderPartial(w, "tree_node", view)
|
||||||
return
|
return
|
||||||
@@ -355,7 +355,7 @@ func (s *Server) handleUIRestoreTree(w stdhttp.ResponseWriter, r *stdhttp.Reques
|
|||||||
HostID: host.ID, SnapshotID: snapshotID, Path: pathArg,
|
HostID: host.ID, SnapshotID: snapshotID, Path: pathArg,
|
||||||
Error: result.Error,
|
Error: result.Error,
|
||||||
}
|
}
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.Page = page
|
view.Page = page
|
||||||
_ = s.deps.UI.RenderPartial(w, "tree_node", view)
|
_ = s.deps.UI.RenderPartial(w, "tree_node", view)
|
||||||
return
|
return
|
||||||
@@ -382,7 +382,7 @@ func (s *Server) handleUIRestoreTree(w stdhttp.ResponseWriter, r *stdhttp.Reques
|
|||||||
HostID: host.ID, SnapshotID: snapshotID, Path: pathArg,
|
HostID: host.ID, SnapshotID: snapshotID, Path: pathArg,
|
||||||
Children: children,
|
Children: children,
|
||||||
}
|
}
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.Page = page
|
view.Page = page
|
||||||
if err := s.deps.UI.RenderPartial(w, "tree_node", view); err != nil {
|
if err := s.deps.UI.RenderPartial(w, "tree_node", view); err != nil {
|
||||||
slog.Warn("ui restore tree: render partial", "err", err)
|
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.ScheduleCount = len(scheds)
|
||||||
chrome.SourceGroupCount = len(groups)
|
chrome.SourceGroupCount = len(groups)
|
||||||
|
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.Title = host.Name + " schedules · restic-manager"
|
view.Title = host.Name + " schedules · restic-manager"
|
||||||
view.Page = hostSchedulesPage{
|
view.Page = hostSchedulesPage{
|
||||||
hostChromeData: chrome,
|
hostChromeData: chrome,
|
||||||
@@ -140,7 +140,7 @@ func (s *Server) handleUIScheduleNewGet(w stdhttp.ResponseWriter, r *stdhttp.Req
|
|||||||
stdhttp.Error(w, "internal", stdhttp.StatusInternalServerError)
|
stdhttp.Error(w, "internal", stdhttp.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.Title = "New schedule · " + host.Name + " · restic-manager"
|
view.Title = "New schedule · " + host.Name + " · restic-manager"
|
||||||
view.Page = scheduleEditPage{
|
view.Page = scheduleEditPage{
|
||||||
hostChromeData: s.loadHostChrome(r, *host, "schedules", "new schedule"),
|
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 {
|
for _, gid := range sc.SourceGroupIDs {
|
||||||
selected[gid] = true
|
selected[gid] = true
|
||||||
}
|
}
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.Title = "Edit schedule · " + host.Name + " · restic-manager"
|
view.Title = "Edit schedule · " + host.Name + " · restic-manager"
|
||||||
view.Page = scheduleEditPage{
|
view.Page = scheduleEditPage{
|
||||||
hostChromeData: s.loadHostChrome(r, *host, "schedules", "edit schedule"),
|
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"
|
saveAction = "/hosts/" + host.ID + "/schedules/" + sid + "/edit"
|
||||||
crumb = "edit schedule"
|
crumb = "edit schedule"
|
||||||
}
|
}
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.Title = "Schedule · " + host.Name + " · restic-manager"
|
view.Title = "Schedule · " + host.Name + " · restic-manager"
|
||||||
view.Page = scheduleEditPage{
|
view.Page = scheduleEditPage{
|
||||||
hostChromeData: s.loadHostChrome(r, *host, "schedules", crumb),
|
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.
|
// loadHostChrome already counted groups; reuse count we just got.
|
||||||
chrome.SourceGroupCount = len(groups)
|
chrome.SourceGroupCount = len(groups)
|
||||||
|
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.Title = host.Name + " sources · restic-manager"
|
view.Title = host.Name + " sources · restic-manager"
|
||||||
view.Page = hostSourcesPage{hostChromeData: chrome, Groups: rows}
|
view.Page = hostSourcesPage{hostChromeData: chrome, Groups: rows}
|
||||||
if err := s.deps.UI.Render(w, "host_sources", view); err != nil {
|
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 {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.Title = "New source group · " + host.Name + " · restic-manager"
|
view.Title = "New source group · " + host.Name + " · restic-manager"
|
||||||
view.Page = sourceGroupEditPage{
|
view.Page = sourceGroupEditPage{
|
||||||
hostChromeData: s.loadHostChrome(r, *host, "sources", "new source group"),
|
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)
|
stdhttp.Error(w, "internal", stdhttp.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
view := s.baseView(u)
|
view := s.baseView(r, u)
|
||||||
view.Title = g.Name + " · " + host.Name + " · restic-manager"
|
view.Title = g.Name + " · " + host.Name + " · restic-manager"
|
||||||
form := formFromGroup(*g)
|
form := formFromGroup(*g)
|
||||||
form.PreHook = s.decryptHookOrFallback(g.PreHook, "", host.ID, "pre")
|
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
|
// typed input intact + an error banner. Returns 422 to signal "form
|
||||||
// rejected" while still returning HTML (mirrors handleUIAddHostPost).
|
// 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) {
|
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"
|
view.Title = "Source group · " + host.Name + " · restic-manager"
|
||||||
saveAction := "/hosts/" + host.ID + "/sources/new"
|
saveAction := "/hosts/" + host.ID + "/sources/new"
|
||||||
crumb := "new source group"
|
crumb := "new source group"
|
||||||
|
|||||||
Reference in New Issue
Block a user