internal/web/web.go
67 lines · 1586 bytes
1// Package web holds the server-rendered templates and static assets for the
2// read-only UI. No JavaScript, no build step.
3package web
4
5import (
6 "embed"
7 "html/template"
8 "io"
9 "runtime/debug"
10 "sync"
11 "time"
12)
13
14//go:embed templates/*.html
15var templateFS embed.FS
16
17//go:embed static/style.css
18var StyleCSS []byte
19
20//go:embed static/favicon.svg
21var FaviconSVG []byte
22
23// version returns the short VCS revision baked into the binary, or "" when
24// built outside a checkout. Used by the layout footer.
25var version = sync.OnceValue(func() string {
26 info, ok := debug.ReadBuildInfo()
27 if !ok {
28 return ""
29 }
30 for _, s := range info.Settings {
31 if s.Key == "vcs.revision" && len(s.Value) >= 10 {
32 return s.Value[:10]
33 }
34 }
35 return ""
36})
37
38var funcs = template.FuncMap{
39 "gitbayVersion": func() string { return version() },
40 // short abbreviates a commit SHA for display.
41 "short": func(s string) string {
42 if len(s) > 10 {
43 return s[:10]
44 }
45 return s
46 },
47 // when formats a stored RFC3339 timestamp for display; unparseable
48 // values pass through unchanged.
49 "when": func(s string) string {
50 t, err := time.Parse(time.RFC3339Nano, s)
51 if err != nil {
52 return s
53 }
54 return t.UTC().Format("2006-01-02 15:04")
55 },
56}
57
58// Render executes the named page template with the shared layout.
59func Render(w io.Writer, page string, data any) error {
60 t, err := template.Must(
61 template.New("layout.html").Funcs(funcs).ParseFS(templateFS, "templates/layout.html"),
62 ).ParseFS(templateFS, "templates/"+page)
63 if err != nil {
64 return err
65 }
66 return t.ExecuteTemplate(w, "layout", data)
67}