feat(backup): UI for backup-integrity verifier — tab + post-restore CTA
Frontend half of the diagnostic shipped in 4812fcd. Adds:
- BackupIntegrityCard component — runs the check on demand, surfaces
the five summary counters (total / verifiedOk / existsButNoHash /
missing / hashMismatches), and expands collapsible result tables
for missing files + hash mismatches. existsButNoHash is exposed as
a separate amber-toned bucket so admins can distinguish hash-
verified evidence from existence-only at a glance — the latter is
explicitly weaker in a legal dispute and the UI says so.
- "Integrity" tab on BackupManagement, alongside the existing
Dashboard / Configuration / History / Restore tabs. Card is
portable — when the System Health page (backlog item) lands it
can lift the component without changes.
- Post-restore CTA on the RestoreWizard success card (D2 follow-
through): "Verify document integrity now" button that switches
the parent tab to Integrity. The audit trail captured at sign /
issue time is worth nothing if the documents it refers to are
missing from the restored copy — verifier surfaces that drift
in one click before the admin trusts the restored state.
i18n strings added in EN + DE (per user_languages — only those two
are native; other locales fall back to the English defaults and
should be flagged for native-speaker review per
feedback_translation_flagging if anyone picks them up).
This commit is contained in:
@@ -201,6 +201,50 @@ export interface Activity {
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
// Backup-integrity verifier — diagnostic endpoint that walks every
|
||||
// CRM document-artefact path column and confirms files exist on disk
|
||||
// (plus SHA-256 match where the schema stores one). Mirrors the
|
||||
// shape returned by backupIntegrityService.verifyDocumentArtefacts.
|
||||
export type BackupIntegrityScope =
|
||||
| 'quote'
|
||||
| 'contract'
|
||||
| 'contract-signature'
|
||||
| 'invoice';
|
||||
|
||||
export interface BackupIntegrityMissingRow {
|
||||
table: string;
|
||||
rowId: number;
|
||||
column: string;
|
||||
expectedPath: string;
|
||||
}
|
||||
|
||||
export interface BackupIntegrityHashMismatchRow extends BackupIntegrityMissingRow {
|
||||
expectedSha: string;
|
||||
actualSha: string;
|
||||
}
|
||||
|
||||
export interface BackupIntegrityExistsButNoHashRow {
|
||||
table: string;
|
||||
rowId: number;
|
||||
column: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface BackupIntegrityReport {
|
||||
scannedAt: string;
|
||||
scopes: BackupIntegrityScope[];
|
||||
summary: {
|
||||
totalRows: number;
|
||||
verifiedOk: number;
|
||||
missingFiles: number;
|
||||
hashMismatches: number;
|
||||
existsButNoHash: number;
|
||||
};
|
||||
missing: BackupIntegrityMissingRow[];
|
||||
hashMismatches: BackupIntegrityHashMismatchRow[];
|
||||
existsButNoHash: BackupIntegrityExistsButNoHashRow[];
|
||||
}
|
||||
|
||||
export interface AdminProfile {
|
||||
id: number;
|
||||
username: string;
|
||||
@@ -262,6 +306,19 @@ export const adminService = {
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Backup-integrity verifier (read-only diagnostic). `scope` filters
|
||||
// which document classes to walk; omit for a full scan.
|
||||
async getBackupIntegrity(
|
||||
scope?: BackupIntegrityScope[],
|
||||
): Promise<BackupIntegrityReport> {
|
||||
const params = scope && scope.length > 0 ? { scope: scope.join(',') } : undefined;
|
||||
const response = await api.get<{ report: BackupIntegrityReport }>(
|
||||
'/admin/system-health/backup-integrity',
|
||||
{ params },
|
||||
);
|
||||
return response.data.report;
|
||||
},
|
||||
|
||||
// Format activity message
|
||||
formatActivityMessage(activity: Activity): string {
|
||||
// Feature-flag toggles carry a `changed` diff in metadata. Render
|
||||
|
||||
Reference in New Issue
Block a user