mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-26 13:35:49 +08:00
feat(version): warn when the server was updated but not restarted (#898)
When the package is updated on disk but the long-lived server process is not restarted, the new frontend bundle (served from disk) talks to the old running backend. New DB-backed features then fail silently — e.g. deleting/archiving a session appears to do nothing — because the new schema/routes only take effect on restart. Nothing currently detects this skew: useVersionCheck only compares the frontend's build-time version against the latest GitHub release. This exposes the running server's version (captured once at startup) via /health, compares it to the frontend's build-time version in useVersionCheck, and shows a "restart required" banner in the sidebar (and a small indicator in the collapsed sidebar) when they differ. - server: add `version` (RUNNING_VERSION, read once at startup) to /health - useVersionCheck: return `restartRequired` / `runningVersion` - SidebarFooter / SidebarCollapsed: surface a restart-required banner - i18n: add `version.restartRequired` to all 10 sidebar locales Verified with `tsc --noEmit` (client + server) and eslint. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Simos Mikelatos <simosmik@gmail.com>
This commit is contained in:
@@ -43,7 +43,7 @@ function Sidebar({
|
||||
}: SidebarProps) {
|
||||
const { t } = useTranslation(['sidebar', 'common']);
|
||||
const { isPWA } = useDeviceSettings({ trackMobile: false });
|
||||
const { updateAvailable, latestVersion, currentVersion, releaseInfo, installMode } = useVersionCheck(
|
||||
const { updateAvailable, restartRequired, latestVersion, currentVersion, releaseInfo, installMode } = useVersionCheck(
|
||||
'siteboon',
|
||||
'claudecodeui',
|
||||
);
|
||||
@@ -224,6 +224,7 @@ function Sidebar({
|
||||
onExpand={handleExpandSidebar}
|
||||
onShowSettings={onShowSettings}
|
||||
updateAvailable={updateAvailable}
|
||||
restartRequired={restartRequired}
|
||||
onShowVersionModal={() => setShowVersionModal(true)}
|
||||
t={t}
|
||||
/>
|
||||
@@ -296,6 +297,7 @@ function Sidebar({
|
||||
onCreateProject={() => setShowNewProject(true)}
|
||||
onCollapseSidebar={handleCollapseSidebar}
|
||||
updateAvailable={updateAvailable}
|
||||
restartRequired={restartRequired}
|
||||
releaseInfo={releaseInfo}
|
||||
latestVersion={latestVersion}
|
||||
currentVersion={currentVersion}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Settings, Sparkles, PanelLeftOpen, Bug } from 'lucide-react';
|
||||
import { Settings, Sparkles, PanelLeftOpen, Bug, AlertTriangle } from 'lucide-react';
|
||||
import type { TFunction } from 'i18next';
|
||||
|
||||
const DISCORD_INVITE_URL = 'https://discord.gg/buxwujPNRE';
|
||||
@@ -16,6 +16,7 @@ type SidebarCollapsedProps = {
|
||||
onExpand: () => void;
|
||||
onShowSettings: () => void;
|
||||
updateAvailable: boolean;
|
||||
restartRequired: boolean;
|
||||
onShowVersionModal: () => void;
|
||||
t: TFunction;
|
||||
};
|
||||
@@ -24,6 +25,7 @@ export default function SidebarCollapsed({
|
||||
onExpand,
|
||||
onShowSettings,
|
||||
updateAvailable,
|
||||
restartRequired,
|
||||
onShowVersionModal,
|
||||
t,
|
||||
}: SidebarCollapsedProps) {
|
||||
@@ -75,6 +77,18 @@ export default function SidebarCollapsed({
|
||||
<DiscordIcon className="h-4 w-4 text-muted-foreground transition-colors group-hover:text-foreground" />
|
||||
</a>
|
||||
|
||||
{/* Restart-required indicator */}
|
||||
{restartRequired && (
|
||||
<div
|
||||
className="relative flex h-8 w-8 items-center justify-center rounded-lg"
|
||||
aria-label={t('version.restartRequired')}
|
||||
title={t('version.restartRequired')}
|
||||
>
|
||||
<AlertTriangle className="h-4 w-4 text-amber-500" />
|
||||
<span className="absolute right-1.5 top-1.5 h-1.5 w-1.5 animate-pulse rounded-full bg-amber-500" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Update indicator */}
|
||||
{updateAvailable && (
|
||||
<button
|
||||
|
||||
@@ -141,6 +141,7 @@ type SidebarContentProps = {
|
||||
onCreateProject: () => void;
|
||||
onCollapseSidebar: () => void;
|
||||
updateAvailable: boolean;
|
||||
restartRequired: boolean;
|
||||
releaseInfo: ReleaseInfo | null;
|
||||
latestVersion: string | null;
|
||||
currentVersion: string;
|
||||
@@ -178,6 +179,7 @@ export default function SidebarContent({
|
||||
onCreateProject,
|
||||
onCollapseSidebar,
|
||||
updateAvailable,
|
||||
restartRequired,
|
||||
releaseInfo,
|
||||
latestVersion,
|
||||
currentVersion,
|
||||
@@ -553,6 +555,7 @@ export default function SidebarContent({
|
||||
|
||||
<SidebarFooter
|
||||
updateAvailable={updateAvailable}
|
||||
restartRequired={restartRequired}
|
||||
releaseInfo={releaseInfo}
|
||||
latestVersion={latestVersion}
|
||||
currentVersion={currentVersion}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Settings, ArrowUpCircle, Bug } from 'lucide-react';
|
||||
import { Settings, ArrowUpCircle, Bug, AlertTriangle } from 'lucide-react';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { IS_PLATFORM } from '../../../../constants/config';
|
||||
import type { ReleaseInfo } from '../../../../types/sharedTypes';
|
||||
@@ -18,6 +18,7 @@ function DiscordIcon({ className }: { className?: string }) {
|
||||
|
||||
type SidebarFooterProps = {
|
||||
updateAvailable: boolean;
|
||||
restartRequired: boolean;
|
||||
releaseInfo: ReleaseInfo | null;
|
||||
latestVersion: string | null;
|
||||
currentVersion: string;
|
||||
@@ -28,6 +29,7 @@ type SidebarFooterProps = {
|
||||
|
||||
export default function SidebarFooter({
|
||||
updateAvailable,
|
||||
restartRequired,
|
||||
releaseInfo,
|
||||
latestVersion,
|
||||
currentVersion,
|
||||
@@ -37,6 +39,22 @@ export default function SidebarFooter({
|
||||
}: SidebarFooterProps) {
|
||||
return (
|
||||
<div className="flex-shrink-0" style={{ paddingBottom: 'env(safe-area-inset-bottom, 0)' }}>
|
||||
{/* Restart-required banner: the running server version differs from the
|
||||
installed/frontend version (updated but not restarted). */}
|
||||
{restartRequired && (
|
||||
<>
|
||||
<div className="nav-divider" />
|
||||
<div className="px-2 py-1.5 md:px-2 md:py-1.5">
|
||||
<div className="flex items-center gap-2.5 rounded-lg border border-amber-300/60 bg-amber-50/80 px-2.5 py-2 dark:border-amber-700/40 dark:bg-amber-900/15">
|
||||
<AlertTriangle className="h-4 w-4 flex-shrink-0 text-amber-500 dark:text-amber-400" />
|
||||
<span className="min-w-0 flex-1 text-xs font-medium text-amber-700 dark:text-amber-300">
|
||||
{t('version.restartRequired')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Update banner */}
|
||||
{updateAvailable && (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user