feat(setup): show running build version in the Setup tab bar
Build desktop / desktop (push) Successful in 4m21s
Build & push images / images (push) Successful in 3m6s
CI / check (push) Successful in 42s

CI already computes <branch>-<short-sha> for image tags but never
surfaced it anywhere reachable from the app, so there was no way to
tell what's actually deployed on a booth without cross-referencing
komodo/resources.toml's TAG by hand.

Thread it through: CI passes BUILD_VERSION as a Docker build-arg,
the Dockerfile captures it as a runtime env var, GET /api/version
(gated by the existing site:read permission) exposes it, and the
Setup page's tab bar shows it right-aligned, muted, absent entirely
on a local/dev build with no CI-supplied value.

Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
This commit is contained in:
2026-08-30 19:01:15 +02:00
parent cb9f4d4979
commit 642c5f4f70
6 changed files with 69 additions and 0 deletions
+9
View File
@@ -253,6 +253,15 @@ export async function fetchBackupStatus(): Promise<BackupStatus> {
return apiFetch("/api/backup/status");
}
export interface VersionInfo {
/** "<branch>-<short-sha>" baked in at image build time; null on a local/dev build. */
buildVersion: string | null;
}
export async function fetchVersion(): Promise<VersionInfo> {
return apiFetch("/api/version");
}
export interface BackupConfigPatch {
/** "" clears the target. Omit a field to leave it unchanged; null resets retention to default. */
targetDir?: string | null;
+13
View File
@@ -14,6 +14,7 @@ import {
can,
closeShift,
fetchShiftReport,
fetchVersion,
logout,
openShift,
setLanguagePref,
@@ -94,6 +95,17 @@ function SetupTab({ to, label, exact = false }: { to: string; label: string; exa
);
}
/** The running deploy's "<branch>-<short-sha>" (matches the Komodo Stack's TAG in
* komodo/resources.toml), gated the same as the "Park" tab (site:read) since it's the
* same kind of read-only app metadata. Renders nothing if the value isn't known (e.g. a
* local/dev build with no CI-supplied BUILD_VERSION) rather than showing an empty badge. */
function VersionBadge() {
const q = useQuery({ queryKey: ["version"], queryFn: fetchVersion, staleTime: Infinity });
const version = q.data?.buildVersion;
if (!version) return null;
return <span className="ml-auto shrink-0 pl-3 text-[0.7rem] text-term-muted">{version}</span>;
}
/** Setup layout — the config hub. Renders a permission-gated tab bar and the active
* tab's screen via <Outlet>. Each tab is a child route (its own URL + guard), so
* deep links and the back button work and a denied tab redirects to the booth. */
@@ -112,6 +124,7 @@ function SetupLayout() {
{show("recyclebin:read") && <SetupTab to="/setup/recycle-bin" label={t("nav.recycleBin")} />}
{show("log:read") && <SetupTab to="/setup/logs" label={t("nav.logs")} />}
{show("backup:read") && <SetupTab to="/setup/backup" label={t("nav.backup")} />}
{show("site:read") && <VersionBadge />}
</nav>
<Outlet />
</div>