alert: wire engine into ws hello + MarkJobFinished + offline sweep

- ws.HandlerDeps gains an AlertEngine *alert.Engine field; populated
  from http.Deps.AlertEngine (nil until G1 constructs the engine)
- runAgentLoop calls NotifyHostOnline after MarkHostHello succeeds
- dispatchAgentMessage MsgJobFinished case calls NotifyJobFinished,
  looking up the job Kind via Store.GetJob before notifying
- store.MarkHostsOfflineStaleReturnIDs added: SELECT+UPDATE in one
  transaction, returns the IDs that flipped to offline
- offline sweeper in cmd/server/main.go switched to the new variant;
  TODO(G1) comment marks where NotifyHostOffline calls will land
This commit is contained in:
2026-05-04 19:54:39 +01:00
parent 5e655d756d
commit c710743231
4 changed files with 78 additions and 2 deletions
+18
View File
@@ -12,6 +12,7 @@ import (
"github.com/coder/websocket"
"gitea.dcglab.co.uk/steve/restic-manager/internal/alert"
"gitea.dcglab.co.uk/steve/restic-manager/internal/api"
"gitea.dcglab.co.uk/steve/restic-manager/internal/auth"
"gitea.dcglab.co.uk/steve/restic-manager/internal/store"
@@ -22,6 +23,9 @@ type HandlerDeps struct {
Hub *Hub
Store *store.Store
JobHub *JobHub
// AlertEngine receives job-finished and host-online events so the
// alert engine can evaluate its rules. Optional; nil = no-op.
AlertEngine *alert.Engine
// OnHello is called once per successful hello, after the host row
// has been touched and the conn registered. Used by the HTTP
// layer to push host_credentials down as a config.update before
@@ -140,6 +144,9 @@ func runAgentLoop(ctx context.Context, c *Conn, hostID string, deps HandlerDeps)
helloPayload.ProtocolVersion, now); err != nil {
slog.Error("ws mark host hello failed", "host_id", hostID, "err", err)
}
if deps.AlertEngine != nil {
deps.AlertEngine.NotifyHostOnline(hostID)
}
deps.Hub.Register(hostID, c)
defer deps.Hub.Unregister(hostID, c)
@@ -210,6 +217,17 @@ func dispatchAgentMessage(ctx context.Context, c *Conn, hostID string, env api.E
if deps.JobHub != nil {
deps.JobHub.Broadcast(p.JobID, env)
}
if deps.AlertEngine != nil {
if job, err := deps.Store.GetJob(ctx, p.JobID); err == nil && job != nil {
deps.AlertEngine.NotifyJobFinished(alert.JobFinishedEvent{
HostID: hostID,
JobID: p.JobID,
Kind: job.Kind,
Status: string(p.Status),
When: p.FinishedAt,
})
}
}
case api.MsgLogStream:
var p api.LogStreamLine