package api import "time" // Alert is the server's record of what Alertmanager said. It is read-only: // acknowledging, assigning, noting and resolving all happen on the Incident an // alert belongs to. type Alert struct { ID int64 `json:"id"` TeamID int64 `json:"team_id"` TeamName string `json:"team_name,omitempty"` Fingerprint string `json:"fingerprint"` Name string `json:"name"` Status string `json:"status"` Labels map[string]string `json:"labels"` Annotations map[string]string `json:"annotations"` StartsAt time.Time `json:"starts_at"` EndsAt *time.Time `json:"ends_at"` GeneratorURL string `json:"generator_url"` ReceivedAt time.Time `json:"received_at"` ArchivedAt *time.Time `json:"archived_at,omitempty"` // IncidentID is the most recent incident this alert belongs to. An alert row // is reused across occurrences of the same fingerprint, so it belongs to a // series of incidents over its life and this is only the newest. IncidentID *int64 `json:"incident_id,omitempty"` // ResolutionSource records why a resolved alert left the firing state: // "alertmanager" for a real resolved webhook, "expiry" when the server // inferred it after the alert stopped being refreshed, "deadman" for a // dead man's switch that came back. Treat the value set as open. ResolutionSource *string `json:"resolution_source,omitempty"` } // Incident statuses. const ( StatusTriggered = "triggered" StatusAcknowledged = "acknowledged" StatusResolved = "resolved" ) // Incident is the work item: what a person acknowledges, assigns, snoozes, // discusses and resolves. Many alerts map to one incident, correlated by the // groupKey Alertmanager computed from the operator's group_by configuration. type Incident struct { ID int64 `json:"id"` TeamID int64 `json:"team_id"` TeamName string `json:"team_name,omitempty"` GroupKey string `json:"group_key"` Title string `json:"title"` GroupLabels map[string]string `json:"group_labels"` Status string `json:"status"` // Severity is a high-water mark across the incident's alerts, never lowered, // so a resolved incident still says how bad it got. Severity string `json:"severity,omitempty"` TriggeredAt time.Time `json:"triggered_at"` AcknowledgedByID *int64 `json:"acknowledged_by_id,omitempty"` AcknowledgedBy string `json:"acknowledged_by,omitempty"` AcknowledgedAt *time.Time `json:"acknowledged_at,omitempty"` AssignedToID *int64 `json:"assigned_to_id,omitempty"` AssignedTo string `json:"assigned_to,omitempty"` SnoozedUntil *time.Time `json:"snoozed_until,omitempty"` ResolvedAt *time.Time `json:"resolved_at,omitempty"` // ResolutionSource is "alerts" when every alert stopped firing, "manual" // when a person closed it, or "recovered" when a dead man's switch came back. // Treat the value set as open. ResolutionSource *string `json:"resolution_source,omitempty"` // EscalationLevel is how far up the team's escalation ladder the incident has // climbed (0 = not escalated). EscalationDueAt is when the next step fires, // and is nil once the ladder is exhausted or the incident is acknowledged. EscalationLevel int `json:"escalation_level"` EscalationDueAt *time.Time `json:"escalation_due_at,omitempty"` ArchivedAt *time.Time `json:"archived_at,omitempty"` // Alerts is populated by GET /api/incidents/{id} only. Alerts []Alert `json:"alerts,omitempty"` } // IsSnoozed reports whether the incident is currently quietened. A snooze // expires by falling into the past; nothing on the server sweeps it. func (i Incident) IsSnoozed() bool { return i.SnoozedUntil != nil && i.SnoozedUntil.After(time.Now()) } // IsOpen reports whether the incident is still work in progress. func (i Incident) IsOpen() bool { return i.ResolvedAt == nil } // Incident timeline event types written by the server. New ones may be added, // so render unrecognised types generically rather than dropping them. const ( EventTriggered = "triggered" EventAlertAdded = "alert_added" EventAlertResolved = "alert_resolved" EventAcknowledged = "acknowledged" EventUnacknowledged = "unacknowledged" EventAssigned = "assigned" EventSnoozed = "snoozed" EventUnsnoozed = "unsnoozed" EventResolved = "resolved" EventNote = "note" // A note marked as what fixed the incident. The server leads similar // incidents with these. EventResolutionNote = "resolution_note" // Written when a team's dead man's switch stops reporting. EventDeadmanSilent = "deadman_silent" // Written by the server's notifier from the delivery result, not at enqueue. // Detail carries the notification kind ("triggered", "reminder", "resolved"), // and on a failure the reason after it. An absent user means the page went to // the shared fallback topic rather than to a person. EventNotified = "notified" EventNotifyFailed = "notify_failed" ) // IncidentEvent is one entry in an incident's timeline. An empty Username means // the server acted rather than a person. On an "assigned" event the user is the // assignee, not the actor. type IncidentEvent struct { ID int64 `json:"id"` IncidentID int64 `json:"incident_id"` Type string `json:"type"` UserID *int64 `json:"user_id,omitempty"` Username string `json:"username,omitempty"` AlertID *int64 `json:"alert_id,omitempty"` Detail string `json:"detail,omitempty"` CreatedAt time.Time `json:"created_at"` } type AlertStats struct { Total int `json:"total"` Firing int `json:"firing"` Resolved int `json:"resolved"` } // IncidentStats carries the queue counts plus mean time to acknowledge and to // resolve. Both averages are nil until something has actually been acknowledged // or resolved — that is "no data", not zero. type IncidentStats struct { Total int `json:"total"` Triggered int `json:"triggered"` Acknowledged int `json:"acknowledged"` Resolved int `json:"resolved"` MTTASeconds *float64 `json:"mtta_seconds"` MTTRSeconds *float64 `json:"mttr_seconds"` } type TopAlert struct { Name string `json:"name"` Count int `json:"count"` } type HourStat struct { Hour int `json:"hour"` Count int `json:"count"` } type DayStat struct { Day int `json:"day"` DayName string `json:"day_name"` Count int `json:"count"` } type ScheduleEntry struct { ID int64 `json:"id"` TeamID int64 `json:"team_id"` TeamName string `json:"team_name,omitempty"` UserID int64 `json:"user_id"` Username string `json:"username"` Date string `json:"date"` // YYYY-MM-DD CreatedAt time.Time `json:"created_at"` } type User struct { ID int64 `json:"id"` Username string `json:"username"` Email string `json:"email"` CreatedAt time.Time `json:"created_at"` // IsAdmin marks a system administrator: the only kind of user who can create // or delete users and act on other people's passwords and keys. IsAdmin bool `json:"is_admin"` // DisabledAt is set when an administrator has disabled the account. A // disabled user cannot sign in or use their keys. DisabledAt *time.Time `json:"disabled_at,omitempty"` // NtfyTopic is where this user's push notifications go. Nil and empty mean // the same thing — no topic of their own — because the server stores a blank // string as NULL. Their incidents fall back to the server's shared fallback // topic, which carries no Acknowledge button. NtfyTopic *string `json:"ntfy_topic,omitempty"` } // Topic reads the user's ntfy topic, flattening the nil and empty cases the // server treats alike. func (u User) Topic() string { if u.NtfyTopic == nil { return "" } return *u.NtfyTopic } // IsDisabled reports whether the account has been disabled. func (u User) IsDisabled() bool { return u.DisabledAt != nil } // Me is GET /api/me: the caller, and whether they can sign in to the web UI. type Me struct { User User `json:"user"` HasPassword bool `json:"has_password"` } // Team roles. const ( RoleOwner = "owner" RoleMember = "member" ) // Team is a group that owns integrations, incidents, a schedule and an // escalation ladder. Role is the caller's role in it, and is only present on // the caller's own team lists (GET /api/teams). type Team struct { ID int64 `json:"id"` Name string `json:"name"` CreatedAt time.Time `json:"created_at"` Role string `json:"role,omitempty"` } // TeamMember is one person's membership of a team. type TeamMember struct { TeamID int64 `json:"team_id"` UserID int64 `json:"user_id"` Username string `json:"username"` Role string `json:"role"` JoinedAt time.Time `json:"joined_at"` } type APIKey struct { ID int64 `json:"id"` UserID int64 `json:"user_id"` Name string `json:"name"` Key string `json:"key,omitempty"` // only present in create response CreatedAt time.Time `json:"created_at"` LastUsedAt *time.Time `json:"last_used_at"` } // SimilarIncident is an earlier, resolved incident with the same signature // (alert name plus stable group labels) as the one being viewed. ResolutionNotes // are its "what fixed it" notes; NoteCount counts its plain notes. type SimilarIncident struct { ID int64 `json:"id"` Title string `json:"title"` TriggeredAt time.Time `json:"triggered_at"` ResolvedAt time.Time `json:"resolved_at"` NoteCount int `json:"note_count"` ResolutionNotes []IncidentEvent `json:"resolution_notes"` }