diff options
| author | Fuwn <[email protected]> | 2026-01-19 16:53:39 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-01-19 16:53:39 -0800 |
| commit | b83baaae6735eccca31a94e630fdeb67919733a0 (patch) | |
| tree | 625deb2572ed2e8cefa607e88c764c7c21344802 /internal/monitor | |
| parent | feat: Add reset_on_next_check flag to wipe monitor history (diff) | |
| download | kaze-b83baaae6735eccca31a94e630fdeb67919733a0.tar.xz kaze-b83baaae6735eccca31a94e630fdeb67919733a0.zip | |
feat: Add group defaults, content checking, SSL tracking for Gemini, hide/round options
Diffstat (limited to 'internal/monitor')
| -rw-r--r-- | internal/monitor/gemini.go | 52 | ||||
| -rw-r--r-- | internal/monitor/http.go | 88 | ||||
| -rw-r--r-- | internal/monitor/monitor.go | 27 | ||||
| -rw-r--r-- | internal/monitor/tcp.go | 32 |
4 files changed, 142 insertions, 57 deletions
diff --git a/internal/monitor/gemini.go b/internal/monitor/gemini.go index d92499b..fb75b7d 100644 --- a/internal/monitor/gemini.go +++ b/internal/monitor/gemini.go @@ -14,12 +14,14 @@ import ( // GeminiMonitor monitors Gemini protocol endpoints type GeminiMonitor struct { - name string - target string - interval time.Duration - timeout time.Duration - retries int - verifySSL bool + name string + target string + interval time.Duration + timeout time.Duration + retries int + verifySSL bool + hideSSLDays bool + roundResponseTime bool } // NewGeminiMonitor creates a new Gemini monitor @@ -50,12 +52,14 @@ func NewGeminiMonitor(cfg config.MonitorConfig) (*GeminiMonitor, error) { } return &GeminiMonitor{ - name: cfg.Name, - target: target, - interval: cfg.Interval.Duration, - timeout: cfg.Timeout.Duration, - retries: cfg.Retries, - verifySSL: verifySSL, + name: cfg.Name, + target: target, + interval: cfg.Interval.Duration, + timeout: cfg.Timeout.Duration, + retries: cfg.Retries, + verifySSL: verifySSL, + hideSSLDays: cfg.HideSSLDays, + roundResponseTime: cfg.RoundResponseTime, }, nil } @@ -84,6 +88,16 @@ func (m *GeminiMonitor) Retries() int { return m.retries } +// HideSSLDays returns whether to hide SSL days from display +func (m *GeminiMonitor) HideSSLDays() bool { + return m.hideSSLDays +} + +// RoundResponseTime returns whether to round response time +func (m *GeminiMonitor) RoundResponseTime() bool { + return m.roundResponseTime +} + // Check performs the Gemini protocol check func (m *GeminiMonitor) Check(ctx context.Context) *Result { result := &Result{ @@ -119,14 +133,12 @@ func (m *GeminiMonitor) Check(ctx context.Context) *Result { } defer conn.Close() - // Check SSL certificate - if m.verifySSL { - connState := conn.ConnectionState() - if len(connState.PeerCertificates) > 0 { - cert := connState.PeerCertificates[0] - result.SSLExpiry = &cert.NotAfter - result.SSLDaysLeft = int(time.Until(cert.NotAfter).Hours() / 24) - } + // Check SSL certificate (always track, even if not verifying) + connState := conn.ConnectionState() + if len(connState.PeerCertificates) > 0 { + cert := connState.PeerCertificates[0] + result.SSLExpiry = &cert.NotAfter + result.SSLDaysLeft = int(time.Until(cert.NotAfter).Hours() / 24) } // Set deadline for the entire operation diff --git a/internal/monitor/http.go b/internal/monitor/http.go index 5587a5f..b8a0177 100644 --- a/internal/monitor/http.go +++ b/internal/monitor/http.go @@ -15,18 +15,21 @@ import ( // HTTPMonitor monitors HTTP and HTTPS endpoints type HTTPMonitor struct { - name string - monitorType string - target string - interval time.Duration - timeout time.Duration - retries int - method string - headers map[string]string - body string - expectedStatus int - verifySSL bool - client *http.Client + name string + monitorType string + target string + interval time.Duration + timeout time.Duration + retries int + method string + headers map[string]string + body string + expectedStatus int + expectedContent string + verifySSL bool + hideSSLDays bool + roundResponseTime bool + client *http.Client } // NewHTTPMonitor creates a new HTTP/HTTPS monitor @@ -77,18 +80,21 @@ func NewHTTPMonitor(cfg config.MonitorConfig) (*HTTPMonitor, error) { } return &HTTPMonitor{ - name: cfg.Name, - monitorType: cfg.Type, - target: target, - interval: cfg.Interval.Duration, - timeout: cfg.Timeout.Duration, - retries: cfg.Retries, - method: cfg.Method, - headers: cfg.Headers, - body: cfg.Body, - expectedStatus: cfg.ExpectedStatus, - verifySSL: verifySSL, - client: client, + name: cfg.Name, + monitorType: cfg.Type, + target: target, + interval: cfg.Interval.Duration, + timeout: cfg.Timeout.Duration, + retries: cfg.Retries, + method: cfg.Method, + headers: cfg.Headers, + body: cfg.Body, + expectedStatus: cfg.ExpectedStatus, + expectedContent: cfg.ExpectedContent, + verifySSL: verifySSL, + hideSSLDays: cfg.HideSSLDays, + roundResponseTime: cfg.RoundResponseTime, + client: client, }, nil } @@ -117,6 +123,16 @@ func (m *HTTPMonitor) Retries() int { return m.retries } +// HideSSLDays returns whether to hide SSL days from display +func (m *HTTPMonitor) HideSSLDays() bool { + return m.hideSSLDays +} + +// RoundResponseTime returns whether to round response time +func (m *HTTPMonitor) RoundResponseTime() bool { + return m.roundResponseTime +} + // Check performs the HTTP/HTTPS check func (m *HTTPMonitor) Check(ctx context.Context) *Result { result := &Result{ @@ -155,8 +171,20 @@ func (m *HTTPMonitor) Check(ctx context.Context) *Result { } defer resp.Body.Close() - // Discard body to allow connection reuse - io.Copy(io.Discard, resp.Body) + // Read body if we need to check content, otherwise discard + var bodyContent string + if m.expectedContent != "" { + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + result.Status = StatusDown + result.Error = fmt.Errorf("failed to read response body: %w", err) + return result + } + bodyContent = string(bodyBytes) + } else { + // Discard body to allow connection reuse + io.Copy(io.Discard, resp.Body) + } result.StatusCode = resp.StatusCode @@ -179,6 +207,14 @@ func (m *HTTPMonitor) Check(ctx context.Context) *Result { result.Error = fmt.Errorf("bad status code: %d", resp.StatusCode) } + // Check expected content if specified + if m.expectedContent != "" && result.Status == StatusUp { + if !strings.Contains(bodyContent, m.expectedContent) { + result.Status = StatusDown + result.Error = fmt.Errorf("expected content not found in response") + } + } + // Check for slow response (degraded if > 2 seconds) if result.Status == StatusUp && result.ResponseTime > 2*time.Second { result.Status = StatusDegraded diff --git a/internal/monitor/monitor.go b/internal/monitor/monitor.go index b0c2f4f..cbf01b3 100644 --- a/internal/monitor/monitor.go +++ b/internal/monitor/monitor.go @@ -34,7 +34,7 @@ type Monitor interface { // Name returns the monitor's name Name() string - // Type returns the monitor type (http, https, tcp) + // Type returns the monitor type (http, https, tcp, gemini) Type() string // Target returns the monitor target (URL or host:port) @@ -46,6 +46,12 @@ type Monitor interface { // Retries returns the number of retry attempts Retries() int + // HideSSLDays returns whether to hide SSL days from display + HideSSLDays() bool + + // RoundResponseTime returns whether to round response time + RoundResponseTime() bool + // Check performs the monitoring check and returns the result Check(ctx context.Context) *Result } @@ -89,3 +95,22 @@ func (r *Result) ToCheckResult() *storage.CheckResult { } return cr } + +// ToCheckResultWithOptions converts a monitor Result to a storage CheckResult with display options applied +func (r *Result) ToCheckResultWithOptions(mon Monitor) *storage.CheckResult { + cr := r.ToCheckResult() + + // Apply rounding if enabled + if mon.RoundResponseTime() { + // Round to nearest second (1000ms) + cr.ResponseTime = ((cr.ResponseTime + 500) / 1000) * 1000 + } + + // Hide SSL days if enabled + if mon.HideSSLDays() { + cr.SSLDaysLeft = 0 + cr.SSLExpiry = nil + } + + return cr +} diff --git a/internal/monitor/tcp.go b/internal/monitor/tcp.go index d315545..d14a5f1 100644 --- a/internal/monitor/tcp.go +++ b/internal/monitor/tcp.go @@ -11,11 +11,12 @@ import ( // TCPMonitor monitors TCP endpoints type TCPMonitor struct { - name string - target string - interval time.Duration - timeout time.Duration - retries int + name string + target string + interval time.Duration + timeout time.Duration + retries int + roundResponseTime bool } // NewTCPMonitor creates a new TCP monitor @@ -27,11 +28,12 @@ func NewTCPMonitor(cfg config.MonitorConfig) (*TCPMonitor, error) { } return &TCPMonitor{ - name: cfg.Name, - target: cfg.Target, - interval: cfg.Interval.Duration, - timeout: cfg.Timeout.Duration, - retries: cfg.Retries, + name: cfg.Name, + target: cfg.Target, + interval: cfg.Interval.Duration, + timeout: cfg.Timeout.Duration, + retries: cfg.Retries, + roundResponseTime: cfg.RoundResponseTime, }, nil } @@ -60,6 +62,16 @@ func (m *TCPMonitor) Retries() int { return m.retries } +// HideSSLDays returns whether to hide SSL days from display +func (m *TCPMonitor) HideSSLDays() bool { + return false // TCP doesn't use SSL +} + +// RoundResponseTime returns whether to round response time +func (m *TCPMonitor) RoundResponseTime() bool { + return m.roundResponseTime +} + // Check performs the TCP connection check func (m *TCPMonitor) Check(ctx context.Context) *Result { result := &Result{ |