feat(templates,campaigns): validate MITRE technique IDs, surface template suggestions first, manager can re-approve rejected campaigns
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
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
- TestTemplate/TemplateSuggestion creation and updates now reject any mitre_technique_id that doesn't match a real, already-synced MITRE ATT&CK technique. Frontend swaps the free-text ID input for a technique picker. - Test Catalog now shows pending template suggestions before the catalog grid, with full field detail (platform, severity, tool, atomic ID, source URL, remediation) instead of just name/procedure. - A manager can edit and directly re-approve a campaign they previously rejected, instead of needing the original lead to resubmit it.
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
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"];
|
||||
@@ -22,6 +23,11 @@ export default function CreateTemplateModal({
|
||||
const queryClient = useQueryClient();
|
||||
const { showToast } = useToast();
|
||||
|
||||
const { data: techniques, isLoading: techniquesLoading } = useQuery({
|
||||
queryKey: ["techniques"],
|
||||
queryFn: () => getTechniques(),
|
||||
});
|
||||
|
||||
const [form, setForm] = useState<CreateTemplatePayload>({
|
||||
mitre_technique_id: "",
|
||||
name: "",
|
||||
@@ -91,15 +97,22 @@ export default function CreateTemplateModal({
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-xs font-medium text-gray-500">
|
||||
MITRE Technique ID <span className="text-red-400">*</span>
|
||||
MITRE Technique <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
<select
|
||||
required
|
||||
disabled={techniquesLoading}
|
||||
value={form.mitre_technique_id}
|
||||
onChange={(e) => set("mitre_technique_id", e.target.value)}
|
||||
placeholder="T1059.001"
|
||||
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"
|
||||
/>
|
||||
>
|
||||
<option value="">Select a technique</option>
|
||||
{techniques?.map((tech: TechniqueSummary) => (
|
||||
<option key={tech.id} value={tech.mitre_id}>
|
||||
{tech.mitre_id} - {tech.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-xs font-medium text-gray-500">
|
||||
|
||||
Reference in New Issue
Block a user