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:
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
26
apps/nextjs/src/app/[locale]/manage/settings/page.tsx
Normal file
26
apps/nextjs/src/app/[locale]/manage/settings/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user