Documentation
¶
Overview ¶
Package ui owns the dashboard's templates, static assets and rendering.
It deliberately depends on nothing but the standard library. Handlers live in internal/httpx and pass in whatever data a page needs, so this package can be tested by rendering a page and reading the HTML rather than by standing up services — and so a template change cannot quietly acquire a dependency on a service type.
Everything is embedded, because the deployment unit is one binary. There is no filesystem path to get wrong, no volume to forget to mount, and no way for the running instance to disagree with the templates it was built from.
Index ¶
- Constants
- type Bar
- type Chart
- type DayCount
- type Renderer
- func (r *Renderer) AssetURL(name string) string
- func (r *Renderer) MissingAssets() []string
- func (r *Renderer) Pages() []string
- func (r *Renderer) Render(w http.ResponseWriter, status int, page string, data any) error
- func (r *Renderer) RenderPartial(w http.ResponseWriter, status int, page, block string, data any) error
- func (r *Renderer) StaticHandler(prefix string) http.Handler
- type Tick
Constants ¶
const StylesheetPath = "css/app.css"
StylesheetPath is the asset a build is expected to have generated.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Chart ¶
type Chart struct {
W, H int
PlotH int
MaxY int64
Bars []Bar
Ticks []Tick
First string // label of the first day, for the x axis
Last string
}
Chart is a bar chart laid out in Go rather than in the browser.
The dashboard's charts are server-rendered SVG. A charting library would be the only piece of custom JavaScript in the product, for four rectangles and an axis — and the CSP disallows inline styles, which most of them generate. Computing integer geometry here keeps the template a dumb loop.
type DayCount ¶
DayCount is one day of a click series as the chart helpers consume it.
A local type rather than analytics.DayPoint, because this package depends on nothing outside the standard library. Handlers convert; the conversion is three assignments and it keeps a template change from ever pulling a service package into the UI.
type Renderer ¶
type Renderer struct {
// contains filtered or unexported fields
}
Renderer holds the parsed template set and the fingerprinted asset table.
Templates are parsed once at boot. A syntax error therefore fails startup rather than the first request that happens to reach that page, which is the difference between a deploy that refuses to come up and one that looks healthy until someone clicks the wrong tab.
func (*Renderer) AssetURL ¶
AssetURL returns the fingerprinted URL for an asset, or the plain path if the asset is absent — a missing stylesheet should 404 visibly rather than render as an empty href.
func (*Renderer) MissingAssets ¶
MissingAssets reports expected assets that no build produced.
Returned rather than fatal: a stylesheet-less dashboard is ugly but working, and refusing to start would turn a forgotten build step into an outage. The caller logs it loudly at boot.
func (*Renderer) Pages ¶
Pages lists the parsed page names, for the test that asserts every page renders.
func (*Renderer) Render ¶
Render writes a full page.
Rendered into a buffer first. Executing straight to the ResponseWriter would commit a 200 and a half-written page the moment a template referenced a missing field, leaving the browser with truncated HTML and the operator with no error to look at.
func (*Renderer) RenderPartial ¶
func (r *Renderer) RenderPartial(w http.ResponseWriter, status int, page, block string, data any) error
RenderPartial writes one named block, for an HTMX swap.
The block is looked up in the page's own template set, so a partial can use anything that page defines. Same buffering rule as Render.
func (*Renderer) StaticHandler ¶
StaticHandler serves the embedded assets.
Served from memory with a strong ETag and a one-year max-age. The long lifetime is safe because every URL the templates emit carries a content fingerprint: a new build changes the URL, so nothing can be served stale. Requests without the fingerprint still work and still validate.