Files
homarr/src/pages/unraid/tools/diagnostics.tsx
Kaloyan Danchev 1f92f0593f
Some checks failed
Master CI / yarn_install_and_build (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
Fix TypeScript build errors and configure Traefik deployment
Fix type mismatches across Unraid UI pages (SystemInfo, ServerVars,
Notification properties), replace unavailable Mantine components
(ScrollArea.Autosize, IconHardDrive), correct Orchis theme types,
add missing tRPC endpoints (users, syslog, notification actions),
and configure docker-compose for Traefik reverse proxy on dockerproxy
network with unmarr.xtrm-lab.org routing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 23:55:31 +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?.os.kernel || '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.brand || '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,
},
};
};