Files
homarr/src/pages/unraid/index.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

358 lines
10 KiB
TypeScript

/**
* Unraid Dashboard Page
* Main overview page for Unraid server management
*/
import { useState } from 'react';
import {
Alert,
Container,
Grid,
Group,
Loader,
Stack,
Text,
ThemeIcon,
Title,
useMantineTheme,
} from '@mantine/core';
import { notifications } from '@mantine/notifications';
import { IconServer, IconAlertCircle, IconCheck } from '@tabler/icons-react';
import { GetServerSidePropsContext } from 'next';
import { SystemInfoCard, ArrayCard, DockerCard, VmsCard } from '~/components/Unraid/Dashboard';
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';
export default function UnraidDashboardPage() {
const theme = useMantineTheme();
const [loadingContainers, setLoadingContainers] = useState<string[]>([]);
const [loadingVms, setLoadingVms] = useState<string[]>([]);
const [arrayLoading, setArrayLoading] = useState(false);
// Fetch dashboard data
const {
data: dashboard,
isLoading,
error,
refetch,
} = api.unraid.dashboard.useQuery(undefined, {
refetchInterval: 10000, // Refresh every 10 seconds
});
// Mutations
const startContainer = api.unraid.startContainer.useMutation({
onMutate: ({ id }) => setLoadingContainers((prev) => [...prev, id]),
onSettled: (_, __, { id }) =>
setLoadingContainers((prev) => prev.filter((cid) => cid !== id)),
onSuccess: () => {
notifications.show({
title: 'Container Started',
message: 'Container started successfully',
color: 'green',
icon: <IconCheck size={16} />,
});
refetch();
},
onError: (error) => {
notifications.show({
title: 'Error',
message: error.message,
color: 'red',
icon: <IconAlertCircle size={16} />,
});
},
});
const stopContainer = api.unraid.stopContainer.useMutation({
onMutate: ({ id }) => setLoadingContainers((prev) => [...prev, id]),
onSettled: (_, __, { id }) =>
setLoadingContainers((prev) => prev.filter((cid) => cid !== id)),
onSuccess: () => {
notifications.show({
title: 'Container Stopped',
message: 'Container stopped successfully',
color: 'green',
icon: <IconCheck size={16} />,
});
refetch();
},
onError: (error) => {
notifications.show({
title: 'Error',
message: error.message,
color: 'red',
icon: <IconAlertCircle size={16} />,
});
},
});
const startVm = api.unraid.startVm.useMutation({
onMutate: ({ id }) => setLoadingVms((prev) => [...prev, id]),
onSettled: (_, __, { id }) => setLoadingVms((prev) => prev.filter((vid) => vid !== id)),
onSuccess: () => {
notifications.show({
title: 'VM Started',
message: 'Virtual machine started successfully',
color: 'green',
icon: <IconCheck size={16} />,
});
refetch();
},
onError: (error) => {
notifications.show({
title: 'Error',
message: error.message,
color: 'red',
icon: <IconAlertCircle size={16} />,
});
},
});
const stopVm = api.unraid.stopVm.useMutation({
onMutate: ({ id }) => setLoadingVms((prev) => [...prev, id]),
onSettled: (_, __, { id }) => setLoadingVms((prev) => prev.filter((vid) => vid !== id)),
onSuccess: () => {
notifications.show({
title: 'VM Stopped',
message: 'Virtual machine stopped successfully',
color: 'green',
icon: <IconCheck size={16} />,
});
refetch();
},
onError: (error) => {
notifications.show({
title: 'Error',
message: error.message,
color: 'red',
icon: <IconAlertCircle size={16} />,
});
},
});
const pauseVm = api.unraid.pauseVm.useMutation({
onMutate: ({ id }) => setLoadingVms((prev) => [...prev, id]),
onSettled: (_, __, { id }) => setLoadingVms((prev) => prev.filter((vid) => vid !== id)),
onSuccess: () => {
notifications.show({
title: 'VM Paused',
message: 'Virtual machine paused successfully',
color: 'green',
icon: <IconCheck size={16} />,
});
refetch();
},
});
const resumeVm = api.unraid.resumeVm.useMutation({
onMutate: ({ id }) => setLoadingVms((prev) => [...prev, id]),
onSettled: (_, __, { id }) => setLoadingVms((prev) => prev.filter((vid) => vid !== id)),
onSuccess: () => {
notifications.show({
title: 'VM Resumed',
message: 'Virtual machine resumed successfully',
color: 'green',
icon: <IconCheck size={16} />,
});
refetch();
},
});
const rebootVm = api.unraid.rebootVm.useMutation({
onMutate: ({ id }) => setLoadingVms((prev) => [...prev, id]),
onSettled: (_, __, { id }) => setLoadingVms((prev) => prev.filter((vid) => vid !== id)),
onSuccess: () => {
notifications.show({
title: 'VM Rebooting',
message: 'Virtual machine is rebooting',
color: 'green',
icon: <IconCheck size={16} />,
});
refetch();
},
});
const startArray = api.unraid.startArray.useMutation({
onMutate: () => setArrayLoading(true),
onSettled: () => setArrayLoading(false),
onSuccess: () => {
notifications.show({
title: 'Array Starting',
message: 'Array is starting...',
color: 'green',
icon: <IconCheck size={16} />,
});
refetch();
},
onError: (error) => {
notifications.show({
title: 'Error',
message: error.message,
color: 'red',
icon: <IconAlertCircle size={16} />,
});
},
});
const stopArray = api.unraid.stopArray.useMutation({
onMutate: () => setArrayLoading(true),
onSettled: () => setArrayLoading(false),
onSuccess: () => {
notifications.show({
title: 'Array Stopping',
message: 'Array is stopping...',
color: 'green',
icon: <IconCheck size={16} />,
});
refetch();
},
onError: (error) => {
notifications.show({
title: 'Error',
message: error.message,
color: 'red',
icon: <IconAlertCircle size={16} />,
});
},
});
const unreadNotifications = dashboard?.notifications?.filter((n) => !n.read).length || 0;
// Loading state
if (isLoading) {
return (
<UnraidLayout>
<Container size="xl" py="xl">
<Stack align="center" spacing="md">
<Loader size="xl" />
<Text color="dimmed">Connecting to Unraid server...</Text>
</Stack>
</Container>
</UnraidLayout>
);
}
// Error state
if (error) {
return (
<UnraidLayout>
<Container size="xl" py="xl">
<Alert
icon={<IconAlertCircle size={16} />}
title="Connection Error"
color="red"
variant="filled"
>
{error.message}
</Alert>
</Container>
</UnraidLayout>
);
}
// No data
if (!dashboard) {
return (
<UnraidLayout>
<Container size="xl" py="xl">
<Alert
icon={<IconAlertCircle size={16} />}
title="No Data"
color="yellow"
>
No data received from Unraid server. Please check your configuration.
</Alert>
</Container>
</UnraidLayout>
);
}
return (
<UnraidLayout notifications={unreadNotifications}>
<Container size="xl" py="xl">
<Stack spacing="xl">
{/* Header */}
<Group position="apart">
<Group>
<ThemeIcon size={48} radius="md" variant="gradient" gradient={{ from: 'blue', to: 'cyan' }}>
<IconServer size={28} />
</ThemeIcon>
<div>
<Title order={1}>Unraid Dashboard</Title>
<Text color="dimmed" size="sm">
{dashboard.vars.name} - Unraid {dashboard.info.versions.unraid}
</Text>
</div>
</Group>
</Group>
{/* Dashboard Grid */}
<Grid>
{/* System Info */}
<Grid.Col md={6} lg={4}>
<SystemInfoCard
info={dashboard.info}
vars={dashboard.vars}
registration={dashboard.registration}
/>
</Grid.Col>
{/* Array */}
<Grid.Col md={6} lg={8}>
<ArrayCard
array={dashboard.array}
onStartArray={() => startArray.mutate()}
onStopArray={() => stopArray.mutate()}
isLoading={arrayLoading}
/>
</Grid.Col>
{/* Docker */}
<Grid.Col md={6}>
<DockerCard
docker={dashboard.docker}
onStartContainer={(id) => startContainer.mutate({ id })}
onStopContainer={(id) => stopContainer.mutate({ id })}
loadingContainers={loadingContainers}
/>
</Grid.Col>
{/* VMs */}
<Grid.Col md={6}>
<VmsCard
vms={dashboard.vms}
onStartVm={(id) => startVm.mutate({ id })}
onStopVm={(id) => stopVm.mutate({ id })}
onPauseVm={(id) => pauseVm.mutate({ id })}
onResumeVm={(id) => resumeVm.mutate({ id })}
onRebootVm={(id) => rebootVm.mutate({ id })}
loadingVms={loadingVms}
/>
</Grid.Col>
</Grid>
</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,
},
};
};