mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-18 23:45:26 +02:00
feat: Add cron report status to AdminRoute
This commit is contained in:
parent
3b130d054f
commit
e07eb3e75d
@ -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}}."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -106,6 +106,7 @@ const router = createBrowserRouter([
|
||||
},
|
||||
{
|
||||
path: "/admin",
|
||||
loader: getServerSettings,
|
||||
element: <AdminRoute />,
|
||||
},
|
||||
{
|
||||
|
@ -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<CronReport> {
|
||||
export default async function getLatestCronReport(): Promise<ServerCronReport> {
|
||||
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,
|
||||
},
|
||||
|
33
src/apis/helpers/decrypt-cron-report-data.ts
Normal file
33
src/apis/helpers/decrypt-cron-report-data.ts
Normal 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),
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
77
src/route-widgets/AdminRoute/ServerStatus.tsx
Normal file
77
src/route-widgets/AdminRoute/ServerStatus.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -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 (
|
||||
<SimplePageBuilder.Page title={t("routes.AdminRoute.title")}>
|
||||
<ServerStatus />
|
||||
<List>
|
||||
<ListItemButton component={Link} to="/admin/reserved-aliases">
|
||||
<ListItemIcon>
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user