feat: Add cron report status to AdminRoute

This commit is contained in:
Myzel394 2023-02-18 12:53:28 +01:00
parent 3b130d054f
commit e07eb3e75d
8 changed files with 138 additions and 38 deletions

View File

@ -421,6 +421,11 @@
"continueAction": "Delete Reserved Alias" "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}}."
} }
} }
}, },

View File

@ -106,6 +106,7 @@ const router = createBrowserRouter([
}, },
{ {
path: "/admin", path: "/admin",
loader: getServerSettings,
element: <AdminRoute />, element: <AdminRoute />,
}, },
{ {

View File

@ -1,9 +1,9 @@
import {CronReport} from "~/server-types" import {ServerCronReport} from "~/server-types"
import {client} from "~/constants/axios-client" import {client} from "~/constants/axios-client"
export default async function getLatestCronReport(): Promise<CronReport> { export default async function getLatestCronReport(): Promise<ServerCronReport> {
const {data} = await client.get( 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, withCredentials: true,
}, },

View File

@ -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<CronReport["reportData"]> {
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),
},
},
},
)
}

View File

@ -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<PaginationResult<ReservedAlias>, AxiosError>(
["getReservedAliases"],
() => getReservedAliases(),
)
return (
<QueryResult<PaginationResult<ReservedAlias>, AxiosError> query={query}>
{({items}) => (
<List>
{items.map(alias => (
<ListItem key={alias.id}>
<ListItemText primary={`${alias.local}@${alias.domain}`} />
</ListItem>
))}
</List>
)}
</QueryResult>
)
}

View File

@ -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<CronReport, AxiosError>(["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 (
<QueryResult<CronReport> query={query}>
{report => {
const thresholdDate = subDays(new Date(), MAX_REPORT_DAY_THRESHOLD)
if (report.createdAt < thresholdDate) {
return (
<Alert severity="warning">
{t("routes.AdminRoute.serverStatus.noRecentReports", {
date: format(new Date(report.createdAt), "Pp"),
})}
</Alert>
)
}
if (report.reportData.report.status === "error") {
return (
<Alert severity="error">
{t("routes.AdminRoute.serverStatus.error", {
relativeDescription: formatRelative(
new Date(report.createdAt),
new Date(),
),
})}
</Alert>
)
}
return (
<Alert severity="success">
{t("routes.AdminRoute.serverStatus.success", {
relativeDescription: formatRelative(
new Date(report.createdAt),
new Date(),
),
})}
</Alert>
)
}}
</QueryResult>
)
}

View File

@ -8,14 +8,13 @@ import {List, ListItemButton, ListItemIcon, ListItemText} from "@mui/material"
import {SimplePageBuilder} from "~/components" import {SimplePageBuilder} from "~/components"
import {useNavigateToNext, useUser} from "~/hooks" import {useNavigateToNext, useUser} from "~/hooks"
import ServerStatus from "~/route-widgets/AdminRoute/ServerStatus"
export default function AdminRoute(): ReactElement { export default function AdminRoute(): ReactElement {
const {t} = useTranslation() const {t} = useTranslation()
const navigateToNext = useNavigateToNext() const navigateToNext = useNavigateToNext()
const user = useUser() const user = useUser()
console.log(user)
useLayoutEffect(() => { useLayoutEffect(() => {
if (!user.isAdmin) { if (!user.isAdmin) {
navigateToNext() navigateToNext()
@ -24,6 +23,7 @@ export default function AdminRoute(): ReactElement {
return ( return (
<SimplePageBuilder.Page title={t("routes.AdminRoute.title")}> <SimplePageBuilder.Page title={t("routes.AdminRoute.title")}>
<ServerStatus />
<List> <List>
<ListItemButton component={Link} to="/admin/reserved-aliases"> <ListItemButton component={Link} to="/admin/reserved-aliases">
<ListItemIcon> <ListItemIcon>

View File

@ -214,10 +214,26 @@ export interface AdminSettings {
allowAliasDeletion: boolean | null allowAliasDeletion: boolean | null
} }
export interface CronReport { export interface ServerCronReport {
id: string id: string
createdAt: Date createdAt: Date
reportData: { reportData: {
encryptedReport: string 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
}
}
}