import client from "./client"; import type { TestTemplate, TestTemplateSummary } from "../types/models"; // ── Filters ──────────────────────────────────────────────────────── export interface TemplateFilters { source?: string; platform?: string; severity?: string; mitre_technique_id?: string; search?: string; is_active?: boolean; offset?: number; limit?: number; } // ── Create payload ───────────────────────────────────────────────── export interface CreateTemplatePayload { mitre_technique_id: string; name: string; description?: string; source?: string; source_url?: string; attack_procedure?: string; expected_detection?: string; platform?: string; tool_suggested?: string; severity?: string; atomic_test_id?: string; } // ── Import response ──────────────────────────────────────────────── export interface ImportAtomicResponse { message: string; imported: number; skipped: number; total_parsed: number; } // ── List (paginated, filtered) ───────────────────────────────────── /** Fetch a paginated, filtered list of test templates. */ export async function getTemplates( filters?: TemplateFilters, ): Promise { const params = new URLSearchParams(); if (filters?.source) params.append("source", filters.source); if (filters?.platform) params.append("platform", filters.platform); if (filters?.severity) params.append("severity", filters.severity); if (filters?.mitre_technique_id) params.append("mitre_technique_id", filters.mitre_technique_id); if (filters?.search) params.append("search", filters.search); // Default to active-only for catalog; admin uses getAllTemplates without this filter params.append("is_active", filters?.is_active !== undefined ? String(filters.is_active) : "true"); if (filters?.offset !== undefined) params.append("offset", String(filters.offset)); if (filters?.limit !== undefined) params.append("limit", String(filters.limit)); const { data } = await client.get( `/test-templates${params.toString() ? `?${params}` : ""}`, ); return data; } /** Total count of templates matching the same filters as getTemplates() — for pagination. */ export async function getTemplateCount( filters?: Omit, ): Promise { const params = new URLSearchParams(); if (filters?.source) params.append("source", filters.source); if (filters?.platform) params.append("platform", filters.platform); if (filters?.severity) params.append("severity", filters.severity); if (filters?.mitre_technique_id) params.append("mitre_technique_id", filters.mitre_technique_id); if (filters?.search) params.append("search", filters.search); params.append("is_active", filters?.is_active !== undefined ? String(filters.is_active) : "true"); const { data } = await client.get<{ total: number }>( `/test-templates/count${params.toString() ? `?${params}` : ""}`, ); return data.total; } // ── Detail ───────────────────────────────────────────────────────── /** Fetch a single test template by ID. */ export async function getTemplateById(id: string): Promise { const { data } = await client.get(`/test-templates/${id}`); return data; } // ── By technique ─────────────────────────────────────────────────── /** Fetch all templates mapped to a specific MITRE technique. */ export async function getTemplatesByTechnique( mitreId: string, ): Promise { const { data } = await client.get( `/test-templates/by-technique/${mitreId}`, ); return data; } // ── Create (admin) ───────────────────────────────────────────────── /** Create a custom test template. Admin only. */ export async function createTemplate( payload: CreateTemplatePayload, ): Promise { const { data } = await client.post( "/test-templates", payload, ); return data; } // ── Update (admin) ──────────────────────────────────────────────── /** Update fields of an existing test template. Admin/lead only. */ export async function updateTemplate( id: string, payload: Partial, ): Promise { const { data } = await client.patch(`/test-templates/${id}`, payload); return data; } // ── Stats (admin) ────────────────────────────────────────────────── export interface TemplateStats { total: number; active: number; inactive: number; by_source: Record; by_platform: Record; } /** Fetch template catalog statistics. Admin only. */ export async function getTemplateStats(): Promise { const { data } = await client.get("/test-templates/stats"); return data; } // ── Toggle active (admin) ────────────────────────────────────────── /** Toggle a template between active/inactive. Admin only. */ export async function toggleTemplateActive( id: string, ): Promise { const { data } = await client.patch( `/test-templates/${id}/toggle-active`, ); return data; } // ── All templates (include inactive, for admin) ──────────────────── /** Fetch all templates including inactive ones (for admin management). * Does NOT filter by is_active so the backend returns all templates. */ export async function getAllTemplates( filters?: TemplateFilters, ): Promise { const params = new URLSearchParams(); if (filters?.source) params.append("source", filters.source); if (filters?.platform) params.append("platform", filters.platform); if (filters?.search) params.append("search", filters.search); if (filters?.offset !== undefined) params.append("offset", String(filters.offset)); if (filters?.limit !== undefined) params.append("limit", String(filters.limit)); // Explicitly don't pass is_active so backend returns ALL templates const { data } = await client.get( `/test-templates${params.toString() ? `?${params}` : ""}`, ); return data; } // ── Bulk activate/deactivate (admin) ────────────────────────────── export interface BulkActivateResponse { detail: string; affected: number; is_active: boolean; } /** Activate or deactivate all templates. Admin only. */ export async function bulkActivateTemplates( activate: boolean, ): Promise { const { data } = await client.patch( `/test-templates/bulk-activate?activate=${activate}`, ); return data; } // ── Import Atomic Red Team ───────────────────────────────────────── /** Trigger Atomic Red Team import. Admin only. */ export async function importAtomicTests(): Promise { const { data } = await client.post( "/system/import-atomic-tests", ); return data; }