1a974265de
Adds a View button (eye icon) on each evidence card for previewable file types. Opens a full-screen modal: - Images (png/jpg/gif/webp/svg/…): rendered directly via <img> tag - JSON: fetched authenticated, pretty-printed in green mono - Text/log/md/csv/xml/yaml/…: fetched authenticated, shown in <pre> Non-previewable files only show the Download button as before. Modal closes on Escape or backdrop click.
92 lines
3.2 KiB
TypeScript
92 lines
3.2 KiB
TypeScript
import client from "./client";
|
|
import type { Evidence, TeamSide } from "../types/models";
|
|
|
|
// ── Response type (with download URL) ──────────────────────────────
|
|
|
|
export interface EvidenceOut extends Evidence {
|
|
download_url: string;
|
|
}
|
|
|
|
// ── Upload ─────────────────────────────────────────────────────────
|
|
|
|
/** Upload an evidence file for the given test.
|
|
*
|
|
* The ``team`` field is sent as form data alongside the file so the
|
|
* backend can enforce Red/Blue access control.
|
|
*/
|
|
export async function uploadEvidence(
|
|
testId: string,
|
|
file: File,
|
|
team: TeamSide,
|
|
notes?: string,
|
|
): Promise<EvidenceOut> {
|
|
const formData = new FormData();
|
|
formData.append("file", file);
|
|
formData.append("team", team);
|
|
if (notes) {
|
|
formData.append("notes", notes);
|
|
}
|
|
|
|
const { data } = await client.post<EvidenceOut>(
|
|
`/tests/${testId}/evidence`,
|
|
formData,
|
|
{
|
|
headers: {
|
|
"Content-Type": "multipart/form-data",
|
|
},
|
|
},
|
|
);
|
|
return data;
|
|
}
|
|
|
|
// ── List ───────────────────────────────────────────────────────────
|
|
|
|
/** List evidences for a test, optionally filtered by team. */
|
|
export async function getTestEvidences(
|
|
testId: string,
|
|
team?: TeamSide,
|
|
): Promise<EvidenceOut[]> {
|
|
const params = new URLSearchParams();
|
|
if (team) params.append("team", team);
|
|
|
|
const { data } = await client.get<EvidenceOut[]>(
|
|
`/tests/${testId}/evidence${params.toString() ? `?${params}` : ""}`,
|
|
);
|
|
return data;
|
|
}
|
|
|
|
// ── Detail ─────────────────────────────────────────────────────────
|
|
|
|
/** Get evidence metadata with presigned download URL. */
|
|
export async function getEvidence(evidenceId: string): Promise<EvidenceOut> {
|
|
const { data } = await client.get<EvidenceOut>(`/evidence/${evidenceId}`);
|
|
return data;
|
|
}
|
|
|
|
// ── Content fetch (for inline preview) ────────────────────────────
|
|
|
|
/** Fetch raw file content for inline preview (text / JSON). */
|
|
export async function getEvidenceRawContent(
|
|
evidenceId: string,
|
|
): Promise<{ text: string; contentType: string }> {
|
|
const response = await client.get(`/evidence/${evidenceId}/file`, {
|
|
responseType: "blob",
|
|
});
|
|
const blob = response.data as Blob;
|
|
const text = await blob.text();
|
|
const contentType = (response.headers["content-type"] as string) || "";
|
|
return { text, contentType };
|
|
}
|
|
|
|
// ── Delete ─────────────────────────────────────────────────────────
|
|
|
|
/** Delete an evidence record (only in editable states). */
|
|
export async function deleteEvidence(
|
|
evidenceId: string,
|
|
): Promise<{ detail: string }> {
|
|
const { data } = await client.delete<{ detail: string }>(
|
|
`/evidence/${evidenceId}`,
|
|
);
|
|
return data;
|
|
}
|