feat(settings): Add admin settings page (draft)

This commit is contained in:
Myzel394 2023-02-09 21:51:38 +01:00
parent 6a1629114c
commit e6cdfcbc5a
9 changed files with 212 additions and 1 deletions

View File

@ -313,7 +313,8 @@
"AdminRoute": {
"title": "Site configuration",
"routes": {
"reservedAliases": "Reserved Aliases"
"reservedAliases": "Reserved Aliases",
"settings": "Global Settings"
},
"forms": {
"reservedAliases": {
@ -334,6 +335,48 @@
"step2": "Sends mail to",
"step4": "KleckRelay forwards to"
}
},
"settings": {
"randomEmailIdMinLength": {
"label": "Minimum random alias ID length",
"description": "The minimum length for randomly generated emails. The server will automatically increase the length if required so."
},
"randomEmailIdChars": {
"label": "Random alias character pool",
"description": "Characters that are used to generate random emails."
},
"randomEmailIdLengthIncreaseOnPercentage": {
"label": "Percentage of used aliases",
"description": "If the percentage of used random email IDs is higher than this value, the length of the random email ID will be increased. This is used to prevent spammers from guessing the email ID."
},
"customEmailSuffixLength": {
"label": "Custom email suffix length",
"description": "The length of the custom email suffix."
},
"customEmailSuffixChars": {
"label": "Custom email suffix character pool",
"description": "Characters that are used to generate custom email suffixes."
},
"imageProxyStorageLifeTimeInHours": {
"label": "Image proxy storage lifetime",
"description": "The lifetime of images that are stored on the server. After this time, the image will be deleted."
},
"enableImageProxy": {
"label": "Enable image proxy",
"description": "If enabled, images will be stored on the server and forwarded to the user. This is useful if you want to prevent the user from seeing the IP address of the server. This will only affect new images."
},
"userEmailEnableDisposableEmails": {
"label": "Enable disposable emails for new accounts",
"description": "If enabled, users will be able to use disposable emails when creating a new account. This will only affect new accounts."
},
"userEmailEnableOtherRelays": {
"label": "Enable other relays for new accounts",
"description": "If enabled, users will be able to use other relays (such as SimpleLogin or DuckDuckGo's Email Tracking Protection) when creating a new account. This will only affect new accounts."
},
"allowStatistics": {
"label": "Allow statistics",
"description": "If enabled, your instance will collect anonymous statistics and share them. They will only be stored locally on this instance but made public."
}
}
}
}

View File

@ -16,6 +16,7 @@ import AuthenticatedRoute from "~/routes/AuthenticatedRoute"
import CompleteAccountRoute from "~/routes/CompleteAccountRoute"
import CreateReservedAliasRoute from "~/routes/CreateReservedAliasRoute"
import EnterDecryptionPassword from "~/routes/EnterDecryptionPassword"
import GlobalSettingsRoute from "~/routes/GlobalSettingsRoute"
import I18nHandler from "./I18nHandler"
import LoginRoute from "~/routes/LoginRoute"
import LogoutRoute from "~/routes/LogoutRoute"
@ -119,6 +120,10 @@ const router = createBrowserRouter([
loader: getServerSettings,
element: <CreateReservedAliasRoute />,
},
{
path: "/admin/settings",
element: <GlobalSettingsRoute />,
},
],
},
],

View File

@ -0,0 +1,10 @@
import {client} from "~/constants/axios-client"
import {AdminSettings} from "~/server-types"
export default async function getAdminSettings(): Promise<AdminSettings> {
const {data} = await client.get(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/admin/settings`, {
withCredentials: true,
})
return data
}

View File

@ -48,3 +48,7 @@ export * from "./get-reserved-alias"
export {default as getReservedAlias} from "./get-reserved-alias"
export * from "./update-reserved-alias"
export {default as updateReservedAlias} from "./update-reserved-alias"
export * from "./get-admin-settings"
export {default as getAdminSettings} from "./get-admin-settings"
export * from "./update-admin-settings"
export {default as updateAdminSettings} from "./update-admin-settings"

View File

@ -0,0 +1,16 @@
import {client} from "~/constants/axios-client"
import {AdminSettings} from "~/server-types"
export default async function updateAdminSettings(
settings: Partial<AdminSettings>,
): Promise<AdminSettings> {
const {data} = await client.patch(
`${import.meta.env.VITE_SERVER_BASE_URL}/v1/admin/settings`,
settings,
{
withCredentials: true,
},
)
return data
}

View File

@ -0,0 +1,92 @@
import * as yup from "yup"
import {AdminSettings} from "~/server-types"
import {useTranslation} from "react-i18next"
import {useFormik} from "formik"
import {SimpleForm} from "~/components"
import {TextField} from "@mui/material"
export interface SettingsFormProps {
settings: AdminSettings
}
export default function SettingsForm({settings}: SettingsFormProps) {
const {t} = useTranslation()
const validationSchema = yup.object().shape({
randomEmailIdMinLength: yup
.number()
.min(1)
.max(1_023)
.label(t("routes.AdminRoute.forms.settings.randomEmailIdMinLength.label")),
randomEmailIdChars: yup
.string()
.label(t("routes.AdminRoute.forms.settings.randomEmailIdChars.label")),
randomEmailLengthIncreaseOnPercentage: yup
.number()
.label(
t("routes.AdminRoute.forms.settings.randomEmailLengthIncreaseOnPercentage.label"),
),
imageProxyStorageLifeTimeInHours: yup
.number()
.label(t("routes.AdminRoute.forms.settings.imageProxyStorageLifeTimeInHours.label")),
customEmailSuffixLength: yup
.number()
.min(1)
.max(1_023)
.label(t("routes.AdminRoute.forms.settings.customEmailSuffixLength-label")),
customEmailSuffixChars: yup
.string()
.label(t("routes.AdminRoute.forms.settings.customEmailSuffixChars.label")),
userEmailEnableDisposableEmails: yup
.boolean()
.label(t("routes.AdminRoute.forms.settings.userEmailEnableDisposableEmails.label")),
userEmailEnableOtherRelays: yup
.boolean()
.label(t("routes.AdminRoute.forms.settings.userEmailEnableOtherRelays.label")),
enableImageProxy: yup
.boolean()
.label(t("routes.AdminRoute.forms.settings.enableImageProxy.label")),
allowStatistics: yup
.boolean()
.label(t("routes.AdminRoute.forms.settings.allowStatistics.label")),
})
const formik = useFormik<AdminSettings>({
validationSchema,
onSubmit: console.log,
initialValues: settings,
})
return (
<form onSubmit={formik.handleSubmit}>
<SimpleForm
isSubmitting={formik.isSubmitting}
title={t("routes.AdminRoute.settings.title")}
description={t("routes.AdminRoute.settings.description")}
continueActionLabel={t("general.save")}
>
{[
<TextField
key="random_email_id_min_length"
label={t("routes.AdminRoute.forms.settings.randomEmailIdMinLength.label")}
name="randomEmailIdMinLength"
value={formik.values.randomEmailIdMinLength}
onChange={formik.handleChange}
error={
formik.touched.randomEmailIdMinLength &&
Boolean(formik.errors.randomEmailIdMinLength)
}
helperText={
(formik.touched.randomEmailIdMinLength &&
formik.errors.randomEmailIdMinLength) ||
t("routes.AdminRoute.forms.settings.randomEmailIdMinLength.description")
}
type="number"
disabled={formik.isSubmitting}
inputMode="numeric"
/>,
]}
</SimpleForm>
</form>
)
}

View File

@ -1,6 +1,7 @@
import {ReactElement, useLayoutEffect} from "react"
import {useTranslation} from "react-i18next"
import {BsStarFill} from "react-icons/bs"
import {AiFillEdit} from "react-icons/ai"
import {Link} from "react-router-dom"
import {List, ListItemButton, ListItemIcon, ListItemText} from "@mui/material"
@ -13,6 +14,8 @@ export default function AdminRoute(): ReactElement {
const navigateToNext = useNavigateToNext()
const user = useUser()
console.log(user)
useLayoutEffect(() => {
if (!user.isAdmin) {
navigateToNext()
@ -28,6 +31,12 @@ export default function AdminRoute(): ReactElement {
</ListItemIcon>
<ListItemText primary={t("routes.AdminRoute.routes.reservedAliases")} />
</ListItemButton>
<ListItemButton component={Link} to="/admin/settings">
<ListItemIcon>
<AiFillEdit />
</ListItemIcon>
<ListItemText primary={t("routes.AdminRoute.routes.settings")} />
</ListItemButton>
</List>
</SimplePageBuilder.Page>
)

View File

@ -0,0 +1,19 @@
import {ReactElement} from "react"
import {useQuery} from "@tanstack/react-query"
import {AdminSettings} from "~/server-types"
import {AxiosError} from "axios"
import {getAdminSettings} from "~/apis"
import {QueryResult} from "~/components"
import {useTranslation} from "react-i18next"
import SettingsForm from "~/route-widgets/GlobalSettingsRoute/SettingForm"
export default function GlobalSettingsRoute(): ReactElement {
const {t} = useTranslation()
const query = useQuery<AdminSettings, AxiosError>(["get_admin_settings"], getAdminSettings)
return (
<QueryResult<AdminSettings> query={query}>
{settings => <SettingsForm settings={settings} />}
</QueryResult>
)
}

View File

@ -198,3 +198,16 @@ export interface GetPageData {
page?: number
size?: number
}
export interface AdminSettings {
randomEmailIdMinLength: number
randomEmailIdChars: string
randomEmailLengthIncreaseOnPercentage: number
customEmailSuffixLength: number
customEmailSuffixChars: string
imageProxyStorageLifeTimeInHours: number
enableImageProxy: boolean
userEmailEnableDisposableEmails: boolean
userEmailEnableOtherRelays: boolean
allowStatistics: boolean
}