aboutsummaryrefslogtreecommitdiff
path: root/internal/server/server.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/server/server.go')
-rw-r--r--internal/server/server.go122
1 files changed, 78 insertions, 44 deletions
diff --git a/internal/server/server.go b/internal/server/server.go
index 4217dd2..499ca31 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -162,12 +162,15 @@ type MonitorData struct {
ResponseTime int64
HidePing bool // Hide response time from display
UptimePercent float64
+ UptimeTooltip string // JSON data for uptime tooltip with last failure info
Ticks []*storage.TickData // Aggregated tick data for history bar
SSLDaysLeft int
SSLExpiryDate time.Time
SSLTooltip string // JSON data for SSL expiration tooltip
LastCheck time.Time
LastError string
+ LastFailure *time.Time // Time of last failure
+ LastFailureError string // Error from last failure
DisablePingTooltips bool
}
@@ -269,6 +272,11 @@ func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
md.SSLTooltip = formatSSLTooltip(*stat.SSLExpiry, stat.SSLDaysLeft, s.config.Display.Timezone)
}
+ // Set last failure info and uptime tooltip
+ md.LastFailure = stat.LastFailure
+ md.LastFailureError = stat.LastFailureError
+ md.UptimeTooltip = formatUptimeTooltip(stat.UptimePercent, stat.TotalChecks, stat.LastFailure, stat.LastFailureError, s.config.Display.Timezone)
+
// Track most recent check time for footer
if stat.LastCheck.After(mostRecentCheck) {
mostRecentCheck = stat.LastCheck
@@ -559,50 +567,7 @@ func templateFuncs() template.FuncMap {
}
return "bg-neutral-200 dark:bg-neutral-800"
},
- "simplifyError": func(err string) string {
- if err == "" {
- return ""
- }
- // Common error patterns to simplify
- switch {
- case strings.Contains(err, "no such host"):
- return "DNS lookup failed"
- case strings.Contains(err, "connection refused"):
- return "Connection refused"
- case strings.Contains(err, "connection reset"):
- return "Connection reset"
- case strings.Contains(err, "timeout"):
- return "Timeout"
- case strings.Contains(err, "certificate"):
- return "SSL/TLS error"
- case strings.Contains(err, "EOF"):
- return "Connection closed"
- case strings.Contains(err, "status code"):
- // Extract status code if present
- if idx := strings.Index(err, "status code"); idx != -1 {
- rest := err[idx:]
- // Try to find the number
- for i, c := range rest {
- if c >= '0' && c <= '9' {
- end := i
- for end < len(rest) && rest[end] >= '0' && rest[end] <= '9' {
- end++
- }
- return "HTTP " + rest[i:end]
- }
- }
- }
- return "Unexpected status"
- case strings.Contains(err, "expected content"):
- return "Content mismatch"
- case strings.Contains(err, "i/o timeout"):
- return "I/O timeout"
- case strings.Contains(err, "network is unreachable"):
- return "Network unreachable"
- default:
- return "Error"
- }
- },
+ "simplifyError": simplifyErrorMessage,
"tickTooltipData": func(tick *storage.TickData, mode, timezone string, hidePing bool) string {
if tick == nil {
data := map[string]interface{}{"header": "No data"}
@@ -912,6 +877,75 @@ func formatLastUpdatedTooltip(t time.Time, timezone string) string {
return string(b)
}
+// formatUptimeTooltip creates JSON data for uptime percentage tooltip
+func formatUptimeTooltip(uptimePercent float64, totalChecks int64, lastFailure *time.Time, lastFailureError string, timezone string) string {
+ loc := time.Local
+
+ if timezone != "" && timezone != "Local" && timezone != "Browser" {
+ if l, err := time.LoadLocation(timezone); err == nil {
+ loc = l
+ }
+ }
+
+ rows := []map[string]string{
+ {"label": "Uptime", "value": fmt.Sprintf("%.2f%%", uptimePercent), "class": ""},
+ {"label": "Total Checks", "value": fmt.Sprintf("%d", totalChecks), "class": ""},
+ }
+
+ if lastFailure != nil {
+ t := lastFailure.In(loc)
+ failureTime := t.Format("Jan 2, 2006 15:04:05")
+ rows = append(rows, map[string]string{"label": "Last Failure", "value": failureTime, "class": "error"})
+
+ if lastFailureError != "" {
+ // Simplify the error for display
+ simplifiedError := simplifyErrorMessage(lastFailureError)
+ rows = append(rows, map[string]string{"label": "Failure Reason", "value": simplifiedError, "class": ""})
+ }
+ } else {
+ rows = append(rows, map[string]string{"label": "Last Failure", "value": "Never", "class": "success"})
+ }
+
+ data := map[string]interface{}{
+ "header": "Uptime Statistics",
+ "rows": rows,
+ }
+
+ b, _ := json.Marshal(data)
+ return string(b)
+}
+
+// simplifyErrorMessage simplifies error messages for display
+func simplifyErrorMessage(err string) string {
+ if err == "" {
+ return ""
+ }
+ switch {
+ case strings.Contains(err, "no such host"):
+ return "DNS lookup failed"
+ case strings.Contains(err, "connection refused"):
+ return "Connection refused"
+ case strings.Contains(err, "connection reset"):
+ return "Connection reset"
+ case strings.Contains(err, "timeout"):
+ return "Timeout"
+ case strings.Contains(err, "certificate"):
+ return "SSL/TLS error"
+ case strings.Contains(err, "EOF"):
+ return "Connection closed"
+ case strings.Contains(err, "status code"):
+ return "Unexpected status"
+ case strings.Contains(err, "expected content"):
+ return "Content mismatch"
+ case strings.Contains(err, "i/o timeout"):
+ return "I/O timeout"
+ case strings.Contains(err, "network is unreachable"):
+ return "Network unreachable"
+ default:
+ return "Error"
+ }
+}
+
// statusToClass converts a status to a CSS class
func statusToClass(status string) string {
switch status {