add theming

This commit is contained in:
mr0xb 2026-08-24 15:43:03 -04:00
commit 8639799f6d
10 changed files with 1069 additions and 287 deletions

View file

@ -62,6 +62,10 @@ type ReportData struct {
// Set when this is a per-day report in split-by-day mode.
IndexFile string // non-empty → render "← Back to Index" nav link
DayTitle string // e.g. "2024-02-20", shown in the header
// Theme selects the visual theme (see themes.go). Empty or unrecognized
// falls back to DefaultTheme.
Theme string
}
// DaySummary is one row in the index page's daily breakdown table.
@ -82,6 +86,10 @@ type IndexData struct {
TotalUniqueIPs int
TotalBytes int64
OverallErrorRate float64
// Theme selects the visual theme (see themes.go). Empty or unrecognized
// falls back to DefaultTheme.
Theme string
}
// DayReport bundles one calendar day's fully-computed ReportData with
@ -110,6 +118,7 @@ func GroupByDay(
accessEntries []parser.AccessEntry,
errorEntries []parser.ErrorEntry,
geoPoints map[string]GeoPoint,
themeName string,
) []DayReport {
accessByDay := make(map[string][]parser.AccessEntry)
for _, e := range accessEntries {
@ -145,6 +154,7 @@ func GroupByDay(
GeneratedAt: time.Now(),
AccessEntries: dayAccess,
ErrorEntries: dayErrors,
Theme: themeName,
}
ComputeStats(&rd)
@ -173,8 +183,21 @@ func GroupByDay(
return days
}
// indexRenderData wraps IndexData with the resolved theme's stylesheet for
// template execution.
type indexRenderData struct {
IndexData
ThemeCSS template.CSS
}
// GenerateIndexHTML renders the index page from IndexData.
func GenerateIndexHTML(idx IndexData) (string, error) {
th := themeFor(idx.Theme)
view := indexRenderData{
IndexData: idx,
ThemeCSS: template.CSS(th.CSS),
}
funcMap := template.FuncMap{
"formatBytes": func(b int64) string {
switch {
@ -206,7 +229,7 @@ func GenerateIndexHTML(idx IndexData) (string, error) {
return "", err
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, idx); err != nil {
if err := tmpl.Execute(&buf, view); err != nil {
return "", err
}
return buf.String(), nil
@ -295,8 +318,47 @@ func ComputeStats(data *ReportData) {
data.TimeSeriesData = ts
}
// reportRenderData wraps ReportData with the resolved theme's stylesheet and
// chart/map color values for template execution.
type reportRenderData struct {
ReportData
ThemeCSS template.CSS
ThemeExtraCSS template.CSS
ChartFont string
ChartText string
ChartGrid string
Chart1 string
Chart2 string
Chart2Fill string
Chart3 string
ChartWarn string
ChartErr string
MapTileURL string
MapAttribution string
MarkerColor string
}
// GenerateHTML renders the full HTML report from data.
func GenerateHTML(data ReportData) (string, error) {
th := themeFor(data.Theme)
view := reportRenderData{
ReportData: data,
ThemeCSS: template.CSS(th.CSS),
ThemeExtraCSS: template.CSS(th.ExtraCSS),
ChartFont: th.ChartFont,
ChartText: th.ChartText,
ChartGrid: th.ChartGrid,
Chart1: th.Chart1,
Chart2: th.Chart2,
Chart2Fill: th.Chart2Fill,
Chart3: th.Chart3,
ChartWarn: th.ChartWarn,
ChartErr: th.ChartErr,
MapTileURL: th.MapTileURL,
MapAttribution: th.MapAttribution,
MarkerColor: th.MarkerColor,
}
funcMap := template.FuncMap{
"add": func(a, b int) int { return a + b },
"formatBytes": func(b int64) string {
@ -428,7 +490,7 @@ func GenerateHTML(data ReportData) (string, error) {
return "", err
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
if err := tmpl.Execute(&buf, view); err != nil {
return "", err
}
return buf.String(), nil