internal/web/web.go

7b5be134cce1a087ae1096772cbd6f76716d307f
gitbay/internal/web/web.go history · blame · raw

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}