feat(frontend): side-by-side red-team/SOC columns on simulation screen

Wrap RT and SOC form cards in a responsive 2-column grid
(grid gap-xl lg:grid-cols-2 items-start) on the simulation edit view.
Drop the max-w-3xl constraint from the outer container so the grid
can use full page width (matching EngagementDetailPage). Header,
banners, submitError, and sticky action bar remain full-width above
the grid. The isNew create form keeps its current narrow layout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Knacky
2026-06-10 18:17:54 +02:00
parent 573281f454
commit 1997a8c621

View File

@@ -257,7 +257,7 @@ export function SimulationFormPage(): JSX.Element {
updateMutation.isPending || transitionMutation.isPending || deleteMutation.isPending; updateMutation.isPending || transitionMutation.isPending || deleteMutation.isPending;
return ( return (
<div className="flex flex-col gap-xl max-w-3xl"> <div className="flex flex-col gap-xl">
<header className="flex items-start justify-between gap-md"> <header className="flex items-start justify-between gap-md">
<div className="flex flex-col gap-sm"> <div className="flex flex-col gap-sm">
<Link to={`/engagements/${engagementId}`} className="btn-text-link text-[14px]"> <Link to={`/engagements/${engagementId}`} className="btn-text-link text-[14px]">
@@ -298,140 +298,143 @@ export function SimulationFormPage(): JSX.Element {
</div> </div>
)} )}
{/* Red Team card */} {/* 2-column grid: RT left, SOC right. Stacks vertically below lg. */}
<form <div className="grid gap-xl lg:grid-cols-2 items-start">
id="rt-form" {/* Red Team card */}
onSubmit={canEditRT && !isDone ? onSaveRT : (e) => e.preventDefault()} <form
noValidate id="rt-form"
className="card-product flex flex-col gap-md" onSubmit={canEditRT && !isDone ? onSaveRT : (e) => e.preventDefault()}
> noValidate
<h2 className="text-[20px] font-medium text-ink">Red Team</h2> className="card-product flex flex-col gap-md"
>
<h2 className="text-[20px] font-medium text-ink">Red Team</h2>
<FormField label="Name" htmlFor="sim-name" required error={nameError}> <FormField label="Name" htmlFor="sim-name" required error={nameError}>
<TextInput <TextInput
id="sim-name" id="sim-name"
name="name" name="name"
value={rt.name} value={rt.name}
onChange={(e) => setRt({ ...rt, name: e.target.value })} onChange={(e) => setRt({ ...rt, name: e.target.value })}
disabled={rtDisabled} disabled={rtDisabled}
required required
/> />
</FormField> </FormField>
<div className="flex flex-col gap-xs"> <div className="flex flex-col gap-xs">
<span className="text-[14px] font-medium text-ink">MITRE Techniques &amp; Tactics</span> <span className="text-[14px] font-medium text-ink">MITRE Techniques &amp; Tactics</span>
<MitreTechniquesField <MitreTechniquesField
value={simulation?.techniques ?? []} value={simulation?.techniques ?? []}
tactics={simulation?.tactics ?? []} tactics={simulation?.tactics ?? []}
simulationId={simulationId as number} simulationId={simulationId as number}
engagementId={engagementId as number} engagementId={engagementId as number}
disabled={rtDisabled} disabled={rtDisabled}
/> />
</div> </div>
<FormField label="Description" htmlFor="sim-description"> <FormField label="Description" htmlFor="sim-description">
<TextArea <TextArea
id="sim-description" id="sim-description"
name="description" name="description"
value={rt.description} value={rt.description}
onChange={(e) => setRt({ ...rt, description: e.target.value })} onChange={(e) => setRt({ ...rt, description: e.target.value })}
disabled={rtDisabled} disabled={rtDisabled}
/> />
</FormField> </FormField>
<FormField label="Commands" htmlFor="sim-commands" hint="One command per line"> <FormField label="Commands" htmlFor="sim-commands" hint="One command per line">
<TextArea <TextArea
id="sim-commands" id="sim-commands"
name="commands" name="commands"
value={rt.commands} value={rt.commands}
onChange={(e) => setRt({ ...rt, commands: e.target.value })} onChange={(e) => setRt({ ...rt, commands: e.target.value })}
disabled={rtDisabled} disabled={rtDisabled}
className="min-h-[160px] font-mono text-[14px]" className="min-h-[160px] font-mono text-[14px]"
/> />
</FormField> </FormField>
<FormField label="Prerequisites" htmlFor="sim-prerequisites"> <FormField label="Prerequisites" htmlFor="sim-prerequisites">
<TextArea <TextArea
id="sim-prerequisites" id="sim-prerequisites"
name="prerequisites" name="prerequisites"
value={rt.prerequisites} value={rt.prerequisites}
onChange={(e) => setRt({ ...rt, prerequisites: e.target.value })} onChange={(e) => setRt({ ...rt, prerequisites: e.target.value })}
disabled={rtDisabled} disabled={rtDisabled}
/> />
</FormField> </FormField>
<FormField label="Executed at" htmlFor="sim-executed-at"> <FormField label="Executed at" htmlFor="sim-executed-at">
<TextInput <TextInput
id="sim-executed-at" id="sim-executed-at"
type="datetime-local" type="datetime-local"
name="executed_at" name="executed_at"
value={rt.executed_at} value={rt.executed_at}
onChange={(e) => setRt({ ...rt, executed_at: e.target.value })} onChange={(e) => setRt({ ...rt, executed_at: e.target.value })}
disabled={rtDisabled} disabled={rtDisabled}
/> />
</FormField> </FormField>
<FormField label="Execution result" htmlFor="sim-exec-result"> <FormField label="Execution result" htmlFor="sim-exec-result">
<TextArea <TextArea
id="sim-exec-result" id="sim-exec-result"
name="execution_result" name="execution_result"
value={rt.execution_result} value={rt.execution_result}
onChange={(e) => setRt({ ...rt, execution_result: e.target.value })} onChange={(e) => setRt({ ...rt, execution_result: e.target.value })}
disabled={rtDisabled} disabled={rtDisabled}
rows={5} rows={5}
/> />
</FormField> </FormField>
</form> </form>
{/* SOC card */} {/* SOC card */}
<form <form
id="soc-form" id="soc-form"
onSubmit={canSaveSoc ? onSaveSOC : (e) => e.preventDefault()} onSubmit={canSaveSoc ? onSaveSOC : (e) => e.preventDefault()}
noValidate noValidate
className="card-product flex flex-col gap-md" className="card-product flex flex-col gap-md"
> >
<h2 className="text-[20px] font-medium text-ink">SOC</h2> <h2 className="text-[20px] font-medium text-ink">SOC</h2>
<FormField label="Log source" htmlFor="sim-log-source"> <FormField label="Log source" htmlFor="sim-log-source">
<TextInput <TextInput
id="sim-log-source" id="sim-log-source"
name="log_source" name="log_source"
value={soc.log_source} value={soc.log_source}
onChange={(e) => setSoc({ ...soc, log_source: e.target.value })} onChange={(e) => setSoc({ ...soc, log_source: e.target.value })}
disabled={socDisabled} disabled={socDisabled}
/> />
</FormField> </FormField>
<FormField label="Logs" htmlFor="sim-logs"> <FormField label="Logs" htmlFor="sim-logs">
<TextArea <TextArea
id="sim-logs" id="sim-logs"
name="logs" name="logs"
value={soc.logs} value={soc.logs}
onChange={(e) => setSoc({ ...soc, logs: e.target.value })} onChange={(e) => setSoc({ ...soc, logs: e.target.value })}
disabled={socDisabled} disabled={socDisabled}
/> />
</FormField> </FormField>
<FormField label="SOC comment" htmlFor="sim-soc-comment"> <FormField label="SOC comment" htmlFor="sim-soc-comment">
<TextArea <TextArea
id="sim-soc-comment" id="sim-soc-comment"
name="soc_comment" name="soc_comment"
value={soc.soc_comment} value={soc.soc_comment}
onChange={(e) => setSoc({ ...soc, soc_comment: e.target.value })} onChange={(e) => setSoc({ ...soc, soc_comment: e.target.value })}
disabled={socDisabled} disabled={socDisabled}
/> />
</FormField> </FormField>
<FormField label="Incident number" htmlFor="sim-incident"> <FormField label="Incident number" htmlFor="sim-incident">
<TextInput <TextInput
id="sim-incident" id="sim-incident"
name="incident_number" name="incident_number"
value={soc.incident_number} value={soc.incident_number}
onChange={(e) => setSoc({ ...soc, incident_number: e.target.value })} onChange={(e) => setSoc({ ...soc, incident_number: e.target.value })}
disabled={socDisabled} disabled={socDisabled}
/> />
</FormField> </FormField>
</form> </form>
</div>
{submitError ? ( {submitError ? (
<div role="alert" className="text-[14px] text-bloom-deep">{submitError}</div> <div role="alert" className="text-[14px] text-bloom-deep">{submitError}</div>