Some checks failed
Master CI / yarn_install_and_build (push) Has been cancelled
- 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>
384 lines
11 KiB
TypeScript
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,
|
|
},
|
|
};
|
|
};
|