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