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

@ -5,7 +5,9 @@ import (
"net"
"os"
"path/filepath"
"slices"
"sort"
"strings"
"time"
"github.com/mr0xb/nxstats/internal/geo"
@ -22,13 +24,14 @@ var (
flagNoGzip bool
flagGeoIP string
flagSplitByDay bool
flagTheme string
)
var rootCmd = &cobra.Command{
Use: "nxstats",
Short: "Nginx log parser and HTML report generator",
Long: `nxstats parses nginx access and error logs (including gzip-compressed
rotated logs) and generates a rich cyberpunk-themed HTML report with
rotated logs) and generates a rich themeable HTML report with
charts, searchable tables, and optional GeoIP2 hit maps.`,
RunE: runE,
}
@ -41,6 +44,17 @@ func init() {
rootCmd.Flags().BoolVar(&flagNoGzip, "no-gzip", false, "Skip .gz compressed rotated logs")
rootCmd.Flags().StringVar(&flagGeoIP, "geoip", "", "Path to MaxMind GeoLite2-City.mmdb (enables map)")
rootCmd.Flags().BoolVar(&flagSplitByDay, "split-by-day", false, "Write one HTML report per calendar day plus an index page")
rootCmd.Flags().StringVar(&flagTheme, "theme", report.DefaultTheme,
"Report visual theme ("+strings.Join(report.ThemeNames(), ", ")+")")
}
// validateTheme returns an error if name is not a recognized theme.
func validateTheme(name string) error {
valid := report.ThemeNames()
if slices.Contains(valid, name) {
return nil
}
return fmt.Errorf("invalid --theme %q: must be one of %s", name, strings.Join(valid, ", "))
}
// Execute runs the root command.
@ -51,6 +65,10 @@ func Execute() {
}
func runE(cmd *cobra.Command, args []string) error {
if err := validateTheme(flagTheme); err != nil {
return err
}
accessFiles, errorFiles, err := resolveLogFiles()
if err != nil {
return err
@ -89,6 +107,7 @@ func runE(cmd *cobra.Command, args []string) error {
GeneratedAt: time.Now(),
AccessEntries: accessEntries,
ErrorEntries: errorEntries,
Theme: flagTheme,
}
report.ComputeStats(&data)
@ -184,7 +203,7 @@ func runSplitByDay(
) error {
indexBase := filepath.Base(flagOutput)
days := report.GroupByDay(flagOutput, accessEntries, errorEntries, geoPoints)
days := report.GroupByDay(flagOutput, accessEntries, errorEntries, geoPoints, flagTheme)
if len(days) == 0 {
fmt.Fprintln(os.Stderr, "nxstats: no entries found, writing empty index")
}
@ -287,5 +306,6 @@ func buildIndexData(allAccess []parser.AccessEntry, days []report.DayReport) rep
TotalUniqueIPs: len(ipSet),
TotalBytes: totalBytes,
OverallErrorRate: errorRate,
Theme: flagTheme,
}
}