diff --git a/admin/app/services/system_service.ts b/admin/app/services/system_service.ts index 66aa3b1..60b51b7 100644 --- a/admin/app/services/system_service.ts +++ b/admin/app/services/system_service.ts @@ -324,10 +324,12 @@ export class SystemService { throw new Error('Invalid response from GitHub API') } - const latestVersion = response.data.tag_name.replace(/^v/, '') // Remove leading 'v' if present - logger.info(`Current version: ${currentVersion}, Latest version: ${latestVersion}`) + const latestVersion = response.data.tag_name.replace(/^v/, '').trim() // Remove leading 'v' and whitespace + logger.info(`Current version: ${currentVersion}, Latest version: ${latestVersion}`) - const updateAvailable = process.env.NODE_ENV === 'development' ? false : latestVersion !== currentVersion + const updateAvailable = process.env.NODE_ENV === 'development' + ? false + : this.isNewerVersion(latestVersion, currentVersion.trim()) // Cache the results in KVStore for frontend checks await KVStore.setValue('system.updateAvailable', updateAvailable.toString()) @@ -463,4 +465,27 @@ export class SystemService { } }) } + + /** + * Compare two semantic version strings to determine if the first is newer than the second. + * @param version1 - The version to check (e.g., "1.25.0") + * @param version2 - The current version (e.g., "1.24.0") + * @returns true if version1 is newer than version2 + */ + private isNewerVersion(version1: string, version2: string): boolean { + const v1Parts = version1.split('.').map((part) => parseInt(part, 10)) + const v2Parts = version2.split('.').map((part) => parseInt(part, 10)) + + const maxLength = Math.max(v1Parts.length, v2Parts.length) + + for (let i = 0; i < maxLength; i++) { + const v1Part = v1Parts[i] || 0 + const v2Part = v2Parts[i] || 0 + + if (v1Part > v2Part) return true + if (v1Part < v2Part) return false + } + + return false // Versions are equal + } } diff --git a/admin/inertia/pages/settings/update.tsx b/admin/inertia/pages/settings/update.tsx index 9d0ac5f..dc604f3 100644 --- a/admin/inertia/pages/settings/update.tsx +++ b/admin/inertia/pages/settings/update.tsx @@ -77,7 +77,7 @@ function ContentUpdatesSection() { addNotification({ type: 'success', message: `Started ${succeeded} update(s)` }) } if (failed > 0) { - addNotification({ type: 'warning', message: `${failed} update(s) could not be started` }) + addNotification({ type: 'error', message: `${failed} update(s) could not be started` }) } // Remove successful updates from the list const successIds = new Set(result.results.filter((r) => r.success).map((r) => r.resource_id)) @@ -230,6 +230,7 @@ export default function SystemUpdatePage(props: { const [showLogs, setShowLogs] = useState(false) const [logs, setLogs] = useState('') const [email, setEmail] = useState('') + const [versionInfo, setVersionInfo] = useState(props.system) useEffect(() => { if (!isUpdating) return @@ -311,6 +312,34 @@ export default function SystemUpdatePage(props: { } } + const checkVersionMutation = useMutation({ + mutationKey: ['checkLatestVersion'], + mutationFn: () => api.checkLatestVersion(true), + onSuccess: (data) => { + if (data) { + setVersionInfo({ + updateAvailable: data.updateAvailable, + latestVersion: data.latestVersion, + currentVersion: data.currentVersion, + }) + if (data.updateAvailable) { + addNotification({ + type: 'success', + message: `Update available: ${data.latestVersion}`, + }) + } else { + addNotification({ type: 'success', message: 'System is up to date' }) + } + setError(null) + } + }, + onError: (error: any) => { + const errorMessage = error?.message || 'Failed to check for updates' + setError(errorMessage) + addNotification({ type: 'error', message: errorMessage }) + }, + }) + const getProgressBarColor = () => { if (updateStatus?.stage === 'error') return 'bg-desert-red' if (updateStatus?.stage === 'complete') return 'bg-desert-olive' @@ -415,10 +444,10 @@ export default function SystemUpdatePage(props: {

Current Version

- {props.system.currentVersion} + {versionInfo.currentVersion}

- {props.system.updateAvailable && ( + {versionInfo.updateAvailable && ( <>

Latest Version

- {props.system.latestVersion} + {versionInfo.latestVersion}

@@ -464,15 +493,16 @@ export default function SystemUpdatePage(props: { size="lg" icon="IconDownload" onClick={handleStartUpdate} - disabled={!props.system.updateAvailable} + disabled={!versionInfo.updateAvailable} > - {props.system.updateAvailable ? 'Start Update' : 'No Update Available'} + {versionInfo.updateAvailable ? 'Start Update' : 'No Update Available'} window.location.reload()} + onClick={() => checkVersionMutation.mutate()} + loading={checkVersionMutation.isPending} > Check Again