feat(tests): containment fields hidden until detected, required datetime validation
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
This commit is contained in:
@@ -148,6 +148,11 @@ export default function TeamTabs({
|
|||||||
((role === "blue_lead" || role === "admin") ||
|
((role === "blue_lead" || role === "admin") ||
|
||||||
(role === "blue_tech" && !!test.blue_work_started_at));
|
(role === "blue_tech" && !!test.blue_work_started_at));
|
||||||
|
|
||||||
|
// Containment fields only visible when attack was detected (draft or saved value)
|
||||||
|
const isDetected = canEditBlue
|
||||||
|
? blueDraft.detection_result === "detected" || blueDraft.detection_result === "partially_detected"
|
||||||
|
: test.detection_result === "detected" || test.detection_result === "partially_detected";
|
||||||
|
|
||||||
// Hint messages shown to operators when editing is locked
|
// Hint messages shown to operators when editing is locked
|
||||||
const redLockedHint =
|
const redLockedHint =
|
||||||
test.state === "draft" && role === "red_tech"
|
test.state === "draft" && role === "red_tech"
|
||||||
@@ -238,11 +243,12 @@ export default function TeamTabs({
|
|||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1.5 block text-sm font-medium text-gray-300">
|
<label className="mb-1.5 block text-sm font-medium text-gray-300">
|
||||||
Execution Start (UTC)
|
Execution Start (UTC) <span className="text-red-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
{canEditRed ? (
|
{canEditRed ? (
|
||||||
<input
|
<input
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
|
required
|
||||||
value={redDraft.execution_start_time}
|
value={redDraft.execution_start_time}
|
||||||
onChange={(e) => onRedFieldChange("execution_start_time", e.target.value)}
|
onChange={(e) => onRedFieldChange("execution_start_time", 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 focus:ring-1 focus:ring-cyan-500"
|
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 focus:ring-1 focus:ring-cyan-500"
|
||||||
@@ -255,11 +261,12 @@ export default function TeamTabs({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1.5 block text-sm font-medium text-gray-300">
|
<label className="mb-1.5 block text-sm font-medium text-gray-300">
|
||||||
Execution End (UTC)
|
Execution End (UTC) <span className="text-red-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
{canEditRed ? (
|
{canEditRed ? (
|
||||||
<input
|
<input
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
|
required
|
||||||
value={redDraft.execution_end_time}
|
value={redDraft.execution_end_time}
|
||||||
onChange={(e) => onRedFieldChange("execution_end_time", e.target.value)}
|
onChange={(e) => onRedFieldChange("execution_end_time", 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 focus:ring-1 focus:ring-cyan-500"
|
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 focus:ring-1 focus:ring-cyan-500"
|
||||||
@@ -391,13 +398,14 @@ export default function TeamTabs({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Containment Result */}
|
{/* Containment Result — only when detected/partially_detected */}
|
||||||
|
{isDetected && (
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-2 block text-sm font-medium text-gray-300">
|
<label className="mb-2 block text-sm font-medium text-gray-300">
|
||||||
Containment Result
|
Containment Result
|
||||||
</label>
|
</label>
|
||||||
{canEditBlue ? (
|
{canEditBlue ? (
|
||||||
<div className={`flex flex-wrap gap-2 ${blueDraft.detection_result === "not_detected" ? "opacity-40 pointer-events-none" : ""}`}>
|
<div className="flex flex-wrap gap-2">
|
||||||
{CONTAINMENT_RESULTS.map((opt) => (
|
{CONTAINMENT_RESULTS.map((opt) => (
|
||||||
<button
|
<button
|
||||||
key={opt.value}
|
key={opt.value}
|
||||||
@@ -411,9 +419,6 @@ export default function TeamTabs({
|
|||||||
{opt.label}
|
{opt.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
{blueDraft.detection_result === "not_detected" && (
|
|
||||||
<span className="self-center text-xs text-gray-500">N/A — attack not detected</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
@@ -429,25 +434,25 @@ export default function TeamTabs({
|
|||||||
>
|
>
|
||||||
{test.containment_result.replace(/_/g, " ")}
|
{test.containment_result.replace(/_/g, " ")}
|
||||||
</span>
|
</span>
|
||||||
) : test.detection_result === "not_detected" ? (
|
|
||||||
<span className="text-gray-500">N/A</span>
|
|
||||||
) : (
|
) : (
|
||||||
<span className="text-gray-500">Not evaluated yet</span>
|
<span className="text-gray-500">Not evaluated yet</span>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Detection & Containment times — only relevant when detected */}
|
{/* Detection & Containment times — only when detected/partially_detected */}
|
||||||
{(canEditBlue || test.detection_result === "detected" || test.detection_result === "partially_detected") && (
|
{isDetected && (
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1.5 block text-sm font-medium text-gray-300">
|
<label className="mb-1.5 block text-sm font-medium text-gray-300">
|
||||||
Detection Time (UTC)
|
Detection Time (UTC) <span className="text-red-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
{canEditBlue ? (
|
{canEditBlue ? (
|
||||||
<input
|
<input
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
|
required
|
||||||
value={blueDraft.detection_time}
|
value={blueDraft.detection_time}
|
||||||
onChange={(e) => onBlueFieldChange("detection_time", e.target.value)}
|
onChange={(e) => onBlueFieldChange("detection_time", 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-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-200 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
||||||
@@ -460,11 +465,12 @@ export default function TeamTabs({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1.5 block text-sm font-medium text-gray-300">
|
<label className="mb-1.5 block text-sm font-medium text-gray-300">
|
||||||
Containment Time (UTC)
|
Containment Time (UTC) <span className="text-red-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
{canEditBlue ? (
|
{canEditBlue ? (
|
||||||
<input
|
<input
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
|
required
|
||||||
value={blueDraft.containment_time}
|
value={blueDraft.containment_time}
|
||||||
onChange={(e) => onBlueFieldChange("containment_time", e.target.value)}
|
onChange={(e) => onBlueFieldChange("containment_time", 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-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
className="w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-sm text-gray-200 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
||||||
|
|||||||
@@ -471,7 +471,11 @@ export default function TestDetailPage() {
|
|||||||
<div className="flex justify-end gap-3">
|
<div className="flex justify-end gap-3">
|
||||||
{canSaveRed && (
|
{canSaveRed && (
|
||||||
<button
|
<button
|
||||||
onClick={() => saveRedMutation.mutate()}
|
onClick={() => {
|
||||||
|
if (!redDraft.execution_start_time) { showToast("Execution Start time is required", "error"); return; }
|
||||||
|
if (!redDraft.execution_end_time) { showToast("Execution End time is required", "error"); return; }
|
||||||
|
saveRedMutation.mutate();
|
||||||
|
}}
|
||||||
disabled={saveRedMutation.isPending}
|
disabled={saveRedMutation.isPending}
|
||||||
className="flex items-center gap-1.5 rounded-lg bg-orange-600 px-4 py-2 text-sm font-medium text-white hover:bg-orange-500 disabled:opacity-50 transition-colors"
|
className="flex items-center gap-1.5 rounded-lg bg-orange-600 px-4 py-2 text-sm font-medium text-white hover:bg-orange-500 disabled:opacity-50 transition-colors"
|
||||||
>
|
>
|
||||||
@@ -481,7 +485,12 @@ export default function TestDetailPage() {
|
|||||||
)}
|
)}
|
||||||
{canSaveBlue && (
|
{canSaveBlue && (
|
||||||
<button
|
<button
|
||||||
onClick={() => saveBlueMutation.mutate()}
|
onClick={() => {
|
||||||
|
const isDetected = blueDraft.detection_result === "detected" || blueDraft.detection_result === "partially_detected";
|
||||||
|
if (isDetected && !blueDraft.detection_time) { showToast("Detection Time is required", "error"); return; }
|
||||||
|
if (isDetected && !blueDraft.containment_time) { showToast("Containment Time is required", "error"); return; }
|
||||||
|
saveBlueMutation.mutate();
|
||||||
|
}}
|
||||||
disabled={saveBlueMutation.isPending}
|
disabled={saveBlueMutation.isPending}
|
||||||
className="flex items-center gap-1.5 rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-500 disabled:opacity-50 transition-colors"
|
className="flex items-center gap-1.5 rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-500 disabled:opacity-50 transition-colors"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user