package admin import ( "embed" "fmt" "html/template" "io" "net/http" "time" ) //go:embed templates/*.html static/* var assetsFS embed.FS // pageData is the common envelope passed to every rendered page. type pageData struct { Username string CSRF string Active string // nav key for highlight Flash string Data any } // renderer holds the parsed template set. type renderer struct { pages map[string]*template.Template } var tmplFuncs = template.FuncMap{ "fmtTime": func(t time.Time) string { if t.IsZero() { return "-" } return t.UTC().Format("2006-01-02 15:04:05Z") }, } // newRenderer parses base.html with each page template into its own set. func newRenderer() (*renderer, error) { pages := map[string][]string{ "login": {"templates/login.html"}, "dashboard": {"templates/base.html", "templates/dashboard.html"}, "codes": {"templates/base.html", "templates/codes.html"}, "nodes": {"templates/base.html", "templates/nodes.html"}, "audit": {"templates/base.html", "templates/audit.html"}, } r := &renderer{pages: make(map[string]*template.Template, len(pages))} for name, files := range pages { t, err := template.New("").Funcs(tmplFuncs).ParseFS(assetsFS, files...) if err != nil { return nil, fmt.Errorf("admin.newRenderer parse %s: %w", name, err) } r.pages[name] = t } return r, nil } // render writes the named page. The login page renders itself; all others // render through the "base" layout. func (r *renderer) render(w http.ResponseWriter, name string, data pageData) { t, ok := r.pages[name] if !ok { http.Error(w, "template not found", http.StatusInternalServerError) return } root := "base" if name == "login" { root = "login" } w.Header().Set("Content-Type", "text/html; charset=utf-8") if err := t.ExecuteTemplate(w, root, data); err != nil { // Header may already be written; log via stderr only. fmt.Fprintf(io.Discard, "render error: %v", err) } }