From e07eb3e75dec8bed83cf58eb0f78018292554cb8 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 18 Feb 2023 12:53:28 +0100 Subject: [PATCH] feat: Add cron report status to AdminRoute --- public/locales/en-US/translation.json | 5 ++ src/App.tsx | 1 + src/apis/get-latest-cron-report.ts | 6 +- src/apis/helpers/decrypt-cron-report-data.ts | 33 ++++++++ .../AdminPage/ReservedAliasesList.tsx | 32 -------- src/route-widgets/AdminRoute/ServerStatus.tsx | 77 +++++++++++++++++++ src/routes/AdminRoute.tsx | 4 +- src/server-types.ts | 18 ++++- 8 files changed, 138 insertions(+), 38 deletions(-) create mode 100644 src/apis/helpers/decrypt-cron-report-data.ts delete mode 100644 src/route-widgets/AdminPage/ReservedAliasesList.tsx create mode 100644 src/route-widgets/AdminRoute/ServerStatus.tsx diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 6e65679..0f99c43 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -421,6 +421,11 @@ "continueAction": "Delete Reserved Alias" } } + }, + "serverStatus": { + "noRecentReports": "There seems to be some issues with your server. The server hasn't done its cleanup in the last few days. The last report was on {{date}}.", + "error": "There was an error during the last server cleanup job from {{relativeDescription}}. Please check the logs for more information.", + "success": "Everything okay with your server! The last cleanup job was {{relativeDescription}}." } } }, diff --git a/src/App.tsx b/src/App.tsx index cf21964..83d9842 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -106,6 +106,7 @@ const router = createBrowserRouter([ }, { path: "/admin", + loader: getServerSettings, element: , }, { diff --git a/src/apis/get-latest-cron-report.ts b/src/apis/get-latest-cron-report.ts index b3ad51e..17e8786 100644 --- a/src/apis/get-latest-cron-report.ts +++ b/src/apis/get-latest-cron-report.ts @@ -1,9 +1,9 @@ -import {CronReport} from "~/server-types" +import {ServerCronReport} from "~/server-types" import {client} from "~/constants/axios-client" -export default async function getLatestCronReport(): Promise { +export default async function getLatestCronReport(): Promise { const {data} = await client.get( - `${import.meta.env.VITE_SERVER_BASE_URL}/v1/cron-report/latest/`, + `${import.meta.env.VITE_SERVER_BASE_URL}/v1/admin/cron-report/latest/`, { withCredentials: true, }, diff --git a/src/apis/helpers/decrypt-cron-report-data.ts b/src/apis/helpers/decrypt-cron-report-data.ts new file mode 100644 index 0000000..d03c0fc --- /dev/null +++ b/src/apis/helpers/decrypt-cron-report-data.ts @@ -0,0 +1,33 @@ +import camelcaseKeys from "camelcase-keys" +import update from "immutability-helper" + +import {AuthContextType} from "~/components" +import {CronReport} from "~/server-types" +import {extractCleartextFromSignedMessage} from "~/utils" + +export default async function decryptCronReportData( + signedMessage: string, + decryptContent: AuthContextType["_decryptUsingPrivateKey"], + publicKeyInPEM: string, +): Promise { + const encryptedMessage = await extractCleartextFromSignedMessage(signedMessage, publicKeyInPEM) + + return update( + camelcaseKeys( + JSON.parse(await decryptContent(encryptedMessage)) as CronReport["reportData"], + { + deep: true, + }, + ), + { + report: { + startedAt: { + $apply: startedAt => new Date(startedAt), + }, + finishedAt: { + $apply: finishedAt => new Date(finishedAt), + }, + }, + }, + ) +} diff --git a/src/route-widgets/AdminPage/ReservedAliasesList.tsx b/src/route-widgets/AdminPage/ReservedAliasesList.tsx deleted file mode 100644 index b856845..0000000 --- a/src/route-widgets/AdminPage/ReservedAliasesList.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import {ReactElement} from "react" -import {AxiosError} from "axios" - -import {useQuery} from "@tanstack/react-query" -import {List, ListItem, ListItemText} from "@mui/material" - -import {getReservedAliases} from "~/apis" -import {PaginationResult, ReservedAlias} from "~/server-types" -import {QueryResult} from "~/components" - -export interface ReservedAliasesListProps {} - -export default function ReservedAliasesList({}: ReservedAliasesListProps): ReactElement { - const query = useQuery, AxiosError>( - ["getReservedAliases"], - () => getReservedAliases(), - ) - - return ( - , AxiosError> query={query}> - {({items}) => ( - - {items.map(alias => ( - - - - ))} - - )} - - ) -} diff --git a/src/route-widgets/AdminRoute/ServerStatus.tsx b/src/route-widgets/AdminRoute/ServerStatus.tsx new file mode 100644 index 0000000..8ed9c7f --- /dev/null +++ b/src/route-widgets/AdminRoute/ServerStatus.tsx @@ -0,0 +1,77 @@ +import {ReactElement, useContext} from "react" +import {AxiosError} from "axios" +import {useLoaderData} from "react-router-dom" +import {useTranslation} from "react-i18next" +import format from "date-fns/format" +import subDays from "date-fns/subDays" + +import {useQuery} from "@tanstack/react-query" +import {Alert} from "@mui/material" + +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 { + const serverSettings = useLoaderData() as ServerSettings + const {t} = useTranslation() + const {_decryptUsingPrivateKey} = useContext(AuthContext) + + const query = useQuery(["get_latest_cron_report"], async () => { + const encryptedReport = await getLatestCronReport() + + ;(encryptedReport as any as CronReport).reportData = await decryptCronReportData( + encryptedReport.reportData.encryptedReport, + _decryptUsingPrivateKey, + serverSettings.publicKey, + ) + + return encryptedReport as any as CronReport + }) + + return ( + query={query}> + {report => { + const thresholdDate = subDays(new Date(), MAX_REPORT_DAY_THRESHOLD) + + if (report.createdAt < thresholdDate) { + return ( + + {t("routes.AdminRoute.serverStatus.noRecentReports", { + date: format(new Date(report.createdAt), "Pp"), + })} + + ) + } + + if (report.reportData.report.status === "error") { + return ( + + {t("routes.AdminRoute.serverStatus.error", { + relativeDescription: formatRelative( + new Date(report.createdAt), + new Date(), + ), + })} + + ) + } + + return ( + + {t("routes.AdminRoute.serverStatus.success", { + relativeDescription: formatRelative( + new Date(report.createdAt), + new Date(), + ), + })} + + ) + }} + + ) +} diff --git a/src/routes/AdminRoute.tsx b/src/routes/AdminRoute.tsx index 37ce762..994dc15 100644 --- a/src/routes/AdminRoute.tsx +++ b/src/routes/AdminRoute.tsx @@ -8,14 +8,13 @@ import {List, ListItemButton, ListItemIcon, ListItemText} from "@mui/material" import {SimplePageBuilder} from "~/components" import {useNavigateToNext, useUser} from "~/hooks" +import ServerStatus from "~/route-widgets/AdminRoute/ServerStatus" export default function AdminRoute(): ReactElement { const {t} = useTranslation() const navigateToNext = useNavigateToNext() const user = useUser() - console.log(user) - useLayoutEffect(() => { if (!user.isAdmin) { navigateToNext() @@ -24,6 +23,7 @@ export default function AdminRoute(): ReactElement { return ( + diff --git a/src/server-types.ts b/src/server-types.ts index e8128fe..a51acbe 100644 --- a/src/server-types.ts +++ b/src/server-types.ts @@ -214,10 +214,26 @@ export interface AdminSettings { allowAliasDeletion: boolean | null } -export interface CronReport { +export interface ServerCronReport { id: string createdAt: Date reportData: { encryptedReport: string } } + +export interface CronReport { + id: string + createdAt: Date + reportData: { + version: "1.0" + id: string + report: { + startedAt: Date + finishedAt: Date + status: "success" | "error" + expiredImages: number + nonVerifiedUsers: number + } + } +}