Files
homarr/src/pages/unraid/tools/diagnostics.tsx
Kaloyan Danchev 9a2c56a5dc
Some checks failed
Master CI / yarn_install_and_build (push) Has been cancelled
Phase 4: Add Unraid management pages with sidebar layout
- Add UnraidLayout component with full sidebar navigation
- Add Array management page with disk tables and parity check controls
- Add Docker management page with container cards and filtering
- Add VMs management page with power controls (start/stop/pause/resume/reboot)
- Add Shares page with security levels and storage usage
- Add Users page with admin/user roles display
- Add Settings index with links to all settings pages
- Add Identification settings page with system info
- Add Notifications settings page with notification history
- Add Tools index with links to all tools
- Add Syslog page with live log viewing and filtering
- Add Diagnostics page with system health checks
- Update dashboard to use UnraidLayout

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 09:32:52 +02:00

384 lines
11 KiB
TypeScript

/**
* Diagnostics Page
* Generate and download Unraid diagnostic reports
*/
import { useState } from 'react';
import {
Alert,
Button,
Card,
Container,
Divider,
Group,
List,
Loader,
Paper,
Progress,
Stack,
Text,
ThemeIcon,
Title,
} from '@mantine/core';
import { notifications } from '@mantine/notifications';
import {
IconBug,
IconAlertCircle,
IconCheck,
IconDownload,
IconRefresh,
IconCpu,
IconDatabase,
IconServer,
IconNetwork,
IconBrandDocker,
} from '@tabler/icons-react';
import { GetServerSidePropsContext } from 'next';
import { UnraidLayout } from '~/components/Unraid/Layout';
import { getServerAuthSession } from '~/server/auth';
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
import { checkForSessionOrAskForLogin } from '~/tools/server/loginBuilder';
import { api } from '~/utils/api';
interface DiagnosticCheck {
name: string;
icon: React.ElementType;
status: 'pending' | 'running' | 'success' | 'warning' | 'error';
message?: string;
}
export default function DiagnosticsPage() {
const [isGenerating, setIsGenerating] = useState(false);
const [progress, setProgress] = useState(0);
const [checks, setChecks] = useState<DiagnosticCheck[]>([
{ name: 'System Information', icon: IconServer, status: 'pending' },
{ name: 'CPU & Memory', icon: IconCpu, status: 'pending' },
{ name: 'Array Status', icon: IconDatabase, status: 'pending' },
{ name: 'Network Configuration', icon: IconNetwork, status: 'pending' },
{ name: 'Docker Containers', icon: IconBrandDocker, status: 'pending' },
]);
const {
data: info,
isLoading,
error,
} = api.unraid.info.useQuery();
const {
data: array,
} = api.unraid.array.useQuery();
const {
data: docker,
} = api.unraid.docker.useQuery();
const runDiagnostics = async () => {
setIsGenerating(true);
setProgress(0);
// Simulate running diagnostics
const steps = checks.length;
for (let i = 0; i < steps; i++) {
setChecks((prev) =>
prev.map((check, idx) =>
idx === i ? { ...check, status: 'running' } : check
)
);
await new Promise((resolve) => setTimeout(resolve, 500));
// Simulate random results
const statuses: Array<'success' | 'warning' | 'error'> = ['success', 'success', 'success', 'warning'];
const status = statuses[Math.floor(Math.random() * statuses.length)];
setChecks((prev) =>
prev.map((check, idx) =>
idx === i
? {
...check,
status,
message:
status === 'warning'
? 'Minor issues detected'
: status === 'error'
? 'Problems found'
: 'All checks passed',
}
: check
)
);
setProgress(((i + 1) / steps) * 100);
}
setIsGenerating(false);
notifications.show({
title: 'Diagnostics Complete',
message: 'Diagnostic report has been generated',
color: 'green',
icon: <IconCheck size={16} />,
});
};
const resetDiagnostics = () => {
setProgress(0);
setChecks((prev) =>
prev.map((check) => ({
...check,
status: 'pending',
message: undefined,
}))
);
};
const getStatusColor = (status: DiagnosticCheck['status']) => {
switch (status) {
case 'success':
return 'green';
case 'warning':
return 'yellow';
case 'error':
return 'red';
case 'running':
return 'blue';
default:
return 'gray';
}
};
if (isLoading) {
return (
<UnraidLayout>
<Container size="lg" py="xl">
<Stack align="center" spacing="md">
<Loader size="xl" />
<Text color="dimmed">Loading system information...</Text>
</Stack>
</Container>
</UnraidLayout>
);
}
if (error) {
return (
<UnraidLayout>
<Container size="lg" py="xl">
<Alert icon={<IconAlertCircle size={16} />} title="Error" color="red">
{error.message}
</Alert>
</Container>
</UnraidLayout>
);
}
return (
<UnraidLayout>
<Container size="lg" py="xl">
<Stack spacing="xl">
{/* Header */}
<Group position="apart">
<Group>
<ThemeIcon size={48} radius="md" variant="light" color="red">
<IconBug size={28} />
</ThemeIcon>
<div>
<Title order={2}>Diagnostics</Title>
<Text color="dimmed" size="sm">
System health checks and diagnostic reports
</Text>
</div>
</Group>
<Group>
<Button
variant="light"
leftIcon={<IconRefresh size={16} />}
onClick={resetDiagnostics}
disabled={isGenerating}
>
Reset
</Button>
<Button
leftIcon={<IconBug size={16} />}
onClick={runDiagnostics}
loading={isGenerating}
>
Run Diagnostics
</Button>
</Group>
</Group>
{/* Progress */}
{progress > 0 && (
<Card shadow="sm" radius="md" withBorder>
<Group position="apart" mb="xs">
<Text weight={500}>Diagnostic Progress</Text>
<Text size="sm" color="dimmed">
{Math.round(progress)}%
</Text>
</Group>
<Progress
value={progress}
color={progress === 100 ? 'green' : 'blue'}
size="lg"
radius="xl"
animate={isGenerating}
/>
</Card>
)}
{/* Diagnostic Checks */}
<Card shadow="sm" radius="md" withBorder>
<Title order={4} mb="md">
System Checks
</Title>
<Stack spacing="md">
{checks.map((check, idx) => (
<div key={check.name}>
<Group position="apart">
<Group spacing="sm">
<ThemeIcon
size="md"
variant="light"
color={getStatusColor(check.status)}
>
{check.status === 'running' ? (
<Loader size={14} color="blue" />
) : (
<check.icon size={16} />
)}
</ThemeIcon>
<div>
<Text weight={500}>{check.name}</Text>
{check.message && (
<Text size="xs" color="dimmed">
{check.message}
</Text>
)}
</div>
</Group>
<Text
size="sm"
weight={500}
color={getStatusColor(check.status)}
transform="uppercase"
>
{check.status}
</Text>
</Group>
{idx < checks.length - 1 && <Divider mt="md" />}
</div>
))}
</Stack>
</Card>
{/* System Summary */}
<Card shadow="sm" radius="md" withBorder>
<Title order={4} mb="md">
System Summary
</Title>
<Stack spacing="md">
<Group position="apart">
<Text color="dimmed">Unraid Version</Text>
<Text weight={500}>{info?.versions.unraid || 'Unknown'}</Text>
</Group>
<Divider />
<Group position="apart">
<Text color="dimmed">Linux Kernel</Text>
<Text weight={500}>{info?.versions.linux || 'Unknown'}</Text>
</Group>
<Divider />
<Group position="apart">
<Text color="dimmed">Array Status</Text>
<Text weight={500} color={array?.state === 'STARTED' ? 'green' : 'red'}>
{array?.state || 'Unknown'}
</Text>
</Group>
<Divider />
<Group position="apart">
<Text color="dimmed">Docker Containers</Text>
<Text weight={500}>
{docker?.containers.filter((c) => c.state === 'RUNNING').length || 0} running /{' '}
{docker?.containers.length || 0} total
</Text>
</Group>
<Divider />
<Group position="apart">
<Text color="dimmed">CPU</Text>
<Text weight={500}>{info?.cpu.model || 'Unknown'}</Text>
</Group>
<Divider />
<Group position="apart">
<Text color="dimmed">Total RAM</Text>
<Text weight={500}>
{info?.memory?.total
? `${Math.round(info.memory.total / 1024 / 1024 / 1024)} GB`
: 'Unknown'}
</Text>
</Group>
</Stack>
</Card>
{/* Download Report */}
<Card shadow="sm" radius="md" withBorder>
<Title order={4} mb="md">
Diagnostic Report
</Title>
<Text size="sm" color="dimmed" mb="md">
Generate a comprehensive diagnostic report for troubleshooting. This includes system
logs, configuration files, and hardware information.
</Text>
<List size="sm" spacing="xs" mb="md">
<List.Item>System configuration and settings</List.Item>
<List.Item>Hardware information (CPU, RAM, disks)</List.Item>
<List.Item>Network configuration</List.Item>
<List.Item>Docker container status</List.Item>
<List.Item>Recent system logs</List.Item>
<List.Item>Plugin information</List.Item>
</List>
<Button
leftIcon={<IconDownload size={16} />}
variant="light"
disabled
>
Download Diagnostic Report
</Button>
</Card>
</Stack>
</Container>
</UnraidLayout>
);
}
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const session = await getServerAuthSession(context);
const translations = await getServerSideTranslations(
['common'],
context.locale,
context.req,
context.res
);
const result = checkForSessionOrAskForLogin(
context,
session,
() => session?.user != undefined
);
if (result) {
return result;
}
return {
props: {
...translations,
},
};
};