Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4361ccf7f0 | ||
|
|
d5fd6905ea | ||
|
|
b99f28f8c1 | ||
|
|
1d7c121dad | ||
|
|
0a4a9f98ab | ||
|
|
f902ca439d |
No files matched your search
@@ -2,13 +2,14 @@
|
|||||||
|
|
||||||
import type { PropsWithChildren } from "react";
|
import type { PropsWithChildren } from "react";
|
||||||
import type { MantineColorScheme, MantineColorSchemeManager } from "@mantine/core";
|
import type { MantineColorScheme, MantineColorSchemeManager } from "@mantine/core";
|
||||||
import { createTheme, DirectionProvider, MantineProvider } from "@mantine/core";
|
import { DirectionProvider, MantineProvider } from "@mantine/core";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { useSession } from "@homarr/auth/client";
|
import { useSession } from "@homarr/auth/client";
|
||||||
import { parseCookies, setClientCookie } from "@homarr/common";
|
import { parseCookies, setClientCookie } from "@homarr/common";
|
||||||
import { colorSchemeCookieKey } from "@homarr/definitions";
|
import { colorSchemeCookieKey } from "@homarr/definitions";
|
||||||
|
import { theme } from "@homarr/ui";
|
||||||
|
|
||||||
export const CustomMantineProvider = ({
|
export const CustomMantineProvider = ({
|
||||||
children,
|
children,
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export default async function InviteUsagePage(props: InviteUsagePageProps) {
|
|||||||
{t("subtitle")}
|
{t("subtitle")}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Card withBorder w={64 * 6} maw="90vw">
|
<Card w={64 * 6} maw="90vw">
|
||||||
<RegistrationForm invite={invite} />
|
<RegistrationForm invite={invite} />
|
||||||
</Card>
|
</Card>
|
||||||
<Text size="xs" c="gray.5" ta="center">
|
<Text size="xs" c="gray.5" ta="center">
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default async function Login(props: LoginProps) {
|
|||||||
{t("subtitle")}
|
{t("subtitle")}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Card withBorder w={64 * 6} maw="90vw">
|
<Card w={64 * 6} maw="90vw">
|
||||||
<LoginForm
|
<LoginForm
|
||||||
providers={env.AUTH_PROVIDERS}
|
providers={env.AUTH_PROVIDERS}
|
||||||
oidcClientName={env.AUTH_OIDC_CLIENT_NAME}
|
oidcClientName={env.AUTH_OIDC_CLIENT_NAME}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ const AddMenu = () => {
|
|||||||
}, [openAppSelectModal, createItem]);
|
}, [openAppSelectModal, createItem]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu position="bottom-end" withArrow>
|
<Menu position="bottom-end">
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<HeaderButton w="auto" px={4}>
|
<HeaderButton w="auto" px={4}>
|
||||||
<Group gap={4} wrap="nowrap">
|
<Group gap={4} wrap="nowrap">
|
||||||
@@ -183,7 +183,7 @@ const SelectBoardsMenu = () => {
|
|||||||
const { data: boards = [] } = clientApi.board.getAllBoards.useQuery();
|
const { data: boards = [] } = clientApi.board.getAllBoards.useQuery();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu position="bottom-end" withArrow>
|
<Menu position="bottom-end">
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<HeaderButton w="auto" px={4}>
|
<HeaderButton w="auto" px={4}>
|
||||||
<IconReplace stroke={1.5} />
|
<IconReplace stroke={1.5} />
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const InitFinish = async () => {
|
|||||||
const tFinish = await getScopedI18n("init.step.finish");
|
const tFinish = await getScopedI18n("init.step.finish");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card w={64 * 6} maw="90vw" withBorder>
|
<Card w={64 * 6} maw="90vw">
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text>{tFinish("description")}</Text>
|
<Text>{tFinish("description")}</Text>
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const InitGroup = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card w={64 * 6} maw="90vw" withBorder>
|
<Card w={64 * 6} maw="90vw">
|
||||||
<form onSubmit={form.onSubmit(handleSubmitAsync)}>
|
<form onSubmit={form.onSubmit(handleSubmitAsync)}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ interface FileInfoCardProps {
|
|||||||
export const FileInfoCard = ({ file, onRemove }: FileInfoCardProps) => {
|
export const FileInfoCard = ({ file, onRemove }: FileInfoCardProps) => {
|
||||||
const tFileInfo = useScopedI18n("init.step.import.fileInfo");
|
const tFileInfo = useScopedI18n("init.step.import.fileInfo");
|
||||||
return (
|
return (
|
||||||
<Card w={64 * 12 + 8} maw="90vw">
|
<Card w={64 * 12 + 8} maw="90vw" withBorder={false}>
|
||||||
<Group justify="space-between" align="center" wrap="nowrap">
|
<Group justify="space-between" align="center" wrap="nowrap">
|
||||||
<Group>
|
<Group>
|
||||||
<Text fw={500} lineClamp={1} style={{ wordBreak: "break-all" }}>
|
<Text fw={500} lineClamp={1} style={{ wordBreak: "break-all" }}>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const InitImport = () => {
|
|||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return (
|
return (
|
||||||
<Card w={64 * 12 + 8} maw="90vw" withBorder>
|
<Card w={64 * 12 + 8} maw="90vw">
|
||||||
<ImportDropZone
|
<ImportDropZone
|
||||||
loading={isPending}
|
loading={isPending}
|
||||||
updateFile={(file) => {
|
updateFile={(file) => {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const InitSettings = () => {
|
|||||||
return (
|
return (
|
||||||
<form onSubmit={form.onSubmit(handleSubmitAsync)}>
|
<form onSubmit={form.onSubmit(handleSubmitAsync)}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Card w={64 * 12 + 8} maw="90vw" withBorder>
|
<Card w={64 * 12 + 8} maw="90vw">
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Text fw={500}>{tSection("analytics.title")}</Text>
|
<Text fw={500}>{tSection("analytics.title")}</Text>
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ export const InitSettings = () => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
<Card w={64 * 12 + 8} maw="90vw" withBorder>
|
<Card w={64 * 12 + 8} maw="90vw">
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Text fw={500}>{tSection("crawlingAndIndexing.title")}</Text>
|
<Text fw={500}>{tSection("crawlingAndIndexing.title")}</Text>
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const InitStart = async () => {
|
|||||||
const tStart = await getScopedI18n("init.step.start");
|
const tStart = await getScopedI18n("init.step.start");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card w={64 * 6} maw="90vw" withBorder>
|
<Card w={64 * 6} maw="90vw">
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text>{tStart("description")}</Text>
|
<Text>{tStart("description")}</Text>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { InitUserForm } from "./init-user-form";
|
|||||||
|
|
||||||
export const InitUser = () => {
|
export const InitUser = () => {
|
||||||
return (
|
return (
|
||||||
<Card w={64 * 6} maw="90vw" withBorder>
|
<Card w={64 * 6} maw="90vw">
|
||||||
<InitUserForm />
|
<InitUserForm />
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -44,13 +44,13 @@ const fontSans = Inter({
|
|||||||
export const generateMetadata = async (): Promise<Metadata> => ({
|
export const generateMetadata = async (): Promise<Metadata> => ({
|
||||||
title: "Homarr",
|
title: "Homarr",
|
||||||
description:
|
description:
|
||||||
"Simplify the management of your server with Homarr - a sleek, modern dashboard that puts all of your apps and services at your fingertips.",
|
"A self-hosted dashboard for the *arr stack and your entire homelab. Integrates with 50+ services, real-time widgets, no config files.",
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: "Homarr Dashboard",
|
title: "Homarr Dashboard",
|
||||||
description:
|
description:
|
||||||
"Simplify the management of your server with Homarr - a sleek, modern dashboard that puts all of your apps and services at your fingertips.",
|
"A self-hosted dashboard for the *arr stack and your entire homelab. Integrates with 50+ services, real-time widgets, no config files.",
|
||||||
url: "https://homarr.dev",
|
url: "https://homarr.dev",
|
||||||
siteName: "Homarr Documentation",
|
siteName: "Homarr",
|
||||||
},
|
},
|
||||||
icons: {
|
icons: {
|
||||||
icon: "/logo/logo.png",
|
icon: "/logo/logo.png",
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ export const HeroBanner = async () => {
|
|||||||
<Image
|
<Image
|
||||||
key={`grid-column-${columnIndex}-scroll-1-${index}`}
|
key={`grid-column-${columnIndex}-scroll-1-${index}`}
|
||||||
src={icon}
|
src={icon}
|
||||||
radius="md"
|
|
||||||
fit={"contain"}
|
fit={"contain"}
|
||||||
w={50}
|
w={50}
|
||||||
h={50}
|
h={50}
|
||||||
@@ -57,7 +56,6 @@ export const HeroBanner = async () => {
|
|||||||
<Image
|
<Image
|
||||||
key={`grid-column-${columnIndex}-scroll-2-${index}`}
|
key={`grid-column-${columnIndex}-scroll-2-${index}`}
|
||||||
src={icon}
|
src={icon}
|
||||||
radius="md"
|
|
||||||
fit={"contain"}
|
fit={"contain"}
|
||||||
w={50}
|
w={50}
|
||||||
h={50}
|
h={50}
|
||||||
|
|||||||
@@ -90,9 +90,7 @@ export default async function AboutPage() {
|
|||||||
<Group py="lg">
|
<Group py="lg">
|
||||||
<Image src={homarrLogoPath} width={100} height={100} alt="" />
|
<Image src={homarrLogoPath} width={100} height={100} alt="" />
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
<Title order={1} tt="uppercase">
|
<Title order={1}>Homarr</Title>
|
||||||
Homarr
|
|
||||||
</Title>
|
|
||||||
<Title order={2}>{t("version", { version })}</Title>
|
<Title order={2}>{t("version", { version })}</Title>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -187,7 +185,7 @@ export default async function AboutPage() {
|
|||||||
<SimpleGrid cols={{ xs: 1, sm: 2, md: 3, lg: 4, xl: 5 }} spacing="md">
|
<SimpleGrid cols={{ xs: 1, sm: 2, md: 3, lg: 4, xl: 5 }} spacing="md">
|
||||||
{Object.entries(dependencies)
|
{Object.entries(dependencies)
|
||||||
.filter(([, value]) => !value.includes("workspace:"))
|
.filter(([, value]) => !value.includes("workspace:"))
|
||||||
.sort(([key1], [key2]) => key1.localeCompare(key2))
|
.toSorted(([key1], [key2]) => key1.localeCompare(key2))
|
||||||
.map(([name, version]) => (
|
.map(([name, version]) => (
|
||||||
<UnstyledButton
|
<UnstyledButton
|
||||||
key={name}
|
key={name}
|
||||||
@@ -195,9 +193,9 @@ export default async function AboutPage() {
|
|||||||
href={`https://www.npmjs.com/package/${name}`}
|
href={`https://www.npmjs.com/package/${name}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<Card withBorder radius="md" p="xs" className={classes.dependencyCard}>
|
<Card radius="md" p="xs" className={classes.dependencyCard}>
|
||||||
<Group gap="sm" wrap="nowrap">
|
<Group gap="sm" wrap="nowrap">
|
||||||
<ThemeIcon variant="light" size="lg" radius="md">
|
<ThemeIcon variant="light" size="lg">
|
||||||
<IconPackage size="1.5rem" stroke={1.5} />
|
<IconPackage size="1.5rem" stroke={1.5} />
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ const AppCard = async ({ app }: AppCardProps) => {
|
|||||||
const session = await auth();
|
const session = await auth();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card withBorder>
|
<Card>
|
||||||
<Group justify="space-between" wrap="nowrap">
|
<Group justify="space-between" wrap="nowrap">
|
||||||
<Group align="top" justify="start" wrap="nowrap" style={{ flex: "1" }}>
|
<Group align="top" justify="start" wrap="nowrap" style={{ flex: "1" }}>
|
||||||
<Avatar
|
<Avatar
|
||||||
|
|||||||
@@ -70,16 +70,14 @@ const BoardCard = async ({ board }: BoardCardProps) => {
|
|||||||
const VisibilityIcon = board.isPublic ? IconWorld : IconLock;
|
const VisibilityIcon = board.isPublic ? IconWorld : IconLock;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card radius="lg" withBorder>
|
<Card>
|
||||||
<CardSection p="sm" withBorder>
|
<CardSection p="sm" withBorder>
|
||||||
<Group justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
<Group gap="sm">
|
<Group gap="sm">
|
||||||
<Tooltip label={t(`visibility.${visibility}`)}>
|
<Tooltip label={t(`visibility.${visibility}`)}>
|
||||||
<VisibilityIcon size={20} stroke={1.5} />
|
<VisibilityIcon size={20} stroke={1.5} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Text fw="bolder" tt="uppercase">
|
<Text fw={700}>{board.name}</Text>
|
||||||
{board.name}
|
|
||||||
</Text>
|
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Group>
|
<Group>
|
||||||
@@ -127,6 +125,7 @@ const BoardCard = async ({ board }: BoardCardProps) => {
|
|||||||
style={{ borderTop: "none", borderBottom: "none", borderRight: "none", borderRadius: 0 }}
|
style={{ borderTop: "none", borderBottom: "none", borderRight: "none", borderRadius: 0 }}
|
||||||
variant="default"
|
variant="default"
|
||||||
size="lg"
|
size="lg"
|
||||||
|
aria-label={t("action.settings.label")}
|
||||||
>
|
>
|
||||||
<IconDotsVertical size={16} stroke={1.5} />
|
<IconDotsVertical size={16} stroke={1.5} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
|||||||
+7
-2
@@ -33,7 +33,7 @@ export const SecretCard = ({ secret, children, onCancel }: SecretCardProps) => {
|
|||||||
const KindIcon = integrationSecretIcons[secret.kind];
|
const KindIcon = integrationSecretIcons[secret.kind];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card withBorder>
|
<Card>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Group>
|
<Group>
|
||||||
@@ -58,7 +58,12 @@ export const SecretCard = ({ secret, children, onCancel }: SecretCardProps) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{isPublic ? (
|
{isPublic ? (
|
||||||
<ActionIcon color="gray" variant="subtle" onClick={togglePublicSecretDisplay}>
|
<ActionIcon
|
||||||
|
color="gray"
|
||||||
|
variant="subtle"
|
||||||
|
onClick={togglePublicSecretDisplay}
|
||||||
|
aria-label={t(publicSecretDisplayOpened ? "common.action.hide" : "common.action.show")}
|
||||||
|
>
|
||||||
<DisplayIcon size={16} stroke={1.5} />
|
<DisplayIcon size={16} stroke={1.5} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ export const IntegrationTestConnectionError = ({ error, url }: IntegrationTestCo
|
|||||||
const causeArray = useMemo(() => toCauseArray(error.cause), [error.cause]);
|
const causeArray = useMemo(() => toCauseArray(error.cause), [error.cause]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card withBorder style={{ borderColor: getMantineColor("red", 8) }}>
|
<Card style={{ borderColor: getMantineColor("red", 8) }}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Text size="lg" fw={500} c="red.8">
|
<Text size="lg" fw={500} c="red.8">
|
||||||
|
|||||||
+1
-1
@@ -216,7 +216,7 @@ export const CertificateDetailsCard = ({ certificate }: CertificateDetailsProps)
|
|||||||
const tCertificateField = useScopedI18n("certificate.field");
|
const tCertificateField = useScopedI18n("certificate.field");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card withBorder>
|
<Card>
|
||||||
<Text fw={500}>{tDetails("title")}</Text>
|
<Text fw={500}>{tDetails("title")}</Text>
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Text size="sm" c="dimmed">
|
<Text size="sm" c="dimmed">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
transition: transform 150ms ease;
|
transition: background-color 150ms ease;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&[data-first="true"] {
|
&[data-first="true"] {
|
||||||
@@ -20,11 +20,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&[data-active] {
|
&[data-active] {
|
||||||
transform: scale(1.01);
|
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
background-color: var(--mantine-color-body);
|
background-color: var(--mantine-color-body);
|
||||||
border-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-4));
|
border-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-4));
|
||||||
box-shadow: var(--mantine-shadow-md);
|
|
||||||
border-radius: var(--mantine-radius-lg);
|
border-radius: var(--mantine-radius-lg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ export default async function NotFound() {
|
|||||||
return (
|
return (
|
||||||
<Center h="100%">
|
<Center h="100%">
|
||||||
<Stack align="center">
|
<Stack align="center">
|
||||||
<Title order={1} tt="uppercase">
|
<Title order={1}>{t("title")}</Title>
|
||||||
{t("title")}
|
|
||||||
</Title>
|
|
||||||
<Text>{t("text")}</Text>
|
<Text>{t("text")}</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Center>
|
</Center>
|
||||||
|
|||||||
@@ -29,20 +29,20 @@ export default async function ManagementPage() {
|
|||||||
<Space h="md" />
|
<Space h="md" />
|
||||||
<SimpleGrid cols={{ xs: 1, sm: 2, md: 3 }}>
|
<SimpleGrid cols={{ xs: 1, sm: 2, md: 3 }}>
|
||||||
{statistics.map((statistic) => (
|
{statistics.map((statistic) => (
|
||||||
<Card component={Link} href={statistic.path} key={statistic.path} radius="lg">
|
<Card component={Link} href={statistic.path} key={statistic.path}>
|
||||||
<Group justify="space-between" wrap="nowrap">
|
<Group justify="space-between" wrap="nowrap">
|
||||||
<Group wrap="nowrap">
|
<Group wrap="nowrap">
|
||||||
<Text size="2.4rem" fw="bolder">
|
<Text size="xl" fw={700}>
|
||||||
{statistic.count}
|
{statistic.count}
|
||||||
</Text>
|
</Text>
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
<Text c="red" size="xs">
|
<Text c="dimmed" size="xs">
|
||||||
{t(`statisticLabel.${statistic.subtitleKey}`)}
|
{t(`statisticLabel.${statistic.subtitleKey}`)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text fw="bold">{t(`statistic.${statistic.titleKey}`)}</Text>
|
<Text fw={600}>{t(`statistic.${statistic.titleKey}`)}</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Group>
|
</Group>
|
||||||
<IconArrowRight />
|
<IconArrowRight size={16} stroke={1.5} color="var(--mantine-color-dimmed)" />
|
||||||
</Group>
|
</Group>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ const SearchEngineCard = async ({ searchEngine }: SearchEngineCardProps) => {
|
|||||||
const session = await auth();
|
const session = await auth();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card withBorder>
|
<Card>
|
||||||
<Group justify="space-between" wrap="nowrap">
|
<Group justify="space-between" wrap="nowrap">
|
||||||
<Group align="top" justify="start" wrap="nowrap" style={{ flex: 1 }}>
|
<Group align="top" justify="start" wrap="nowrap" style={{ flex: 1 }}>
|
||||||
<Avatar
|
<Avatar
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ export const AnalyticsSettings = ({ initialData }: AnalyticsSettingsProps) => {
|
|||||||
<>
|
<>
|
||||||
<Title order={2}>{t("title")}</Title>
|
<Title order={2}>{t("title")}</Title>
|
||||||
|
|
||||||
<Card pos="relative" withBorder>
|
<Card pos="relative">
|
||||||
<LoadingOverlay visible={isPending} zIndex={1000} overlayProps={{ radius: "sm", blur: 2 }} />
|
<LoadingOverlay visible={isPending} />
|
||||||
<Stack>
|
<Stack>
|
||||||
<SwitchSetting form={form} formKey="enableGeneral" title={t("general.title")} text={t("general.text")} />
|
<SwitchSetting form={form} formKey="enableGeneral" title={t("general.title")} text={t("general.text")} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
+2
-2
@@ -41,11 +41,11 @@ export const CrawlingAndIndexingSettings = ({ initialData }: CrawlingAndIndexing
|
|||||||
<>
|
<>
|
||||||
<Title order={2}>{t("title")}</Title>
|
<Title order={2}>{t("title")}</Title>
|
||||||
|
|
||||||
<Card pos="relative" withBorder>
|
<Card pos="relative">
|
||||||
<Text c={"dimmed"} mb={"lg"}>
|
<Text c={"dimmed"} mb={"lg"}>
|
||||||
{t("warning")}
|
{t("warning")}
|
||||||
</Text>
|
</Text>
|
||||||
<LoadingOverlay visible={isPending} zIndex={1000} overlayProps={{ radius: "sm", blur: 2 }} />
|
<LoadingOverlay visible={isPending} />
|
||||||
<Stack>
|
<Stack>
|
||||||
<SwitchSetting form={form} formKey="noIndex" title={t("noIndex.title")} text={t("noIndex.text")} />
|
<SwitchSetting form={form} formKey="noIndex" title={t("noIndex.title")} text={t("noIndex.text")} />
|
||||||
<SwitchSetting form={form} formKey="noFollow" title={t("noFollow.title")} text={t("noFollow.text")} />
|
<SwitchSetting form={form} formKey="noFollow" title={t("noFollow.title")} text={t("noFollow.text")} />
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export default async function CertificatesPage({ params }: CertificatesPageProps
|
|||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.sort((certA, certB) => {
|
.toSorted((certA, certB) => {
|
||||||
if (certA.isError) return -1;
|
if (certA.isError) return -1;
|
||||||
if (certB.isError) return 1;
|
if (certB.isError) return 1;
|
||||||
return certA.x509.validToDate.getTime() - certB.x509.validToDate.getTime();
|
return certA.x509.validToDate.getTime() - certB.x509.validToDate.getTime();
|
||||||
@@ -79,7 +79,7 @@ export default async function CertificatesPage({ params }: CertificatesPageProps
|
|||||||
|
|
||||||
<SimpleGrid cols={{ sm: 1, lg: 2, xl: 3 }} spacing="lg">
|
<SimpleGrid cols={{ sm: 1, lg: 2, xl: 3 }} spacing="lg">
|
||||||
{x509Certificates.map((cert) => (
|
{x509Certificates.map((cert) => (
|
||||||
<Card key={cert.fileName} withBorder>
|
<Card key={cert.fileName}>
|
||||||
<Group wrap="nowrap">
|
<Group wrap="nowrap">
|
||||||
{cert.isError ? (
|
{cert.isError ? (
|
||||||
<IconAlertTriangle
|
<IconAlertTriangle
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ const ContainerActionBar = ({ selectedContainers }: ContainerActionBarProps) =>
|
|||||||
<ContainerActionBarButton icon={IconPlayerStop} color="red" action="stop" selectedIds={selectedIds} />
|
<ContainerActionBarButton icon={IconPlayerStop} color="red" action="stop" selectedIds={selectedIds} />
|
||||||
<ContainerActionBarButton icon={IconRotateClockwise} color="orange" action="restart" selectedIds={selectedIds} />
|
<ContainerActionBarButton icon={IconRotateClockwise} color="orange" action="restart" selectedIds={selectedIds} />
|
||||||
<ContainerActionBarButton icon={IconTrash} color="red" action="remove" selectedIds={selectedIds} />
|
<ContainerActionBarButton icon={IconTrash} color="red" action="remove" selectedIds={selectedIds} />
|
||||||
<Button leftSection={<IconCategoryPlus />} color={"red"} onClick={handleClick} variant="light" radius="md">
|
<Button leftSection={<IconCategoryPlus />} color={"red"} onClick={handleClick} variant="light">
|
||||||
{t("addToHomarr.label")}
|
{t("addToHomarr.label")}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -253,7 +253,6 @@ const ContainerActionBarButton = (props: ContainerActionBarButtonProps) => {
|
|||||||
onClick={handleClickAsync}
|
onClick={handleClickAsync}
|
||||||
loading={isPending}
|
loading={isPending}
|
||||||
variant="light"
|
variant="light"
|
||||||
radius="md"
|
|
||||||
>
|
>
|
||||||
{t(`${props.action}.label`)}
|
{t(`${props.action}.label`)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
+7
-19
@@ -4,28 +4,16 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: var(--mantine-spacing-xs);
|
padding: var(--mantine-spacing-xs);
|
||||||
padding-left: calc(var(--mantine-spacing-xs) * 2);
|
padding-left: calc(var(--mantine-spacing-xs) * 2);
|
||||||
|
border-left: 1px solid var(--mantine-color-blue-6);
|
||||||
@mixin hover {
|
background-color: light-dark(
|
||||||
box-shadow: var(--mantine-shadow-md);
|
var(--mantine-color-blue-0),
|
||||||
}
|
color-mix(in oklch, var(--mantine-color-blue-9), transparent 85%)
|
||||||
|
);
|
||||||
&::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 6px;
|
|
||||||
background-image: linear-gradient(0, var(--mantine-color-blue-4), var(--mantine-color-blue-9));
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin rtl {
|
@mixin rtl {
|
||||||
padding-left: var(--mantine-spacing-xs);
|
padding-left: var(--mantine-spacing-xs);
|
||||||
padding-right: calc(var(--mantine-spacing-xs) * 2);
|
padding-right: calc(var(--mantine-spacing-xs) * 2);
|
||||||
|
border-left: none;
|
||||||
&::before {
|
border-right: 1px solid var(--mantine-color-blue-6);
|
||||||
left: unset;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
-1
@@ -23,7 +23,6 @@ export function HeaderCard(props: HeaderCardProps) {
|
|||||||
<Flex align="center" justify={isRTL ? "space-between" : "flex-start"} gap="md" direction={"row"}>
|
<Flex align="center" justify={isRTL ? "space-between" : "flex-start"} gap="md" direction={"row"}>
|
||||||
<ThemeIcon
|
<ThemeIcon
|
||||||
size="xl"
|
size="xl"
|
||||||
radius="md"
|
|
||||||
variant="gradient"
|
variant="gradient"
|
||||||
gradient={{
|
gradient={{
|
||||||
deg: 0,
|
deg: 0,
|
||||||
|
|||||||
+4
-2
@@ -8,6 +8,8 @@ import { useCurrentLocale, useI18n } from "@homarr/translation/client";
|
|||||||
import { ResourceIcon } from "~/app/[locale]/manage/tools/kubernetes/cluster-dashboard/resource-gauge/resource-icon";
|
import { ResourceIcon } from "~/app/[locale]/manage/tools/kubernetes/cluster-dashboard/resource-gauge/resource-icon";
|
||||||
import classes from "./resource-gauge.module.css";
|
import classes from "./resource-gauge.module.css";
|
||||||
|
|
||||||
|
const KUBERNETES_BRAND_COLOR = "#326ce5";
|
||||||
|
|
||||||
interface KubernetesResourceGaugeProps {
|
interface KubernetesResourceGaugeProps {
|
||||||
kubernetesCapacity: KubernetesCapacity;
|
kubernetesCapacity: KubernetesCapacity;
|
||||||
}
|
}
|
||||||
@@ -18,8 +20,8 @@ export function ResourceGauge(props: KubernetesResourceGaugeProps) {
|
|||||||
const isRTL = Boolean(isLocaleRTL(currentLocale));
|
const isRTL = Boolean(isLocaleRTL(currentLocale));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper radius="md" withBorder className={classes.paper} mt={20}>
|
<Paper className={classes.paper} mt={20}>
|
||||||
<ThemeIcon className={classes.icon} size={60} radius={60} bg={"#326ce5"}>
|
<ThemeIcon className={classes.icon} size={60} radius={60} bg={KUBERNETES_BRAND_COLOR}>
|
||||||
<ResourceIcon type={props.kubernetesCapacity.type} />
|
<ResourceIcon type={props.kubernetesCapacity.type} />
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
|
|
||||||
|
|||||||
+1
-6
@@ -17,12 +17,7 @@ interface ResourceTileProps {
|
|||||||
export function ResourceTile(props: ResourceTileProps) {
|
export function ResourceTile(props: ResourceTileProps) {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card component={Link} href={`/manage/tools/kubernetes/${props.label}`} className={classes.cardContainer}>
|
||||||
withBorder
|
|
||||||
component={Link}
|
|
||||||
href={`/manage/tools/kubernetes/${props.label}`}
|
|
||||||
className={classes.cardContainer}
|
|
||||||
>
|
|
||||||
<Group justify="space-between" wrap="nowrap">
|
<Group justify="space-between" wrap="nowrap">
|
||||||
<Image src={`/images/kubernetes/${props.label}.svg`} alt={props.label} width={64} height={64} />
|
<Image src={`/images/kubernetes/${props.label}.svg`} alt={props.label} width={64} height={64} />
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
|
|||||||
+1
-1
@@ -95,7 +95,7 @@ export const UserProfileAvatarForm = ({ user }: UserProfileAvatarForm) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box pos="relative">
|
<Box pos="relative">
|
||||||
<Menu opened={opened} keepMounted onChange={toggle} position="bottom-start" withArrow>
|
<Menu opened={opened} keepMounted onChange={toggle} position="bottom-start">
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<UnstyledButton onClick={toggle}>
|
<UnstyledButton onClick={toggle}>
|
||||||
<UserAvatar user={user} size={200} />
|
<UserAvatar user={user} size={200} />
|
||||||
|
|||||||
+6
-6
@@ -149,7 +149,7 @@ export const UserCreateStepperComponent = ({ initialGroups }: UserCreateStepperC
|
|||||||
color={!generalForm.isValid() ? "red" : undefined}
|
color={!generalForm.isValid() ? "red" : undefined}
|
||||||
>
|
>
|
||||||
<form>
|
<form>
|
||||||
<Card p="xl" shadow="md" withBorder>
|
<Card p="xl">
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
label={tUserField("username.label")}
|
label={tUserField("username.label")}
|
||||||
@@ -165,7 +165,7 @@ export const UserCreateStepperComponent = ({ initialGroups }: UserCreateStepperC
|
|||||||
</Stepper.Step>
|
</Stepper.Step>
|
||||||
<Stepper.Step label={t("step.security.label")} allowStepSelect={false} allowStepClick={false}>
|
<Stepper.Step label={t("step.security.label")} allowStepSelect={false} allowStepClick={false}>
|
||||||
<form>
|
<form>
|
||||||
<Card p="xl" shadow="md" withBorder>
|
<Card p="xl">
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<CustomPasswordInput
|
<CustomPasswordInput
|
||||||
withPasswordRequirements
|
withPasswordRequirements
|
||||||
@@ -185,7 +185,7 @@ export const UserCreateStepperComponent = ({ initialGroups }: UserCreateStepperC
|
|||||||
</form>
|
</form>
|
||||||
</Stepper.Step>
|
</Stepper.Step>
|
||||||
<Stepper.Step label={t("step.groups.label")} allowStepSelect={false} allowStepClick={false}>
|
<Stepper.Step label={t("step.groups.label")} allowStepSelect={false} allowStepClick={false}>
|
||||||
<Card p="xl" shadow="md" withBorder>
|
<Card p="xl">
|
||||||
<GroupsForm
|
<GroupsForm
|
||||||
initialGroups={initialGroups}
|
initialGroups={initialGroups}
|
||||||
addGroup={(groupId) =>
|
addGroup={(groupId) =>
|
||||||
@@ -198,20 +198,20 @@ export const UserCreateStepperComponent = ({ initialGroups }: UserCreateStepperC
|
|||||||
</Card>
|
</Card>
|
||||||
</Stepper.Step>
|
</Stepper.Step>
|
||||||
<Stepper.Step label={t("step.review.label")} allowStepSelect={false} allowStepClick={false}>
|
<Stepper.Step label={t("step.review.label")} allowStepSelect={false} allowStepClick={false}>
|
||||||
<Card p="xl" shadow="md" withBorder>
|
<Card p="xl">
|
||||||
<Stack maw={300} align="center" mx="auto">
|
<Stack maw={300} align="center" mx="auto">
|
||||||
<UserAvatar
|
<UserAvatar
|
||||||
size="xl"
|
size="xl"
|
||||||
user={{ name: generalForm.values.username, email: generalForm.values.email ?? null, image: null }}
|
user={{ name: generalForm.values.username, email: generalForm.values.email ?? null, image: null }}
|
||||||
/>
|
/>
|
||||||
<Text tt="uppercase" fw="bolder" size="xl">
|
<Text fw={700} size="xl">
|
||||||
{generalForm.values.username}
|
{generalForm.values.username}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
</Stepper.Step>
|
</Stepper.Step>
|
||||||
<Stepper.Completed>
|
<Stepper.Completed>
|
||||||
<Card p="xl" shadow="md" withBorder>
|
<Card p="xl">
|
||||||
<Stack align="center" maw={300} mx="auto">
|
<Stack align="center" maw={300} mx="auto">
|
||||||
<IconUserCheck size="3rem" />
|
<IconUserCheck size="3rem" />
|
||||||
<Title order={2}>{t("step.completed.title")}</Title>
|
<Title order={2}>{t("step.completed.title")}</Title>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const StepperNavigationComponent = ({
|
|||||||
}: StepperNavigationComponentProps) => {
|
}: StepperNavigationComponentProps) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
return (
|
return (
|
||||||
<Card shadow="md" withBorder>
|
<Card>
|
||||||
{!isComplete ? (
|
{!isComplete ? (
|
||||||
<Group justify="space-between" wrap="nowrap">
|
<Group justify="space-between" wrap="nowrap">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
+1
-1
@@ -101,7 +101,7 @@ export const SaveAffix = ({ groupId }: SaveAffixProps) => {
|
|||||||
<div style={{ position: "sticky", bottom: 20 }}>
|
<div style={{ position: "sticky", bottom: 20 }}>
|
||||||
<Transition transition="slide-up" mounted={form.isDirty()}>
|
<Transition transition="slide-up" mounted={form.isDirty()}>
|
||||||
{(transitionStyles) => (
|
{(transitionStyles) => (
|
||||||
<Card style={transitionStyles} withBorder>
|
<Card style={transitionStyles}>
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Text fw={500}>{tForm("unsavedChanges")}</Text>
|
<Text fw={500}>{tForm("unsavedChanges")}</Text>
|
||||||
<Group>
|
<Group>
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ const PermissionCard = async ({ group, isDanger }: PermissionCardProps) => {
|
|||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
p="md"
|
p="md"
|
||||||
withBorder
|
|
||||||
style={{
|
style={{
|
||||||
borderColor: isDanger ? "var(--mantine-color-red-8)" : undefined,
|
borderColor: isDanger ? "var(--mantine-color-red-8)" : undefined,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ const SaveAffix = ({ visible, isPending, onDiscard, onSave }: SaveAffixProps) =>
|
|||||||
<div style={{ position: "sticky", bottom: 20 }}>
|
<div style={{ position: "sticky", bottom: 20 }}>
|
||||||
<Transition transition="slide-up" mounted={visible}>
|
<Transition transition="slide-up" mounted={visible}>
|
||||||
{(transitionStyles) => (
|
{(transitionStyles) => (
|
||||||
<Card style={transitionStyles} withBorder>
|
<Card style={transitionStyles}>
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Text fw={500}>{t("common.unsavedChanges")}</Text>
|
<Text fw={500}>{t("common.unsavedChanges")}</Text>
|
||||||
<Group>
|
<Group>
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export const WidgetPreviewPageContent = ({ kind, integrationData }: WidgetPrevie
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card withBorder w={dimensions.width} h={dimensions.height} p={dimensions.height >= 96 ? undefined : 4}>
|
<Card w={dimensions.width} h={dimensions.height} p={dimensions.height >= 96 ? undefined : 4}>
|
||||||
<QueryErrorResetBoundary>
|
<QueryErrorResetBoundary>
|
||||||
{({ reset }) => (
|
{({ reset }) => (
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
@@ -123,17 +123,35 @@ export const WidgetPreviewPageContent = ({ kind, integrationData }: WidgetPrevie
|
|||||||
</QueryErrorResetBoundary>
|
</QueryErrorResetBoundary>
|
||||||
</Card>
|
</Card>
|
||||||
<Affix bottom={12} right={72}>
|
<Affix bottom={12} right={72}>
|
||||||
<ActionIcon size={48} variant="default" radius="xl" onClick={handleOpenEditWidgetModal}>
|
<ActionIcon
|
||||||
|
size={48}
|
||||||
|
variant="default"
|
||||||
|
radius="xl"
|
||||||
|
onClick={handleOpenEditWidgetModal}
|
||||||
|
aria-label={t("common.action.edit")}
|
||||||
|
>
|
||||||
<IconPencil size={24} />
|
<IconPencil size={24} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Affix>
|
</Affix>
|
||||||
<Affix bottom={12} right={72 + 60}>
|
<Affix bottom={12} right={72 + 60}>
|
||||||
<ActionIcon size={48} variant="default" radius="xl" onClick={toggleEditMode}>
|
<ActionIcon
|
||||||
|
size={48}
|
||||||
|
variant="default"
|
||||||
|
radius="xl"
|
||||||
|
onClick={toggleEditMode}
|
||||||
|
aria-label={editMode ? t("widgetPreview.toggle.disabled") : t("widgetPreview.toggle.enabled")}
|
||||||
|
>
|
||||||
{editMode ? <IconToggleLeft size={24} /> : <IconToggleRight size={24} />}
|
{editMode ? <IconToggleLeft size={24} /> : <IconToggleRight size={24} />}
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Affix>
|
</Affix>
|
||||||
<Affix bottom={12} right={72 + 120}>
|
<Affix bottom={12} right={72 + 120}>
|
||||||
<ActionIcon size={48} variant="default" radius="xl" onClick={openDimensionsModal}>
|
<ActionIcon
|
||||||
|
size={48}
|
||||||
|
variant="default"
|
||||||
|
radius="xl"
|
||||||
|
onClick={openDimensionsModal}
|
||||||
|
aria-label={t("widgetPreview.dimensions.title")}
|
||||||
|
>
|
||||||
<IconDimensions size={24} />
|
<IconDimensions size={24} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Affix>
|
</Affix>
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ export const BoardItemContent = ({ item }: BoardItemContentProps) => {
|
|||||||
item.advancedOptions.customCssClasses.join(" "),
|
item.advancedOptions.customCssClasses.join(" "),
|
||||||
)}
|
)}
|
||||||
radius={board.itemRadius}
|
radius={board.itemRadius}
|
||||||
withBorder
|
|
||||||
styles={{
|
styles={{
|
||||||
root: {
|
root: {
|
||||||
"--opacity": board.opacity / 100,
|
"--opacity": board.opacity / 100,
|
||||||
|
|||||||
@@ -105,9 +105,17 @@ const BoardItemMenuInner = ({ offset, item, resetErrorBoundary }: BoardItemMenuP
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu withinPortal withArrow position="right-start" arrowPosition="center">
|
<Menu withinPortal position="right-start" arrowPosition="center">
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<ActionIcon variant="default" radius={"xl"} pos="absolute" top={offset} right={offset} style={{ zIndex: 10 }}>
|
<ActionIcon
|
||||||
|
variant="default"
|
||||||
|
radius={"xl"}
|
||||||
|
pos="absolute"
|
||||||
|
top={offset}
|
||||||
|
right={offset}
|
||||||
|
style={{ zIndex: 10 }}
|
||||||
|
aria-label={tItem("menu.label.settings")}
|
||||||
|
>
|
||||||
<IconDotsVertical size={"1rem"} />
|
<IconDotsVertical size={"1rem"} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ const WidgetItem = ({
|
|||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<SupportedIntegrations mt="auto" integrations={item.supportedIntegrations} />
|
<SupportedIntegrations mt="auto" integrations={item.supportedIntegrations} />
|
||||||
<Button onClick={onSelect} variant="light" size="xs" radius="md" fullWidth>
|
<Button onClick={onSelect} variant="light" size="xs" fullWidth>
|
||||||
{t(`item.create.addToBoard`)}
|
{t(`item.create.addToBoard`)}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -170,7 +170,7 @@ const SupportedIntegrations = ({ integrations, mt }: SupportedIntegrationsProps)
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Center mt={mt}>
|
<Center mt={mt}>
|
||||||
<Tooltip.Group openDelay={300} closeDelay={100}>
|
<Tooltip.Group closeDelay={100}>
|
||||||
<Group gap={2}>
|
<Group gap={2}>
|
||||||
{integrations.slice(0, countToShow).map((integration) => (
|
{integrations.slice(0, countToShow).map((integration) => (
|
||||||
<Tooltip key={integration} label={getIntegrationName(integration)} withArrow>
|
<Tooltip key={integration} label={getIntegrationName(integration)} withArrow>
|
||||||
|
|||||||
@@ -45,13 +45,7 @@ export const BoardCategorySection = ({ section }: Props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card style={{ "--opacity": board.opacity / 100 }} radius={board.itemRadius} p={0} className={classes.itemCard}>
|
||||||
style={{ "--opacity": board.opacity / 100 }}
|
|
||||||
radius={board.itemRadius}
|
|
||||||
withBorder
|
|
||||||
p={0}
|
|
||||||
className={classes.itemCard}
|
|
||||||
>
|
|
||||||
<Stack>
|
<Stack>
|
||||||
<Group wrap="nowrap" gap="sm">
|
<Group wrap="nowrap" gap="sm">
|
||||||
<UnstyledButton w="100%" p="sm" onClick={toggle}>
|
<UnstyledButton w="100%" p="sm" onClick={toggle}>
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ export const CategoryMenu = ({ category }: Props) => {
|
|||||||
const t = useScopedI18n("section.category");
|
const t = useScopedI18n("section.category");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu withArrow>
|
<Menu>
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<ActionIcon mr="sm" variant="transparent">
|
<ActionIcon mr="sm" variant="transparent" aria-label={t("menu.label.settings")}>
|
||||||
<IconDotsVertical size={20} />
|
<IconDotsVertical size={20} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export const BoardDynamicSection = ({ section }: Props) => {
|
|||||||
className={combineClasses(classes.itemCard, section.options.customCssClasses.join(" "))}
|
className={combineClasses(classes.itemCard, section.options.customCssClasses.join(" "))}
|
||||||
w="100%"
|
w="100%"
|
||||||
h="100%"
|
h="100%"
|
||||||
withBorder
|
|
||||||
styles={{
|
styles={{
|
||||||
root: {
|
root: {
|
||||||
overflow: "visible",
|
overflow: "visible",
|
||||||
|
|||||||
@@ -43,9 +43,17 @@ export const BoardDynamicSectionMenu = ({ section }: { section: DynamicSectionIt
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu withinPortal withArrow position="right-start" arrowPosition="center">
|
<Menu withinPortal position="right-start" arrowPosition="center">
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<ActionIcon variant="default" radius={"xl"} pos="absolute" top={4} right={4} style={{ zIndex: 10 }}>
|
<ActionIcon
|
||||||
|
variant="default"
|
||||||
|
radius={"xl"}
|
||||||
|
pos="absolute"
|
||||||
|
top={4}
|
||||||
|
right={4}
|
||||||
|
style={{ zIndex: 10 }}
|
||||||
|
aria-label={tItem("menu.label.settings")}
|
||||||
|
>
|
||||||
<IconDotsVertical size={"1rem"} />
|
<IconDotsVertical size={"1rem"} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
|
|||||||
@@ -12,16 +12,7 @@ export const CommonNavLink = (props: ClientNavigationLink) =>
|
|||||||
const NavLinkHref = (props: NavigationLinkHref) => {
|
const NavLinkHref = (props: NavigationLinkHref) => {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
return props.external ? (
|
return props.external ? (
|
||||||
<NavLink
|
<NavLink component="a" label={props.label} leftSection={props.icon} href={props.href} target="_blank" />
|
||||||
component="a"
|
|
||||||
label={props.label}
|
|
||||||
leftSection={props.icon}
|
|
||||||
href={props.href}
|
|
||||||
style={{
|
|
||||||
borderRadius: 5,
|
|
||||||
}}
|
|
||||||
target="_blank"
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<NavLink
|
<NavLink
|
||||||
component={Link}
|
component={Link}
|
||||||
@@ -29,9 +20,6 @@ const NavLinkHref = (props: NavigationLinkHref) => {
|
|||||||
leftSection={props.icon}
|
leftSection={props.icon}
|
||||||
href={props.href}
|
href={props.href}
|
||||||
active={pathname === props.href}
|
active={pathname === props.href}
|
||||||
style={{
|
|
||||||
borderRadius: 5,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -40,14 +28,7 @@ const NavLinkWithItems = (props: NavigationLinkWithItems) => {
|
|||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const isActive = props.items.some((item) => item.href === pathname);
|
const isActive = props.items.some((item) => item.href === pathname);
|
||||||
return (
|
return (
|
||||||
<NavLink
|
<NavLink label={props.label} leftSection={props.icon} defaultOpened={isActive}>
|
||||||
style={{
|
|
||||||
borderRadius: 5,
|
|
||||||
}}
|
|
||||||
label={props.label}
|
|
||||||
leftSection={props.icon}
|
|
||||||
defaultOpened={isActive}
|
|
||||||
>
|
|
||||||
{props.items.map((item) => (
|
{props.items.map((item) => (
|
||||||
<NavLinkHref key={item.label} {...item} />
|
<NavLinkHref key={item.label} {...item} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const DangerZoneRoot = async ({ children }: DangerZoneRootProps) => {
|
|||||||
<Title c="red.8" order={2}>
|
<Title c="red.8" order={2}>
|
||||||
{t("common.dangerZone")}
|
{t("common.dangerZone")}
|
||||||
</Title>
|
</Title>
|
||||||
<Card withBorder style={{ borderColor: "var(--mantine-color-red-8)", borderWidth: 3 }}>
|
<Card style={{ borderColor: "var(--mantine-color-red-8)", borderWidth: 3 }}>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
{Array.isArray(children)
|
{Array.isArray(children)
|
||||||
? children.map((child, index) => (
|
? children.map((child, index) => (
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ interface NoResultsProps {
|
|||||||
|
|
||||||
export const NoResults = ({ icon: Icon, title, action }: NoResultsProps) => {
|
export const NoResults = ({ icon: Icon, title, action }: NoResultsProps) => {
|
||||||
return (
|
return (
|
||||||
<Card withBorder bg="transparent">
|
<Card bg="transparent" withBorder={false}>
|
||||||
<Stack align="center" gap="sm">
|
<Stack align="center" gap="sm">
|
||||||
<Icon size="2rem" />
|
<Icon size="2rem" />
|
||||||
<Text fw={500} size="lg">
|
<Text fw={500} size="lg">
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const UserAvatarMenu = ({ children, availableUpdatesPromise }: UserAvatar
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
// We use keepMounted so we can add event listeners to prevent navigating away without saving the board
|
// We use keepMounted so we can add event listeners to prevent navigating away without saving the board
|
||||||
<Menu width={300} withArrow withinPortal keepMounted>
|
<Menu width={300} withinPortal keepMounted>
|
||||||
<Menu.Dropdown>
|
<Menu.Dropdown>
|
||||||
<AvailableUpdatesMenuItem availableUpdatesPromise={availableUpdatesPromise} />
|
<AvailableUpdatesMenuItem availableUpdatesPromise={availableUpdatesPromise} />
|
||||||
<Menu.Item component={Link} href="/boards" leftSection={<IconHome size="1rem" />}>
|
<Menu.Item component={Link} href="/boards" leftSection={<IconHome size="1rem" />}>
|
||||||
|
|||||||
@@ -108,10 +108,9 @@ export const IconPicker = ({
|
|||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
className={classes.iconCard}
|
className={classes.iconCard}
|
||||||
withBorder
|
|
||||||
>
|
>
|
||||||
<Box w={25} h={25}>
|
<Box w={25} h={25}>
|
||||||
<Image src={item.url} w={25} h={25} radius="md" />
|
<Image src={item.url} w={25} h={25} />
|
||||||
</Box>
|
</Box>
|
||||||
</Card>
|
</Card>
|
||||||
</Indicator>
|
</Indicator>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export const AppSelectModal = createModal<AppSelectModalProps>(({ actions, inner
|
|||||||
{t("app.action.create.description")}
|
{t("app.action.create.description")}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Button onClick={handleAddNewApp} variant="light" size="xs" mt="auto" radius="md" fullWidth>
|
<Button onClick={handleAddNewApp} variant="light" size="xs" mt="auto" fullWidth>
|
||||||
{t("app.action.create.action")}
|
{t("app.action.create.action")}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -100,7 +100,7 @@ export const AppSelectModal = createModal<AppSelectModalProps>(({ actions, inner
|
|||||||
{app.description ?? ""}
|
{app.description ?? ""}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Button onClick={() => handleSelect(app)} variant="light" size="xs" mt="auto" radius="md" fullWidth>
|
<Button onClick={() => handleSelect(app)} variant="light" size="xs" mt="auto" fullWidth>
|
||||||
{t("app.action.select.action", { app: app.name })}
|
{t("app.action.select.action", { app: app.name })}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const AddDockerAppToHomarrModal = createModal<AddDockerAppToHomarrProps>(
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||||
<LoadingOverlay visible={isPending} zIndex={1000} overlayProps={{ radius: "sm", blur: 2 }} />
|
<LoadingOverlay visible={isPending} />
|
||||||
<Stack>
|
<Stack>
|
||||||
<List spacing={"xs"}>
|
<List spacing={"xs"}>
|
||||||
{innerProps.selectedContainers.map((container, index) => (
|
{innerProps.selectedContainers.map((container, index) => (
|
||||||
|
|||||||
@@ -83,15 +83,10 @@ export const RequestMediaModal = createModal<RequestMediaModalProps>(({ actions,
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<LoadingOverlay visible={isPending} zIndex={1000} overlayProps={{ radius: "sm", blur: 2 }} />
|
<LoadingOverlay visible={isPending} />
|
||||||
{data && (
|
{data && (
|
||||||
<Group wrap="nowrap" align="start">
|
<Group wrap="nowrap" align="start">
|
||||||
<Image
|
<Image src={`https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`} alt="poster" w={100} />
|
||||||
src={`https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`}
|
|
||||||
alt="poster"
|
|
||||||
w={100}
|
|
||||||
radius="md"
|
|
||||||
/>
|
|
||||||
<Text c="dimmed" style={{ flex: "1" }}>
|
<Text c="dimmed" style={{ flex: "1" }}>
|
||||||
{data.overview}
|
{data.overview}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export const BoardSelectionCard = ({ selections, updateSelections }: BoardSelect
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card w={64 * 12 + 8} maw="90vw">
|
<Card w={64 * 12 + 8} maw="90vw" withBorder={false}>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
<Group justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
@@ -57,7 +57,7 @@ export const BoardSelectionCard = ({ selections, updateSelections }: BoardSelect
|
|||||||
|
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
{[...selections.entries()].map(([name, selected]) => (
|
{[...selections.entries()].map(([name, selected]) => (
|
||||||
<Card key={name} withBorder>
|
<Card key={name}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selected}
|
checked={selected}
|
||||||
onChange={registerToggle(name)}
|
onChange={registerToggle(name)}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ interface ImportSettingsCardProps {
|
|||||||
export const ImportSettingsCard = ({ settings, updateSetting }: ImportSettingsCardProps) => {
|
export const ImportSettingsCard = ({ settings, updateSetting }: ImportSettingsCardProps) => {
|
||||||
const tImportSettings = useScopedI18n("init.step.import.importSettings");
|
const tImportSettings = useScopedI18n("init.step.import.importSettings");
|
||||||
return (
|
return (
|
||||||
<Card w={64 * 12 + 8} maw="90vw">
|
<Card w={64 * 12 + 8} maw="90vw" withBorder={false}>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
<Text fw={500}>{tImportSettings("title")}</Text>
|
<Text fw={500}>{tImportSettings("title")}</Text>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ interface ImportSummaryCardProps {
|
|||||||
export const ImportSummaryCard = ({ counts, onSubmit, loading }: ImportSummaryCardProps) => {
|
export const ImportSummaryCard = ({ counts, onSubmit, loading }: ImportSummaryCardProps) => {
|
||||||
const tSummary = useScopedI18n("init.step.import.summary");
|
const tSummary = useScopedI18n("init.step.import.summary");
|
||||||
return (
|
return (
|
||||||
<Card w={64 * 12 + 8} maw="90vw">
|
<Card w={64 * 12 + 8} maw="90vw" withBorder={false}>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
<Text fw={500}>{tSummary("title")}</Text>
|
<Text fw={500}>{tSummary("title")}</Text>
|
||||||
@@ -24,7 +24,7 @@ export const ImportSummaryCard = ({ counts, onSubmit, loading }: ImportSummaryCa
|
|||||||
|
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
{objectEntries(counts).map(([key, count]) => (
|
{objectEntries(counts).map(([key, count]) => (
|
||||||
<Card key={key} withBorder p="sm">
|
<Card key={key} p="sm">
|
||||||
<Group justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{tSummary(`entities.${key}`)}
|
{tSummary(`entities.${key}`)}
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ const SpotlightWithActiveMode = ({ modeState, queryState, activeMode }: Spotligh
|
|||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
}}
|
}}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
|
aria-label={t("common.action.close")}
|
||||||
>
|
>
|
||||||
<IconX stroke={1.5} />
|
<IconX stroke={1.5} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
|||||||
@@ -1087,7 +1087,9 @@
|
|||||||
"checkoutDocs": "Check out the documentation",
|
"checkoutDocs": "Check out the documentation",
|
||||||
"checkLogs": "Check logs for more details",
|
"checkLogs": "Check logs for more details",
|
||||||
"tryAgain": "Try again",
|
"tryAgain": "Try again",
|
||||||
"loading": "Loading"
|
"loading": "Loading",
|
||||||
|
"show": "Show",
|
||||||
|
"hide": "Hide"
|
||||||
},
|
},
|
||||||
"here": "here",
|
"here": "here",
|
||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
@@ -1252,7 +1254,8 @@
|
|||||||
"menu": {
|
"menu": {
|
||||||
"label": {
|
"label": {
|
||||||
"create": "New category",
|
"create": "New category",
|
||||||
"changePosition": "Change position"
|
"changePosition": "Change position",
|
||||||
|
"settings": "Settings"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"openAllInNewTabs": {
|
"openAllInNewTabs": {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ interface UserAvatarGroupProps {
|
|||||||
|
|
||||||
export const UserAvatarGroup = ({ size, limit, users }: UserAvatarGroupProps) => {
|
export const UserAvatarGroup = ({ size, limit, users }: UserAvatarGroupProps) => {
|
||||||
return (
|
return (
|
||||||
<TooltipGroup openDelay={300} closeDelay={300}>
|
<TooltipGroup closeDelay={300}>
|
||||||
<AvatarGroup>
|
<AvatarGroup>
|
||||||
{users.slice(0, limit).map((user) => (
|
{users.slice(0, limit).map((user) => (
|
||||||
<Tooltip key={user.name} label={user.name} withArrow>
|
<Tooltip key={user.name} label={user.name} withArrow>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { MantineProviderProps } from "@mantine/core";
|
|||||||
|
|
||||||
import { theme } from "./theme";
|
import { theme } from "./theme";
|
||||||
|
|
||||||
|
export { theme } from "./theme";
|
||||||
export * from "./components";
|
export * from "./components";
|
||||||
|
|
||||||
export const uiConfiguration = {
|
export const uiConfiguration = {
|
||||||
|
|||||||
@@ -1,13 +1,103 @@
|
|||||||
import { createTheme } from "@mantine/core";
|
import { createTheme, rem } from "@mantine/core";
|
||||||
|
|
||||||
import { primaryColor } from "./theme/colors/primary";
|
|
||||||
import { secondaryColor } from "./theme/colors/secondary";
|
|
||||||
|
|
||||||
export const theme = createTheme({
|
export const theme = createTheme({
|
||||||
colors: {
|
primaryColor: "red",
|
||||||
primaryColor,
|
autoContrast: true,
|
||||||
secondaryColor,
|
respectReducedMotion: true,
|
||||||
|
cursorType: "pointer",
|
||||||
|
|
||||||
|
fontFamily: "Inter, system-ui, -apple-system, sans-serif",
|
||||||
|
fontFamilyMonospace: "ui-monospace, 'Cascadia Code', 'Fira Code', monospace",
|
||||||
|
|
||||||
|
headings: {
|
||||||
|
fontFamily: "Inter, system-ui, -apple-system, sans-serif",
|
||||||
|
fontWeight: "600",
|
||||||
|
sizes: {
|
||||||
|
h1: { fontSize: rem(36), lineHeight: "1.1", fontWeight: "700" },
|
||||||
|
h2: { fontSize: rem(24), lineHeight: "1.2", fontWeight: "700" },
|
||||||
|
h3: { fontSize: rem(20), lineHeight: "1.3", fontWeight: "600" },
|
||||||
|
h4: { fontSize: rem(16), lineHeight: "1.4", fontWeight: "600" },
|
||||||
|
h5: { fontSize: rem(14), lineHeight: "1.5", fontWeight: "600" },
|
||||||
|
h6: { fontSize: rem(12), lineHeight: "1.5", fontWeight: "600" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
fontSizes: {
|
||||||
|
xs: rem(12),
|
||||||
|
sm: rem(14),
|
||||||
|
md: rem(16),
|
||||||
|
lg: rem(18),
|
||||||
|
xl: rem(20),
|
||||||
|
},
|
||||||
|
|
||||||
|
spacing: {
|
||||||
|
xs: rem(10),
|
||||||
|
sm: rem(12),
|
||||||
|
md: rem(16),
|
||||||
|
lg: rem(20),
|
||||||
|
xl: rem(32),
|
||||||
|
},
|
||||||
|
|
||||||
|
radius: {
|
||||||
|
xs: rem(4),
|
||||||
|
sm: rem(4),
|
||||||
|
md: rem(8),
|
||||||
|
lg: rem(12),
|
||||||
|
xl: rem(16),
|
||||||
|
},
|
||||||
|
defaultRadius: "md",
|
||||||
|
|
||||||
|
shadows: {
|
||||||
|
xs: "0 1px 2px rgba(0, 0, 0, 0.04)",
|
||||||
|
sm: "0 1px 3px rgba(0, 0, 0, 0.06)",
|
||||||
|
md: "0 2px 8px rgba(0, 0, 0, 0.08)",
|
||||||
|
lg: "0 4px 12px rgba(0, 0, 0, 0.10)",
|
||||||
|
xl: "0 8px 24px rgba(0, 0, 0, 0.12)",
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
Card: {
|
||||||
|
defaultProps: {
|
||||||
|
withBorder: true,
|
||||||
|
radius: "lg",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Paper: {
|
||||||
|
defaultProps: {
|
||||||
|
withBorder: true,
|
||||||
|
radius: "md",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Button: {
|
||||||
|
defaultProps: {
|
||||||
|
radius: "md",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ActionIcon: {
|
||||||
|
defaultProps: {
|
||||||
|
radius: "md",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Tooltip: {
|
||||||
|
defaultProps: {
|
||||||
|
openDelay: 300,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Menu: {
|
||||||
|
defaultProps: {
|
||||||
|
withArrow: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NavLink: {
|
||||||
|
defaultProps: {
|
||||||
|
style: { borderRadius: 5 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LoadingOverlay: {
|
||||||
|
defaultProps: {
|
||||||
|
zIndex: 1000,
|
||||||
|
overlayProps: { radius: "sm", blur: 2 },
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
primaryColor: "primaryColor",
|
|
||||||
defaultRadius: "sm",
|
|
||||||
});
|
});
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import type { MantineColorsTuple } from "@mantine/core";
|
|
||||||
|
|
||||||
export const primaryColor: MantineColorsTuple = [
|
|
||||||
"#eafbf0",
|
|
||||||
"#ddefe3",
|
|
||||||
"#bedcc7",
|
|
||||||
"#9bc8aa",
|
|
||||||
"#7eb892",
|
|
||||||
"#6bad81",
|
|
||||||
"#60a878",
|
|
||||||
"#509265",
|
|
||||||
"#438359",
|
|
||||||
"#35724a",
|
|
||||||
];
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import type { MantineColorsTuple } from "@mantine/core";
|
|
||||||
|
|
||||||
export const secondaryColor: MantineColorsTuple = [
|
|
||||||
"#e6f7ff",
|
|
||||||
"#d9e8f6",
|
|
||||||
"#b6cde6",
|
|
||||||
"#90b2d4",
|
|
||||||
"#6f9ac5",
|
|
||||||
"#5a8bbd",
|
|
||||||
"#4d84ba",
|
|
||||||
"#3d71a4",
|
|
||||||
"#326595",
|
|
||||||
"#205885",
|
|
||||||
];
|
|
||||||
@@ -194,8 +194,6 @@ const Item = <TItem, TOptionValue extends UniqueIdentifier>({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
withBorder
|
|
||||||
shadow="xs"
|
|
||||||
padding="sm"
|
padding="sm"
|
||||||
radius="md"
|
radius="md"
|
||||||
style={
|
style={
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function InstanceCard({ instance, options, isTiny, widgetKey }: InstanceC
|
|||||||
).length;
|
).length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card p={0} radius="sm" withBorder>
|
<Card p={0} radius="sm">
|
||||||
<Group
|
<Group
|
||||||
p="xs"
|
p="xs"
|
||||||
justify="space-between"
|
justify="space-between"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { buildServerResourceCounts } from "./coolify-utils";
|
|||||||
import { ServersSection } from "./servers-section";
|
import { ServersSection } from "./servers-section";
|
||||||
import { ServicesSection } from "./services-section";
|
import { ServicesSection } from "./services-section";
|
||||||
import type { CoolifyOptions, InstanceData } from "./types";
|
import type { CoolifyOptions, InstanceData } from "./types";
|
||||||
import { COOLIFY_ICON_URL } from "./types";
|
import { COOLIFY_BRAND_COLOR, COOLIFY_ICON_URL } from "./types";
|
||||||
|
|
||||||
interface SingleInstanceLayoutProps {
|
interface SingleInstanceLayoutProps {
|
||||||
instance: InstanceData;
|
instance: InstanceData;
|
||||||
@@ -44,10 +44,10 @@ export function SingleInstanceLayout({ instance, options, isTiny, widgetKey }: S
|
|||||||
return (
|
return (
|
||||||
<ScrollArea h="100%">
|
<ScrollArea h="100%">
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
<Group p="xs" justify="center" gap="xs" style={{ borderBottom: "2px solid #8B5CF6" }}>
|
<Group p="xs" justify="center" gap="xs" style={{ borderBottom: `2px solid ${COOLIFY_BRAND_COLOR}` }}>
|
||||||
<Group gap={2}>
|
<Group gap={2}>
|
||||||
<Image src={COOLIFY_ICON_URL} alt="Coolify" w={isTiny ? 18 : 24} h={isTiny ? 18 : 24} />
|
<Image src={COOLIFY_ICON_URL} alt="Coolify" w={isTiny ? 18 : 24} h={isTiny ? 18 : 24} />
|
||||||
<Text fz={isTiny ? "xs" : "sm"} fw={700} style={{ color: "#8B5CF6" }}>
|
<Text fz={isTiny ? "xs" : "sm"} fw={700} style={{ color: COOLIFY_BRAND_COLOR }}>
|
||||||
oolify
|
oolify
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -75,7 +75,11 @@ export function SingleInstanceLayout({ instance, options, isTiny, widgetKey }: S
|
|||||||
)}
|
)}
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<Group justify="space-between" p={4} style={{ borderTop: "1px solid var(--mantine-color-dark-4)" }}>
|
<Group
|
||||||
|
justify="space-between"
|
||||||
|
p={4}
|
||||||
|
style={{ borderTop: "1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4))" }}
|
||||||
|
>
|
||||||
<Group gap={2}>
|
<Group gap={2}>
|
||||||
<Image src={COOLIFY_ICON_URL} alt="Coolify" w={16} h={16} />
|
<Image src={COOLIFY_ICON_URL} alt="Coolify" w={16} h={16} />
|
||||||
<Text size="xs" c="dimmed">
|
<Text size="xs" c="dimmed">
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { CoolifyInstanceInfo } from "@homarr/integrations/types";
|
|||||||
import type { WidgetComponentProps } from "../definition";
|
import type { WidgetComponentProps } from "../definition";
|
||||||
|
|
||||||
export const COOLIFY_ICON_URL = "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/coolify.svg";
|
export const COOLIFY_ICON_URL = "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/coolify.svg";
|
||||||
|
export const COOLIFY_BRAND_COLOR = "#8B5CF6";
|
||||||
|
|
||||||
export interface InstanceData {
|
export interface InstanceData {
|
||||||
integrationId: string;
|
integrationId: string;
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ const createColumns = (
|
|||||||
Cell({ renderedCellValue, row }) {
|
Cell({ renderedCellValue, row }) {
|
||||||
return (
|
return (
|
||||||
<Group gap="xs" wrap="nowrap">
|
<Group gap="xs" wrap="nowrap">
|
||||||
<Avatar variant="outline" radius="md" size={20} src={row.original.iconUrl} />
|
<Avatar variant="outline" size={20} src={row.original.iconUrl} />
|
||||||
<Text p="0.5" size="sm" style={{ overflow: "hidden", textOverflow: "ellipsis" }}>
|
<Text p="0.5" size="sm" style={{ overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||||
{renderedCellValue}
|
{renderedCellValue}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -864,7 +864,7 @@ const ClientsControl = ({ clients, filters, setFilters, availableStatuses }: Cli
|
|||||||
<Stack key={client.integration.id} gap="10px">
|
<Stack key={client.integration.id} gap="10px">
|
||||||
<Divider />
|
<Divider />
|
||||||
<Group wrap="nowrap" w="100%">
|
<Group wrap="nowrap" w="100%">
|
||||||
<Paper withBorder radius={999}>
|
<Paper radius={999}>
|
||||||
<Group gap={5} pl={10} pr={15} fz={16} w={275} justify="space-between" wrap="nowrap">
|
<Group gap={5} pl={10} pr={15} fz={16} w={275} justify="space-between" wrap="nowrap">
|
||||||
<Avatar radius={0} src={getIconUrl(client.integration.kind)} />
|
<Avatar radius={0} src={getIconUrl(client.integration.kind)} />
|
||||||
{client.status ? (
|
{client.status ? (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Group, Indicator, Popover, Table, TableTbody, TableThead, TableTr, Text } from "@mantine/core";
|
import { Group, Indicator, Popover, Table, TableTbody, TableTh, TableThead, TableTr, Text } from "@mantine/core";
|
||||||
|
|
||||||
import type { Resource } from "@homarr/integrations/types";
|
import type { Resource } from "@homarr/integrations/types";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
@@ -17,29 +17,29 @@ export const ResourceTable = ({ type, data, isTiny }: ResourceTableProps) => {
|
|||||||
<Table highlightOnHover>
|
<Table highlightOnHover>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr fz={isTiny ? "8px" : "xs"}>
|
<TableTr fz={isTiny ? "8px" : "xs"}>
|
||||||
<Table.Th ta="start" p={0}>
|
<TableTh ta="start" p={0}>
|
||||||
{t("widget.healthMonitoring.cluster.table.header.name")}
|
{t("widget.healthMonitoring.cluster.table.header.name")}
|
||||||
</Table.Th>
|
</TableTh>
|
||||||
{type !== "storage" ? (
|
{type !== "storage" ? (
|
||||||
<Table.Th ta="start" p={0}>
|
<TableTh ta="start" p={0}>
|
||||||
{t("widget.healthMonitoring.cluster.table.header.cpu")}
|
{t("widget.healthMonitoring.cluster.table.header.cpu")}
|
||||||
</Table.Th>
|
</TableTh>
|
||||||
) : null}
|
) : null}
|
||||||
{type !== "storage" ? (
|
{type !== "storage" ? (
|
||||||
<Table.Th ta="start" p={0}>
|
<TableTh ta="start" p={0}>
|
||||||
{t("widget.healthMonitoring.cluster.table.header.memory")}
|
{t("widget.healthMonitoring.cluster.table.header.memory")}
|
||||||
</Table.Th>
|
</TableTh>
|
||||||
) : null}
|
) : null}
|
||||||
{type === "storage" ? (
|
{type === "storage" ? (
|
||||||
<Table.Th ta="start" p={0}>
|
<TableTh ta="start" p={0}>
|
||||||
{t("widget.healthMonitoring.cluster.table.header.node")}
|
{t("widget.healthMonitoring.cluster.table.header.node")}
|
||||||
</Table.Th>
|
</TableTh>
|
||||||
) : null}
|
) : null}
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{data
|
{data
|
||||||
.sort((itemA, itemB) => {
|
.toSorted((itemA, itemB) => {
|
||||||
const nodeResult = itemA.node.localeCompare(itemB.node);
|
const nodeResult = itemA.node.localeCompare(itemB.node);
|
||||||
if (nodeResult !== 0) return nodeResult;
|
if (nodeResult !== 0) return nodeResult;
|
||||||
return itemA.name.localeCompare(itemB.name);
|
return itemA.name.localeCompare(itemB.name);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export default function MediaServerWidget({
|
|||||||
const newData = filteredData.concat(
|
const newData = filteredData.concat(
|
||||||
data.requests.map((request) => ({ ...request, integrationId: data.integrationId })),
|
data.requests.map((request) => ({ ...request, integrationId: data.integrationId })),
|
||||||
);
|
);
|
||||||
return newData.sort((dataA, dataB) => {
|
return newData.toSorted((dataA, dataB) => {
|
||||||
if (dataA.status === dataB.status) {
|
if (dataA.status === dataB.status) {
|
||||||
return dataB.createdAt.getTime() - dataA.createdAt.getTime();
|
return dataB.createdAt.getTime() - dataA.createdAt.getTime();
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,6 @@ const MediaRequestCard = ({ request, isTiny, options }: MediaRequestCardProps) =
|
|||||||
className={`mediaRequests-list-item-wrapper mediaRequests-list-item-${request.type} mediaRequests-list-item-${request.status}`}
|
className={`mediaRequests-list-item-wrapper mediaRequests-list-item-${request.type} mediaRequests-list-item-${request.status}`}
|
||||||
radius={board.itemRadius}
|
radius={board.itemRadius}
|
||||||
p="xs"
|
p="xs"
|
||||||
withBorder
|
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
className="mediaRequests-list-item-background"
|
className="mediaRequests-list-item-background"
|
||||||
@@ -214,7 +213,6 @@ const DecisionButtons = ({ requestId, integrationId }: DecisionButtonsProps) =>
|
|||||||
variant="light"
|
variant="light"
|
||||||
color="green"
|
color="green"
|
||||||
size="xs"
|
size="xs"
|
||||||
radius="md"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDecision("approve");
|
handleDecision("approve");
|
||||||
}}
|
}}
|
||||||
@@ -228,7 +226,6 @@ const DecisionButtons = ({ requestId, integrationId }: DecisionButtonsProps) =>
|
|||||||
variant="light"
|
variant="light"
|
||||||
color="red"
|
color="red"
|
||||||
size="xs"
|
size="xs"
|
||||||
radius="md"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDecision("decline");
|
handleDecision("decline");
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ import type { WidgetComponentProps } from "../../definition";
|
|||||||
import { NoIntegrationDataError } from "../../errors/no-data-integration";
|
import { NoIntegrationDataError } from "../../errors/no-data-integration";
|
||||||
import classes from "./component.module.css";
|
import classes from "./component.module.css";
|
||||||
|
|
||||||
|
const OVERSEERR_COLOR = "#ECB000";
|
||||||
|
const JELLYSEERR_COLOR = "#6677CC";
|
||||||
|
|
||||||
export default function MediaServerWidget({
|
export default function MediaServerWidget({
|
||||||
integrationIds,
|
integrationIds,
|
||||||
isEditMode,
|
isEditMode,
|
||||||
@@ -151,7 +154,7 @@ export default function MediaServerWidget({
|
|||||||
className="mediaRequests-stats-users-user-avatar"
|
className="mediaRequests-stats-users-user-avatar"
|
||||||
size={20}
|
size={20}
|
||||||
src={user.avatar}
|
src={user.avatar}
|
||||||
bd={`2px solid ${user.integration.kind === "overseerr" ? "#ECB000" : "#6677CC"}`}
|
bd={`2px solid ${user.integration.kind === "overseerr" ? OVERSEERR_COLOR : JELLYSEERR_COLOR}`}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Text className="mediaRequests-stats-users-user-userName" size="sm">
|
<Text className="mediaRequests-stats-users-user-userName" size="sm">
|
||||||
|
|||||||
@@ -27,7 +27,11 @@ export function HealthCheckStatus(props: HealthCheckStatusProps) {
|
|||||||
<IconHeartbeat size={16} />
|
<IconHeartbeat size={16} />
|
||||||
</Indicator>
|
</Indicator>
|
||||||
</HoverCard.Target>
|
</HoverCard.Target>
|
||||||
<HoverCard.Dropdown bg={colorScheme === "light" ? "gray.2" : "dark.8"}>
|
<HoverCard.Dropdown
|
||||||
|
style={{
|
||||||
|
backgroundColor: "light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-8))",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Stack gap="sm" align="center">
|
<Stack gap="sm" align="center">
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<IconHeartbeat size={18} />
|
<IconHeartbeat size={18} />
|
||||||
|
|||||||
@@ -1,4 +1,17 @@
|
|||||||
import { Center, Group, ScrollArea, Table, TableTd, TableTh, TableTr, Text, Title, Tooltip } from "@mantine/core";
|
import {
|
||||||
|
Center,
|
||||||
|
Group,
|
||||||
|
ScrollArea,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from "@mantine/core";
|
||||||
import { IconHeartbeat, IconTransform } from "@tabler/icons-react";
|
import { IconHeartbeat, IconTransform } from "@tabler/icons-react";
|
||||||
|
|
||||||
import { humanFileSize } from "@homarr/common";
|
import { humanFileSize } from "@homarr/common";
|
||||||
@@ -25,7 +38,7 @@ export function QueuePanel(props: QueuePanelProps) {
|
|||||||
return (
|
return (
|
||||||
<ScrollArea style={{ flex: "1" }}>
|
<ScrollArea style={{ flex: "1" }}>
|
||||||
<Table style={{ tableLayout: "fixed" }}>
|
<Table style={{ tableLayout: "fixed" }}>
|
||||||
<Table.Thead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh ta="start" py={4}>
|
<TableTh ta="start" py={4}>
|
||||||
<Text size="xs" fw="bold">
|
<Text size="xs" fw="bold">
|
||||||
@@ -38,8 +51,8 @@ export function QueuePanel(props: QueuePanelProps) {
|
|||||||
</Text>
|
</Text>
|
||||||
</TableTh>
|
</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</Table.Thead>
|
</TableThead>
|
||||||
<Table.Tbody>
|
<TableTbody>
|
||||||
{queue.array.map((item) => (
|
{queue.array.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd py={2}>
|
<TableTd py={2}>
|
||||||
@@ -63,7 +76,7 @@ export function QueuePanel(props: QueuePanelProps) {
|
|||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
))}
|
))}
|
||||||
</Table.Tbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ function StatisticItem(props: StatisticItemProps) {
|
|||||||
const board = useRequiredBoard();
|
const board = useRequiredBoard();
|
||||||
return (
|
return (
|
||||||
<Tooltip label={props.label}>
|
<Tooltip label={props.label}>
|
||||||
<Card p={0} withBorder radius={board.itemRadius} miw={48} flex={1}>
|
<Card p={0} radius={board.itemRadius} miw={48} flex={1}>
|
||||||
<Group justify="center" align="center" gap="xs" w="100%" wrap="nowrap">
|
<Group justify="center" align="center" gap="xs" w="100%" wrap="nowrap">
|
||||||
<props.icon size={16} style={{ minWidth: 16 }} />
|
<props.icon size={16} style={{ minWidth: 16 }} />
|
||||||
<Text size="md">{props.value}</Text>
|
<Text size="md">{props.value}</Text>
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import {
|
|||||||
Progress,
|
Progress,
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
Table,
|
Table,
|
||||||
|
TableTbody,
|
||||||
TableTd,
|
TableTd,
|
||||||
TableTh,
|
TableTh,
|
||||||
|
TableThead,
|
||||||
TableTr,
|
TableTr,
|
||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
@@ -35,7 +37,7 @@ export function WorkersPanel(props: WorkersPanelProps) {
|
|||||||
return (
|
return (
|
||||||
<ScrollArea style={{ flex: "1" }}>
|
<ScrollArea style={{ flex: "1" }}>
|
||||||
<Table style={{ tableLayout: "fixed" }}>
|
<Table style={{ tableLayout: "fixed" }}>
|
||||||
<Table.Thead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh ta="start" py={4}>
|
<TableTh ta="start" py={4}>
|
||||||
<Text size="xs" fw="bold">
|
<Text size="xs" fw="bold">
|
||||||
@@ -53,8 +55,8 @@ export function WorkersPanel(props: WorkersPanelProps) {
|
|||||||
</Text>
|
</Text>
|
||||||
</TableTh>
|
</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</Table.Thead>
|
</TableThead>
|
||||||
<Table.Tbody>
|
<TableTbody>
|
||||||
{props.workers.map((worker) => {
|
{props.workers.map((worker) => {
|
||||||
const fileName = worker.filePath.split("\\").pop()?.split("/").pop() ?? worker.filePath;
|
const fileName = worker.filePath.split("\\").pop()?.split("/").pop() ?? worker.filePath;
|
||||||
return (
|
return (
|
||||||
@@ -101,7 +103,7 @@ export function WorkersPanel(props: WorkersPanelProps) {
|
|||||||
</TableTr>
|
</TableTr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Table.Tbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -421,7 +421,6 @@ export function Notebook({ options, setOptions, isEditMode, boardId, itemId }: W
|
|||||||
color={primaryColor}
|
color={primaryColor}
|
||||||
variant="light"
|
variant="light"
|
||||||
size={30}
|
size={30}
|
||||||
radius={"md"}
|
|
||||||
onClick={handleEditToggle}
|
onClick={handleEditToggle}
|
||||||
>
|
>
|
||||||
{isEditing ? <IconDeviceFloppy {...iconProps} /> : <IconEdit {...iconProps} />}
|
{isEditing ? <IconDeviceFloppy {...iconProps} /> : <IconEdit {...iconProps} />}
|
||||||
@@ -438,7 +437,6 @@ export function Notebook({ options, setOptions, isEditMode, boardId, itemId }: W
|
|||||||
color={primaryColor}
|
color={primaryColor}
|
||||||
variant="light"
|
variant="light"
|
||||||
size={30}
|
size={30}
|
||||||
radius={"md"}
|
|
||||||
onClick={handleEditCancel}
|
onClick={handleEditCancel}
|
||||||
>
|
>
|
||||||
<IconX {...iconProps} />
|
<IconX {...iconProps} />
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export default function NotificationsWidget({ options, integrationIds }: WidgetC
|
|||||||
<Stack w={"100%"} gap="sm">
|
<Stack w={"100%"} gap="sm">
|
||||||
{sortedNotifications.length > 0 ? (
|
{sortedNotifications.length > 0 ? (
|
||||||
sortedNotifications.map((notification) => (
|
sortedNotifications.map((notification) => (
|
||||||
<Card key={notification.id} withBorder radius={board.itemRadius} w="100%" p="sm">
|
<Card key={notification.id} radius={board.itemRadius} w="100%" p="sm">
|
||||||
<Flex gap="sm" direction="column" w="100%">
|
<Flex gap="sm" direction="column" w="100%">
|
||||||
{notification.title && (
|
{notification.title && (
|
||||||
<Text fz="sm" lh="sm" lineClamp={2}>
|
<Text fz="sm" lh="sm" lineClamp={2}>
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
.releasesRepository {
|
.releasesRepository {
|
||||||
border-left: 2px solid transparent;
|
|
||||||
|
|
||||||
&:has(.releasesRepositoryHeader:hover),
|
&:has(.releasesRepositoryHeader:hover),
|
||||||
&:has(.releasesRepositoryHeader.active),
|
&:has(.releasesRepositoryHeader.active),
|
||||||
&:has(.releasesRepositoryDetails:hover) {
|
&:has(.releasesRepositoryDetails:hover) {
|
||||||
border-left-color: var(--mantine-color-secondaryColor-text);
|
background-color: var(--mantine-color-secondaryColor-light);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const useLiveFeedEntries = (input: RouterInputs["widget"]["rssFeed"]["getFeeds"]
|
|||||||
return oldData
|
return oldData
|
||||||
?.filter((entry) => entry.feedUrl !== updatedData.url)
|
?.filter((entry) => entry.feedUrl !== updatedData.url)
|
||||||
.concat(updatedData.entries)
|
.concat(updatedData.entries)
|
||||||
.sort((entryA, entryB) => {
|
.toSorted((entryA, entryB) => {
|
||||||
return entryA.published && entryB.published
|
return entryA.published && entryB.published
|
||||||
? new Date(entryB.published).getTime() - new Date(entryA.published).getTime()
|
? new Date(entryB.published).getTime() - new Date(entryA.published).getTime()
|
||||||
: 0;
|
: 0;
|
||||||
@@ -57,7 +57,6 @@ export default function RssFeed({ options, width }: WidgetComponentProps<"rssFee
|
|||||||
{feedEntries.map((feedEntry) => (
|
{feedEntries.map((feedEntry) => (
|
||||||
<Card
|
<Card
|
||||||
key={feedEntry.id}
|
key={feedEntry.id}
|
||||||
withBorder
|
|
||||||
component={"a"}
|
component={"a"}
|
||||||
href={feedEntry.link}
|
href={feedEntry.link}
|
||||||
radius={board.itemRadius}
|
radius={board.itemRadius}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export default function SmartHomeTriggerAutomationWidget({
|
|||||||
</Center>
|
</Center>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
)}
|
)}
|
||||||
<LoadingOverlay visible={isPending} zIndex={1000} overlayProps={{ radius: "sm", blur: 2 }} />
|
<LoadingOverlay visible={isPending} />
|
||||||
<Center w="100%" h="100%">
|
<Center w="100%" h="100%">
|
||||||
<Stack align="center" gap="md">
|
<Stack align="center" gap="md">
|
||||||
<IconAutomation size={isTiny ? 16 : undefined} />
|
<IconAutomation size={isTiny ? 16 : undefined} />
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export const CombinedNetworkTrafficChart = ({
|
|||||||
tooltipProps={{
|
tooltipProps={{
|
||||||
content: ({ payload }) => {
|
content: ({ payload }) => {
|
||||||
return (
|
return (
|
||||||
<Paper px={3} py={2} withBorder shadow="md" radius="md">
|
<Paper px={3} py={2} shadow="md">
|
||||||
<Stack gap={0}>
|
<Stack gap={0}>
|
||||||
{payload.map((payloadData) => (
|
{payload.map((payloadData) => (
|
||||||
<Group key={payloadData.key} gap={4}>
|
<Group key={payloadData.key} gap={4}>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export const SystemResourceCPUChart = ({
|
|||||||
content: ({ payload }) => {
|
content: ({ payload }) => {
|
||||||
const value = payload[0] ? Number(payload[0].value) : 0;
|
const value = payload[0] ? Number(payload[0].value) : 0;
|
||||||
return (
|
return (
|
||||||
<Paper px={3} py={2} withBorder shadow="md" radius="md">
|
<Paper px={3} py={2} shadow="md">
|
||||||
<Text c="dimmed" size="xs">
|
<Text c="dimmed" size="xs">
|
||||||
{value.toFixed(0)}%
|
{value.toFixed(0)}%
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export const SystemResourceGPUChart = ({
|
|||||||
content: ({ payload }) => {
|
content: ({ payload }) => {
|
||||||
const value = payload[0] ? Number(payload[0].value) : 0;
|
const value = payload[0] ? Number(payload[0].value) : 0;
|
||||||
return (
|
return (
|
||||||
<Paper px={3} py={2} withBorder shadow="md" radius="md">
|
<Paper px={3} py={2} shadow="md">
|
||||||
<Text c="dimmed" size="xs">
|
<Text c="dimmed" size="xs">
|
||||||
{value.toFixed(0)}%
|
{value.toFixed(0)}%
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const SystemResourceMemoryChart = ({
|
|||||||
content: ({ payload }) => {
|
content: ({ payload }) => {
|
||||||
const value = payload[0] ? Number(payload[0].value) : 0;
|
const value = payload[0] ? Number(payload[0].value) : 0;
|
||||||
return (
|
return (
|
||||||
<Paper px={3} py={2} withBorder shadow="md" radius="md">
|
<Paper px={3} py={2} shadow="md">
|
||||||
<Text c="dimmed" size="xs">
|
<Text c="dimmed" size="xs">
|
||||||
{humanFileSize(Math.round(value))} / {humanFileSize(totalCapacityInBytes)} (
|
{humanFileSize(Math.round(value))} / {humanFileSize(totalCapacityInBytes)} (
|
||||||
{Math.round((value / totalCapacityInBytes) * 100)}%)
|
{Math.round((value / totalCapacityInBytes) * 100)}%)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const NetworkTrafficChart = ({
|
|||||||
content: ({ payload }) => {
|
content: ({ payload }) => {
|
||||||
const value = payload[0] ? Number(payload[0].value) : 0;
|
const value = payload[0] ? Number(payload[0].value) : 0;
|
||||||
return (
|
return (
|
||||||
<Paper px={3} py={2} withBorder shadow="md" radius="md">
|
<Paper px={3} py={2} shadow="md">
|
||||||
<Text c="dimmed" size="xs">
|
<Text c="dimmed" size="xs">
|
||||||
{humanFileSize(Math.round(value))}/s
|
{humanFileSize(Math.round(value))}/s
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function RecentActivityList({ sessions }: { sessions: TracearrHistorySess
|
|||||||
: session.mediaTitle;
|
: session.mediaTitle;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper key={session.id} p="sm" radius="lg" withBorder>
|
<Paper key={session.id} p="sm" radius="lg">
|
||||||
<Group justify="space-between" wrap="nowrap">
|
<Group justify="space-between" wrap="nowrap">
|
||||||
<Group gap="xs" wrap="nowrap" style={{ overflow: "hidden" }}>
|
<Group gap="xs" wrap="nowrap" style={{ overflow: "hidden" }}>
|
||||||
<Avatar src={session.user.avatarUrl} alt={session.user.username} radius="xl" size="sm" />
|
<Avatar src={session.user.avatarUrl} alt={session.user.username} radius="xl" size="sm" />
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function StatsBar({
|
|||||||
|
|
||||||
function StatCard({ icon, label, value }: { icon: React.ReactNode; label: string; value: string | number }) {
|
function StatCard({ icon, label, value }: { icon: React.ReactNode; label: string; value: string | number }) {
|
||||||
return (
|
return (
|
||||||
<Paper p="xs" radius="lg" withBorder>
|
<Paper p="xs" radius="lg">
|
||||||
<Group gap={4} wrap="nowrap">
|
<Group gap={4} wrap="nowrap">
|
||||||
{icon}
|
{icon}
|
||||||
<Stack gap={0} style={{ overflow: "hidden" }}>
|
<Stack gap={0} style={{ overflow: "hidden" }}>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ function StreamCard({ stream, compact }: { stream: TracearrStream; compact: bool
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper p="xs" radius="lg" withBorder style={{ position: "relative", overflow: "hidden" }}>
|
<Paper p="xs" radius="lg" style={{ position: "relative", overflow: "hidden" }}>
|
||||||
{stream.posterUrl && (
|
{stream.posterUrl && (
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
@@ -100,7 +100,7 @@ function StreamCard({ stream, compact }: { stream: TracearrStream; compact: bool
|
|||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Tooltip label={mediaLabel} openDelay={300}>
|
<Tooltip label={mediaLabel}>
|
||||||
<Text size="xs" c="dimmed" lineClamp={1}>
|
<Text size="xs" c="dimmed" lineClamp={1}>
|
||||||
{mediaLabel}
|
{mediaLabel}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function ViolationsList({ violations }: { violations: TracearrViolation[]
|
|||||||
) : (
|
) : (
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
{violations.map((violation) => (
|
{violations.map((violation) => (
|
||||||
<Paper key={violation.id} p="xs" radius="lg" withBorder>
|
<Paper key={violation.id} p="xs" radius="lg">
|
||||||
<Group justify="space-between" wrap="nowrap">
|
<Group justify="space-between" wrap="nowrap">
|
||||||
<Group gap="xs" wrap="nowrap" style={{ overflow: "hidden" }}>
|
<Group gap="xs" wrap="nowrap" style={{ overflow: "hidden" }}>
|
||||||
<IconAlertTriangle
|
<IconAlertTriangle
|
||||||
|
|||||||
Reference in new issue
Block a user