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:
Luca
2026-05-29 13:11:43 +02:00
parent 4812fcdec3
commit 7e2feca12f
6 changed files with 445 additions and 9 deletions
+57
View File
@@ -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