feat(web): mitre_url deeplinks + lazy groups subpanel in TTPInspector

Every technique_id in TechniqueBar and TTPInspector now links to its
canonical attack.mitre.org page. The inspector drawer gains a GROUPS
subpanel that lazy-fetches the new /ttp/techniques/{id}/groups endpoint
and renders each MITRE-tracked intrusion-set with deeplink and aliases.

Centralizes TTP row interfaces into src/types/ttp.ts and API wrappers
into src/utils/ttpApi.ts to give the new GroupRef type a clean home and
avoid a third inline fetch declaration.
This commit is contained in:
2026-05-09 06:57:10 -04:00
parent 1d3086a5c7
commit c4d6eb5bb3
5 changed files with 240 additions and 54 deletions

View File

@@ -0,0 +1,26 @@
import api from './api';
import type { GroupRef, TechniqueRow, TTPTagDetailRow } from '../types/ttp';
export type TTPScope = 'identity' | 'attacker' | 'session';
export async function fetchTechniques(scope: TTPScope, uuid: string): Promise<TechniqueRow[]> {
const res = await api.get(`/ttp/by-${scope}/${uuid}`);
return Array.isArray(res.data) ? res.data : [];
}
export async function fetchTagsForTechnique(
scope: TTPScope,
uuid: string,
techniqueId: string,
subTechniqueId?: string | null,
): Promise<TTPTagDetailRow[]> {
const params: Record<string, string> = {};
if (subTechniqueId) params.sub_technique_id = subTechniqueId;
const res = await api.get(`/ttp/tags/by-${scope}/${uuid}/${techniqueId}`, { params });
return Array.isArray(res.data) ? res.data : [];
}
export async function fetchGroupsForTechnique(techniqueId: string): Promise<GroupRef[]> {
const res = await api.get(`/ttp/techniques/${techniqueId}/groups`);
return Array.isArray(res.data) ? res.data : [];
}