Files
mimic-big/frontend/src/screens/engagements/EngagementsPage.tsx

125 lines
4.1 KiB
TypeScript
Raw Normal View History

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+.
2026-05-21 20:31:24 +02:00
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>
);
}