Files
worker/app/models/check.go
Gleb Tv 2c7a0236da feat: publish standalone worker
Separate worker packaging and service lifecycle from the control plane.
2026-07-13 17:55:14 +03:00

172 строки
4.5 KiB
Go

package models
import (
"encoding/json"
"strings"
"time"
"unicode"
"github.com/lib/pq"
"gorm.io/datatypes"
"gorm.io/gorm"
)
// Check provides functionality.
type Check struct {
ID int64 `gorm:"primarykey" json:"id"`
Enabled *bool `gorm:"not null;default:true" json:"enabled"`
MonitorID int64 `gorm:"index;type:bigint REFERENCES monitors(id)" json:"monitor_id,omitempty"`
Monitor *Monitor `json:"-"`
Name *string `json:"name"`
Kind string `json:"kind"`
Interval int `json:"interval" validate:"required,gte=60"`
// URL to monitor
URL *string `json:"url,omitempty"`
// Other settings of the check
Settings datatypes.JSON `gorm:"not null;" json:"settings"`
State string `gorm:"not null;default:'UNK'" json:"state"`
LastStart *time.Time `json:"last_start"`
LastEnd *time.Time `json:"last_end"`
LastOk *time.Time `json:"last_ok"`
LastFail *time.Time `json:"last_fail"`
WasUp *time.Time `json:"was_up"`
Fails int `json:"fails"`
Expires *time.Time `json:"expires"`
Error *string `json:"error"`
Warnings pq.StringArray `gorm:"type:varchar(255)[]" json:"warnings"`
Infos pq.StringArray `gorm:"type:varchar(255)[]" json:"infos"`
// RequireQuorum enables multi-region result aggregation (Phase 3 of
// docs/todo.md): when >1 the check's State is NOT written directly by
// ApplyRemoteCheckResult — instead CheckRegionResult rows accumulate
// until app/models/check_aggregator.go decides OK/ERR/DEGRADED.
// Default 1 keeps the legacy single-region behavior unchanged.
RequireQuorum int `gorm:"not null;default:1" json:"require_quorum"`
// AggregationWindowSeconds is how long the aggregator waits for
// regional CheckRegionResult rows before deciding the check's State.
// Stored as int seconds (matching the existing GORM style — no
// time.Duration columns) and exposed via AggregationWindow(). Default
// 5s; ignored when RequireQuorum <= 1.
AggregationWindowSeconds int `gorm:"not null;default:5" json:"aggregation_window_seconds"`
IsNew bool `gorm:"-:all" sql:"-" json:"is_new,omitempty"`
Deleted bool `gorm:"-:all" sql:"-" json:"deleted,omitempty"`
Events []Event `json:"-" gorm:"many2many:event_checks;"`
Audited
}
// ExpScope provides functionality.
func ExpScope(q *gorm.DB) *gorm.DB {
return q.Where("kind IN ('whois', 'ssl')").
Preload("Monitor").
Preload("Monitor.Group").
Preload("Monitor.Group.Notifications").
Preload("Monitor.Group.Notifications.Contacts").
Where("expires < ?", time.Now().Add(time.Hour*7*24))
}
// IntervalOK provides functionality.
func (c *Check) IntervalOK() bool {
if c.Kind == kindRKN {
return true
}
if c.Kind == kindWhois {
return c.Interval >= 43200
}
return c.Interval >= 30
}
// GetLabel provides functionality.
func (c *Check) GetLabel() string {
if c.Name != nil {
return *c.Name
}
if c.URL != nil {
return *c.URL
}
return c.Kind
}
// KindLabel provides functionality.
func (c *Check) KindLabel() string {
if c.Kind == kindWhois {
return "регистрация домена"
}
if c.Kind == kindSSL {
return "SSL сертификат"
}
return c.Kind
}
// GetSettings provides functionality.
func (c *Check) GetSettings() CheckSettings {
d := CheckSettings{}
err := json.Unmarshal(c.Settings, &d)
if err != nil {
panic(err)
}
return d
}
// ValidateSettings provides functionality.
func (c *Check) ValidateSettings() error {
if len(c.Settings) == 0 {
c.Settings = []byte("{}")
}
return nil
}
// GetURL provides functionality.
func (c *Check) GetURL() (string, error) {
// return c.GetSettings()["url"].(string)
if c.URL != nil {
return *c.URL, nil
}
return "http://" + c.Monitor.Host, nil
}
// MetricName provides functionality.
func (c *Check) MetricName() string {
sanitized := strings.Map(func(r rune) rune {
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == ':' {
return r
}
return '_'
}, c.Kind)
return "c" + sanitized
}
// QuorumEnabled reports whether this check should be aggregated by
// app/models/check_aggregator.go. When false (RequireQuorum <= 1),
// ApplyRemoteCheckResult keeps the legacy direct State update path.
func (c *Check) QuorumEnabled() bool {
return c.RequireQuorum > 1
}
// AggregationWindow returns AggregationWindowSeconds as a time.Duration.
// Defaults to 5s when the underlying int is zero/negative, mirroring the
// GORM column default; callers can rely on a strictly positive value.
func (c *Check) AggregationWindow() time.Duration {
if c.AggregationWindowSeconds <= 0 {
return 5 * time.Second
}
return time.Duration(c.AggregationWindowSeconds) * time.Second
}