feat(frontend): sprint 2 — simulations UI + MITRE picker

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Knacky
2026-05-26 11:13:14 +02:00
parent 006c4c2c5f
commit 765bb5a1a4
16 changed files with 1778 additions and 7 deletions

View File

@@ -0,0 +1,9 @@
import { apiClient } from './client';
import type { MitreTechnique } from './types';
export async function searchMitreTechniques(query: string): Promise<MitreTechnique[]> {
const { data } = await apiClient.get<MitreTechnique[]>('/mitre/techniques', {
params: { q: query },
});
return data;
}

View File

@@ -0,0 +1,37 @@
import { apiClient } from './client';
import type { Simulation, SimulationCreateInput, SimulationPatchInput, SimulationStatus } from './types';
export async function listSimulations(engagementId: number): Promise<Simulation[]> {
const { data } = await apiClient.get<Simulation[]>(`/engagements/${engagementId}/simulations`);
return data;
}
export async function createSimulation(
engagementId: number,
input: SimulationCreateInput,
): Promise<Simulation> {
const { data } = await apiClient.post<Simulation>(`/engagements/${engagementId}/simulations`, input);
return data;
}
export async function getSimulation(id: number): Promise<Simulation> {
const { data } = await apiClient.get<Simulation>(`/simulations/${id}`);
return data;
}
export async function updateSimulation(id: number, patch: SimulationPatchInput): Promise<Simulation> {
const { data } = await apiClient.patch<Simulation>(`/simulations/${id}`, patch);
return data;
}
export async function deleteSimulation(id: number): Promise<void> {
await apiClient.delete(`/simulations/${id}`);
}
export async function transitionSimulation(
id: number,
to: Extract<SimulationStatus, 'review_required' | 'done'>,
): Promise<Simulation> {
const { data } = await apiClient.post<Simulation>(`/simulations/${id}/transition`, { to });
return data;
}

View File

@@ -52,3 +52,51 @@ export interface UserPatchInput {
export interface ApiError {
error: string;
}
export type SimulationStatus = 'pending' | 'in_progress' | 'review_required' | 'done';
export interface MitreTechnique {
id: string;
name: string;
tactics: string[];
}
export interface Simulation {
id: number;
engagement_id: number;
name: string;
mitre_technique_id: string | null;
mitre_technique_name: string | null;
description: string | null;
commands: string | null;
prerequisites: string | null;
executed_at: string | null;
execution_result: string | null;
log_source: string | null;
logs: string | null;
soc_comment: string | null;
incident_number: string | null;
status: SimulationStatus;
created_at: string;
updated_at: string | null;
created_by: { id: number; username: string };
}
export interface SimulationCreateInput {
name: string;
}
export interface SimulationPatchInput {
name?: string;
mitre_technique_id?: string | null;
mitre_technique_name?: string | null;
description?: string | null;
commands?: string | null;
prerequisites?: string | null;
executed_at?: string | null;
execution_result?: string | null;
log_source?: string | null;
logs?: string | null;
soc_comment?: string | null;
incident_number?: string | null;
}