Added build ID to footer. Added check for client-server mismatch.

This commit is contained in:
Pen Anderson 2026-03-03 13:38:31 -06:00
parent 10037add99
commit 0d940749b1
5 changed files with 77 additions and 2 deletions

View file

@ -1,10 +1,10 @@
.PHONY: build frontend-build dev clean test
build: frontend-build
CGO_ENABLED=0 go build -o turnpike .
CGO_ENABLED=0 go build -ldflags "-X main.buildID=$$(git rev-parse --short HEAD)" -o turnpike .
frontend-build:
cd frontend && npm ci && npm run build
cd frontend && npm ci && BUILD_ID=$$(git rev-parse --short HEAD) npm run build
dev:
@echo "Run in two terminals:"

1
frontend/.gitignore vendored
View file

@ -8,6 +8,7 @@ pnpm-debug.log*
lerna-debug.log*
node_modules
.vite
dist
dist-ssr
*.local

View file

@ -17,14 +17,27 @@
import Nav from './components/Nav.svelte'
import SyncStatus from './components/SyncStatus.svelte'
const clientBuild = __BUILD_ID__
let session = $state(null)
let loading = $state(true)
let route = $state(window.location.hash || '#/')
let updateAvailable = $state(false)
// Check if this is a kiosk token URL before doing anything else
const kioskToken = $derived(window.location.hash.match(/^#\/v\/([A-Z0-9]+)/i)?.[1] ?? '')
async function checkVersion() {
try {
const res = await fetch('/api/version')
const { build } = await res.json()
if (build && build !== clientBuild) updateAvailable = true
} catch {}
}
onMount(async () => {
checkVersion()
// Kiosk pages don't need auth
if (kioskToken) {
loading = false
@ -40,6 +53,9 @@
window.addEventListener('hashchange', () => {
route = window.location.hash || '#/'
})
// Periodically check for updates
setInterval(checkVersion, 60000)
})
function onLogin(s) {
@ -58,6 +74,13 @@
const role = $derived(session?.user?.role ?? '')
</script>
{#if updateAvailable}
<div class="update-banner">
A new version is available.
<button onclick={() => location.reload()}>Refresh</button>
</div>
{/if}
{#if loading}
<!-- checking session -->
{:else if kioskToken}
@ -100,3 +123,45 @@
</div>
</div>
{/if}
<footer class="build-footer">{clientBuild}</footer>
<style>
.build-footer {
position: fixed;
bottom: 4px;
right: 8px;
font-size: 11px;
color: var(--c-muted);
opacity: 0.5;
pointer-events: none;
}
.update-banner {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 9999;
padding: 8px 16px;
background: var(--c-accent);
color: white;
text-align: center;
font-size: 14px;
}
.update-banner button {
margin-left: 12px;
padding: 2px 12px;
border: 1px solid rgba(255,255,255,0.5);
border-radius: var(--radius);
background: transparent;
color: white;
cursor: pointer;
font-size: 14px;
}
.update-banner button:hover {
background: rgba(255,255,255,0.15);
}
</style>

View file

@ -3,6 +3,9 @@ import { svelte } from '@sveltejs/vite-plugin-svelte'
export default defineConfig({
plugins: [svelte()],
define: {
__BUILD_ID__: JSON.stringify(process.env.BUILD_ID || 'dev'),
},
server: {
proxy: {
'/api': 'http://localhost:8180',

View file

@ -12,6 +12,8 @@ import (
"os"
)
var buildID = "dev"
//go:embed frontend/dist
var frontendFS embed.FS
@ -145,6 +147,10 @@ func (app *App) registerRoutes(mux *http.ServeMux) {
mux.HandleFunc("GET /api/sync/pull", auth(app.handleSyncPull))
mux.HandleFunc("GET /api/sync/stream", auth(app.handleSyncStream))
mux.HandleFunc("GET /api/version", func(w http.ResponseWriter, r *http.Request) {
writeJSON(w, map[string]string{"build": buildID})
})
// Kiosk — authenticated by volunteer token, no JWT required.
mux.HandleFunc("GET /api/v/{token}", app.handleKioskGet)
mux.HandleFunc("POST /api/v/{token}/shifts/{id}", app.handleKioskClaim)