172 строки
4.5 KiB
Go
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
|
|
}
|