feat(campaigns): remove lead-set start dates, add pending-approval filter
This commit is contained in:
@@ -17,6 +17,7 @@ import { useAuth } from "../context/AuthContext";
|
|||||||
|
|
||||||
const statusColors: Record<string, string> = {
|
const statusColors: Record<string, string> = {
|
||||||
draft: "bg-gray-800/50 text-gray-400 border-gray-600/30",
|
draft: "bg-gray-800/50 text-gray-400 border-gray-600/30",
|
||||||
|
pending_approval: "bg-amber-900/50 text-amber-400 border-amber-500/30",
|
||||||
active: "bg-cyan-900/50 text-cyan-400 border-cyan-500/30",
|
active: "bg-cyan-900/50 text-cyan-400 border-cyan-500/30",
|
||||||
completed: "bg-green-900/50 text-green-400 border-green-500/30",
|
completed: "bg-green-900/50 text-green-400 border-green-500/30",
|
||||||
archived: "bg-gray-800/50 text-gray-500 border-gray-700/30",
|
archived: "bg-gray-800/50 text-gray-500 border-gray-700/30",
|
||||||
@@ -49,13 +50,11 @@ export default function CampaignsPage() {
|
|||||||
const [showCreateForm, setShowCreateForm] = useState(false);
|
const [showCreateForm, setShowCreateForm] = useState(false);
|
||||||
const [showActorSelector, setShowActorSelector] = useState(false);
|
const [showActorSelector, setShowActorSelector] = useState(false);
|
||||||
const [actorSearch, setActorSearch] = useState("");
|
const [actorSearch, setActorSearch] = useState("");
|
||||||
const [actorStartDate, setActorStartDate] = useState("");
|
|
||||||
const [newCampaign, setNewCampaign] = useState({
|
const [newCampaign, setNewCampaign] = useState({
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
type: "custom",
|
type: "custom",
|
||||||
target_platform: "",
|
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";
|
||||||
@@ -75,7 +74,7 @@ export default function CampaignsPage() {
|
|||||||
onSuccess: (campaign) => {
|
onSuccess: (campaign) => {
|
||||||
queryClient.invalidateQueries({ queryKey: ["campaigns"] });
|
queryClient.invalidateQueries({ queryKey: ["campaigns"] });
|
||||||
setShowCreateForm(false);
|
setShowCreateForm(false);
|
||||||
setNewCampaign({ name: "", description: "", type: "custom", target_platform: "", start_date: "" });
|
setNewCampaign({ name: "", description: "", type: "custom", target_platform: "" });
|
||||||
navigate(`/campaigns/${campaign.id}`);
|
navigate(`/campaigns/${campaign.id}`);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -88,12 +87,10 @@ export default function CampaignsPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const generateMutation = useMutation({
|
const generateMutation = useMutation({
|
||||||
mutationFn: (actorId: string) =>
|
mutationFn: (actorId: string) => generateCampaignFromThreatActor(actorId),
|
||||||
generateCampaignFromThreatActor(actorId, actorStartDate ? { start_date: actorStartDate } : undefined),
|
|
||||||
onSuccess: (campaign) => {
|
onSuccess: (campaign) => {
|
||||||
queryClient.invalidateQueries({ queryKey: ["campaigns"] });
|
queryClient.invalidateQueries({ queryKey: ["campaigns"] });
|
||||||
setShowActorSelector(false);
|
setShowActorSelector(false);
|
||||||
setActorStartDate("");
|
|
||||||
navigate(`/campaigns/${campaign.id}`);
|
navigate(`/campaigns/${campaign.id}`);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -170,6 +167,7 @@ export default function CampaignsPage() {
|
|||||||
>
|
>
|
||||||
<option value="">All Statuses</option>
|
<option value="">All Statuses</option>
|
||||||
<option value="draft">Draft</option>
|
<option value="draft">Draft</option>
|
||||||
|
<option value="pending_approval">Pending Approval</option>
|
||||||
<option value="active">Active</option>
|
<option value="active">Active</option>
|
||||||
<option value="completed">Completed</option>
|
<option value="completed">Completed</option>
|
||||||
<option value="archived">Archived</option>
|
<option value="archived">Archived</option>
|
||||||
@@ -228,29 +226,6 @@ export default function CampaignsPage() {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Start date */}
|
|
||||||
<div className="mt-4">
|
|
||||||
<label className="mb-1.5 block text-sm font-medium text-gray-300">
|
|
||||||
Start date
|
|
||||||
<span className="ml-2 text-xs font-normal text-gray-500">
|
|
||||||
(optional — campaign activates automatically on this date)
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
value={newCampaign.start_date}
|
|
||||||
min={new Date().toISOString().split("T")[0]}
|
|
||||||
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-300 focus:border-cyan-500 focus:outline-none [color-scheme:dark]"
|
|
||||||
/>
|
|
||||||
{newCampaign.start_date && (
|
|
||||||
<p className="mt-1.5 flex items-center gap-1.5 text-xs text-amber-400">
|
|
||||||
<span>⏰</span>
|
|
||||||
Tests won't be queued until {new Date(newCampaign.start_date + "T00:00:00").toLocaleDateString("en-GB", { day: "numeric", month: "long", year: "numeric" })}. Manual activation before that date is blocked.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-6 flex justify-end gap-3">
|
<div className="mt-6 flex justify-end gap-3">
|
||||||
<button
|
<button
|
||||||
@@ -292,29 +267,6 @@ export default function CampaignsPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Start date */}
|
|
||||||
<div className="mb-4">
|
|
||||||
<label className="mb-1.5 block text-sm font-medium text-gray-300">
|
|
||||||
Start date
|
|
||||||
<span className="ml-2 text-xs font-normal text-gray-500">
|
|
||||||
(optional — campaign activates automatically on this date)
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
value={actorStartDate}
|
|
||||||
min={new Date().toISOString().split("T")[0]}
|
|
||||||
onChange={(e) => setActorStartDate(e.target.value)}
|
|
||||||
className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-300 focus:border-cyan-500 focus:outline-none [color-scheme:dark]"
|
|
||||||
/>
|
|
||||||
{actorStartDate && (
|
|
||||||
<p className="mt-1.5 flex items-center gap-1.5 text-xs text-amber-400">
|
|
||||||
<span>⏰</span>
|
|
||||||
Tests won't be queued until {new Date(actorStartDate + "T00:00:00").toLocaleDateString("en-GB", { day: "numeric", month: "long", year: "numeric" })}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Actor list */}
|
{/* Actor list */}
|
||||||
<div className="max-h-72 overflow-y-auto rounded-lg border border-gray-800">
|
<div className="max-h-72 overflow-y-auto rounded-lg border border-gray-800">
|
||||||
{isLoadingActors ? (
|
{isLoadingActors ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user