Add governed Files integrity operations UI

This commit is contained in:
2026-08-04 01:04:39 +02:00
parent 04882f1628
commit 7e6be4b017
15 changed files with 841 additions and 16 deletions
+136
View File
@@ -207,6 +207,57 @@ export type FileConnectorCredentialUpdatePayload = Partial<Omit<FileConnectorCre
clear_token?: boolean;
};
export type FileIntegrityScan = {
id: string;
tenant_id: string;
storage_backend: string;
storage_prefix: string;
status: string;
revision: number;
phase: string;
verify_checksums: boolean;
batch_size: number;
scanned_blob_count: number;
verified_blob_count: number;
quarantined_blob_count: number;
scanned_object_count: number;
orphan_object_count: number;
created_by_user_id?: string | null;
started_at?: string | null;
completed_at?: string | null;
last_error?: string | null;
created_at: string;
updated_at: string;
};
export type FileIntegrityFinding = {
id: string;
scan_id: string;
tenant_id: string;
kind: string;
state: string;
revision: number;
blob_id?: string | null;
storage_key: string;
expected_size_bytes?: number | null;
observed_size_bytes?: number | null;
expected_checksum_sha256?: string | null;
observed_checksum_sha256?: string | null;
resolved_at?: string | null;
resolved_by_user_id?: string | null;
created_at: string;
updated_at: string;
};
export type FileIntegrityActionResult = {
action: string;
changed: boolean;
dry_run: boolean;
finding: FileIntegrityFinding;
inspection_kind?: string | null;
inspection_valid?: boolean | null;
};
export type FileConnectorBrowseItem = {
kind: "library" | "folder" | "file";
name: string;
@@ -1039,6 +1090,91 @@ payload: {
return apiFetch<TransferResponse>(settings, "/api/v1/files/transfer", { method: "POST", body: JSON.stringify(payload) });
}
export async function listFileIntegrityScans(
settings: ApiSettings,
limit = 100
): Promise<FileIntegrityScan[]> {
const response = await apiFetch<{ scans: FileIntegrityScan[] }>(
settings,
`/api/v1/files/integrity/scans?limit=${Math.max(1, Math.min(limit, 200))}`
);
return response.scans;
}
export function createFileIntegrityScan(
settings: ApiSettings,
payload: { verify_checksums: boolean; batch_size: number }
): Promise<FileIntegrityScan> {
return apiFetch<FileIntegrityScan>(settings, "/api/v1/files/integrity/scans", {
method: "POST",
body: JSON.stringify(payload)
});
}
export function runFileIntegrityScanBatch(
settings: ApiSettings,
scan: Pick<FileIntegrityScan, "id" | "revision">
): Promise<FileIntegrityScan> {
return apiFetch<FileIntegrityScan>(
settings,
`/api/v1/files/integrity/scans/${encodeURIComponent(scan.id)}/run`,
{
method: "POST",
body: JSON.stringify({ expected_revision: scan.revision })
}
);
}
export async function listFileIntegrityFindings(
settings: ApiSettings,
scanId: string,
state?: string
): Promise<FileIntegrityFinding[]> {
const search = new URLSearchParams({ limit: "1000" });
if (state) search.set("state", state);
const response = await apiFetch<{ findings: FileIntegrityFinding[] }>(
settings,
`/api/v1/files/integrity/scans/${encodeURIComponent(scanId)}/findings?${search}`
);
return response.findings;
}
export function recheckFileIntegrityFinding(
settings: ApiSettings,
finding: Pick<FileIntegrityFinding, "id" | "revision">,
dryRun = false
): Promise<FileIntegrityActionResult> {
return apiFetch<FileIntegrityActionResult>(
settings,
`/api/v1/files/integrity/findings/${encodeURIComponent(finding.id)}/recheck`,
{
method: "POST",
body: JSON.stringify({
dry_run: dryRun,
expected_revision: finding.revision
})
}
);
}
export function cleanupFileIntegrityFinding(
settings: ApiSettings,
finding: Pick<FileIntegrityFinding, "id" | "revision">,
dryRun: boolean
): Promise<FileIntegrityActionResult> {
return apiFetch<FileIntegrityActionResult>(
settings,
`/api/v1/files/integrity/findings/${encodeURIComponent(finding.id)}/cleanup`,
{
method: "POST",
body: JSON.stringify({
dry_run: dryRun,
expected_revision: finding.revision
})
}
);
}
export async function downloadFile(settings: ApiSettings, file: ManagedFile): Promise<void> {
const response = await fetch(apiUrl(settings, `/api/v1/files/${file.id}/download`), { headers: authHeaders(settings), credentials: "include" });
if (!response.ok) throw new Error(`${response.status} ${response.statusText}: ${await response.text()}`);