From 7dfb0899c97645ecaefa690088c8f78a238641d8 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Mon, 20 Feb 2023 14:37:01 +0100 Subject: [PATCH 1/6] fix: wrap WithEncryptionRequired around ServerStatus.tsx --- index.html | 1 - src/route-widgets/AdminRoute/ServerStatus.tsx | 7 +++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index 31a68b8..e0d1c84 100755 --- a/index.html +++ b/index.html @@ -2,7 +2,6 @@ - Vite + React + TS diff --git a/src/route-widgets/AdminRoute/ServerStatus.tsx b/src/route-widgets/AdminRoute/ServerStatus.tsx index 8ed9c7f..dd2e698 100644 --- a/src/route-widgets/AdminRoute/ServerStatus.tsx +++ b/src/route-widgets/AdminRoute/ServerStatus.tsx @@ -3,20 +3,21 @@ import {AxiosError} from "axios" import {useLoaderData} from "react-router-dom" import {useTranslation} from "react-i18next" import format from "date-fns/format" +import formatRelative from "date-fns/formatRelative" import subDays from "date-fns/subDays" import {useQuery} from "@tanstack/react-query" import {Alert} from "@mui/material" +import {WithEncryptionRequired} from "~/hocs" import {CronReport, ServerSettings} from "~/server-types" import {getLatestCronReport} from "~/apis" import {AuthContext, QueryResult} from "~/components" import decryptCronReportData from "~/apis/helpers/decrypt-cron-report-data" -import formatRelative from "date-fns/formatRelative" const MAX_REPORT_DAY_THRESHOLD = 5 -export default function ServerStatus(): ReactElement { +function ServerStatus(): ReactElement { const serverSettings = useLoaderData() as ServerSettings const {t} = useTranslation() const {_decryptUsingPrivateKey} = useContext(AuthContext) @@ -75,3 +76,5 @@ export default function ServerStatus(): ReactElement { ) } + +export default WithEncryptionRequired(ServerStatus) From ffc1f26c43de28eb0bf24b1e98350d4eacbc597b Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Mon, 20 Feb 2023 14:57:59 +0100 Subject: [PATCH 2/6] fix: Update SettingsForm to new API --- .../GlobalSettingsRoute/SettingsForm.tsx | 37 ++++++------------- src/routes/GlobalSettingsRoute.tsx | 6 +-- src/server-types.ts | 16 ++++---- 3 files changed, 20 insertions(+), 39 deletions(-) diff --git a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx index 87b542f..2877bec 100644 --- a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx +++ b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx @@ -26,9 +26,10 @@ import {useErrorSuccessSnacks} from "~/hooks" import {queryClient} from "~/constants/react-query" import {parseFastAPIError} from "~/utils" import {DEFAULT_ADMIN_SETTINGS} from "~/constants/admin-settings" -import AliasesPercentageAmount from "./AliasPercentageAmount" import RandomAliasGenerator from "~/route-widgets/GlobalSettingsRoute/RandomAliasGenerator" +import AliasesPercentageAmount from "./AliasPercentageAmount" + export interface SettingsFormProps { settings: AdminSettings queryKey: readonly string[] @@ -89,34 +90,18 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { UpdateAdminSettingsResponse, AxiosError, Partial - >( - async settings => { - // Set values to `null` that are their defaults - const strippedSettings = Object.fromEntries( - Object.entries(settings as AdminSettings).map(([key, value]) => { - if (value === DEFAULT_ADMIN_SETTINGS[key as keyof AdminSettings]) { - return [key, null] - } + >(async settings => updateAdminSettings(settings), { + onError: showError, + onSuccess: ({code, detail, ...newSettings}) => { + if (code === "error:settings:global_settings_disabled") { + return + } - return [key, value] - }), - ) + showSuccess(t("routes.AdminRoute.settings.successMessage")) - return updateAdminSettings(strippedSettings) + queryClient.setQueryData>(queryKey, newSettings) }, - { - onError: showError, - onSuccess: ({code, detail, ...newSettings}) => { - if (code === "error:settings:global_settings_disabled") { - return - } - - showSuccess(t("routes.AdminRoute.settings.successMessage")) - - queryClient.setQueryData>(queryKey, newSettings) - }, - }, - ) + }) const formik = useFormik({ validationSchema, diff --git a/src/routes/GlobalSettingsRoute.tsx b/src/routes/GlobalSettingsRoute.tsx index 59cfbbc..ef30997 100644 --- a/src/routes/GlobalSettingsRoute.tsx +++ b/src/routes/GlobalSettingsRoute.tsx @@ -1,13 +1,11 @@ import {ReactElement} from "react" import {AxiosError} from "axios" -import _ from "lodash" import {useQuery} from "@tanstack/react-query" import {AdminSettings} from "~/server-types" import {getAdminSettings} from "~/apis" import {QueryResult} from "~/components" -import {DEFAULT_ADMIN_SETTINGS} from "~/constants/admin-settings" import SettingsDisabled from "~/route-widgets/GlobalSettingsRoute/SettingsDisabled" import SettingsForm from "~/route-widgets/GlobalSettingsRoute/SettingsForm" @@ -19,9 +17,7 @@ export default function GlobalSettingsRoute(): ReactElement { if (code === "error:settings:global_settings_disabled") { return null } else { - return _.mergeWith({}, DEFAULT_ADMIN_SETTINGS, settings, (o, s) => - _.isNull(s) ? o : s, - ) as AdminSettings + return settings as AdminSettings } }) diff --git a/src/server-types.ts b/src/server-types.ts index 45192f4..d2dd5e1 100644 --- a/src/server-types.ts +++ b/src/server-types.ts @@ -201,17 +201,17 @@ export interface GetPageData { } export interface AdminSettings { - randomEmailIdMinLength: number | null - randomEmailIdChars: string | null - randomEmailLengthIncreaseOnPercentage: number | null + randomEmailIdMinLength: number + randomEmailIdChars: string + randomEmailLengthIncreaseOnPercentage: number customEmailSuffixLength: number - customEmailSuffixChars: string | null + customEmailSuffixChars: string imageProxyStorageLifeTimeInHours: number - enableImageProxy: boolean | null + enableImageProxy: boolean userEmailEnableDisposableEmails: boolean - userEmailEnableOtherRelays: boolean | null - allowStatistics: boolean | null - allowAliasDeletion: boolean | null + userEmailEnableOtherRelays: boolean + allowStatistics: boolean + allowAliasDeletion: boolean } export interface ServerCronReport { From 2ab9665afd549a3d28798ae3026360e7bff38cdf Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Mon, 20 Feb 2023 15:13:30 +0100 Subject: [PATCH 3/6] fix: empty latest cron report throwing error --- src/apis/get-latest-cron-report.ts | 7 ++++++- src/components/widgets/QueryResult.tsx | 11 ++++++----- src/route-widgets/AdminRoute/ServerStatus.tsx | 16 ++++++++++++---- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/apis/get-latest-cron-report.ts b/src/apis/get-latest-cron-report.ts index 17e8786..a705ef5 100644 --- a/src/apis/get-latest-cron-report.ts +++ b/src/apis/get-latest-cron-report.ts @@ -1,7 +1,12 @@ import {ServerCronReport} from "~/server-types" import {client} from "~/constants/axios-client" -export default async function getLatestCronReport(): Promise { +export type GetLatestCronReportResponse = ServerCronReport & { + detail: string + code: "error:cron_report:no_reports_found" +} + +export default async function getLatestCronReport(): Promise { const {data} = await client.get( `${import.meta.env.VITE_SERVER_BASE_URL}/v1/admin/cron-report/latest/`, { diff --git a/src/components/widgets/QueryResult.tsx b/src/components/widgets/QueryResult.tsx index 402b064..243ea2e 100644 --- a/src/components/widgets/QueryResult.tsx +++ b/src/components/widgets/QueryResult.tsx @@ -10,13 +10,14 @@ import LoadingData from "./LoadingData" export interface QueryResultProps { query: UseQueryResult - children: (data: TQueryFnData) => ReactElement + children: (data: TQueryFnData) => ReactElement | null } -export default function QueryResult({ - query, - children: render, -}: QueryResultProps): ReactElement { +export default function QueryResult< + TQueryFnData, + TError = AxiosError, + ReturnType = ReactElement | null, +>({query, children: render}: QueryResultProps): ReactElement | null { if (query.data !== undefined) { return render(query.data) } diff --git a/src/route-widgets/AdminRoute/ServerStatus.tsx b/src/route-widgets/AdminRoute/ServerStatus.tsx index dd2e698..8140b5e 100644 --- a/src/route-widgets/AdminRoute/ServerStatus.tsx +++ b/src/route-widgets/AdminRoute/ServerStatus.tsx @@ -17,13 +17,17 @@ import decryptCronReportData from "~/apis/helpers/decrypt-cron-report-data" const MAX_REPORT_DAY_THRESHOLD = 5 -function ServerStatus(): ReactElement { +function ServerStatus(): ReactElement | null { const serverSettings = useLoaderData() as ServerSettings const {t} = useTranslation() const {_decryptUsingPrivateKey} = useContext(AuthContext) - const query = useQuery(["get_latest_cron_report"], async () => { - const encryptedReport = await getLatestCronReport() + const query = useQuery(["get_latest_cron_report"], async () => { + const {code, detail, ...encryptedReport} = await getLatestCronReport() + + if (code === "error:cron_report:no_reports_found") { + return null + } ;(encryptedReport as any as CronReport).reportData = await decryptCronReportData( encryptedReport.reportData.encryptedReport, @@ -35,8 +39,12 @@ function ServerStatus(): ReactElement { }) return ( - query={query}> + query={query}> {report => { + if (report === null) { + return null + } + const thresholdDate = subDays(new Date(), MAX_REPORT_DAY_THRESHOLD) if (report.createdAt < thresholdDate) { From 438ec51e17c613fb149b1bd6d80e1e4fc134faef Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Mon, 20 Feb 2023 15:49:52 +0100 Subject: [PATCH 4/6] feat: Add maxAliasesPerUser to SettingsForm.tsx --- public/locales/en-US/translation.json | 4 + .../GlobalSettingsRoute/SettingsForm.tsx | 81 ++++++++++++++----- src/server-types.ts | 1 + 3 files changed, 66 insertions(+), 20 deletions(-) diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 0f99c43..4807812 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -395,6 +395,10 @@ "allowAliasDeletion": { "label": "Allow alias deletion", "description": "If enabled, users will be able to delete their aliases." + }, + "maxAliasesPerUser": { + "label": "Maximum aliases per user", + "description": "The maximum number of aliases a user can create. 0 means unlimited. Existing aliases will not be affected." } } }, diff --git a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx index 2877bec..6737e73 100644 --- a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx +++ b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx @@ -6,6 +6,9 @@ import {MdCheck, MdClear, MdOutlineChangeCircle, MdTextFormat} from "react-icons import {BsImage} from "react-icons/bs" import {AxiosError} from "axios" +import {FaMask} from "react-icons/fa" +import AliasesPercentageAmount from "./AliasPercentageAmount" + import { Checkbox, FormControlLabel, @@ -28,8 +31,6 @@ import {parseFastAPIError} from "~/utils" import {DEFAULT_ADMIN_SETTINGS} from "~/constants/admin-settings" import RandomAliasGenerator from "~/route-widgets/GlobalSettingsRoute/RandomAliasGenerator" -import AliasesPercentageAmount from "./AliasPercentageAmount" - export interface SettingsFormProps { settings: AdminSettings queryKey: readonly string[] @@ -84,7 +85,14 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { allowStatistics: yup .boolean() .label(t("routes.AdminRoute.forms.settings.allowStatistics.label")), - }) + allowAliasDeletion: yup + .boolean() + .label(t("routes.AdminRoute.forms.settings.allowAliasDeletion.label")), + maxAliasesPerUser: yup + .number() + .label(t("routes.AdminRoute.forms.settings.maxAliasesPerUser.label")) + .min(0), + } as Record) const {mutateAsync} = useMutation< UpdateAdminSettingsResponse, @@ -144,7 +152,40 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { - + + + + + ), + }} + /> + + - + - + - + - + - + - + - + - + - + @@ -423,12 +464,12 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { - + @@ -452,12 +493,12 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { - + @@ -481,12 +522,12 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { - + diff --git a/src/server-types.ts b/src/server-types.ts index d2dd5e1..bfe1b79 100644 --- a/src/server-types.ts +++ b/src/server-types.ts @@ -212,6 +212,7 @@ export interface AdminSettings { userEmailEnableOtherRelays: boolean allowStatistics: boolean allowAliasDeletion: boolean + maxAliasesPerUser: number } export interface ServerCronReport { From a11f7310089c562ad8c2a22e66b44142ab53bd08 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Mon, 20 Feb 2023 15:52:34 +0100 Subject: [PATCH 5/6] fix: translation --- public/locales/en-US/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 4807812..ab1c5aa 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -378,7 +378,7 @@ }, "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." + "description": "If enabled, images will be stored on the server and forwarded to the user. This makes email tracking nearly impossible as every message will be marked as read instantly, which is obviously not true as a user is typically not able to view an email in just a few seconds after it has been sent. This will only affect new images." }, "userEmailEnableDisposableEmails": { "label": "Enable disposable emails for new accounts", From 98e3b5ead51771125d2a6ae2484907b94591f9b4 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Mon, 20 Feb 2023 16:00:46 +0100 Subject: [PATCH 6/6] fix: Add maxAliasesPerUser to default admin settings --- src/constants/admin-settings.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/constants/admin-settings.ts b/src/constants/admin-settings.ts index b85bf6b..95d0edc 100644 --- a/src/constants/admin-settings.ts +++ b/src/constants/admin-settings.ts @@ -12,4 +12,5 @@ export const DEFAULT_ADMIN_SETTINGS: AdminSettings = { enableImageProxy: true, allowStatistics: true, allowAliasDeletion: false, + maxAliasesPerUser: 0, }