diff --git a/.gitignore b/.gitignore index adbb97d..0bdaddb 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +*.db data/ \ No newline at end of file diff --git a/TODO.md b/TODO.md index 4abb399..bb6b488 100644 --- a/TODO.md +++ b/TODO.md @@ -2,4 +2,3 @@ - [ ] View incidents - [ ] Latency graphs? - [ ] README -- [ ] Site metadata diff --git a/config.toml b/config.toml index 67599c8..3c4d2bd 100644 --- a/config.toml +++ b/config.toml @@ -5,7 +5,7 @@ title = "trafficlunar" link_text = "go back" link_url = "https://trafficlunar.net" -# Default timeline shown on the index page +# Default timeline shown # Users can still switch views manually # # Available options: @@ -14,7 +14,7 @@ link_url = "https://trafficlunar.net" # "days" - past 30 days default_view = "hours" -# Default theme for the status page +# Default theme # Available themes: https://github.com/trafficlunar/statsys/tree/main/www/themes # Defaults to "monochrome" default_theme = "color" @@ -22,6 +22,10 @@ default_theme = "color" # Defaults to true enable_theme_switcher = true +# Set to true to show a small statsys watermark +# Defaults to true +enable_watermark = true + # List of services to monitor [[services]] name = "website" diff --git a/internal/config.go b/internal/config.go index 418e53d..d309cfd 100644 --- a/internal/config.go +++ b/internal/config.go @@ -20,6 +20,7 @@ type Config struct { DefaultView string `toml:"default_view"` DefaultTheme string `toml:"default_theme"` EnableThemeSwitcher bool `toml:"enable_theme_switcher"` + EnableWatermark bool `toml:"enable_watermark"` Services []ServiceConfig `toml:"services"` } @@ -39,6 +40,7 @@ func LoadConfig() { templateData.LinkText = config.LinkText templateData.LinkUrl = config.LinkUrl templateData.EnableThemeSwitcher = config.EnableThemeSwitcher + templateData.EnableWatermark = config.EnableWatermark templateData.Services = make([]Service, len(config.Services)) for index, ser := range config.Services { // default to 1000ms diff --git a/internal/database.go b/internal/database.go index d3258d9..198842e 100644 --- a/internal/database.go +++ b/internal/database.go @@ -45,6 +45,8 @@ func InitDatabase() { log.Fatalf("failed to create table: %v", err) } + templateDataMu.Lock() + // load template data from database for index, service := range templateData.Services { // timelines @@ -97,6 +99,7 @@ func InitDatabase() { templateData.Services[index] = service } + templateDataMu.Unlock() log.Printf("database initalized at '%s'", *DatabasePath) } @@ -332,8 +335,10 @@ func AddToTimeline(serviceIndex int, status string) { } } + templateDataMu.Lock() templateData.Services[serviceIndex] = service calculateUptimePercentages(serviceIndex) + templateDataMu.Unlock() } func AddIncident(serviceIndex int, status string, startTime time.Time) { @@ -348,7 +353,9 @@ func AddIncident(serviceIndex int, status string, startTime time.Time) { Status: status, StartTime: startTime, }) + templateDataMu.Lock() templateData.Services[serviceIndex] = service + templateDataMu.Unlock() log.Println("(" + service.Name + ") incident started") } @@ -364,6 +371,8 @@ func ResolveIncident(serviceIndex int, serviceName string, endTime time.Time) { } service.Incidents[len(service.Incidents)-1].EndTime = &endTime + templateDataMu.Lock() templateData.Services[serviceIndex] = service + templateDataMu.Unlock() log.Println("(" + service.Name + ") incident resolved") } diff --git a/internal/http.go b/internal/http.go index d05e283..d1374af 100644 --- a/internal/http.go +++ b/internal/http.go @@ -5,6 +5,7 @@ import ( "log" "net/http" "strings" + "sync" "time" "github.com/klauspost/compress/gzhttp" @@ -45,6 +46,7 @@ type TemplateData struct { LinkText string LinkUrl string EnableThemeSwitcher bool + EnableWatermark bool LastUpdated int64 IsOperational bool Services []Service @@ -61,8 +63,10 @@ func ToUpper(s string) string { var templateData = TemplateData{ EnableThemeSwitcher: true, + EnableWatermark: true, LastUpdated: time.Now().UTC().UnixMilli(), } +var templateDataMu sync.Mutex var tmpl *template.Template var errorTmpl *template.Template @@ -79,16 +83,16 @@ func renderError(w http.ResponseWriter, statusCode int, message string) { } } -func index(w http.ResponseWriter, req *http.Request) { +func index(w http.ResponseWriter, r *http.Request) { // handle 404 - if req.URL.Path != "/" { + if r.URL.Path != "/" { renderError(w, http.StatusNotFound, "Page not found") return } data := templateData - data.View = req.URL.Query().Get("view") - data.Theme = req.URL.Query().Get("theme") + data.View = r.URL.Query().Get("view") + data.Theme = r.URL.Query().Get("theme") switch data.View { case "hours", "minutes", "days": @@ -111,16 +115,19 @@ func index(w http.ResponseWriter, req *http.Request) { } } -func styles(w http.ResponseWriter, req *http.Request) { - http.ServeFile(w, req, "www/styles.css") +func styles(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Cache-Control", "public, max-age=31536000") + http.ServeFile(w, r, "www/styles.css") } -func favicon(w http.ResponseWriter, req *http.Request) { - http.ServeFile(w, req, "www/favicon.ico") +func favicon(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Cache-Control", "public, max-age=31536000") + http.ServeFile(w, r, "www/favicon.ico") } -func robots(w http.ResponseWriter, req *http.Request) { - http.ServeFile(w, req, "www/robots.txt") +func robots(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Cache-Control", "public, max-age=31536000") + http.ServeFile(w, r, "www/robots.txt") } func StartHttpServer() { @@ -147,8 +154,14 @@ func StartHttpServer() { mux.HandleFunc("/styles.css", styles) mux.HandleFunc("/favicon.ico", favicon) mux.HandleFunc("/robots.txt", robots) - mux.Handle("/themes/", gzhttp.GzipHandler(http.StripPrefix("/themes/", http.FileServer(http.Dir("www/themes/"))))) - mux.Handle("/fonts/", http.StripPrefix("/fonts/", http.FileServer(http.Dir("www/fonts/")))) + mux.Handle("/themes/", gzhttp.GzipHandler(http.StripPrefix("/themes/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Cache-Control", "public, max-age=31536000") + http.FileServer(http.Dir("www/themes/")).ServeHTTP(w, r) + })))) + mux.Handle("/fonts/", http.StripPrefix("/fonts/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Cache-Control", "public, max-age=31536000") + http.FileServer(http.Dir("www/fonts/")).ServeHTTP(w, r) + }))) // wrap with gzip middleware handler := gzhttp.GzipHandler(mux) diff --git a/internal/statuses.go b/internal/statuses.go index dac4195..39f6d63 100644 --- a/internal/statuses.go +++ b/internal/statuses.go @@ -25,10 +25,15 @@ func checkStatuses() { startTime := time.Now().UTC() res, err := client.Get(service.Url) latency := time.Since(startTime).Milliseconds() + if res != nil { + defer res.Body.Close() + } status := "Online" - if err != nil || res.StatusCode != 200 { + if err != nil { + status = "Offline" + } else if res.StatusCode != 200 { status = "Offline" } else if latency >= service.LatencyThreshold { status = "Degraded" @@ -49,7 +54,9 @@ func checkStatuses() { } } + templateDataMu.Lock() templateData.Services[index].Status = status + templateDataMu.Unlock() }(index, service) } @@ -65,17 +72,22 @@ func checkStatuses() { } } + templateDataMu.Lock() templateData.IsOperational = isOperational templateData.LastUpdated = time.Now().UTC().UnixMilli() + templateDataMu.Unlock() } func StartCheckingStatuses() { log.Println("started checking statuses...") - go func() { - checkStatuses() + checkStatuses() - for range time.Tick(1 * time.Minute) { + go func() { + ticker := time.NewTicker(1 * time.Minute) + defer ticker.Stop() + + for range ticker.C { checkStatuses() } }() diff --git a/www/index.html b/www/index.html index 7f38457..d8fdf74 100644 --- a/www/index.html +++ b/www/index.html @@ -3,7 +3,27 @@
-