Phase 4: Add Unraid management pages with sidebar layout
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>
This commit is contained in:
Kaloyan Danchev
2026-02-06 09:32:52 +02:00
parent 83a8546521
commit 9a2c56a5dc
14 changed files with 4717 additions and 40 deletions

View File

@@ -0,0 +1,288 @@
/**
* Identification Settings Page
* Server name, description, and basic settings
*/
import {
Alert,
Button,
Card,
Container,
Divider,
Group,
Loader,
Stack,
Switch,
Text,
TextInput,
Textarea,
ThemeIcon,
Title,
} from '@mantine/core';
import { useForm } from '@mantine/form';
import { notifications } from '@mantine/notifications';
import {
IconServer,
IconAlertCircle,
IconCheck,
IconDeviceFloppy,
} 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';
export default function IdentificationSettingsPage() {
const {
data: vars,
isLoading,
error,
} = api.unraid.vars.useQuery();
const {
data: info,
} = api.unraid.info.useQuery();
const form = useForm({
initialValues: {
name: '',
description: '',
model: '',
timezone: '',
},
});
// Update form when data loads
if (vars && !form.isTouched()) {
form.setValues({
name: vars.name || '',
description: vars.comment || '',
model: vars.flashProduct || '',
timezone: vars.timezone || '',
});
}
if (isLoading) {
return (
<UnraidLayout>
<Container size="md" py="xl">
<Stack align="center" spacing="md">
<Loader size="xl" />
<Text color="dimmed">Loading settings...</Text>
</Stack>
</Container>
</UnraidLayout>
);
}
if (error) {
return (
<UnraidLayout>
<Container size="md" py="xl">
<Alert icon={<IconAlertCircle size={16} />} title="Error" color="red">
{error.message}
</Alert>
</Container>
</UnraidLayout>
);
}
return (
<UnraidLayout>
<Container size="md" py="xl">
<Stack spacing="xl">
{/* Header */}
<Group>
<ThemeIcon size={48} radius="md" variant="light" color="blue">
<IconServer size={28} />
</ThemeIcon>
<div>
<Title order={2}>Identification</Title>
<Text color="dimmed" size="sm">
Server name and basic settings
</Text>
</div>
</Group>
{/* Server Identity */}
<Card shadow="sm" radius="md" withBorder>
<Title order={4} mb="md">
Server Identity
</Title>
<Stack spacing="md">
<TextInput
label="Server Name"
description="The name of your Unraid server"
placeholder="Tower"
{...form.getInputProps('name')}
disabled
/>
<Textarea
label="Description"
description="A brief description of this server"
placeholder="Home media server"
{...form.getInputProps('description')}
disabled
/>
</Stack>
</Card>
{/* System Information */}
<Card shadow="sm" radius="md" withBorder>
<Title order={4} mb="md">
System Information
</Title>
<Stack spacing="md">
<Group position="apart">
<Text size="sm" color="dimmed">
Unraid Version
</Text>
<Text size="sm" weight={500}>
{info?.versions.unraid || 'Unknown'}
</Text>
</Group>
<Divider />
<Group position="apart">
<Text size="sm" color="dimmed">
Linux Kernel
</Text>
<Text size="sm" weight={500}>
{info?.versions.linux || 'Unknown'}
</Text>
</Group>
<Divider />
<Group position="apart">
<Text size="sm" color="dimmed">
CPU
</Text>
<Text size="sm" weight={500}>
{info?.cpu.model || 'Unknown'}
</Text>
</Group>
<Divider />
<Group position="apart">
<Text size="sm" color="dimmed">
Motherboard
</Text>
<Text size="sm" weight={500}>
{info?.motherboard?.product || 'Unknown'}
</Text>
</Group>
<Divider />
<Group position="apart">
<Text size="sm" color="dimmed">
Total RAM
</Text>
<Text size="sm" weight={500}>
{info?.memory?.total
? `${Math.round(info.memory.total / 1024 / 1024 / 1024)} GB`
: 'Unknown'}
</Text>
</Group>
<Divider />
<Group position="apart">
<Text size="sm" color="dimmed">
Timezone
</Text>
<Text size="sm" weight={500}>
{vars?.timezone || 'Unknown'}
</Text>
</Group>
</Stack>
</Card>
{/* Flash Drive */}
<Card shadow="sm" radius="md" withBorder>
<Title order={4} mb="md">
Flash Drive
</Title>
<Stack spacing="md">
<Group position="apart">
<Text size="sm" color="dimmed">
Product
</Text>
<Text size="sm" weight={500}>
{vars?.flashProduct || 'Unknown'}
</Text>
</Group>
<Divider />
<Group position="apart">
<Text size="sm" color="dimmed">
Vendor
</Text>
<Text size="sm" weight={500}>
{vars?.flashVendor || 'Unknown'}
</Text>
</Group>
<Divider />
<Group position="apart">
<Text size="sm" color="dimmed">
GUID
</Text>
<Text size="sm" weight={500} style={{ fontFamily: 'monospace' }}>
{vars?.flashGuid || 'Unknown'}
</Text>
</Group>
</Stack>
</Card>
{/* Save Button (disabled for now) */}
<Group position="right">
<Button
leftIcon={<IconDeviceFloppy size={16} />}
disabled
>
Save Changes
</Button>
</Group>
</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,
},
};
};