feat: add server settings (#487)

* feat: add server settings

* feat: remove old migration

* feat: add new migrations

* refactor: format

* fix: build error

* refactor: format

* fix: lint
This commit is contained in:
Manuel
2024-05-19 22:29:15 +02:00
committed by GitHub
parent d9f5158662
commit 919161798e
25 changed files with 2888 additions and 1 deletions

View File

@@ -14,6 +14,7 @@ import {
IconMailForward,
IconPlug,
IconQuestionMark,
IconSettings,
IconTool,
IconUser,
IconUsers,
@@ -87,6 +88,11 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
},
],
},
{
label: t("items.settings"),
href: "/manage/settings",
icon: IconSettings,
},
{
label: t("items.help.label"),
icon: IconQuestionMark,

View File

@@ -0,0 +1,140 @@
"use client";
import type { ReactNode } from "react";
import React from "react";
import type { MantineSpacing } from "@mantine/core";
import {
Card,
Group,
LoadingOverlay,
Stack,
Switch,
Text,
Title,
UnstyledButton,
} from "@mantine/core";
import { clientApi } from "@homarr/api/client";
import type { UseFormReturnType } from "@homarr/form";
import { useForm } from "@homarr/form";
import type { defaultServerSettings } from "@homarr/server-settings";
import { useScopedI18n } from "@homarr/translation/client";
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
interface AnalyticsSettingsProps {
initialData: typeof defaultServerSettings.analytics;
}
export const AnalyticsSettings = ({ initialData }: AnalyticsSettingsProps) => {
const t = useScopedI18n("management.page.settings.section.analytics");
const form = useForm({
initialValues: initialData,
onValuesChange: (updatedValues, _) => {
if (!form.isValid()) {
return;
}
if (
!updatedValues.enableGeneral &&
(updatedValues.enableWidgetData ||
updatedValues.enableIntegrationData ||
updatedValues.enableUserData)
) {
updatedValues.enableIntegrationData = false;
updatedValues.enableUserData = false;
updatedValues.enableWidgetData = false;
}
void mutateAsync({
settingsKey: "analytics",
value: updatedValues,
});
},
});
const { mutateAsync, isPending } =
clientApi.serverSettings.saveSettings.useMutation({
onSettled: async () => {
await revalidatePathActionAsync("/manage/settings");
},
});
return (
<>
<Title order={2}>{t("title")}</Title>
<Card pos="relative" withBorder>
<LoadingOverlay
visible={isPending}
zIndex={1000}
overlayProps={{ radius: "sm", blur: 2 }}
/>
<Stack>
<SwitchSetting
form={form}
formKey="enableGeneral"
title={t("general.title")}
text={t("general.text")}
/>
<SwitchSetting
form={form}
formKey="enableIntegrationData"
ms="xl"
title={t("integrationData.title")}
text={t("integrationData.text")}
/>
<SwitchSetting
form={form}
formKey="enableWidgetData"
ms="xl"
title={t("widgetData.title")}
text={t("widgetData.text")}
/>
<SwitchSetting
form={form}
formKey="enableUserData"
ms="xl"
title={t("usersData.title")}
text={t("usersData.text")}
/>
</Stack>
</Card>
</>
);
};
const SwitchSetting = ({
form,
ms,
title,
text,
formKey,
}: {
form: UseFormReturnType<typeof defaultServerSettings.analytics>;
formKey: keyof typeof defaultServerSettings.analytics;
ms?: MantineSpacing;
title: string;
text: ReactNode;
}) => {
const handleClick = React.useCallback(() => {
form.setFieldValue(formKey, !form.values[formKey]);
}, [form, formKey]);
return (
<UnstyledButton onClick={handleClick}>
<Group
ms={ms}
justify="space-between"
gap="lg"
align="center"
wrap="nowrap"
>
<Stack gap={0}>
<Text fw="bold">{title}</Text>
<Text c="gray.5">{text}</Text>
</Stack>
<Switch {...form.getInputProps(formKey, { type: "checkbox" })} />
</Group>
</UnstyledButton>
);
};

View File

@@ -0,0 +1,26 @@
import { Stack, Title } from "@mantine/core";
import { api } from "@homarr/api/server";
import { getScopedI18n } from "@homarr/translation/server";
import { AnalyticsSettings } from "./_components/analytics.settings";
export async function generateMetadata() {
const t = await getScopedI18n("management");
const metaTitle = `${t("metaTitle")} • Homarr`;
return {
title: metaTitle,
};
}
export default async function SettingsPage() {
const serverSettings = await api.serverSettings.getAll();
const t = await getScopedI18n("management.page.settings");
return (
<Stack>
<Title order={1}>{t("title")}</Title>
<AnalyticsSettings initialData={serverSettings.analytics} />
</Stack>
);
}