package web import ( "bytes" "embed" "fmt" "html/template" "net/http" ) //go:embed templates/*.html var templateFS embed.FS // AssetsFS enthält CSS und JavaScript; wird vom Asset-Handler ausgeliefert. // //go:embed assets var AssetsFS embed.FS // Brand bündelt die White-Label-Angaben. type Brand struct { Title string SupportContact string Version string HasLogo bool } // PageData ist das Wurzelobjekt jedes Templates. type PageData struct { Brand Brand Flash string FlashKind string // "error" oder "info" CSRF string User string Data any } // errorPageData füllt error.html. type errorPageData struct { Title string Message string ShowLoginLink bool ShowOverviewLink bool } // Renderer hält die beim Start geparsten Templates. type Renderer struct { brand Brand pages map[string]*template.Template } // pageNames sind die Inhaltstemplates, die jeweils mit dem Layout kombiniert werden. var pageNames = []string{"login", "overview", "guides", "error"} // NewRenderer parst alle Templates beim Start; ein Fehler bricht den Start ab, // damit ein Tippfehler im Template nicht erst im Betrieb auffällt. func NewRenderer(brand Brand) (*Renderer, error) { funcs := template.FuncMap{"t": T} pages := make(map[string]*template.Template, len(pageNames)) for _, name := range pageNames { tmpl, err := template.New("layout.html").Funcs(funcs). ParseFS(templateFS, "templates/layout.html", "templates/"+name+".html") if err != nil { return nil, fmt.Errorf("Template %s: %w", name, err) } pages[name] = tmpl } return &Renderer{brand: brand, pages: pages}, nil } // Brand liefert die White-Label-Angaben für Handler. func (rn *Renderer) Brand() Brand { return rn.brand } // Render schreibt eine Seite. Es wird zuerst in einen Puffer gerendert, damit // ein Template-Fehler nicht zu einer halb geschriebenen Antwort führt. func (rn *Renderer) Render(w http.ResponseWriter, r *http.Request, status int, page string, data PageData) { tmpl, ok := rn.pages[page] if !ok { http.Error(w, T("error_internal"), http.StatusInternalServerError) return } if data.Brand.Title == "" { data.Brand = rn.brand } var buf bytes.Buffer if err := tmpl.ExecuteTemplate(&buf, "layout", data); err != nil { http.Error(w, T("error_internal"), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(status) w.Write(buf.Bytes()) }