Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBHF4R9EAejDJUMdwr6C68
92 lines
2.4 KiB
Go
92 lines
2.4 KiB
Go
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())
|
|
}
|