feat(frontend): c2 config card + execute modal (sprint 8 phase 1)
- frontend/src/api/c2.ts: 6 typed API calls (getC2Config, putC2Config, deleteC2Config, testC2Config, listCallbacks, executeC2) following the frozen M1+M2 backend contracts - frontend/src/api/types.ts: C2Config, C2ConfigInput, C2TestResult, C2Callback, C2CallbacksResponse, C2Task, C2ExecuteInput, C2ExecuteResponse - frontend/src/hooks/useC2.ts: useC2Config, useUpdateC2Config, useDeleteC2Config, useTestC2Config, useC2Callbacks, useExecuteC2 - frontend/src/components/C2ConfigCard.tsx: engagement-scoped C2 config card (url + write-only token + verify-tls + save/delete/test-connection), 503 disabled state, ConfirmDialog on delete - frontend/src/components/ExecuteViaC2Modal.tsx: callback picker table (mono data cells), commands textarea pre-filled from rt.commands, Launch disabled until row selected + non-empty commands - frontend/src/pages/EngagementFormPage.tsx: embed C2ConfigCard in edit mode only, admin+redteam only (canEditEngagements gate) - frontend/src/pages/SimulationFormPage.tsx: Execute via C2 button in RT card, visible only when !isDone && canEditRT && hasC2Config; opens modal - Tests: 33 new tests across api/c2, components/C2ConfigCard, components/ExecuteViaC2Modal, EngagementFormPage, SimulationFormPage (172 total, 139 baseline + 33 new, all passing) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
60
frontend/src/api/c2.ts
Normal file
60
frontend/src/api/c2.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { apiClient } from './client';
|
||||
import type {
|
||||
C2Config,
|
||||
C2ConfigInput,
|
||||
C2TestResult,
|
||||
C2CallbacksResponse,
|
||||
C2ExecuteInput,
|
||||
C2ExecuteResponse,
|
||||
} from './types';
|
||||
|
||||
export async function getC2Config(engagementId: number): Promise<C2Config | null> {
|
||||
try {
|
||||
const { data } = await apiClient.get<C2Config>(`/engagements/${engagementId}/c2-config`);
|
||||
return data;
|
||||
} catch (err: unknown) {
|
||||
const e = err as { response?: { status?: number } };
|
||||
if (e?.response?.status === 404) return null;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function putC2Config(
|
||||
engagementId: number,
|
||||
input: C2ConfigInput,
|
||||
): Promise<C2Config> {
|
||||
const { data } = await apiClient.put<C2Config>(
|
||||
`/engagements/${engagementId}/c2-config`,
|
||||
input,
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function deleteC2Config(engagementId: number): Promise<void> {
|
||||
await apiClient.delete(`/engagements/${engagementId}/c2-config`);
|
||||
}
|
||||
|
||||
export async function testC2Config(engagementId: number): Promise<C2TestResult> {
|
||||
const { data } = await apiClient.post<C2TestResult>(
|
||||
`/engagements/${engagementId}/c2-config/test`,
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function listCallbacks(engagementId: number): Promise<C2CallbacksResponse> {
|
||||
const { data } = await apiClient.get<C2CallbacksResponse>(
|
||||
`/engagements/${engagementId}/c2/callbacks`,
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function executeC2(
|
||||
simulationId: number,
|
||||
input: C2ExecuteInput,
|
||||
): Promise<C2ExecuteResponse> {
|
||||
const { data } = await apiClient.post<C2ExecuteResponse>(
|
||||
`/simulations/${simulationId}/c2/execute`,
|
||||
input,
|
||||
);
|
||||
return data;
|
||||
}
|
||||
@@ -154,3 +154,52 @@ export interface SimulationPatchInput {
|
||||
soc_comment?: string | null;
|
||||
incident_number?: string | null;
|
||||
}
|
||||
|
||||
// C2 types
|
||||
|
||||
export interface C2Config {
|
||||
has_token: boolean;
|
||||
url: string;
|
||||
verify_tls: boolean;
|
||||
}
|
||||
|
||||
export interface C2ConfigInput {
|
||||
url: string;
|
||||
api_token?: string;
|
||||
verify_tls: boolean;
|
||||
}
|
||||
|
||||
export interface C2TestResult {
|
||||
ok: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export interface C2Callback {
|
||||
display_id: number;
|
||||
active: boolean;
|
||||
host: string;
|
||||
user: string;
|
||||
domain: string;
|
||||
last_checkin: string;
|
||||
}
|
||||
|
||||
export interface C2CallbacksResponse {
|
||||
callbacks: C2Callback[];
|
||||
}
|
||||
|
||||
export interface C2Task {
|
||||
id: number;
|
||||
mythic_task_display_id: number;
|
||||
command: string;
|
||||
status: string;
|
||||
completed: boolean;
|
||||
}
|
||||
|
||||
export interface C2ExecuteInput {
|
||||
callback_display_id: number;
|
||||
commands: string[];
|
||||
}
|
||||
|
||||
export interface C2ExecuteResponse {
|
||||
tasks: C2Task[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user