This commit is contained in:
Mateusz Gruszczyński committed 2026-11-05 12:48:09 +01:00
1 parent 439cc20dcf
commit 722a49f6bc
14 files changed
+564 -283

No files matched your search

+12 -31
View File
@@ -5,13 +5,13 @@ import (
"crypto/sha256"
"embed"
"encoding/hex"
"encoding/json"
"fmt"
"html/template"
"log"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"regexp"
"strconv"
@@ -111,7 +111,7 @@ func (s *Server) Handler() http.Handler {
mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/stats", http.StatusFound) })
mux.HandleFunc("GET /stats", s.stats)
mux.HandleFunc("GET /stats/live", s.statsLive)
mux.HandleFunc("GET /graph/{file}", s.graph)
mux.HandleFunc("GET /stats/graph/{key}", s.graphData)
mux.HandleFunc("GET /channel", s.channel)
mux.HandleFunc("GET /embed/channel", s.channel)
mux.HandleFunc("POST /channel/verify/start", s.verifyStart)
@@ -250,43 +250,26 @@ func (s *Server) asset(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write(data)
}
func (s *Server) graph(w http.ResponseWriter, r *http.Request) {
func (s *Server) graphData(w http.ResponseWriter, r *http.Request) {
ip := s.clientIP(r).String()
// Graph PNGs are cheap to serve: rrd.Manager.Graph caches each
// key+theme render on disk for ~55s, so most hits here are a plain
// file read, not an rrdtool invocation. One page view alone can
// legitimately touch this 6 ranges x 2 themes (lightbox open, theme
// toggle) times, before counting multiple tabs or several visitors
// sharing one IP (same household/NAT) -- see README "Rate limiting"
// for tuning RATE_LIMIT_GRAPH if this still trips in practice.
// Each hit here is one rrdtool fetch returning a few hundred small
// JSON numbers read straight from the local RRD file -- cheap, so
// the limit mainly exists to stop outright abuse. See README "Rate
// limiting" for tuning RATE_LIMIT_GRAPH.
if s.cfg.RateLimitEnabled && !s.limit.allow("graph|"+ip, s.cfg.RateLimitGraph, time.Minute) {
s.tooMany(w)
return
}
ctx, cancel := context.WithTimeout(r.Context(), 8*time.Second)
defer cancel()
file := r.PathValue("file")
if !strings.HasSuffix(file, ".png") {
http.NotFound(w, r)
return
}
key := strings.TrimSuffix(file, ".png")
theme := "dark"
if strings.HasSuffix(key, "-light") {
theme = "light"
key = strings.TrimSuffix(key, "-light")
}
if key == "" || strings.Contains(key, "/") {
http.NotFound(w, r)
return
}
p, err := s.rrd.Graph(ctx, key, theme)
data, err := s.rrd.Fetch(ctx, r.PathValue("key"))
if err != nil {
http.Error(w, "graph unavailable", http.StatusServiceUnavailable)
http.Error(w, "graph data unavailable", http.StatusServiceUnavailable)
return
}
w.Header().Set("Cache-Control", "public, max-age=60")
http.ServeFile(w, r, p)
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(data)
}
func (s *Server) channel(w http.ResponseWriter, r *http.Request) {
@@ -579,5 +562,3 @@ func (s *Server) render(w http.ResponseWriter, name string, data any) {
log.Printf("template error: %v", err)
}
}
func EnsureGraphDir(path string) error { return os.MkdirAll(filepath.Clean(path), 0750) }