refactor: add alertsCache to maintain active alert data in memory

This commit is contained in:
henrygd
2026-03-17 18:32:57 -04:00
parent 48ddc96a0d
commit c6c3950fb0
8 changed files with 628 additions and 57 deletions

View File

@@ -5,13 +5,12 @@ import (
"strings"
"time"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
)
type alertInfo struct {
systemName string
alertRecord *core.Record
alertRecord CachedAlertData
expireTime time.Time
timer *time.Timer
}
@@ -36,10 +35,7 @@ func (am *AlertManager) HandleStatusAlerts(newStatus string, systemRecord *core.
return nil
}
alertRecords, err := am.getSystemStatusAlerts(systemRecord.Id)
if err != nil {
return err
}
alertRecords := am.alertsCache.GetAlertsByName(systemRecord.Id, "Status")
if len(alertRecords) == 0 {
return nil
}
@@ -53,29 +49,17 @@ func (am *AlertManager) HandleStatusAlerts(newStatus string, systemRecord *core.
return nil
}
// getSystemStatusAlerts retrieves all "Status" alert records for a given system ID.
func (am *AlertManager) getSystemStatusAlerts(systemID string) ([]*core.Record, error) {
alertRecords, err := am.hub.FindAllRecords("alerts", dbx.HashExp{
"system": systemID,
"name": "Status",
})
if err != nil {
return nil, err
}
return alertRecords, nil
}
// handleSystemDown manages the logic when a system status changes to "down". It schedules pending alerts for each alert record.
func (am *AlertManager) handleSystemDown(systemName string, alertRecords []*core.Record) {
func (am *AlertManager) handleSystemDown(systemName string, alertRecords []CachedAlertData) {
for _, alertRecord := range alertRecords {
min := max(1, alertRecord.GetInt("min"))
min := max(1, int(alertRecord.Min))
am.schedulePendingStatusAlert(systemName, alertRecord, time.Duration(min)*time.Minute)
}
}
// schedulePendingStatusAlert sets up a timer to send a "down" alert after the specified delay if the system is still down.
// It returns true if the alert was scheduled, or false if an alert was already pending for the given alert record.
func (am *AlertManager) schedulePendingStatusAlert(systemName string, alertRecord *core.Record, delay time.Duration) bool {
func (am *AlertManager) schedulePendingStatusAlert(systemName string, alertRecord CachedAlertData, delay time.Duration) bool {
alert := &alertInfo{
systemName: systemName,
alertRecord: alertRecord,
@@ -96,13 +80,13 @@ func (am *AlertManager) schedulePendingStatusAlert(systemName string, alertRecor
// handleSystemUp manages the logic when a system status changes to "up".
// It cancels any pending alerts and sends "up" alerts.
func (am *AlertManager) handleSystemUp(systemName string, alertRecords []*core.Record) {
func (am *AlertManager) handleSystemUp(systemName string, alertRecords []CachedAlertData) {
for _, alertRecord := range alertRecords {
// If alert exists for record, delete and continue (down alert not sent)
if am.cancelPendingAlert(alertRecord.Id) {
continue
}
if !alertRecord.GetBool("triggered") {
if !alertRecord.Triggered {
continue
}
if err := am.sendStatusAlert("up", systemName, alertRecord); err != nil {
@@ -133,23 +117,22 @@ func (am *AlertManager) processPendingAlert(alertID string) {
}
info := value.(*alertInfo)
if info.alertRecord.GetBool("triggered") {
alertRecord, ok := am.alertsCache.Refresh(info.alertRecord)
if !ok || alertRecord.Triggered {
return
}
if err := am.sendStatusAlert("down", info.systemName, info.alertRecord); err != nil {
if err := am.sendStatusAlert("down", info.systemName, alertRecord); err != nil {
am.hub.Logger().Error("Failed to send alert", "err", err)
}
}
// sendStatusAlert sends a status alert ("up" or "down") to the users associated with the alert records.
func (am *AlertManager) sendStatusAlert(alertStatus string, systemName string, alertRecord *core.Record) error {
switch alertStatus {
case "up":
alertRecord.Set("triggered", false)
case "down":
alertRecord.Set("triggered", true)
func (am *AlertManager) sendStatusAlert(alertStatus string, systemName string, alertRecord CachedAlertData) error {
// Update trigger state for alert record before sending alert
triggered := alertStatus == "down"
if err := am.setAlertTriggered(alertRecord, triggered); err != nil {
return err
}
am.hub.Save(alertRecord)
var emoji string
if alertStatus == "up" {
@@ -162,10 +145,10 @@ func (am *AlertManager) sendStatusAlert(alertStatus string, systemName string, a
message := strings.TrimSuffix(title, emoji)
// Get system ID for the link
systemID := alertRecord.GetString("system")
systemID := alertRecord.SystemID
return am.SendAlert(AlertMessageData{
UserID: alertRecord.GetString("user"),
UserID: alertRecord.UserID,
SystemID: systemID,
Title: title,
Message: message,
@@ -211,12 +194,13 @@ func resolveStatusAlerts(app core.App) error {
func (am *AlertManager) restorePendingStatusAlerts() error {
type pendingStatusAlert struct {
AlertID string `db:"alert_id"`
SystemID string `db:"system_id"`
SystemName string `db:"system_name"`
}
var pending []pendingStatusAlert
err := am.hub.DB().NewQuery(`
SELECT a.id AS alert_id, s.name AS system_name
SELECT a.id AS alert_id, a.system AS system_id, s.name AS system_name
FROM alerts a
JOIN systems s ON a.system = s.id
WHERE a.name = 'Status'
@@ -227,12 +211,15 @@ func (am *AlertManager) restorePendingStatusAlerts() error {
return err
}
// Make sure cache is populated before trying to restore pending alerts
_ = am.alertsCache.PopulateFromDB(false)
for _, item := range pending {
alertRecord, err := am.hub.FindRecordById("alerts", item.AlertID)
if err != nil {
return err
alertRecord, ok := am.alertsCache.GetAlert(item.SystemID, item.AlertID)
if !ok {
continue
}
min := max(1, alertRecord.GetInt("min"))
min := max(1, int(alertRecord.Min))
am.schedulePendingStatusAlert(item.SystemName, alertRecord, time.Duration(min)*time.Minute)
}