Add next broadcast timer and enhance web security

This commit is contained in:
Jeffrey Paul 2025-05-22 09:12:46 -07:00
parent 68ce2c88d2
commit ae93557d15
3 changed files with 76 additions and 5 deletions

View File

@ -32,6 +32,8 @@ type DashboardData struct {
TotalBroadcast int TotalBroadcast int
NewInLastHour int NewInLastHour int
UnsummarizedCount int UnsummarizedCount int
NextBroadcastIn string // Time until the next broadcast attempt
LastBroadcastTime time.Time // When the last broadcast occurred
NextUp []Article NextUp []Article
History []Article History []Article
RecentLogs []LogEntry RecentLogs []LogEntry

View File

@ -186,6 +186,10 @@
<div class="stat-label">Awaiting Summary</div> <div class="stat-label">Awaiting Summary</div>
<div class="stat-number">{{.UnsummarizedCount}}</div> <div class="stat-number">{{.UnsummarizedCount}}</div>
</div> </div>
<div class="stat-box">
<div class="stat-label">Next Broadcast In</div>
<div class="stat-number">{{.NextBroadcastIn}}</div>
</div>
</div> </div>
<div class="section"> <div class="section">

View File

@ -2,6 +2,7 @@ package main
import ( import (
"context" "context"
"fmt"
"html/template" "html/template"
"net/http" "net/http"
"strings" "strings"
@ -19,8 +20,30 @@ func webServer(shutdown chan struct{}) {
return return
} }
// Define HTTP handlers // Create a custom request handler with security headers
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Add security headers
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'")
w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
// Enforce request method
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Limit request body size (1MB)
r.Body = http.MaxBytesReader(w, r.Body, 1<<20)
// Only serve the index page
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
data, err := getDashboardData() data, err := getDashboardData()
if err != nil { if err != nil {
http.Error(w, "Error fetching data: "+err.Error(), http.StatusInternalServerError) http.Error(w, "Error fetching data: "+err.Error(), http.StatusInternalServerError)
@ -42,10 +65,14 @@ func webServer(shutdown chan struct{}) {
} }
}) })
// Start the server // Configure server with appropriate timeouts
server := &http.Server{ server := &http.Server{
Addr: ":8080", Addr: ":8080",
Handler: nil, // Use default mux Handler: handler,
ReadTimeout: 10 * time.Second, // Time to read the request
WriteTimeout: 30 * time.Second, // Time to write the response
IdleTimeout: 60 * time.Second, // Keep-alive connections timeout
MaxHeaderBytes: 1 << 20, // 1MB max header size
} }
// Create a goroutine for the server // Create a goroutine for the server
@ -91,9 +118,15 @@ func getDashboardData() (DashboardData, error) {
} }
// Count broadcast articles, recent articles, and unsummarized articles // Count broadcast articles, recent articles, and unsummarized articles
var lastBroadcastTime time.Time
for _, a := range articles { for _, a := range articles {
if !a.BroadcastTime.IsZero() && a.BroadcastTime.Unix() > 1 { if !a.BroadcastTime.IsZero() && a.BroadcastTime.Unix() > 1 {
data.TotalBroadcast++ data.TotalBroadcast++
// Track the most recent broadcast time
if a.BroadcastTime.After(lastBroadcastTime) {
lastBroadcastTime = a.BroadcastTime
}
} }
if a.FirstSeen.After(hourAgo) { if a.FirstSeen.After(hourAgo) {
@ -105,6 +138,38 @@ func getDashboardData() (DashboardData, error) {
} }
} }
// Set the last broadcast time
data.LastBroadcastTime = lastBroadcastTime
// Calculate time until next broadcast
if lastBroadcastTime.IsZero() {
data.NextBroadcastIn = "As soon as articles are summarized"
} else {
nextBroadcastTime := lastBroadcastTime.Add(BROADCAST_INTERVAL)
if now.After(nextBroadcastTime) {
// If we're past the interval but haven't broadcast yet,
// likely waiting for articles to be summarized
if data.UnsummarizedCount > 0 {
data.NextBroadcastIn = "Waiting for articles to be summarized"
} else {
data.NextBroadcastIn = "Pending (checking every " + BROADCAST_CHECK_INTERVAL.String() + ")"
}
} else {
// We're still within the interval, calculate remaining time
timeUntilNextBroadcast := nextBroadcastTime.Sub(now)
// Format as hours and minutes
hours := int(timeUntilNextBroadcast.Hours())
minutes := int(timeUntilNextBroadcast.Minutes()) % 60
if hours > 0 {
data.NextBroadcastIn = fmt.Sprintf("%dh %dm", hours, minutes)
} else {
data.NextBroadcastIn = fmt.Sprintf("%dm", minutes)
}
}
}
// Get broadcast history (last 100) // Get broadcast history (last 100)
history, err := getBroadcastHistory(100) history, err := getBroadcastHistory(100)
if err != nil { if err != nil {