aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/config/config.go70
-rw-r--r--internal/monitor/scheduler.go53
-rw-r--r--internal/storage/sqlite.go21
3 files changed, 122 insertions, 22 deletions
diff --git a/internal/config/config.go b/internal/config/config.go
index 1c033e1..8839906 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -63,17 +63,18 @@ type GroupConfig struct {
// MonitorConfig represents a single monitor
type MonitorConfig struct {
- Name string `yaml:"name"`
- Type string `yaml:"type"` // http, https, tcp
- Target string `yaml:"target"`
- Interval Duration `yaml:"interval"`
- Timeout Duration `yaml:"timeout"`
- Retries int `yaml:"retries,omitempty"` // Number of retry attempts before marking as down
- ExpectedStatus int `yaml:"expected_status,omitempty"`
- VerifySSL *bool `yaml:"verify_ssl,omitempty"`
- Method string `yaml:"method,omitempty"`
- Headers map[string]string `yaml:"headers,omitempty"`
- Body string `yaml:"body,omitempty"`
+ Name string `yaml:"name"`
+ Type string `yaml:"type"` // http, https, tcp, gemini
+ Target string `yaml:"target"`
+ Interval Duration `yaml:"interval"`
+ Timeout Duration `yaml:"timeout"`
+ Retries int `yaml:"retries,omitempty"` // Number of retry attempts before marking as down
+ ResetOnNextCheck bool `yaml:"reset_on_next_check,omitempty"` // Wipe monitor data on next check and flip to false
+ ExpectedStatus int `yaml:"expected_status,omitempty"`
+ VerifySSL *bool `yaml:"verify_ssl,omitempty"`
+ Method string `yaml:"method,omitempty"`
+ Headers map[string]string `yaml:"headers,omitempty"`
+ Body string `yaml:"body,omitempty"`
}
// IncidentConfig represents an incident or maintenance
@@ -308,3 +309,50 @@ type MonitorWithGroup struct {
GroupName string
Monitor MonitorConfig
}
+
+// UpdateResetFlag updates the reset_on_next_check flag for a specific monitor in the config file
+func UpdateResetFlag(configPath string, monitorName string, value bool) error {
+ // Read the config file
+ data, err := os.ReadFile(configPath)
+ if err != nil {
+ return fmt.Errorf("failed to read config file: %w", err)
+ }
+
+ // Parse as YAML
+ var cfg Config
+ if err := yaml.Unmarshal(data, &cfg); err != nil {
+ return fmt.Errorf("failed to parse config file: %w", err)
+ }
+
+ // Find and update the monitor
+ found := false
+ for i := range cfg.Groups {
+ for j := range cfg.Groups[i].Monitors {
+ if cfg.Groups[i].Monitors[j].Name == monitorName {
+ cfg.Groups[i].Monitors[j].ResetOnNextCheck = value
+ found = true
+ break
+ }
+ }
+ if found {
+ break
+ }
+ }
+
+ if !found {
+ return fmt.Errorf("monitor %q not found in config", monitorName)
+ }
+
+ // Marshal back to YAML
+ newData, err := yaml.Marshal(&cfg)
+ if err != nil {
+ return fmt.Errorf("failed to marshal config: %w", err)
+ }
+
+ // Write back to file
+ if err := os.WriteFile(configPath, newData, 0644); err != nil {
+ return fmt.Errorf("failed to write config file: %w", err)
+ }
+
+ return nil
+}
diff --git a/internal/monitor/scheduler.go b/internal/monitor/scheduler.go
index 5a7e817..1478732 100644
--- a/internal/monitor/scheduler.go
+++ b/internal/monitor/scheduler.go
@@ -12,23 +12,27 @@ import (
// Scheduler manages and runs all monitors
type Scheduler struct {
- monitors []Monitor
- storage *storage.Storage
- logger *slog.Logger
- wg sync.WaitGroup
- ctx context.Context
- cancel context.CancelFunc
+ monitors []Monitor
+ monitorCfg map[string]config.MonitorConfig // Monitor configs by name for reset flag checks
+ configPath string
+ storage *storage.Storage
+ logger *slog.Logger
+ wg sync.WaitGroup
+ ctx context.Context
+ cancel context.CancelFunc
}
// NewScheduler creates a new monitor scheduler
-func NewScheduler(cfg *config.Config, store *storage.Storage, logger *slog.Logger) (*Scheduler, error) {
+func NewScheduler(cfg *config.Config, store *storage.Storage, logger *slog.Logger, configPath string) (*Scheduler, error) {
ctx, cancel := context.WithCancel(context.Background())
s := &Scheduler{
- storage: store,
- logger: logger,
- ctx: ctx,
- cancel: cancel,
+ monitorCfg: make(map[string]config.MonitorConfig),
+ configPath: configPath,
+ storage: store,
+ logger: logger,
+ ctx: ctx,
+ cancel: cancel,
}
// Create monitors from configuration
@@ -40,6 +44,7 @@ func NewScheduler(cfg *config.Config, store *storage.Storage, logger *slog.Logge
return nil, err
}
s.monitors = append(s.monitors, mon)
+ s.monitorCfg[monCfg.Name] = monCfg // Store config for reset flag checks
logger.Info("registered monitor",
"name", mon.Name(),
"type", mon.Type(),
@@ -96,6 +101,32 @@ func (s *Scheduler) runMonitor(mon Monitor) {
// executeCheck performs a single check and saves the result
func (s *Scheduler) executeCheck(mon Monitor) {
+ // Check if reset flag is set for this monitor
+ if monCfg, exists := s.monitorCfg[mon.Name()]; exists && monCfg.ResetOnNextCheck {
+ s.logger.Info("resetting monitor data", "name", mon.Name())
+
+ // Delete all historical data for this monitor
+ if err := s.storage.ResetMonitorData(s.ctx, mon.Name()); err != nil {
+ s.logger.Error("failed to reset monitor data",
+ "name", mon.Name(),
+ "error", err)
+ } else {
+ s.logger.Info("monitor data reset complete", "name", mon.Name())
+
+ // Flip the reset flag to false in the config file
+ if err := config.UpdateResetFlag(s.configPath, mon.Name(), false); err != nil {
+ s.logger.Error("failed to update reset flag in config",
+ "name", mon.Name(),
+ "error", err)
+ } else {
+ // Update in-memory config
+ monCfg.ResetOnNextCheck = false
+ s.monitorCfg[mon.Name()] = monCfg
+ s.logger.Info("reset flag cleared in config", "name", mon.Name())
+ }
+ }
+ }
+
// Create a context with timeout for this check
checkCtx, cancel := context.WithTimeout(s.ctx, mon.Interval())
defer cancel()
diff --git a/internal/storage/sqlite.go b/internal/storage/sqlite.go
index e08e4ee..48cf432 100644
--- a/internal/storage/sqlite.go
+++ b/internal/storage/sqlite.go
@@ -673,6 +673,27 @@ func (s *Storage) Cleanup(ctx context.Context) error {
return nil
}
+// ResetMonitorData deletes all historical data for a specific monitor
+func (s *Storage) ResetMonitorData(ctx context.Context, monitorName string) error {
+ // Delete from check_results
+ _, err := s.db.ExecContext(ctx, `
+ DELETE FROM check_results WHERE monitor_name = ?
+ `, monitorName)
+ if err != nil {
+ return fmt.Errorf("failed to delete check_results for monitor %q: %w", monitorName, err)
+ }
+
+ // Delete from daily_stats
+ _, err = s.db.ExecContext(ctx, `
+ DELETE FROM daily_stats WHERE monitor_name = ?
+ `, monitorName)
+ if err != nil {
+ return fmt.Errorf("failed to delete daily_stats for monitor %q: %w", monitorName, err)
+ }
+
+ return nil
+}
+
// Close closes the database connection
func (s *Storage) Close() error {
return s.db.Close()