/** * Mission types + query-key factory. * * A mission is a *snapshot* of one or more scenario templates: the backend * copies template fields into mission_* tables at creation time, and template * edits after that point do not propagate. Types here mirror the server-side * dataclasses in `app/services/missions.py`. */ export type MissionStatus = 'draft' | 'in_progress' | 'completed' | 'archived'; export type MissionRoleHint = 'red' | 'blue'; export type MissionTestState = | 'pending' | 'executed' | 'reviewed_by_blue' | 'skipped' | 'blocked'; export type MissionVisibilityMode = 'whitebox' | 'titles_only' | 'executed_only'; export type MissionMitreKind = 'tactic' | 'technique' | 'subtechnique'; export type MissionOpsecLevel = 'low' | 'medium' | 'high'; export interface MissionMember { user_id: string; user_email: string; user_display_name: string | null; role_hint: MissionRoleHint; } export interface MissionMitreTag { kind: MissionMitreKind; external_id: string; name: string; url: string | null; } export interface MissionTest { id: string; position: number; snapshot_name: string; snapshot_description: string | null; snapshot_objective: string | null; snapshot_procedure_md: string | null; snapshot_prerequisites_md: string | null; snapshot_expected_red_md: string | null; snapshot_expected_blue_md: string | null; snapshot_opsec_level: MissionOpsecLevel; snapshot_tags: string[]; snapshot_expected_iocs: string[]; state: MissionTestState; executed_at: string | null; executed_at_overridden: boolean; mitre_tags: MissionMitreTag[]; source_test_template_id: string | null; } export interface MissionScenario { id: string; position: number; snapshot_name: string; snapshot_description: string | null; tests: MissionTest[]; source_scenario_template_id: string | null; } export interface MissionListItem { id: string; name: string; client_target: string | null; date_start: string | null; date_end: string | null; status: MissionStatus; description_md: string | null; visibility_mode: MissionVisibilityMode; scenarios_count: number; tests_count: number; members_count: number; deleted_at: string | null; created_at: string; updated_at: string; } export interface Mission extends MissionListItem { scenarios: MissionScenario[]; members: MissionMember[]; } export interface MissionListResponse { items: MissionListItem[]; total: number; limit: number; offset: number; } export interface MissionFilters { q?: string; status?: MissionStatus | ''; client?: string; } export interface MemberPayload { user_id: string; role_hint: MissionRoleHint; } export interface CreateMissionPayload { name: string; client_target?: string | null; date_start?: string | null; date_end?: string | null; description_md?: string | null; scenario_template_ids?: string[]; members?: MemberPayload[]; } export interface UpdateMissionPayload { name?: string; client_target?: string | null; date_start?: string | null; date_end?: string | null; description_md?: string | null; } export interface AddScenariosPayload { scenario_template_ids: string[]; } export interface SetMembersPayload { members: MemberPayload[]; } export interface TransitionPayload { status: MissionStatus; } export const missionKeys = { list: (filters?: MissionFilters) => ['missions', 'list', filters ?? {}] as const, detail: (id: string) => ['missions', 'detail', id] as const, }; export function buildMissionQueryString(filters: MissionFilters | undefined): string { if (!filters) return ''; const params = new URLSearchParams(); if (filters.q) params.set('q', filters.q); if (filters.status) params.set('status', filters.status); if (filters.client) params.set('client', filters.client); const s = params.toString(); return s ? `?${s}` : ''; } export const MISSION_STATUS_ACCENT: Record = { draft: 'cyan', in_progress: 'orange', completed: 'green', archived: 'teal', }; export const MISSION_STATUS_LABEL: Record = { draft: 'Draft', in_progress: 'In Progress', completed: 'Completed', archived: 'Archived', };