Files
Charon/backend/internal/models/uptime_host.go
GitHub Actions b5c066d25d feat: add JSON template support for all services and fix uptime monitoring reliability
BREAKING CHANGE: None - fully backward compatible

Changes:
- feat(notifications): extend JSON templates to Discord, Slack, Gotify, and generic
- fix(uptime): resolve race conditions and false positives with failure debouncing
- chore(tests): add comprehensive test coverage (86.2% backend, 87.61% frontend)
- docs: add feature guides and manual test plan

Technical Details:
- Added supportsJSONTemplates() helper for service capability detection
- Renamed sendCustomWebhook → sendJSONPayload for clarity
- Added FailureCount field requiring 2 consecutive failures before marking down
- Implemented WaitGroup synchronization and host-specific mutexes
- Increased TCP timeout to 10s with 2 retry attempts
- Added template security: 5s timeout, 10KB size limit
- All security scans pass (CodeQL, Trivy)
2025-12-24 20:34:38 +00:00

58 lines
2.2 KiB
Go

package models
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// UptimeHost represents a unique upstream host/IP that may have multiple services.
// This enables host-level health checks to avoid notification storms when a whole server goes down.
type UptimeHost struct {
ID string `gorm:"primaryKey" json:"id"`
Host string `json:"host" gorm:"uniqueIndex;not null"` // IP address or hostname
Name string `json:"name"` // Friendly name (auto-generated or from first service)
Status string `json:"status"` // up, down, pending
LastCheck time.Time `json:"last_check"`
Latency int64 `json:"latency"` // ms for ping/TCP check
// Notification tracking
LastNotifiedDown time.Time `json:"last_notified_down"` // When we last sent DOWN notification
LastNotifiedUp time.Time `json:"last_notified_up"` // When we last sent UP notification
NotifiedServiceCount int `json:"notified_service_count"` // Number of services in last notification
LastStatusChange time.Time `json:"last_status_change"` // When status last changed
FailureCount int `json:"failure_count" gorm:"default:0"` // Consecutive failures for debouncing
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (h *UptimeHost) BeforeCreate(tx *gorm.DB) (err error) {
if h.ID == "" {
h.ID = uuid.New().String()
}
if h.Status == "" {
h.Status = "pending"
}
return
}
// UptimeNotificationEvent tracks notification batches to prevent duplicates
type UptimeNotificationEvent struct {
ID string `gorm:"primaryKey" json:"id"`
HostID string `json:"host_id" gorm:"index"`
EventType string `json:"event_type"` // down, up, partial_recovery
MonitorIDs string `json:"monitor_ids"` // JSON array of monitor IDs included in this notification
Message string `json:"message"`
SentAt time.Time `json:"sent_at"`
CreatedAt time.Time `json:"created_at"`
}
func (e *UptimeNotificationEvent) BeforeCreate(tx *gorm.DB) (err error) {
if e.ID == "" {
e.ID = uuid.New().String()
}
return
}