support snapshots
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import type {
|
||||
AdminDiagnosticsDto,
|
||||
BackupGuidanceDto,
|
||||
AdminPermissionOptionDto,
|
||||
AdminRoleDto,
|
||||
AdminRoleInput,
|
||||
SupportSnapshotDto,
|
||||
AdminUserDto,
|
||||
AdminUserInput,
|
||||
ApiResponse,
|
||||
@@ -136,6 +138,12 @@ export const api = {
|
||||
getAdminDiagnostics(token: string) {
|
||||
return request<AdminDiagnosticsDto>("/api/v1/admin/diagnostics", undefined, token);
|
||||
},
|
||||
getBackupGuidance(token: string) {
|
||||
return request<BackupGuidanceDto>("/api/v1/admin/backup-guidance", undefined, token);
|
||||
},
|
||||
getSupportSnapshot(token: string) {
|
||||
return request<SupportSnapshotDto>("/api/v1/admin/support-snapshot", undefined, token);
|
||||
},
|
||||
getAdminPermissions(token: string) {
|
||||
return request<AdminPermissionOptionDto[]>("/api/v1/admin/permissions", undefined, token);
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AdminDiagnosticsDto } from "@mrp/shared";
|
||||
import type { AdminDiagnosticsDto, BackupGuidanceDto } from "@mrp/shared";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
@@ -20,6 +20,7 @@ function parseMetadata(metadataJson: string) {
|
||||
export function AdminDiagnosticsPage() {
|
||||
const { token } = useAuth();
|
||||
const [diagnostics, setDiagnostics] = useState<AdminDiagnosticsDto | null>(null);
|
||||
const [backupGuidance, setBackupGuidance] = useState<BackupGuidanceDto | null>(null);
|
||||
const [status, setStatus] = useState("Loading diagnostics...");
|
||||
|
||||
useEffect(() => {
|
||||
@@ -29,13 +30,13 @@ export function AdminDiagnosticsPage() {
|
||||
|
||||
let active = true;
|
||||
|
||||
api
|
||||
.getAdminDiagnostics(token)
|
||||
.then((nextDiagnostics) => {
|
||||
Promise.all([api.getAdminDiagnostics(token), api.getBackupGuidance(token)])
|
||||
.then(([nextDiagnostics, nextBackupGuidance]) => {
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
setDiagnostics(nextDiagnostics);
|
||||
setBackupGuidance(nextBackupGuidance);
|
||||
setStatus("Diagnostics loaded.");
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
@@ -50,10 +51,26 @@ export function AdminDiagnosticsPage() {
|
||||
};
|
||||
}, [token]);
|
||||
|
||||
if (!diagnostics) {
|
||||
if (!diagnostics || !backupGuidance) {
|
||||
return <div className="rounded-[28px] border border-line/70 bg-surface/90 p-4 text-sm text-muted shadow-panel">{status}</div>;
|
||||
}
|
||||
|
||||
async function handleExportSupportSnapshot() {
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
const snapshot = await api.getSupportSnapshot(token);
|
||||
const blob = new Blob([JSON.stringify(snapshot, null, 2)], { type: "application/json" });
|
||||
const objectUrl = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = objectUrl;
|
||||
link.download = `mrp-codex-support-snapshot-${new Date().toISOString().replace(/[:.]/g, "-")}.json`;
|
||||
link.click();
|
||||
window.setTimeout(() => window.URL.revokeObjectURL(objectUrl), 60_000);
|
||||
setStatus("Support snapshot exported.");
|
||||
}
|
||||
|
||||
const summaryCards = [
|
||||
["Server time", formatDateTime(diagnostics.serverTime)],
|
||||
["Node runtime", diagnostics.nodeVersion],
|
||||
@@ -97,6 +114,13 @@ export function AdminDiagnosticsPage() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleExportSupportSnapshot}
|
||||
className="rounded-2xl border border-line/70 px-3 py-2 text-sm font-semibold text-text"
|
||||
>
|
||||
Export support snapshot
|
||||
</button>
|
||||
<Link to="/settings/users" className="rounded-2xl border border-line/70 px-3 py-2 text-sm font-semibold text-text">
|
||||
User management
|
||||
</Link>
|
||||
@@ -115,6 +139,47 @@ export function AdminDiagnosticsPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel backdrop-blur 2xl:p-5">
|
||||
<div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-muted">Backup And Restore</p>
|
||||
<h3 className="mt-2 text-lg font-bold text-text">Operational backup workflow</h3>
|
||||
<p className="mt-2 max-w-3xl text-sm text-muted">
|
||||
Use these paths and steps as the support baseline for manual backup and restore procedures.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-2xl border border-line/70 bg-page/70 px-3 py-3 text-sm text-muted">
|
||||
<div>Data: {backupGuidance.dataPath}</div>
|
||||
<div>DB: {backupGuidance.databasePath}</div>
|
||||
<div>Uploads: {backupGuidance.uploadsPath}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 grid gap-4 xl:grid-cols-2">
|
||||
<div className="rounded-2xl border border-line/70 bg-page/70 p-4">
|
||||
<p className="text-sm font-semibold text-text">Backup checklist</p>
|
||||
<div className="mt-3 space-y-3">
|
||||
{backupGuidance.backupSteps.map((step) => (
|
||||
<div key={step.id}>
|
||||
<p className="text-sm font-semibold text-text">{step.label}</p>
|
||||
<p className="mt-1 text-sm text-muted">{step.detail}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-2xl border border-line/70 bg-page/70 p-4">
|
||||
<p className="text-sm font-semibold text-text">Restore checklist</p>
|
||||
<div className="mt-3 space-y-3">
|
||||
{backupGuidance.restoreSteps.map((step) => (
|
||||
<div key={step.id}>
|
||||
<p className="text-sm font-semibold text-text">{step.label}</p>
|
||||
<p className="mt-1 text-sm text-muted">{step.detail}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel backdrop-blur 2xl:p-5">
|
||||
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user