package panel import ( "embed" "fmt" "html/template" "io/fs" "net/http" "os" ) //go:embed templates static var embeddedFS embed.FS var templateFuncs = template.FuncMap{ "percent": func(count, max int) int { if max <= 0 { return 0 } v := count * 100 / max if v > 100 { return 100 } return v }, "fmtMin": func(m int) string { if m <= 0 { return "—" } if m < 60 { return fmt.Sprintf("%dm", m) } return fmt.Sprintf("%dh%dm", m/60, m%60) }, "add": func(a, b int) int { return a + b }, // dict builds a map[string]any for passing multiple values to a sub-template. "dict": func(pairs ...any) map[string]any { m := make(map[string]any, len(pairs)/2) for i := 0; i+1 < len(pairs); i += 2 { k, _ := pairs[i].(string) m[k] = pairs[i+1] } return m }, "buttonClass": func(color string) string { switch color { case "secondary": return "bg-gray-600" case "success": return "bg-green-600" case "danger": return "bg-red-600" default: return "bg-indigo-600" } }, "not": func(v bool) bool { return !v }, } // StaticFS returns the file system to use for /static/* routes. // In dev mode (PANEL_DEV=true), serves from disk for hot reload. func StaticFS() http.FileSystem { if os.Getenv("PANEL_DEV") == "true" { return http.Dir("internal/panel/static") } sub, _ := fs.Sub(embeddedFS, "static") return http.FS(sub) } // ParseTemplates parses a set of template files by name with global template functions. // In dev mode templates are loaded from disk on every call (hot reload). func ParseTemplates(names ...string) (*template.Template, error) { var paths []string for _, n := range names { paths = append(paths, "templates/"+n) } base := template.New("").Funcs(templateFuncs) if os.Getenv("PANEL_DEV") == "true" { return base.ParseFiles(prependDir("internal/panel/", paths)...) } return base.ParseFS(embeddedFS, paths...) } func prependDir(dir string, paths []string) []string { out := make([]string, len(paths)) for i, p := range paths { out[i] = dir + p } return out }