feat(manager): allow manager to create auto-approved campaigns and tests from templates
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

A manager organizes and validates work, so their own campaigns skip the
draft -> submit -> pending_approval queue and go straight to active with
the start_date they provide (they're the same role that would otherwise
approve it). Manager can also now create tests from the catalog, same as
red_lead/blue_lead.
This commit is contained in:
kitos
2026-07-16 12:57:20 +02:00
parent c753797019
commit 4809c4a662
8 changed files with 212 additions and 13 deletions
+3
View File
@@ -73,6 +73,9 @@ export interface CampaignCreatePayload {
target_platform?: string;
tags?: string[];
scheduled_at?: string;
// Only honored when the creator is a manager — auto-approves the
// campaign immediately instead of queuing it for later approval.
start_date?: string;
}
export interface AddTestPayload {
+35 -4
View File
@@ -15,6 +15,7 @@ import { listCampaigns, createCampaign, generateCampaignFromThreatActor, type Ca
import { getThreatActors, type ThreatActorSummary } from "../api/threat-actors";
import { useAuth } from "../context/AuthContext";
import MarkdownText from "../components/MarkdownText";
import { datetimeLocalToIso } from "../utils/datetime";
const statusColors: Record<string, string> = {
draft: "bg-gray-800/50 text-gray-400 border-gray-600/30",
@@ -56,9 +57,18 @@ export default function CampaignsPage() {
description: "",
type: "custom",
target_platform: "",
start_date: "",
});
const canCreate = user?.role === "admin" || user?.role === "red_lead" || user?.role === "blue_lead";
const canCreate =
user?.role === "admin" ||
user?.role === "red_lead" ||
user?.role === "blue_lead" ||
user?.role === "manager";
// A manager's campaign is auto-approved on creation instead of going
// through the draft -> submit -> pending_approval queue, so they must
// pick the start_date up front rather than via a later /approve call.
const isManagerCreate = user?.role === "manager";
const { data, isLoading, error } = useQuery({
queryKey: ["campaigns", filters],
@@ -71,11 +81,15 @@ export default function CampaignsPage() {
});
const createMutation = useMutation({
mutationFn: () => createCampaign(newCampaign),
mutationFn: () =>
createCampaign({
...newCampaign,
start_date: isManagerCreate ? datetimeLocalToIso(newCampaign.start_date) : undefined,
}),
onSuccess: (campaign) => {
queryClient.invalidateQueries({ queryKey: ["campaigns"] });
setShowCreateForm(false);
setNewCampaign({ name: "", description: "", type: "custom", target_platform: "" });
setNewCampaign({ name: "", description: "", type: "custom", target_platform: "", start_date: "" });
navigate(`/campaigns/${campaign.id}`);
},
});
@@ -227,6 +241,23 @@ export default function CampaignsPage() {
</select>
</div>
</div>
{isManagerCreate && (
<div>
<label className="mb-1.5 block text-sm font-medium text-gray-300">
Start Date <span className="text-red-400">*</span>
</label>
<input
type="datetime-local"
required
value={newCampaign.start_date}
onChange={(e) => setNewCampaign((c) => ({ ...c, start_date: 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"
/>
<p className="mt-1 text-xs text-gray-500">
This campaign will be auto-approved and go active immediately no manager review queue needed.
</p>
</div>
)}
</div>
<div className="mt-6 flex justify-end gap-3">
<button
@@ -237,7 +268,7 @@ export default function CampaignsPage() {
</button>
<button
onClick={() => createMutation.mutate()}
disabled={!newCampaign.name || createMutation.isPending}
disabled={!newCampaign.name || (isManagerCreate && !newCampaign.start_date) || createMutation.isPending}
className="flex items-center gap-1.5 rounded-lg bg-cyan-600 px-4 py-2 text-sm font-medium text-white hover:bg-cyan-500 disabled:opacity-50 transition-colors"
>
{createMutation.isPending && <Loader2 className="h-4 w-4 animate-spin" />}
+4 -3
View File
@@ -90,11 +90,12 @@ export default function TestCatalogPage() {
const [searchParams, setSearchParams] = useSearchParams();
const { user } = useAuth();
// Only leads can create tests from templates — admin administers the
// site, not test content.
// Leads and managers can create tests from templates — admin administers
// the site, not test content.
const canUseTemplate =
user?.role === "red_lead" ||
user?.role === "blue_lead";
user?.role === "blue_lead" ||
user?.role === "manager";
// Leads create templates straight into the catalog; operators get the
// same button but their submission goes through lead review first.