mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-25 12:16:00 +08:00
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>
99 lines
3.7 KiB
TypeScript
99 lines
3.7 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { version } from '../../package.json';
|
|
import { ReleaseInfo } from '../types/sharedTypes';
|
|
|
|
/**
|
|
* Compare two semantic version strings
|
|
* Works only with numeric versions separated by dots (e.g. "1.2.3")
|
|
* @param {string} v1
|
|
* @param {string} v2
|
|
* @returns positive if v1 > v2, negative if v1 < v2, 0 if equal
|
|
*/
|
|
const compareVersions = (v1: string, v2: string) => {
|
|
const parts1 = v1.split('.').map(Number);
|
|
const parts2 = v2.split('.').map(Number);
|
|
|
|
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
const p1 = parts1[i] || 0;
|
|
const p2 = parts2[i] || 0;
|
|
if (p1 !== p2) return p1 - p2;
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
export type InstallMode = 'git' | 'npm';
|
|
|
|
export const useVersionCheck = (owner: string, repo: string) => {
|
|
const [updateAvailable, setUpdateAvailable] = useState(false);
|
|
const [latestVersion, setLatestVersion] = useState<string | null>(null);
|
|
const [releaseInfo, setReleaseInfo] = useState<ReleaseInfo | null>(null);
|
|
const [installMode, setInstallMode] = useState<InstallMode>('git');
|
|
const [runningVersion, setRunningVersion] = useState<string | null>(null);
|
|
const [restartRequired, setRestartRequired] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const fetchHealth = async () => {
|
|
try {
|
|
const response = await fetch('/health');
|
|
const data = await response.json();
|
|
if (data.installMode === 'npm' || data.installMode === 'git') {
|
|
setInstallMode(data.installMode);
|
|
}
|
|
// `data.version` is the version the server process is actually running.
|
|
// This module's `version` is baked into the frontend bundle at build
|
|
// time, so it reflects the installed (on-disk) package. If they differ,
|
|
// the package was updated but the server process was not restarted, and
|
|
// DB-backed actions may silently fail until it is.
|
|
if (typeof data.version === 'string' && data.version.length > 0) {
|
|
setRunningVersion(data.version);
|
|
setRestartRequired(data.version !== version);
|
|
}
|
|
} catch {
|
|
// Default to git / no restart hint on error
|
|
}
|
|
};
|
|
fetchHealth();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const checkVersion = async () => {
|
|
try {
|
|
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases/latest`);
|
|
const data = await response.json();
|
|
|
|
// Handle the case where there might not be any releases
|
|
if (data.tag_name) {
|
|
const latest = data.tag_name.replace(/^v/, '');
|
|
setLatestVersion(latest);
|
|
// Only show update if latest version is actually newer
|
|
setUpdateAvailable(compareVersions(latest, version) > 0);
|
|
|
|
// Store release information
|
|
setReleaseInfo({
|
|
title: data.name || data.tag_name,
|
|
body: data.body || '',
|
|
htmlUrl: data.html_url || `https://github.com/${owner}/${repo}/releases/latest`,
|
|
publishedAt: data.published_at
|
|
});
|
|
} else {
|
|
// No releases found, don't show update notification
|
|
setUpdateAvailable(false);
|
|
setLatestVersion(null);
|
|
setReleaseInfo(null);
|
|
}
|
|
} catch (error) {
|
|
console.error('Version check failed:', error);
|
|
// On error, don't show update notification
|
|
setUpdateAvailable(false);
|
|
setLatestVersion(null);
|
|
setReleaseInfo(null);
|
|
}
|
|
};
|
|
|
|
checkVersion();
|
|
const interval = setInterval(checkVersion, 5 * 60 * 1000); // Check every 5 minutes
|
|
return () => clearInterval(interval);
|
|
}, [owner, repo]);
|
|
|
|
return { updateAvailable, latestVersion, currentVersion: version, releaseInfo, installMode, runningVersion, restartRequired };
|
|
};
|