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>
358 lines
10 KiB
TypeScript
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,
|
|
},
|
|
};
|
|
};
|