import { Head } from '@inertiajs/react' import IconArrowBigUpLines from '@tabler/icons-react/dist/esm/icons/IconArrowBigUpLines' import IconCheck from '@tabler/icons-react/dist/esm/icons/IconCheck' import IconAlertCircle from '@tabler/icons-react/dist/esm/icons/IconAlertCircle' import IconRefresh from '@tabler/icons-react/dist/esm/icons/IconRefresh' import SettingsLayout from '~/layouts/SettingsLayout' import StyledButton from '~/components/StyledButton' import Alert from '~/components/Alert' import { useEffect, useState } from 'react' import { IconCircleCheck } from '@tabler/icons-react' import { SystemUpdateStatus } from '../../../types/system' import api from '~/lib/api' export default function SystemUpdatePage(props: { system: { updateAvailable: boolean latestVersion: string currentVersion: string } }) { const [isUpdating, setIsUpdating] = useState(false) const [updateStatus, setUpdateStatus] = useState(null) const [error, setError] = useState(null) const [showLogs, setShowLogs] = useState(false) const [logs, setLogs] = useState('') useEffect(() => { if (!isUpdating) return const interval = setInterval(async () => { try { const response = await api.getSystemUpdateStatus() if (!response) { throw new Error('Failed to fetch update status') } setUpdateStatus(response) // Check if update is complete or errored if (response.stage === 'complete') { // Give a moment for the new container to fully start setTimeout(() => { window.location.reload() }, 2000) } else if (response.stage === 'error') { setIsUpdating(false) setError(response.message) } } catch (err) { // During container restart, we'll lose connection - this is expected // Continue polling to detect when the container comes back up console.log('Polling update status (container may be restarting)...') } }, 2000) return () => clearInterval(interval) }, [isUpdating]) // Poll health endpoint when update is in recreating stage useEffect(() => { if (updateStatus?.stage !== 'recreating') return const interval = setInterval(async () => { try { const response = await api.healthCheck() if (!response) { throw new Error('Health check failed') } if (response.status === 'ok') { // Reload page when container is back up window.location.reload() } } catch (err) { // Still restarting, continue polling... } }, 3000) return () => clearInterval(interval) }, [updateStatus?.stage]) const handleStartUpdate = async () => { try { setError(null) setIsUpdating(true) const response = await api.startSystemUpdate() if (!response || !response.success) { throw new Error('Failed to start update') } } catch (err: any) { setIsUpdating(false) setError(err.response?.data?.error || err.message || 'Failed to start update') } } const handleViewLogs = async () => { try { const response = await api.getSystemUpdateLogs() if (!response) { throw new Error('Failed to fetch update logs') } setLogs(response.logs) setShowLogs(true) } catch (err) { setError('Failed to fetch update logs') } } const getProgressBarColor = () => { if (updateStatus?.stage === 'error') return 'bg-desert-red' if (updateStatus?.stage === 'complete') return 'bg-desert-olive' return 'bg-desert-green' } const getStatusIcon = () => { if (updateStatus?.stage === 'complete') return if (updateStatus?.stage === 'error') return if (isUpdating) return if (props.system.updateAvailable) return return } return (

System Update

Keep your Project N.O.M.A.D. instance up to date with the latest features and improvements.

{error && (
setError(null)} />
)} {isUpdating && updateStatus?.stage === 'recreating' && (
)}
{getStatusIcon()}
{!isUpdating && ( <>

{props.system.updateAvailable ? 'Update Available' : 'System Up to Date'}

{props.system.updateAvailable ? `A new version (${props.system.latestVersion}) is available for your Project N.O.M.A.D. instance.` : 'Your system is running the latest version!'}

)} {isUpdating && updateStatus && ( <>

{updateStatus.stage === 'idle' ? 'Preparing Update' : updateStatus.stage}

{updateStatus.message}

)}

Current Version

{props.system.currentVersion}

{props.system.updateAvailable && ( <>

Latest Version

{props.system.latestVersion}

)}
{isUpdating && updateStatus && (

{updateStatus.progress}% complete

)} {!isUpdating && (
{props.system.updateAvailable ? 'Start Update' : 'No Update Available'} window.location.reload()} > Check Again
)}

What happens during an update?

1

Pull Latest Images

Downloads the newest Docker images for all core containers

2

Recreate Containers

Safely stops and recreates all core containers with the new images

3

Automatic Reload

This page will automatically reload when the update is complete

{isUpdating && (
View Update Logs
)}
{showLogs && (

Update Logs

                    {logs || 'No logs available yet...'}
                  
setShowLogs(false)} fullWidth> Close
)}
) }