0002601b50
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
Test Catalog's pagination showed only 'Page N' with no way to know how many pages existed, and the campaign 'Add Test' modal's template picker had no pagination at all — with 2800+ templates in the catalog, its flat 100-row fetch made most templates unreachable unless a search happened to match. Adds GET /test-templates/count (open to any authenticated user, mirrors the list endpoint's filters) and wires real 'Page N of M' + total-available counts into both.
207 lines
7.9 KiB
TypeScript
207 lines
7.9 KiB
TypeScript
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<TestTemplateSummary[]> {
|
|
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<TestTemplateSummary[]>(
|
|
`/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<TemplateFilters, "offset" | "limit">,
|
|
): Promise<number> {
|
|
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<TestTemplate> {
|
|
const { data } = await client.get<TestTemplate>(`/test-templates/${id}`);
|
|
return data;
|
|
}
|
|
|
|
// ── By technique ───────────────────────────────────────────────────
|
|
|
|
/** Fetch all templates mapped to a specific MITRE technique. */
|
|
export async function getTemplatesByTechnique(
|
|
mitreId: string,
|
|
): Promise<TestTemplateSummary[]> {
|
|
const { data } = await client.get<TestTemplateSummary[]>(
|
|
`/test-templates/by-technique/${mitreId}`,
|
|
);
|
|
return data;
|
|
}
|
|
|
|
// ── Create (admin) ─────────────────────────────────────────────────
|
|
|
|
/** Create a custom test template. Admin only. */
|
|
export async function createTemplate(
|
|
payload: CreateTemplatePayload,
|
|
): Promise<TestTemplate> {
|
|
const { data } = await client.post<TestTemplate>(
|
|
"/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<CreateTemplatePayload>,
|
|
): Promise<TestTemplate> {
|
|
const { data } = await client.patch<TestTemplate>(`/test-templates/${id}`, payload);
|
|
return data;
|
|
}
|
|
|
|
// ── Stats (admin) ──────────────────────────────────────────────────
|
|
|
|
export interface TemplateStats {
|
|
total: number;
|
|
active: number;
|
|
inactive: number;
|
|
by_source: Record<string, number>;
|
|
by_platform: Record<string, number>;
|
|
}
|
|
|
|
/** Fetch template catalog statistics. Admin only. */
|
|
export async function getTemplateStats(): Promise<TemplateStats> {
|
|
const { data } = await client.get<TemplateStats>("/test-templates/stats");
|
|
return data;
|
|
}
|
|
|
|
// ── Toggle active (admin) ──────────────────────────────────────────
|
|
|
|
/** Toggle a template between active/inactive. Admin only. */
|
|
export async function toggleTemplateActive(
|
|
id: string,
|
|
): Promise<TestTemplate> {
|
|
const { data } = await client.patch<TestTemplate>(
|
|
`/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<TestTemplate[]> {
|
|
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<TestTemplate[]>(
|
|
`/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<BulkActivateResponse> {
|
|
const { data } = await client.patch<BulkActivateResponse>(
|
|
`/test-templates/bulk-activate?activate=${activate}`,
|
|
);
|
|
return data;
|
|
}
|
|
|
|
// ── Import Atomic Red Team ─────────────────────────────────────────
|
|
|
|
/** Trigger Atomic Red Team import. Admin only. */
|
|
export async function importAtomicTests(): Promise<ImportAtomicResponse> {
|
|
const { data } = await client.post<ImportAtomicResponse>(
|
|
"/system/import-atomic-tests",
|
|
);
|
|
return data;
|
|
}
|