import { useState, useCallback } from 'react' import { Head } from '@inertiajs/react' import { useTranslation } from 'react-i18next' import i18n from 'i18next' import SettingsLayout from '~/layouts/SettingsLayout' import { SystemInformationResponse } from '../../../types/system' import { formatBytes } from '~/lib/util' import { getAllDiskDisplayItems } from '~/hooks/useDiskDisplayData' import CircularGauge from '~/components/systeminfo/CircularGauge' import HorizontalBarChart from '~/components/HorizontalBarChart' import InfoCard from '~/components/systeminfo/InfoCard' import Alert from '~/components/Alert' import StyledModal from '~/components/StyledModal' import { useSystemInfo } from '~/hooks/useSystemInfo' import { useNotifications } from '~/context/NotificationContext' import { useModals } from '~/context/ModalContext' import api from '~/lib/api' import StatusCard from '~/components/systeminfo/StatusCard' import { IconCpu, IconDatabase, IconServer, IconDeviceDesktop, IconComponents } from '@tabler/icons-react' export default function SettingsPage(props: { system: { info: SystemInformationResponse | undefined } }) { const { t } = useTranslation('settings') const { t: tCommon } = useTranslation('common') const { data: info } = useSystemInfo({ initialData: props.system.info, }) const { addNotification } = useNotifications() const { openModal, closeAllModals } = useModals() const [gpuBannerDismissed, setGpuBannerDismissed] = useState(() => { try { return localStorage.getItem('nomad:gpu-banner-dismissed') === 'true' } catch { return false } }) const [reinstalling, setReinstalling] = useState(false) const handleDismissGpuBanner = () => { setGpuBannerDismissed(true) try { localStorage.setItem('nomad:gpu-banner-dismissed', 'true') } catch {} } const handleForceReinstallOllama = () => { openModal( { closeAllModals() setReinstalling(true) try { const response = await api.forceReinstallService('nomad_ollama') if (!response || !response.success) { throw new Error(response?.message || 'Force reinstall failed') } addNotification({ message: tCommon('alerts.reinstallSuccess'), type: 'success', }) try { localStorage.removeItem('nomad:gpu-banner-dismissed') } catch {} setTimeout(() => window.location.reload(), 5000) } catch (error) { addNotification({ message: tCommon('alerts.reinstallFailed', { error: error instanceof Error ? error.message : 'Unknown error' }), type: 'error', }) setReinstalling(false) } }} onCancel={closeAllModals} open={true} confirmText={tCommon('buttons.reinstall')} cancelText={tCommon('buttons.cancel')} >

{tCommon('alerts.reinstallAIConfirmMessage')}

, 'gpu-health-force-reinstall-modal' ) } // Use (total - available) to reflect actual memory pressure. // mem.used includes reclaimable buff/cache on Linux, which inflates the number. const memoryUsed = info?.mem.total && info?.mem.available != null ? info.mem.total - info.mem.available : info?.mem.used || 0 const memoryUsagePercent = info?.mem.total ? ((memoryUsed / info.mem.total) * 100).toFixed(1) : 0 const swapUsagePercent = info?.mem.swaptotal ? ((info.mem.swapused / info.mem.swaptotal) * 100).toFixed(1) : 0 const uptimeSeconds = info?.uptime.uptime || 0 const uptimeDays = Math.floor(uptimeSeconds / 86400) const uptimeHours = Math.floor((uptimeSeconds % 86400) / 3600) const uptimeMinutes = Math.floor((uptimeSeconds % 3600) / 60) const uptimeDisplay = uptimeDays > 0 ? `${uptimeDays}d ${uptimeHours}h ${uptimeMinutes}m` : uptimeHours > 0 ? `${uptimeHours}h ${uptimeMinutes}m` : `${uptimeMinutes}m` // Build storage display items - fall back to fsSize when disk array is empty const storageItems = getAllDiskDisplayItems(info?.disk, info?.fsSize) return (

{t('system.title')}

{t('system.subtitle')} • {t('system.lastUpdated', { time: new Date().toLocaleString() })} • {' '}{t('system.refreshing')}

{Number(memoryUsagePercent) > 90 && (
)}

{t('sections.resourceUsage')}

} />
} />
} />

{t('sections.systemDetails')}

} variant="elevated" data={[ { label: t('os.distribution'), value: info?.os.distro }, { label: t('os.kernelVersion'), value: info?.os.kernel }, { label: t('os.architecture'), value: info?.os.arch }, { label: t('os.hostname'), value: info?.os.hostname }, { label: t('os.platform'), value: info?.os.platform }, ]} /> } variant="elevated" data={[ { label: t('cpu.manufacturer'), value: info?.cpu.manufacturer }, { label: t('cpu.brand'), value: info?.cpu.brand }, { label: t('cpu.cores'), value: info?.cpu.cores }, { label: t('cpu.physicalCores'), value: info?.cpu.physicalCores }, { label: t('cpu.virtualization'), value: info?.cpu.virtualization ? t('cpu.enabled') : t('cpu.disabled'), }, ]} /> {info?.gpuHealth?.status === 'passthrough_failed' && !gpuBannerDismissed && (
)} {info?.graphics?.controllers && info.graphics.controllers.length > 0 && ( } variant="elevated" data={info.graphics.controllers.map((gpu, i) => { const prefix = info.graphics.controllers.length > 1 ? `GPU ${i + 1} ` : '' return [ { label: `${prefix}${t('gpu.model')}`, value: gpu.model }, { label: `${prefix}${t('gpu.vendor')}`, value: gpu.vendor }, { label: `${prefix}${t('gpu.vram')}`, value: gpu.vram ? `${gpu.vram} MB` : 'N/A' }, ] }).flat()} /> )}

{t('sections.memoryAllocation')}

{formatBytes(info?.mem.total || 0)}
{t('labels.totalRam')}
{formatBytes(memoryUsed)}
{t('labels.usedRam')}
{formatBytes(info?.mem.available || 0)}
{t('labels.availableRam')}
{t('labels.utilized', { percent: memoryUsagePercent })}

{t('sections.storageDevices')}

{storageItems.length > 0 ? ( ) : (
{t('labels.noStorageDetected')}
)}

{t('sections.systemStatus')}

{t('sections.preferences')}

{tCommon('language.label')}

) }