152 lines
3.1 KiB
Go
152 lines
3.1 KiB
Go
|
|
package alerts
|
||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"sort"
|
||
|
|
"strconv"
|
||
|
|
"sync"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
type Status string
|
||
|
|
|
||
|
|
const (
|
||
|
|
StatusOpen Status = "open"
|
||
|
|
StatusAcked Status = "acked"
|
||
|
|
StatusClosed Status = "closed"
|
||
|
|
)
|
||
|
|
|
||
|
|
type Alert struct {
|
||
|
|
ID string `json:"id"`
|
||
|
|
Title string `json:"title"`
|
||
|
|
Severity string `json:"severity"`
|
||
|
|
Status Status `json:"status"`
|
||
|
|
Group string `json:"group"`
|
||
|
|
Code string `json:"code"`
|
||
|
|
Trace string `json:"trace"`
|
||
|
|
Message string `json:"message"`
|
||
|
|
CreatedAt time.Time `json:"created_at"`
|
||
|
|
UpdatedAt time.Time `json:"updated_at"`
|
||
|
|
Meta map[string]string `json:"meta,omitempty"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type Store struct {
|
||
|
|
path string
|
||
|
|
mu sync.Mutex
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewStore(path string) *Store {
|
||
|
|
return &Store{path: path}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Store) Add(alert Alert) error {
|
||
|
|
s.mu.Lock()
|
||
|
|
defer s.mu.Unlock()
|
||
|
|
|
||
|
|
alertsList, err := s.readLocked()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
now := time.Now().UTC()
|
||
|
|
if alert.ID == "" {
|
||
|
|
alert.ID = strconv.FormatInt(now.UnixNano(), 10)
|
||
|
|
}
|
||
|
|
if alert.Status == "" {
|
||
|
|
alert.Status = StatusOpen
|
||
|
|
}
|
||
|
|
if alert.CreatedAt.IsZero() {
|
||
|
|
alert.CreatedAt = now
|
||
|
|
}
|
||
|
|
alert.UpdatedAt = now
|
||
|
|
alertsList = append(alertsList, alert)
|
||
|
|
return s.writeLocked(alertsList)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Store) List(limit int) ([]Alert, error) {
|
||
|
|
s.mu.Lock()
|
||
|
|
defer s.mu.Unlock()
|
||
|
|
alertsList, err := s.readLocked()
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
sort.Slice(alertsList, func(i, j int) bool {
|
||
|
|
return alertsList[i].CreatedAt.After(alertsList[j].CreatedAt)
|
||
|
|
})
|
||
|
|
if limit > 0 && len(alertsList) > limit {
|
||
|
|
return alertsList[:limit], nil
|
||
|
|
}
|
||
|
|
return alertsList, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Store) SetStatus(ids []string, status Status) error {
|
||
|
|
s.mu.Lock()
|
||
|
|
defer s.mu.Unlock()
|
||
|
|
alertsList, err := s.readLocked()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
target := map[string]bool{}
|
||
|
|
for _, id := range ids {
|
||
|
|
target[id] = true
|
||
|
|
}
|
||
|
|
now := time.Now().UTC()
|
||
|
|
for i := range alertsList {
|
||
|
|
if target[alertsList[i].ID] {
|
||
|
|
alertsList[i].Status = status
|
||
|
|
alertsList[i].UpdatedAt = now
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return s.writeLocked(alertsList)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Store) Delete(ids []string) error {
|
||
|
|
s.mu.Lock()
|
||
|
|
defer s.mu.Unlock()
|
||
|
|
alertsList, err := s.readLocked()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
target := map[string]bool{}
|
||
|
|
for _, id := range ids {
|
||
|
|
target[id] = true
|
||
|
|
}
|
||
|
|
kept := make([]Alert, 0, len(alertsList))
|
||
|
|
for _, alert := range alertsList {
|
||
|
|
if !target[alert.ID] {
|
||
|
|
kept = append(kept, alert)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return s.writeLocked(kept)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Store) readLocked() ([]Alert, error) {
|
||
|
|
data, err := os.ReadFile(s.path)
|
||
|
|
if err != nil {
|
||
|
|
if os.IsNotExist(err) {
|
||
|
|
return []Alert{}, nil
|
||
|
|
}
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
if len(data) == 0 {
|
||
|
|
return []Alert{}, nil
|
||
|
|
}
|
||
|
|
var alertsList []Alert
|
||
|
|
if err := json.Unmarshal(data, &alertsList); err != nil {
|
||
|
|
return []Alert{}, nil
|
||
|
|
}
|
||
|
|
return alertsList, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Store) writeLocked(alertsList []Alert) error {
|
||
|
|
if err := os.MkdirAll(filepath.Dir(s.path), 0755); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
data, err := json.MarshalIndent(alertsList, "", " ")
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return os.WriteFile(s.path, data, 0644)
|
||
|
|
}
|