mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-19 15:55: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"
|
"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",
|
path: "/admin",
|
||||||
|
loader: getServerSettings,
|
||||||
element: <AdminRoute />,
|
element: <AdminRoute />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
|
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 {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>
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user