internal/web/web.go

794702c58d07759975699235f4408486305f8008
gitbay/internal/web/web.go history · blame · raw

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