import { useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { X, Loader2 } from "lucide-react"; import { createTemplate, type CreateTemplatePayload } from "../api/test-templates"; import { proposeTemplate } from "../api/template-suggestions"; import { getTechniques, type TechniqueSummary } from "../api/techniques"; import { useToast } from "./Toast"; const SEVERITY_OPTIONS = ["low", "medium", "high", "critical"]; const PLATFORM_OPTIONS = ["windows", "linux", "macos", "azure-ad", "office-365", "containers"]; /** Leads create a template directly into the live catalog; operators * (red_tech/blue_tech) propose one instead — it lands in the review * queue for a lead on their team to approve (with optional edits) or * discard, rather than going live immediately. */ export default function CreateTemplateModal({ canCreateDirectly, onClose, }: { canCreateDirectly: boolean; onClose: () => void; }) { const queryClient = useQueryClient(); const { showToast } = useToast(); const { data: techniques, isLoading: techniquesLoading } = useQuery({ queryKey: ["techniques"], queryFn: () => getTechniques(), }); const [form, setForm] = useState({ mitre_technique_id: "", name: "", description: "", source: "custom", attack_procedure: "", expected_detection: "", platform: "", severity: "", }); const set = (field: keyof CreateTemplatePayload, value: string) => setForm((f) => ({ ...f, [field]: value })); const directMutation = useMutation({ mutationFn: () => createTemplate(form), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["test-templates"] }); queryClient.invalidateQueries({ queryKey: ["test-templates-count"] }); showToast("success", "Template created and added to the catalog"); onClose(); }, onError: () => showToast("error", "Failed to create template"), }); const proposeMutation = useMutation({ mutationFn: () => proposeTemplate(form), onSuccess: () => { showToast("success", "Template proposed — a lead will review it before it's added to the catalog"); onClose(); }, onError: () => showToast("error", "Failed to propose template"), }); const isPending = directMutation.isPending || proposeMutation.isPending; const canSubmit = form.mitre_technique_id.trim() !== "" && form.name.trim() !== ""; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!canSubmit) return; if (canCreateDirectly) { directMutation.mutate(); } else { proposeMutation.mutate(); } }; return (

{canCreateDirectly ? "Create Test Template" : "Propose Test Template"}

{!canCreateDirectly && (

This will be submitted for review — a lead on your team must approve it before it appears in the catalog.

)}
set("name", e.target.value)} className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-200 focus:border-cyan-500 focus:outline-none" />