package monitoring import ( "encoding/json" "fmt" "html/template" "net/http" "strconv" "time" "github.com/fraktal/mev-beta/internal/logger" ) // DashboardServer provides a web-based monitoring dashboard type DashboardServer struct { logger *logger.Logger integrityMonitor *IntegrityMonitor healthChecker *HealthCheckRunner port int server *http.Server } // NewDashboardServer creates a new dashboard server func NewDashboardServer(logger *logger.Logger, integrityMonitor *IntegrityMonitor, healthChecker *HealthCheckRunner, port int) *DashboardServer { return &DashboardServer{ logger: logger, integrityMonitor: integrityMonitor, healthChecker: healthChecker, port: port, } } // Start starts the dashboard HTTP server func (ds *DashboardServer) Start() error { mux := http.NewServeMux() // Register endpoints mux.HandleFunc("/", ds.handleDashboard) mux.HandleFunc("/api/health", ds.handleAPIHealth) mux.HandleFunc("/api/metrics", ds.handleAPIMetrics) mux.HandleFunc("/api/history", ds.handleAPIHistory) mux.HandleFunc("/api/alerts", ds.handleAPIAlerts) mux.HandleFunc("/static/", ds.handleStatic) ds.server = &http.Server{ Addr: fmt.Sprintf(":%d", ds.port), Handler: mux, } ds.logger.Info("Starting monitoring dashboard", "port", ds.port, "url", fmt.Sprintf("http://localhost:%d", ds.port)) return ds.server.ListenAndServe() } // Stop stops the dashboard server func (ds *DashboardServer) Stop() error { if ds.server != nil { return ds.server.Close() } return nil } // handleDashboard serves the main dashboard HTML page func (ds *DashboardServer) handleDashboard(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") // Get current metrics and health data metrics := ds.integrityMonitor.GetMetrics() healthSummary := ds.integrityMonitor.GetHealthSummary() healthHistory := ds.healthChecker.GetRecentSnapshots(20) // Render dashboard template tmpl := ds.getDashboardTemplate() data := struct { Metrics MetricsSnapshot HealthSummary map[string]interface{} HealthHistory []HealthSnapshot Timestamp time.Time }{ Metrics: metrics, HealthSummary: healthSummary, HealthHistory: healthHistory, Timestamp: time.Now(), } if err := tmpl.Execute(w, data); err != nil { ds.logger.Error("Failed to render dashboard", "error", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } } // handleAPIHealth provides JSON health endpoint func (ds *DashboardServer) handleAPIHealth(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") healthSummary := ds.integrityMonitor.GetHealthSummary() healthCheckerSummary := ds.healthChecker.GetHealthSummary() // Combine summaries response := map[string]interface{}{ "integrity_monitor": healthSummary, "health_checker": healthCheckerSummary, "timestamp": time.Now(), } if err := json.NewEncoder(w).Encode(response); err != nil { ds.logger.Error("Failed to encode health response", "error", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) } } // handleAPIMetrics provides JSON metrics endpoint func (ds *DashboardServer) handleAPIMetrics(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") metrics := ds.integrityMonitor.GetMetrics() if err := json.NewEncoder(w).Encode(metrics); err != nil { ds.logger.Error("Failed to encode metrics response", "error", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) } } // handleAPIHistory provides JSON health history endpoint func (ds *DashboardServer) handleAPIHistory(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") // Get count parameter (default 20) countStr := r.URL.Query().Get("count") count := 20 if countStr != "" { if c, err := strconv.Atoi(countStr); err == nil && c > 0 && c <= 100 { count = c } } history := ds.healthChecker.GetRecentSnapshots(count) if err := json.NewEncoder(w).Encode(history); err != nil { ds.logger.Error("Failed to encode history response", "error", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) } } // handleAPIAlerts provides recent alerts for integrity and health monitoring. func (ds *DashboardServer) handleAPIAlerts(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") limit := 20 if q := r.URL.Query().Get("limit"); q != "" { if parsed, err := strconv.Atoi(q); err == nil && parsed > 0 && parsed <= 200 { limit = parsed } } alerts := ds.integrityMonitor.GetRecentAlerts(limit) payload := map[string]interface{}{ "alerts": alerts, "count": len(alerts), "timestamp": time.Now(), } if err := json.NewEncoder(w).Encode(payload); err != nil { ds.logger.Error("Failed to encode alerts response", "error", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) } } // handleStatic serves static assets (CSS, JS) func (ds *DashboardServer) handleStatic(w http.ResponseWriter, r *http.Request) { // For simplicity, we'll inline CSS and JS in the HTML template // In a production system, you'd serve actual static files http.NotFound(w, r) } // getDashboardTemplate returns the HTML template for the dashboard func (ds *DashboardServer) getDashboardTemplate() *template.Template { htmlTemplate := ` MEV Bot - Data Integrity Monitor

🤖 MEV Bot - Data Integrity Monitor

Real-time monitoring of corruption detection and recovery systems

System Health

{{.HealthSummary.health_score | printf "%.1f"}}
Status {{.HealthSummary.health_score | healthStatus}}
Monitor Enabled {{if .HealthSummary.enabled}}✅ Yes{{else}}❌ No{{end}}

Processing Statistics

Total Addresses {{.Metrics.TotalAddressesProcessed | printf "%,d"}}
Corruption Detected {{.Metrics.CorruptAddressesDetected | printf "%,d"}}
Corruption Rate {{.HealthSummary.corruption_rate | printf "%.4f%%"}}
Avg Corruption Score {{.Metrics.AverageCorruptionScore | printf "%.1f"}}
Max Corruption Score {{.Metrics.MaxCorruptionScore}}

Validation Results

Validation Passed {{.Metrics.AddressValidationPassed | printf "%,d"}}
Validation Failed {{.Metrics.AddressValidationFailed | printf "%,d"}}
Success Rate {{.HealthSummary.validation_success_rate | printf "%.2f%%"}}

Contract Calls

Successful Calls {{.Metrics.ContractCallsSucceeded | printf "%,d"}}
Failed Calls {{.Metrics.ContractCallsFailed | printf "%,d"}}
Success Rate {{.HealthSummary.contract_call_success_rate | printf "%.2f%%"}}

Recovery System Activity

{{.Metrics.RetryOperationsTriggered}}
Retry Operations
{{.Metrics.FallbackOperationsUsed}}
Fallback Used
{{.Metrics.CircuitBreakersTripped}}
Circuit Breakers
Last updated: {{.Timestamp.Format "2006-01-02 15:04:05 UTC"}}
Auto-refresh every 30 seconds
🔄 Live
` // Create template with custom functions funcMap := template.FuncMap{ "healthClass": func(score interface{}) string { s := score.(float64) if s >= 0.9 { return "health-excellent" } else if s >= 0.7 { return "health-good" } else if s >= 0.5 { return "health-fair" } return "health-poor" }, "statusClass": func(score interface{}) string { s := score.(float64) if s >= 0.7 { return "status-healthy" } else if s >= 0.5 { return "status-warning" } return "status-critical" }, "healthStatus": func(score interface{}) string { s := score.(float64) if s >= 0.9 { return "Excellent" } else if s >= 0.7 { return "Good" } else if s >= 0.5 { return "Fair" } return "Poor" }, } return template.Must(template.New("dashboard").Funcs(funcMap).Parse(htmlTemplate)) } // GetDashboardURL returns the dashboard URL func (ds *DashboardServer) GetDashboardURL() string { return fmt.Sprintf("http://localhost:%d", ds.port) }