| 1 | // API client for the Detection Catalog — a discovery surface over CoPilot Searches. |
| 2 | // Backend routes live under the existing copilot_searches router because the |
| 3 | // catalog reads from the same in-memory rules cache; the URLs are namespaced |
| 4 | // under /catalog/* so they don't conflict with the rules-grid endpoints. |
| 5 | |
| 6 | import type { |
| 7 | CatalogComplianceFrameworksResponse, |
| 8 | CatalogComplianceResponse, |
| 9 | CatalogCoverageGapsResponse, |
| 10 | CatalogLogTestRequest, |
| 11 | CatalogLogTestResponse, |
| 12 | CatalogStatsResponse, |
| 13 | CatalogStoryDetailResponse, |
| 14 | CatalogStoryListResponse, |
| 15 | CatalogWazuhRuleDetailResponse, |
| 16 | CatalogWazuhRulesResponse |
| 17 | } from "@/types/detectionCatalog.d" |
| 18 | import type { FlaskBaseResponse } from "@/types/flask.d" |
| 19 | import { HttpClient } from "../httpClient" |
| 20 | |
| 21 | export default { |
| 22 | /** Top-level metrics for the catalog overview pane. */ |
| 23 | getStats() { |
| 24 | return HttpClient.get<FlaskBaseResponse & CatalogStatsResponse>(`/copilot_searches/catalog/stats`) |
| 25 | }, |
| 26 | |
| 27 | /** List every analytic story with per-story aggregated summary fields. */ |
| 28 | listStories() { |
| 29 | return HttpClient.get<FlaskBaseResponse & CatalogStoryListResponse>(`/copilot_searches/catalog/stories`) |
| 30 | }, |
| 31 | |
| 32 | /** |
| 33 | * Detail payload for one analytic story (description, why-it-matters narrative, |
| 34 | * detections table, data sources, references). The backend uses ``{story_name:path}`` |
| 35 | * so spaces and other characters are tolerated — we encodeURIComponent here too. |
| 36 | */ |
| 37 | getStory(storyName: string) { |
| 38 | return HttpClient.get<FlaskBaseResponse & CatalogStoryDetailResponse>( |
| 39 | `/copilot_searches/catalog/stories/${encodeURIComponent(storyName)}` |
| 40 | ) |
| 41 | }, |
| 42 | |
| 43 | /** |
| 44 | * List the full Wazuh ruleset for the Wazuh Rules tab. Returns the whole |
| 45 | * corpus (~3–5k rules) in a single shot — pagination/filtering happens |
| 46 | * client-side. When the Wazuh Manager is unreachable, ``available=false`` |
| 47 | * and ``unavailable_reason`` carries the human-readable cause. |
| 48 | * |
| 49 | * Pass ``customerCode`` to scope the firing-stats columns (Hits 30d / |
| 50 | * Hits 7d / Last fired) to a single customer's alerts. The rule list |
| 51 | * itself is unchanged — every rule is still returned — but rules without |
| 52 | * any hits for that customer get zeros. |
| 53 | */ |
| 54 | listWazuhRules(customerCode?: string) { |
| 55 | return HttpClient.get<FlaskBaseResponse & CatalogWazuhRulesResponse>(`/copilot_searches/catalog/wazuh-rules`, { |
| 56 | params: customerCode ? { customer_code: customerCode } : {} |
| 57 | }) |
| 58 | }, |
| 59 | |
| 60 | /** Full meta payload for one Wazuh rule (header, compliance, if-then details, …). */ |
| 61 | getWazuhRule(ruleId: number) { |
| 62 | return HttpClient.get<FlaskBaseResponse & CatalogWazuhRuleDetailResponse>( |
| 63 | `/copilot_searches/catalog/wazuh-rules/${ruleId}` |
| 64 | ) |
| 65 | }, |
| 66 | |
| 67 | /** |
| 68 | * MITRE techniques NOT covered by any rule across either corpus — |
| 69 | * the "where are our blind spots?" view. Sub-techniques are collapsed |
| 70 | * into their parents server-side (a hit on T1059.001 covers T1059). |
| 71 | */ |
| 72 | listCoverageGaps() { |
| 73 | return HttpClient.get<FlaskBaseResponse & CatalogCoverageGapsResponse>( |
| 74 | `/copilot_searches/catalog/coverage-gaps` |
| 75 | ) |
| 76 | }, |
| 77 | |
| 78 | /** |
| 79 | * Run a raw log line through Wazuh's logtest engine. Returns the matched |
| 80 | * rule (if any) + the full alert envelope. Stateless — no Wazuh session |
| 81 | * is created. Wrapped by the backend with mitre_matrix tactic-name |
| 82 | * enrichment so the result matches the rest of the catalog. |
| 83 | */ |
| 84 | runLogTest(payload: CatalogLogTestRequest) { |
| 85 | return HttpClient.post<FlaskBaseResponse & CatalogLogTestResponse>( |
| 86 | `/copilot_searches/catalog/wazuh-rules/test`, |
| 87 | payload |
| 88 | ) |
| 89 | }, |
| 90 | |
| 91 | /** List the compliance frameworks the Compliance tab can pivot by. */ |
| 92 | listComplianceFrameworks() { |
| 93 | return HttpClient.get<FlaskBaseResponse & CatalogComplianceFrameworksResponse>( |
| 94 | `/copilot_searches/catalog/compliance/frameworks` |
| 95 | ) |
| 96 | }, |
| 97 | |
| 98 | /** |
| 99 | * Wazuh rules grouped by control IDs for the given framework. Each group |
| 100 | * carries rule count + total firing hits — the "what coverage do we have |
| 101 | * for PCI 10.2.4?" answer in one round-trip. |
| 102 | */ |
| 103 | getCompliancePivot(framework: string) { |
| 104 | return HttpClient.get<FlaskBaseResponse & CatalogComplianceResponse>( |
| 105 | `/copilot_searches/catalog/compliance/${encodeURIComponent(framework)}` |
| 106 | ) |
| 107 | } |
| 108 | } |