feat(admin): add security and activity management features

This commit is contained in:
2026-05-01 13:10:23 +03:00
parent dd8dd7cdc2
commit 88ab6e808b
26 changed files with 2208 additions and 262 deletions

151
lib/alerts/alerts.go Normal file
View File

@@ -0,0 +1,151 @@
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)
}