package webapp import "net/http" // handleChecks renders the worker's recent result rows. The // runner's in-memory ring buffer supplies the data; Phase 1 reads // from it directly with no extra caching. // // A "Run now" button is rendered on the page but stays disabled // until the control plane accepts one-off check hints. The hint // protocol is gated on the worker-notifier MVP plan // (docs/plans/worker-notifier-mvp.md §N) and on // docs/plans/separate-checks.md §11.6; once both are in place the // RunNowEnabled flag flips to true and the handler reads // s.deps.Runner.SubmitCheckHint(...) instead of the placeholder. func (s *Server) handleChecks(w http.ResponseWriter, r *http.Request) { s.renderRunnerPage(w, r, "Recent checks", "checks.html", func() any { return checksPageData{ basePageData: s.newBasePage(r, "Recent checks", sessionFromContextOrEmpty(r)), Rows: s.deps.Runner.RecentResults(50), RunNowEnabled: false, RunNowTooltip: "Run-now lands with the worker hint protocol (worker-notifier-mvp.md §N + separate-checks.md §11.6).", } }) } type checksPageData struct { basePageData Rows []ResultRow RunNowEnabled bool RunNowTooltip string } // handleNotifications renders the worker's recent notification // rows. Phase 1 only emits selfcheck alerts (email/telegram via the // cached credentials), but the runner ring buffer is shape-stable // for the main-app-issued notifications coming online in a later // phase. // // A "Resend" button is rendered on the page but stays disabled // until the worker resend protocol exists. Same gating as the // "Run now" button on /checks. func (s *Server) handleNotifications(w http.ResponseWriter, r *http.Request) { s.renderRunnerPage(w, r, "Recent notifications", "notifications.html", func() any { return notificationsPageData{ basePageData: s.newBasePage(r, "Recent notifications", sessionFromContextOrEmpty(r)), Rows: s.deps.Runner.RecentNotifications(50), ResendEnabled: false, ResendTooltip: "Resend lands with the worker notification resend protocol (worker-notifier-mvp.md §N).", } }) } type notificationsPageData struct { basePageData Rows []NotificationRow ResendEnabled bool ResendTooltip string } // renderRunnerPage is the small boilerplate-killer shared by // handleChecks / handleNotifications / handleApps: write the // no-store header, look up the session, build the page data via the // caller-supplied closure, execute the template, and log + 500 on // error. The closure receives no arguments because each handler // already has its own copy of *Server and *http.Request in scope // (this method is bound to *Server, so the closure captures them). func (s *Server) renderRunnerPage(w http.ResponseWriter, _ *http.Request, logName, tmpl string, build func() any) { writeNoStore(w) if err := s.templates.Execute(w, tmpl, build()); err != nil { s.deps.Logger.Printf("render %s: %v", logName, err) http.Error(w, "template error", http.StatusInternalServerError) } } // sessionFromContextOrEmpty is a thin wrapper around // sessionFromContext that returns a nil session instead of a bool, // for handlers that pass the session straight into a page-data // struct (the Session zero value is harmless for template // rendering). func sessionFromContextOrEmpty(r *http.Request) *Session { sess, _ := sessionFromContext(r.Context()) return sess }