144 lines
3.5 KiB
Go
144 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"html/template"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// webServer runs the web interface on port 8080
|
|
func webServer(shutdown chan struct{}) {
|
|
// Load templates
|
|
tmpl, err := template.ParseFiles("templates/index.html")
|
|
if err != nil {
|
|
logInfo("web", "Error loading templates", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
// Define HTTP handlers
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
data, err := getDashboardData()
|
|
if err != nil {
|
|
http.Error(w, "Error fetching data: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
err = tmpl.Execute(w, data)
|
|
if err != nil {
|
|
// Check if it's already too late to write headers
|
|
if !isResponseHeaderWritten(err) {
|
|
http.Error(w, "Error rendering template: "+err.Error(), http.StatusInternalServerError)
|
|
} else {
|
|
// Log the error but don't try to write headers again
|
|
logInfo("web", "Template execution error after headers sent", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
return
|
|
}
|
|
})
|
|
|
|
// Start the server
|
|
server := &http.Server{
|
|
Addr: ":8080",
|
|
Handler: nil, // Use default mux
|
|
}
|
|
|
|
// Create a goroutine for the server
|
|
go func() {
|
|
logInfo("web", "Starting web server", map[string]interface{}{
|
|
"port": 8080,
|
|
})
|
|
|
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
logInfo("web", "Web server error", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
}()
|
|
|
|
// Wait for shutdown signal
|
|
<-shutdown
|
|
|
|
// Create a deadline for server shutdown
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
// Attempt graceful shutdown
|
|
if err := server.Shutdown(ctx); err != nil {
|
|
logInfo("web", "Error during server shutdown", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
|
|
logInfo("web", "Web server stopped", nil)
|
|
}
|
|
|
|
// getDashboardData fetches the data needed for the dashboard
|
|
func getDashboardData() (DashboardData, error) {
|
|
articles := loadArticles()
|
|
now := time.Now()
|
|
hourAgo := now.Add(-60 * time.Minute)
|
|
|
|
// Prepare the data structure
|
|
data := DashboardData{
|
|
LastUpdated: now.Format("Jan 02, 2006 15:04:05 MST"),
|
|
TotalArticles: len(articles),
|
|
}
|
|
|
|
// Count broadcast articles, recent articles, and unsummarized articles
|
|
for _, a := range articles {
|
|
if !a.BroadcastTime.IsZero() && a.BroadcastTime.Unix() > 1 {
|
|
data.TotalBroadcast++
|
|
}
|
|
|
|
if a.FirstSeen.After(hourAgo) {
|
|
data.NewInLastHour++
|
|
}
|
|
|
|
if a.Summary == "" || a.Importance == 0 {
|
|
data.UnsummarizedCount++
|
|
}
|
|
}
|
|
|
|
// Get broadcast history (last 100)
|
|
history, err := getBroadcastHistory(100)
|
|
if err != nil {
|
|
return data, err
|
|
}
|
|
data.History = history
|
|
|
|
// Get next up articles (importance sorted, less than 24 hours old)
|
|
nextUp, err := getNextUpArticles()
|
|
if err != nil {
|
|
return data, err
|
|
}
|
|
data.NextUp = nextUp
|
|
|
|
// Get recent logs
|
|
recentLogs, err := getRecentLogs(100)
|
|
if err != nil {
|
|
logInfo("web", "Error fetching recent logs", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
// Continue with empty logs list
|
|
recentLogs = []LogEntry{}
|
|
}
|
|
data.RecentLogs = recentLogs
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// isResponseHeaderWritten checks if an error indicates headers were already written
|
|
func isResponseHeaderWritten(err error) bool {
|
|
// Check common patterns that indicate the response was already partially written
|
|
errStr := err.Error()
|
|
return strings.Contains(errStr, "write: broken pipe") ||
|
|
strings.Contains(errStr, "write: connection reset by peer") ||
|
|
strings.Contains(errStr, "http: superfluous response.WriteHeader")
|
|
}
|