package server import ( "fmt" "html/template" "io/fs" "strings" "time" ) var funcs = template.FuncMap{ "humanTime": humanTime, "humanSize": humanSize, "sparkline": sparkline, "seq": seq, "maxInt": maxInt, "inc": func(i int) int { return i + 1 }, } func parseTemplates() (*template.Template, error) { t := template.New("").Funcs(funcs) err := fs.WalkDir(templateFS, "templates", func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() || !strings.HasSuffix(path, ".html") { return nil } b, err := fs.ReadFile(templateFS, path) if err != nil { return err } name := strings.TrimSuffix(strings.TrimPrefix(path, "templates/"), ".html") _, err = t.New(name).Parse(string(b)) return err }) if err != nil { return nil, err } return t, nil } func humanTime(v any) string { var t time.Time switch x := v.(type) { case time.Time: t = x case *time.Time: if x == nil { return "—" } t = *x default: return "—" } if t.IsZero() { return "—" } d := time.Since(t) switch { case d < time.Minute: return "just now" case d < time.Hour: return fmt.Sprintf("%dm ago", int(d.Minutes())) case d < 24*time.Hour: return fmt.Sprintf("%dh ago", int(d.Hours())) case d < 30*24*time.Hour: return fmt.Sprintf("%dd ago", int(d.Hours()/24)) default: return t.Format("2006-01-02") } } func humanSize(b int64) string { const k = 1024 switch { case b < k: return fmt.Sprintf("%d B", b) case b < k*k: return fmt.Sprintf("%.1f KB", float64(b)/k) case b < k*k*k: return fmt.Sprintf("%.1f MB", float64(b)/(k*k)) default: return fmt.Sprintf("%.1f GB", float64(b)/(k*k*k)) } } var sparkGlyphs = []rune("▁▂▃▄▅▆▇█") func sparkline(vals []int64) string { if len(vals) == 0 { return "" } var max int64 for _, v := range vals { if v > max { max = v } } if max == 0 { return strings.Repeat("·", len(vals)) } var b strings.Builder for _, v := range vals { if v == 0 { b.WriteRune('·') continue } idx := int(float64(v)/float64(max)*float64(len(sparkGlyphs)-1) + 0.5) if idx < 0 { idx = 0 } if idx >= len(sparkGlyphs) { idx = len(sparkGlyphs) - 1 } b.WriteRune(sparkGlyphs[idx]) } return b.String() } func seq(n int) []int { out := make([]int, n) for i := range out { out[i] = i } return out } func maxInt(a, b int) int { if a > b { return a } return b }