internal/web/web.go
99 lines · 2389 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 "strings"
11 "sync"
12 "time"
13)
14
15//go:embed templates/*.html
16var templateFS embed.FS
17
18//go:embed static/style.css
19var StyleCSS []byte
20
21//go:embed static/favicon.svg
22var FaviconSVG []byte
23
24//go:embed static/fonts/*.woff2
25var FontFS embed.FS
26
27// version returns the short VCS revision baked into the binary, or "" when
28// built outside a checkout. Used by the layout footer.
29var version = sync.OnceValue(func() string {
30 info, ok := debug.ReadBuildInfo()
31 if !ok {
32 return ""
33 }
34 for _, s := range info.Settings {
35 if s.Key == "vcs.revision" && len(s.Value) >= 10 {
36 return s.Value[:10]
37 }
38 }
39 return ""
40})
41
42// fullVersion is the complete VCS revision, for linking the footer hash
43// to the upstream commit page.
44var fullVersion = sync.OnceValue(func() string {
45 info, ok := debug.ReadBuildInfo()
46 if !ok {
47 return ""
48 }
49 for _, s := range info.Settings {
50 if s.Key == "vcs.revision" {
51 return s.Value
52 }
53 }
54 return ""
55})
56
57var funcs = template.FuncMap{
58 "gitbayVersion": func() string { return version() },
59 "gitbayCommit": func() string { return fullVersion() },
60 // paragraphs splits plain text on blank lines for safe rich display.
61 "paragraphs": func(s string) []string {
62 var out []string
63 for _, p := range strings.Split(s, "\n\n") {
64 if p = strings.TrimSpace(p); p != "" {
65 out = append(out, p)
66 }
67 }
68 return out
69 },
70 // short abbreviates a commit SHA for display.
71 "short": func(s string) string {
72 if len(s) > 10 {
73 return s[:10]
74 }
75 return s
76 },
77 "add": func(a, b int) int { return a + b },
78 "sub": func(a, b int) int { return a - b },
79 // when formats a stored RFC3339 timestamp for display; unparseable
80 // values pass through unchanged.
81 "when": func(s string) string {
82 t, err := time.Parse(time.RFC3339Nano, s)
83 if err != nil {
84 return s
85 }
86 return t.UTC().Format("2006-01-02 15:04")
87 },
88}
89
90// Render executes the named page template with the shared layout.
91func Render(w io.Writer, page string, data any) error {
92 t, err := template.Must(
93 template.New("layout.html").Funcs(funcs).ParseFS(templateFS, "templates/layout.html"),
94 ).ParseFS(templateFS, "templates/"+page)
95 if err != nil {
96 return err
97 }
98 return t.ExecuteTemplate(w, "layout", data)
99}