Show the running version on Settings > General

The General page ends with a Version block naming the release this
server runs, so an update can be confirmed from the app.

The version is COBBLE_VERSION, the release tag the image was built with
(Dockerfile ARG, passed by the release workflow, which falls back to
package.json's version on a workflow-dispatch build), else package.json's
version, which a development server reports too, with (development)
after it. runningVersion (src/utils/running-version.ts) drops an
unusable version string rather than showing it.

Also includes the working tree's pending signup guard in
src/pages/__root.server.ts: a user who still holds a session while the
install has no users is sent to /auth/logout (and may load it).
This commit is contained in:
2026-10-22 16:03:56 +01:00
parent 89e57cc467
commit 524f0dde36
13 changed files with 215 additions and 3 deletions
@@ -7,6 +7,7 @@ import type { PagePropsType } from "@/src/types";
import type { BunextPageServerFn } from "@moduletrace/bunext/types";
import grabStorageQuotaOverview from "@/src/functions/backend/db/app-settings/grab-storage-quota-overview";
import { grabSystemPromptAppendOverview } from "@/src/functions/backend/db/app-settings/grab-system-prompt-append";
import runningVersion from "@/src/utils/running-version";
type Params = Parameters<BunextPageServerFn<PagePropsType>>[0];
@@ -44,6 +45,7 @@ export default async function grabSettingsServerProps({ req }: Params) {
has_password,
storage_quota: await grabStorageQuotaOverview().catch(() => null),
system_prompt_append: await grabSystemPromptAppendOverview().catch(() => null),
running_version: runningVersion({ env: process.env }),
},
};
}
@@ -0,0 +1,27 @@
import Span from "@moduletrace/twui/layout/Span";
import type { RunningVersion } from "@/src/utils/running-version";
type Props = {
running?: RunningVersion | null;
};
/**
* The release this server runs, in words: settings are where someone
* checks whether an update landed. A development server says so beside
* its version, so the two are never mistaken.
*/
export default function VersionLine({ running }: Props) {
if (!running) return null;
const version = running.version
? `Cobble ${running.version}`
: "Cobble";
return (
<Span className="text-sm leading-5 text-ink">
{running.mode == "development"
? `${version} (development)`
: version}
</Span>
);
}
@@ -0,0 +1,22 @@
import { useContext } from "react";
import { AppContext } from "@/src/pages/__root";
import SettingsGroup from "../../(partials)/settings-group";
import VersionLine from "../(partials)/version-line";
/**
* What this server is running, so an update can be confirmed from the app
* instead of the installer's output.
*/
export default function VersionSection() {
const { pageProps } = useContext(AppContext);
return (
<SettingsGroup
title="Version"
description="The release this server runs, so an update can be confirmed from the app."
id="version"
>
<VersionLine running={pageProps?.running_version} />
</SettingsGroup>
);
}
@@ -1,6 +1,7 @@
import AppearanceSection from "./(sections)/appearance-section";
import PromptSection from "./(sections)/prompt-section";
import StorageSection from "./(sections)/storage-section";
import VersionSection from "./(sections)/version-section";
import SettingsShell from "../(partials)/settings-shell";
import settingsPageMeta from "../(functions)/settings-page-meta";
@@ -13,6 +14,7 @@ export default function AdminSettingsGeneralPage() {
<AppearanceSection />
<PromptSection />
<StorageSection />
<VersionSection />
</SettingsShell>
);
}