diff --git a/internal/server/http/server.go b/internal/server/http/server.go index 14385b2..79868fd 100644 --- a/internal/server/http/server.go +++ b/internal/server/http/server.go @@ -325,6 +325,7 @@ func (s *Server) routes(r chi.Router) { r.Get("/settings/notifications/{id}/edit", s.handleUINotificationEditGet) r.Post("/settings/notifications/{id}/edit", s.handleUINotificationEditPost) r.Post("/settings/notifications/{id}/delete", s.handleUINotificationDelete) + r.Post("/settings/notifications/{id}/toggle", s.handleUINotificationToggle) } // Browser job-log stream (separate from /ws/agent so the auth diff --git a/internal/server/http/ui_notifications.go b/internal/server/http/ui_notifications.go index f409d3a..cf6a548 100644 --- a/internal/server/http/ui_notifications.go +++ b/internal/server/http/ui_notifications.go @@ -661,6 +661,55 @@ func (s *Server) handleUINotificationDelete(w stdhttp.ResponseWriter, r *stdhttp stdhttp.Redirect(w, r, "/settings/notifications", stdhttp.StatusSeeOther) } +// handleUINotificationToggle flips the enabled flag for one channel +// and re-renders the row. Wired to the inline toggle in the channel +// list so operators don't need to enter the edit form just to flip a +// channel on or off. HTMX-aware: returns just the toggle fragment when +// the request carries HX-Request, otherwise redirects back to the list. +func (s *Server) handleUINotificationToggle(w stdhttp.ResponseWriter, r *stdhttp.Request) { + u := s.requireUIUser(w, r) + if u == nil { + return + } + channelID := chi.URLParam(r, "id") + ch, err := s.deps.Store.GetNotificationChannel(r.Context(), channelID) + if err != nil { + if errors.Is(err, store.ErrNotFound) { + stdhttp.NotFound(w, r) + return + } + slog.Error("ui notifications: get for toggle", "id", channelID, "err", err) + stdhttp.Error(w, "internal", stdhttp.StatusInternalServerError) + return + } + now := time.Now().UTC() + want := !ch.Enabled + if err := s.deps.Store.SetNotificationChannelEnabled(r.Context(), ch.ID, want, now); err != nil { + slog.Error("ui notifications: set enabled", "id", ch.ID, "err", err) + stdhttp.Error(w, "internal", stdhttp.StatusInternalServerError) + return + } + _ = s.deps.Store.AppendAudit(r.Context(), store.AuditEntry{ + ID: ulid.Make().String(), + UserID: &u.ID, + Actor: "user", + Action: "notification_channel.toggled", + TargetKind: ptr("notification_channel"), + TargetID: &ch.ID, + TS: now, + }) + if r.Header.Get("HX-Request") == "true" { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + if want { + _, _ = w.Write([]byte(``)) + } else { + _, _ = w.Write([]byte(``)) + } + return + } + stdhttp.Redirect(w, r, "/settings/notifications", stdhttp.StatusSeeOther) +} + // ── API handler ─────────────────────────────────────────────────────────────── // testResultFragment is the JSON body returned by handleAPINotificationTest. diff --git a/internal/store/notification_channels.go b/internal/store/notification_channels.go index 9b3176d..4646e49 100644 --- a/internal/store/notification_channels.go +++ b/internal/store/notification_channels.go @@ -77,6 +77,22 @@ func (s *Store) UpdateNotificationChannel(ctx context.Context, ch NotificationCh return nil } +// SetNotificationChannelEnabled flips the enabled flag without +// touching kind/name/config — used by the inline list-row toggle. +func (s *Store) SetNotificationChannelEnabled(ctx context.Context, id string, enabled bool, when time.Time) error { + v := 0 + if enabled { + v = 1 + } + _, err := s.db.ExecContext(ctx, + `UPDATE notification_channels SET enabled = ?, updated_at = ? WHERE id = ?`, + v, when.UTC().Format(time.RFC3339Nano), id) + if err != nil { + return fmt.Errorf("store: set channel enabled: %w", err) + } + return nil +} + // DeleteNotificationChannel removes a channel row; cascades to notification_log. func (s *Store) DeleteNotificationChannel(ctx context.Context, id string) error { _, err := s.db.ExecContext(ctx, diff --git a/web/templates/pages/settings.html b/web/templates/pages/settings.html index c7ef945..26b38fb 100644 --- a/web/templates/pages/settings.html +++ b/web/templates/pages/settings.html @@ -99,8 +99,18 @@
-