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>
This commit is contained in:
@@ -10,4 +10,10 @@ NEXTAUTH_SECRET="anything"
|
||||
# Disable analytics
|
||||
NEXT_PUBLIC_DISABLE_ANALYTICS="true"
|
||||
|
||||
DEFAULT_COLOR_SCHEME="light"
|
||||
DEFAULT_COLOR_SCHEME="light"
|
||||
|
||||
# Unraid API Configuration
|
||||
UNRAID_HOST=192.168.10.20
|
||||
UNRAID_API_KEY=your-api-key-here
|
||||
UNRAID_USE_SSL=false
|
||||
UNRAID_PORT=80
|
||||
@@ -1,12 +1,10 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
homarr-unraid:
|
||||
unmarr:
|
||||
image: git.xtrm-lab.org/jazzymc/homarr:latest
|
||||
container_name: homarr-unraid-ui
|
||||
container_name: unmarr
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "7576:7575"
|
||||
environment:
|
||||
# Unraid API Configuration
|
||||
- UNRAID_HOST=192.168.10.20
|
||||
@@ -17,18 +15,28 @@ services:
|
||||
- TZ=Europe/Sofia
|
||||
- DATABASE_URL=file:/data/db.sqlite
|
||||
- AUTH_TRUST_HOST=true
|
||||
- NEXTAUTH_URL=http://192.168.10.20:7576
|
||||
- NEXTAUTH_URL=https://unmarr.xtrm-lab.org
|
||||
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET:-changeme}
|
||||
volumes:
|
||||
- /mnt/user/appdata/homarr-unraid/data:/data
|
||||
- /mnt/user/appdata/homarr-unraid/configs:/app/data/configs
|
||||
- /mnt/user/appdata/unmarr/data:/data
|
||||
- /mnt/user/appdata/unmarr/configs:/app/data/configs
|
||||
networks:
|
||||
- unraid-net
|
||||
dockerproxy:
|
||||
ipv4_address: 172.18.0.5
|
||||
labels:
|
||||
# Traefik
|
||||
- "traefik.enable=true"
|
||||
- "traefik.constraint=valid"
|
||||
- "traefik.http.routers.unmarr.rule=Host(`unmarr.xtrm-lab.org`)"
|
||||
- "traefik.http.routers.unmarr.entrypoints=https"
|
||||
- "traefik.http.routers.unmarr.tls=true"
|
||||
- "traefik.http.routers.unmarr.tls.certresolver=cloudflare"
|
||||
- "traefik.http.services.unmarr.loadbalancer.server.port=7575"
|
||||
# Unraid
|
||||
- "net.unraid.docker.managed=true"
|
||||
- "net.unraid.docker.icon=https://homarr.dev/img/logo.png"
|
||||
- "net.unraid.docker.webui=http://[IP]:[PORT:7576]"
|
||||
- "net.unraid.docker.webui=https://unmarr.xtrm-lab.org"
|
||||
|
||||
networks:
|
||||
unraid-net:
|
||||
driver: bridge
|
||||
dockerproxy:
|
||||
external: true
|
||||
|
||||
@@ -32,14 +32,7 @@ echo "Image: ${REGISTRY}/${IMAGE_NAME}:${TAG}"
|
||||
echo ""
|
||||
echo "To deploy on Unraid:"
|
||||
echo "1. SSH to Unraid: ssh root@192.168.10.20 -p 422"
|
||||
echo "2. Create directory: mkdir -p /mnt/user/appdata/homarr-unraid/{data,configs}"
|
||||
echo "3. Pull image: docker pull ${REGISTRY}/${IMAGE_NAME}:${TAG}"
|
||||
echo "4. Run container:"
|
||||
echo " docker run -d \\"
|
||||
echo " --name homarr-unraid-ui \\"
|
||||
echo " -p 7576:7575 \\"
|
||||
echo " -e UNRAID_HOST=192.168.10.20 \\"
|
||||
echo " -e UNRAID_API_KEY=YOUR_API_KEY \\"
|
||||
echo " -v /mnt/user/appdata/homarr-unraid/data:/data \\"
|
||||
echo " -v /mnt/user/appdata/homarr-unraid/configs:/app/data/configs \\"
|
||||
echo " ${REGISTRY}/${IMAGE_NAME}:${TAG}"
|
||||
echo "2. Create directory: mkdir -p /mnt/user/appdata/unmarr/{data,configs}"
|
||||
echo "3. Copy docker-compose.unraid.yml to Unraid"
|
||||
echo "4. Set UNRAID_API_KEY in environment"
|
||||
echo "5. Run: docker compose -f docker-compose.unraid.yml up -d"
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
IconPlayerPlay,
|
||||
IconPlayerStop,
|
||||
IconTemperature,
|
||||
IconHardDrive,
|
||||
IconDisc,
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
import type { UnraidArray, ArrayDisk, ArrayState } from '~/lib/unraid/types';
|
||||
@@ -81,7 +81,7 @@ function DiskRow({ disk }: { disk: ArrayDisk }) {
|
||||
<td>
|
||||
<Group spacing="xs">
|
||||
<ThemeIcon size="sm" variant="light" color={disk.spunDown ? 'gray' : 'blue'}>
|
||||
<IconHardDrive size={14} />
|
||||
<IconDisc size={14} />
|
||||
</ThemeIcon>
|
||||
<Text size="sm" weight={500}>
|
||||
{disk.name}
|
||||
|
||||
@@ -179,7 +179,7 @@ export function DockerCard({
|
||||
</Group>
|
||||
</Card.Section>
|
||||
|
||||
<ScrollArea.Autosize maxHeight={400} mt="xs">
|
||||
<ScrollArea mah={400} mt="xs">
|
||||
<Stack spacing={0}>
|
||||
{sortedContainers.length === 0 ? (
|
||||
<Text size="sm" color="dimmed" align="center" py="lg">
|
||||
@@ -197,7 +197,7 @@ export function DockerCard({
|
||||
))
|
||||
)}
|
||||
</Stack>
|
||||
</ScrollArea.Autosize>
|
||||
</ScrollArea>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ export function VmsCard({
|
||||
</Group>
|
||||
</Card.Section>
|
||||
|
||||
<ScrollArea.Autosize maxHeight={400} mt="xs">
|
||||
<ScrollArea mah={400} mt="xs">
|
||||
<Stack spacing={0}>
|
||||
{sortedVms.length === 0 ? (
|
||||
<Text size="sm" color="dimmed" align="center" py="lg">
|
||||
@@ -269,7 +269,7 @@ export function VmsCard({
|
||||
))
|
||||
)}
|
||||
</Stack>
|
||||
</ScrollArea.Autosize>
|
||||
</ScrollArea>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import type {
|
||||
UnraidApiResponse,
|
||||
UnraidArray,
|
||||
UpsDevice,
|
||||
User,
|
||||
VirtualMachine,
|
||||
} from './types';
|
||||
|
||||
@@ -255,6 +256,38 @@ export class UnraidClient {
|
||||
return data.customization;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// USERS (STUB - Unraid GraphQL API does not expose user management yet)
|
||||
// ============================================================================
|
||||
|
||||
async getUsers(): Promise<User[]> {
|
||||
// TODO: Implement when Unraid GraphQL API supports user queries
|
||||
return [];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SYSLOG (STUB - Unraid GraphQL API does not expose syslog yet)
|
||||
// ============================================================================
|
||||
|
||||
async getSyslog(lines = 100): Promise<string[]> {
|
||||
// TODO: Implement when Unraid GraphQL API supports syslog queries
|
||||
return [];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// NOTIFICATION ACTIONS
|
||||
// ============================================================================
|
||||
|
||||
async markNotificationRead(id: string): Promise<{ success: boolean }> {
|
||||
// TODO: Implement when Unraid GraphQL API supports marking notifications read
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async markAllNotificationsRead(): Promise<{ success: boolean }> {
|
||||
// TODO: Implement when Unraid GraphQL API supports bulk notification actions
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DASHBOARD (COMPOSITE QUERY)
|
||||
// ============================================================================
|
||||
|
||||
@@ -278,6 +278,8 @@ export interface Vms {
|
||||
// SHARES TYPES
|
||||
// ============================================================================
|
||||
|
||||
export type ShareSecurityLevel = 'PUBLIC' | 'SECURE' | 'PRIVATE';
|
||||
|
||||
export interface Share {
|
||||
name: string;
|
||||
comment: string;
|
||||
@@ -292,7 +294,17 @@ export interface Share {
|
||||
splitLevel: number;
|
||||
allocator: 'highwater' | 'fillup' | 'mostfree';
|
||||
export: string;
|
||||
security: string;
|
||||
security: ShareSecurityLevel;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// USER TYPES
|
||||
// ============================================================================
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import {
|
||||
IconDatabase,
|
||||
IconHardDrive,
|
||||
IconDisc,
|
||||
IconTemperature,
|
||||
IconPlayerPlay,
|
||||
IconPlayerStop,
|
||||
@@ -93,7 +93,7 @@ function DiskDetailsRow({ disk }: { disk: ArrayDisk }) {
|
||||
variant="light"
|
||||
color={disk.spunDown ? 'gray' : getStatusColor(disk.status)}
|
||||
>
|
||||
<IconHardDrive size={14} />
|
||||
<IconDisc size={14} />
|
||||
</ThemeIcon>
|
||||
<div>
|
||||
<Text size="sm" weight={500}>
|
||||
@@ -530,7 +530,7 @@ export default function ArrayPage() {
|
||||
<Tabs.Tab value="parity" icon={<IconShield size={14} />}>
|
||||
Parity ({array.parities.length})
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab value="data" icon={<IconHardDrive size={14} />}>
|
||||
<Tabs.Tab value="data" icon={<IconDisc size={14} />}>
|
||||
Data Disks ({array.disks.length})
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab value="cache" icon={<IconCpu size={14} />}>
|
||||
@@ -647,7 +647,7 @@ export default function ArrayPage() {
|
||||
<tr key={device.id}>
|
||||
<td>
|
||||
<Group spacing="xs">
|
||||
<IconHardDrive size={14} />
|
||||
<IconDisc size={14} />
|
||||
<Text size="sm">{device.name}</Text>
|
||||
</Group>
|
||||
</td>
|
||||
|
||||
@@ -59,8 +59,8 @@ export default function IdentificationSettingsPage() {
|
||||
if (vars && !form.isTouched()) {
|
||||
form.setValues({
|
||||
name: vars.name || '',
|
||||
description: vars.comment || '',
|
||||
model: vars.flashProduct || '',
|
||||
description: vars.description || '',
|
||||
model: vars.model || '',
|
||||
timezone: vars.timezone || '',
|
||||
});
|
||||
}
|
||||
@@ -155,7 +155,7 @@ export default function IdentificationSettingsPage() {
|
||||
Linux Kernel
|
||||
</Text>
|
||||
<Text size="sm" weight={500}>
|
||||
{info?.versions.linux || 'Unknown'}
|
||||
{info?.os.kernel || 'Unknown'}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
@@ -166,7 +166,7 @@ export default function IdentificationSettingsPage() {
|
||||
CPU
|
||||
</Text>
|
||||
<Text size="sm" weight={500}>
|
||||
{info?.cpu.model || 'Unknown'}
|
||||
{info?.cpu.brand || 'Unknown'}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
@@ -177,7 +177,7 @@ export default function IdentificationSettingsPage() {
|
||||
Motherboard
|
||||
</Text>
|
||||
<Text size="sm" weight={500}>
|
||||
{info?.motherboard?.product || 'Unknown'}
|
||||
{info?.baseboard?.model || 'Unknown'}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
@@ -207,19 +207,19 @@ export default function IdentificationSettingsPage() {
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
{/* Flash Drive */}
|
||||
{/* Server Model */}
|
||||
<Card shadow="sm" radius="md" withBorder>
|
||||
<Title order={4} mb="md">
|
||||
Flash Drive
|
||||
Hardware
|
||||
</Title>
|
||||
|
||||
<Stack spacing="md">
|
||||
<Group position="apart">
|
||||
<Text size="sm" color="dimmed">
|
||||
Product
|
||||
Model
|
||||
</Text>
|
||||
<Text size="sm" weight={500}>
|
||||
{vars?.flashProduct || 'Unknown'}
|
||||
{vars?.model || 'Unknown'}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
@@ -227,10 +227,10 @@ export default function IdentificationSettingsPage() {
|
||||
|
||||
<Group position="apart">
|
||||
<Text size="sm" color="dimmed">
|
||||
Vendor
|
||||
Protocol
|
||||
</Text>
|
||||
<Text size="sm" weight={500}>
|
||||
{vars?.flashVendor || 'Unknown'}
|
||||
{vars?.protocol || 'Unknown'}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
@@ -238,10 +238,10 @@ export default function IdentificationSettingsPage() {
|
||||
|
||||
<Group position="apart">
|
||||
<Text size="sm" color="dimmed">
|
||||
GUID
|
||||
Port
|
||||
</Text>
|
||||
<Text size="sm" weight={500} style={{ fontFamily: 'monospace' }}>
|
||||
{vars?.flashGuid || 'Unknown'}
|
||||
{vars?.port || 'Unknown'}
|
||||
</Text>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
@@ -74,8 +74,8 @@ function getImportanceIcon(importance: NotificationImportance) {
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(timestamp: number): string {
|
||||
return new Date(timestamp * 1000).toLocaleDateString('en-US', {
|
||||
function formatDate(timestamp: string): string {
|
||||
return new Date(timestamp).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
|
||||
@@ -289,7 +289,7 @@ export default function DiagnosticsPage() {
|
||||
<Divider />
|
||||
<Group position="apart">
|
||||
<Text color="dimmed">Linux Kernel</Text>
|
||||
<Text weight={500}>{info?.versions.linux || 'Unknown'}</Text>
|
||||
<Text weight={500}>{info?.os.kernel || 'Unknown'}</Text>
|
||||
</Group>
|
||||
<Divider />
|
||||
<Group position="apart">
|
||||
@@ -309,7 +309,7 @@ export default function DiagnosticsPage() {
|
||||
<Divider />
|
||||
<Group position="apart">
|
||||
<Text color="dimmed">CPU</Text>
|
||||
<Text weight={500}>{info?.cpu.model || 'Unknown'}</Text>
|
||||
<Text weight={500}>{info?.cpu.brand || 'Unknown'}</Text>
|
||||
</Group>
|
||||
<Divider />
|
||||
<Group position="apart">
|
||||
|
||||
@@ -108,7 +108,7 @@ export default function SyslogPage() {
|
||||
error,
|
||||
refetch,
|
||||
} = api.unraid.syslog.useQuery(
|
||||
{ lines: 500 },
|
||||
undefined,
|
||||
{
|
||||
refetchInterval: autoScroll ? 5000 : false,
|
||||
}
|
||||
@@ -116,7 +116,7 @@ export default function SyslogPage() {
|
||||
|
||||
useEffect(() => {
|
||||
if (syslog) {
|
||||
setLines(syslog.lines || []);
|
||||
setLines(syslog || []);
|
||||
}
|
||||
}, [syslog]);
|
||||
|
||||
|
||||
@@ -337,6 +337,52 @@ export const unraidRouter = createTRPCRouter({
|
||||
return client.archiveNotification(input.id);
|
||||
}),
|
||||
|
||||
// ============================================================================
|
||||
// USERS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Get users
|
||||
*/
|
||||
users: protectedProcedure.query(async () => {
|
||||
const client = getUnraidClient();
|
||||
return client.getUsers();
|
||||
}),
|
||||
|
||||
// ============================================================================
|
||||
// SYSLOG
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Get syslog lines
|
||||
*/
|
||||
syslog: protectedProcedure.query(async () => {
|
||||
const client = getUnraidClient();
|
||||
return client.getSyslog();
|
||||
}),
|
||||
|
||||
// ============================================================================
|
||||
// NOTIFICATION ACTIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Mark notification as read
|
||||
*/
|
||||
markNotificationRead: protectedProcedure
|
||||
.input(notificationIdSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
const client = getUnraidClient();
|
||||
return client.markNotificationRead(input.id);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Mark all notifications as read
|
||||
*/
|
||||
markAllNotificationsRead: protectedProcedure.mutation(async () => {
|
||||
const client = getUnraidClient();
|
||||
return client.markAllNotificationsRead();
|
||||
}),
|
||||
|
||||
// ============================================================================
|
||||
// SERVICES
|
||||
// ============================================================================
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* https://github.com/vinceliuice/Orchis-theme
|
||||
*/
|
||||
|
||||
import { MantineProviderProps, MantineThemeColors, Tuple } from '@mantine/core';
|
||||
import { MantineThemeOverride, Tuple } from '@mantine/core';
|
||||
|
||||
// ============================================================================
|
||||
// ORCHIS COLOR PALETTE
|
||||
@@ -159,7 +159,7 @@ const orchisOrange: Tuple<string, 10> = [
|
||||
// MANTINE THEME CONFIGURATION
|
||||
// ============================================================================
|
||||
|
||||
export const orchisColors: MantineThemeColors = {
|
||||
export const orchisColors: Record<string, Tuple<string, 10>> = {
|
||||
// Override default colors with Orchis palette
|
||||
blue: orchisBlue,
|
||||
gray: orchisGrey,
|
||||
@@ -176,7 +176,7 @@ export const orchisColors: MantineThemeColors = {
|
||||
surface: orchisGrey,
|
||||
};
|
||||
|
||||
export const orchisTheme: MantineProviderProps['theme'] = {
|
||||
export const orchisTheme: MantineThemeOverride = {
|
||||
// Color configuration
|
||||
colors: orchisColors,
|
||||
primaryColor: 'blue',
|
||||
@@ -192,21 +192,21 @@ export const orchisTheme: MantineProviderProps['theme'] = {
|
||||
|
||||
// Border radius - Orchis uses 12px as default
|
||||
radius: {
|
||||
xs: 4,
|
||||
sm: 6,
|
||||
md: 12, // Default Orchis corner radius
|
||||
lg: 18, // Window radius
|
||||
xl: 24,
|
||||
xs: '4px',
|
||||
sm: '6px',
|
||||
md: '12px', // Default Orchis corner radius
|
||||
lg: '18px', // Window radius
|
||||
xl: '24px',
|
||||
},
|
||||
defaultRadius: 'md',
|
||||
|
||||
// Spacing - Orchis base is 6px
|
||||
spacing: {
|
||||
xs: 4,
|
||||
sm: 6,
|
||||
md: 12,
|
||||
lg: 18,
|
||||
xl: 24,
|
||||
xs: '4px',
|
||||
sm: '6px',
|
||||
md: '12px',
|
||||
lg: '18px',
|
||||
xl: '24px',
|
||||
},
|
||||
|
||||
// Shadows - Material Design elevation
|
||||
|
||||
Reference in New Issue
Block a user