feat(frontend): wireframes for 5 MVP screens + audit (F0.3)
Mock-data wireframes covering spec §5 / §9 surface area. All read from
src/mocks/fixtures.ts — no backend wiring yet. Each screen is built from
the design-system primitives (Panel, Pill, Button, label-system, status-dot)
and adheres to the instrumentation-grade visual grammar.
Screens:
- /login LoginPage — RT vs SOC mode switch (segmented), role-tinted.
RT form picks rt_operator / rt_lead at sign-in (mock only).
SOC form takes a session token (out-of-band, D-006).
Left rail carries mission brief + platform telemetry.
- /engagements EngagementsPage — mission roster table (codename, client,
status, c2_type, operators, SOC count, window).
- /runs LiveCockpitPage — the cornerstone screen. 3-column layout:
steps timeline | step detail (resolved command,
output, evidence, detection) | side rail
(DetectionPanel for SOC; EvidencePanel +
DetectionPanel readonly + CleanupPanel for RT).
Control bar (F6 pause/skip/retry/abort) is lead-RT-only.
Stats header: steps done, detected/partial/missed counts.
- /scenarios ScenarioComposerPage — 3-column composer:
filterable TTP library | ordered steps with delays
| inspector (params from params_schema_json, target
host list, jinja2 cleanup template preview).
c2_type locked at scenario level (D-F3 / H33).
- /library TtpLibraryPage — catalog table with stealth-variant
flagging, source provenance (custom/atr/mission),
payload_type chip, tags. Import journal / ATR buttons.
- /reports ReportPage — restricted MITRE matrix (techniques
played only, H29), narration timeline, integrity
hash footer (SHA-256, H19/H24/F9). PDF/JSON/MD
export buttons.
- /audit AuditPage — append-only journal viewer (lead RT only,
F13). Tabular timestamp/actor/role/action/resource.
UX guardrails baked in:
- SOC analysts never see RT-only controls (conditional rendering, not just
disabled state). UI layer mirrors backend RBAC but does not replace it.
- Layout density and dark-first palette tuned for long purple sessions
(sober contrast, no flash, status colors carry information without
being shouted).
- Live cockpit reserves a clear visual slot for cleanup-failed alerts
(R-T5) — currently a Pill, real alert UX lands when the WebSocket is
wired in sprint 1+.
This commit is contained in:
124
frontend/src/screens/engagements/EngagementsPage.tsx
Normal file
124
frontend/src/screens/engagements/EngagementsPage.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Panel } from '@/components/ui/Panel';
|
||||
import { Pill } from '@/components/ui/Pill';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { MOCK_ENGAGEMENTS } from '@/mocks/fixtures';
|
||||
import type { MockEngagement } from '@/mocks/fixtures';
|
||||
|
||||
const STATUS_TONE: Record<MockEngagement['status'], 'running' | 'soc' | 'success' | 'pending'> = {
|
||||
active: 'running',
|
||||
reporting: 'soc',
|
||||
archived: 'pending',
|
||||
planning: 'success',
|
||||
};
|
||||
|
||||
export function EngagementsPage() {
|
||||
return (
|
||||
<div className="px-8 py-6 space-y-6 max-w-[1400px] mx-auto">
|
||||
<header className="flex items-end justify-between">
|
||||
<div>
|
||||
<div className="label-system mb-1">// Engagements</div>
|
||||
<h1 className="font-display text-fg-default" style={{ fontSize: '22px', letterSpacing: '0.02em' }}>
|
||||
Mission roster
|
||||
</h1>
|
||||
<p className="text-fg-muted mt-1" style={{ fontSize: '12.5px' }}>
|
||||
Each engagement is a multi-tenant container. Pick one to access its hosts, scenarios,
|
||||
runs, and reports.
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="primary">+ New engagement</Button>
|
||||
</header>
|
||||
|
||||
<Panel
|
||||
title="Active and recent"
|
||||
meta={
|
||||
<span className="tabular">
|
||||
{MOCK_ENGAGEMENTS.length} entries · sorted by start date
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<table className="w-full" style={{ fontSize: 12.5 }}>
|
||||
<thead>
|
||||
<tr className="text-fg-subtle">
|
||||
<Th>Codename</Th>
|
||||
<Th>Client</Th>
|
||||
<Th>Status</Th>
|
||||
<Th>C2</Th>
|
||||
<Th align="right">Operators</Th>
|
||||
<Th align="right">SOC</Th>
|
||||
<Th>Window</Th>
|
||||
<Th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{MOCK_ENGAGEMENTS.map((eng, idx) => (
|
||||
<tr
|
||||
key={eng.id}
|
||||
style={{
|
||||
borderTop: idx === 0 ? '1px solid var(--line-default)' : '1px solid var(--line-faint)',
|
||||
}}
|
||||
>
|
||||
<Td>
|
||||
<div className="font-display text-fg-default" style={{ letterSpacing: '0.06em' }}>
|
||||
{eng.codename}
|
||||
</div>
|
||||
<div className="font-mono text-fg-faint" style={{ fontSize: '10.5px' }}>
|
||||
{eng.id}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>{eng.client}</Td>
|
||||
<Td>
|
||||
<Pill tone={STATUS_TONE[eng.status]}>
|
||||
<span className="status-dot" style={{ color: 'currentColor' }} />
|
||||
{eng.status}
|
||||
</Pill>
|
||||
</Td>
|
||||
<Td>
|
||||
<span className="font-mono tabular">{eng.c2Type.toUpperCase()}</span>
|
||||
</Td>
|
||||
<Td align="right">
|
||||
<span className="font-mono tabular">{eng.operators}</span>
|
||||
</Td>
|
||||
<Td align="right">
|
||||
<span className="font-mono tabular">{eng.socAnalysts}</span>
|
||||
</Td>
|
||||
<Td>
|
||||
<span className="font-mono tabular text-fg-muted">
|
||||
{eng.startDate} → {eng.endDate}
|
||||
</span>
|
||||
</Td>
|
||||
<Td align="right">
|
||||
<Link to="/runs">
|
||||
<Button variant="ghost" size="sm">
|
||||
Enter →
|
||||
</Button>
|
||||
</Link>
|
||||
</Td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Panel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Th({ children, align = 'left' }: { children?: ReactNode; align?: 'left' | 'right' }) {
|
||||
return (
|
||||
<th
|
||||
className="label-system px-3 py-2"
|
||||
style={{ textAlign: align, fontWeight: 500 }}
|
||||
>
|
||||
{children}
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
function Td({ children, align = 'left' }: { children?: ReactNode; align?: 'left' | 'right' }) {
|
||||
return (
|
||||
<td className="px-3 py-3 align-middle" style={{ textAlign: align }}>
|
||||
{children}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user