mirror of
https://github.com/hanzoai/status.git
synced 2026-10-30 18:21:48 +00:00
Hanzo and Lux status pages run the same image digest. Three of the tab-icon links were brand-templated, but the paths a browser reaches for on its own were not: /favicon.ico, /apple-touch-icon.png, /logo-192x192.png and /logo-512x512.png were real files at the web root, and git records this commit moving them into brands/hanzo/ because that is what they always were. Every brand got Hanzo's bytes. Saving status.lux.network to an iOS home screen saved the Hanzo mark, md5-identical. Artwork now has one home, /brands/<brand>/, and one name to reach it by: ui.brand. Tab icon, home screen icon, PWA icons and the header logo all derive from that name, so a deployment cannot title itself one company and hand out another's mark. The bare root paths are served from the same place rather than being a second copy that drifts, and a brand missing an asset is a boot failure instead of a page quietly wearing nobody. manifest.json was a checked-in static file: it called itself "Status" on a page titled "Hanzo Status", described itself in upstream Gatus's words, and splashed #f7f9fb light grey in front of a UI that paints #020817. It is now rendered from the same config that renders the page, and the theme-color meta reads the colour the app actually paints instead of a rounder-looking #000000. The 180/192/512 sizes were rasterised from each brand's own canonical SVG with rsvg-convert, the same pipeline that produced the existing 16/32 — it reproduces every shipped icon byte for byte, and Hanzo's icons are unchanged from what production serves today. Config drops `logo` and the three-field `favicon` block for the single `brand` key. TestNoBrandWearsAnothersMark asserts the property that was silently false: distinct bytes per brand at every size. Co-authored-by: Hanzo Dev <[email protected]>
70 lines
4.3 KiB
JavaScript
70 lines
4.3 KiB
JavaScript
/**
|
|
* Post-process the Vite build index.html into a Go html/template.
|
|
*
|
|
* The Go backend (api/spa.go) parses web/static/index.html as a Go template
|
|
* and executes it with ViewData{UI: *Config, Theme: string}. This script
|
|
* extracts all <script> and <link> tags from the Vite output and inserts
|
|
* them into a Go-template-compatible HTML shell that includes window.config
|
|
* injection and favicon/theme configuration.
|
|
*/
|
|
|
|
import { readFileSync, writeFileSync } from 'fs'
|
|
import { resolve, dirname } from 'path'
|
|
import { fileURLToPath } from 'url'
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
const outHtml = readFileSync(resolve(__dirname, '../dist/index.html'), 'utf-8')
|
|
const staticDir = resolve(__dirname, '../../static')
|
|
|
|
// Extract <link> and <script> tags from <head>
|
|
const headLinkMatches = [...outHtml.matchAll(/<link\s[^>]*\/?>/g)]
|
|
const headScriptMatches = [...outHtml.matchAll(/<script\s[^>]*>[\s\S]*?<\/script>/g)]
|
|
|
|
// Filter out Vite's default meta/title/favicon -- we replace with Go template versions
|
|
const headLinks = headLinkMatches
|
|
.map(m => m[0])
|
|
.filter(tag => !tag.includes('rel="icon"')) // We inject favicons via Go template
|
|
.join('\n')
|
|
|
|
const headScripts = headScriptMatches.map(m => m[0]).join('\n')
|
|
|
|
// Extract body scripts (Vite puts the module entry in body)
|
|
const bodyMatch = outHtml.match(/<body[^>]*>([\s\S]*)<\/body>/)
|
|
const bodyContent = bodyMatch ? bodyMatch[1] : ''
|
|
|
|
// Collect src from head scripts to deduplicate
|
|
const headSrcs = new Set(
|
|
[...headScripts.matchAll(/src="([^"]+)"/g)].map(m => m[1])
|
|
)
|
|
|
|
const bodyScripts = [...bodyContent.matchAll(/<script[^>]*>[\s\S]*?<\/script>/g)]
|
|
.map(m => m[0])
|
|
.filter(tag => {
|
|
const srcMatch = tag.match(/src="([^"]+)"/)
|
|
if (!srcMatch) return true
|
|
return !headSrcs.has(srcMatch[1])
|
|
})
|
|
.join('\n')
|
|
|
|
// Build the Go-template-compatible index.html
|
|
const template = `<!doctype html><html lang="en" class="{{ .Theme }}"><head><meta charset="utf-8"/><script>window.config = {title: "{{ .UI.Title }}", logo: "{{ .UI.Icons.Logo }}", header: "{{ .UI.Header }}", dashboardHeading: "{{ .UI.DashboardHeading }}", dashboardSubheading: "{{ .UI.DashboardSubheading }}", link: "{{ .UI.Link }}", buttons: [], maximumNumberOfResults: "{{ .UI.MaximumNumberOfResults }}", defaultSortBy: "{{ .UI.DefaultSortBy }}", defaultFilterBy: "{{ .UI.DefaultFilterBy }}"};{{- range .UI.Buttons}}window.config.buttons.push({name:"{{ .Name }}",link:"{{ .Link }}"});{{end}}
|
|
// Initialize theme immediately to prevent flash
|
|
(function() {
|
|
const themeFromCookie = document.cookie.match(/theme=(dark|light);?/)?.[1];
|
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
if (themeFromCookie === 'dark' || (!themeFromCookie && prefersDark)) {
|
|
document.documentElement.classList.add('dark');
|
|
} else {
|
|
document.documentElement.classList.remove('dark');
|
|
}
|
|
})();</script><title>{{ .UI.Title }}</title><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/>{{ if .UI.Brand }}{{ with .UI.Icons }}<link rel="apple-touch-icon" sizes="180x180" href="{{ .Touch180 }}"/><link rel="icon" type="image/svg+xml" href="{{ .SVG }}"/><link rel="icon" type="image/png" sizes="32x32" href="{{ .PNG32 }}"/><link rel="icon" type="image/png" sizes="16x16" href="{{ .PNG16 }}"/><link rel="shortcut icon" href="{{ .ICO }}"/>{{ end }}{{ end }}<link rel="manifest" href="/manifest.json" crossorigin="use-credentials"/><link rel="stylesheet" href="/css/custom.css"/><meta name="description" content="{{ .UI.Description }}"/><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/><meta name="apple-mobile-web-app-title" content="{{ .UI.Title }}"/><meta name="application-name" content="{{ .UI.Title }}"/><meta name="theme-color" content="{{ .UI.Background.Dark }}" media="(prefers-color-scheme: dark)"/><meta name="theme-color" content="{{ .UI.Background.Light }}" media="(prefers-color-scheme: light)"/>
|
|
${headLinks}
|
|
${headScripts}
|
|
</head><body><noscript><strong>Enable JavaScript to view this page.</strong></noscript>
|
|
<div id="root"></div>
|
|
${bodyScripts}
|
|
</body></html>`
|
|
|
|
writeFileSync(resolve(staticDir, 'index.html'), template)
|
|
console.log('[make-template] Wrote Go-template-compatible index.html')
|