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

212 lines
6.4 KiB
TypeScript
Raw Normal View History

feat(frontend): wire LoginPage + EngagementsPage + create dialog to backend LoginPage - RT mode now POSTs /api/v1/auth/login with controlled username/password fields. Success seeds the session cache via queryClient.setQueryData and navigates to /engagements. 401 surfaces as the generic "Identifiants invalides" — no echo of the backend detail (avoids user enumeration leaks). - SOC mode kept visually for masthead continuity but disabled with a "sprint 2" placeholder pointing at the deferred POST /api/v1/auth/soc/session endpoint. - Removed the sprint-0 mock role-picker. EngagementsPage - MOCK_ENGAGEMENTS dropped. useQuery against fetchEngagements (handles both bare-array and { items: [] } envelope shapes — backend has not pinned this yet). - Distinct loading / empty / error states. Error row surfaces an HTTP code and a Retry button. Empty state offers the create dialog. - Column shape aligned with the real Engagement schema (snake_case: name, client_name, c2_type, start_date, end_date). Dropped mock-only columns (operators, socAnalysts) — those land when backend exposes /engagements/:id/members and /engagements/:id/soc-sessions counts. engagementsApi.ts - fetchEngagements + createEngagement, both bound to /api/v1/engagements. - ENGAGEMENTS_QUERY_KEY exported so the dialog can invalidate without re-knowing the key. EngagementCreateDialog (frontend-design skill — new non-trivial component) - "Arm engagement" mission-control dialog. Backdrop is a graphite dim with a faint scanline overlay (no soft blur) — reads as "cockpit paused while you issue a command", not as a SaaS modal. - Surface --surface-3 with corner-marks and an amber hairline accent under the title; underline-style inputs that light amber on focus; label-system uppercase microtypography throughout. - Esc + outside-click close (suspended while the mutation is in flight). - Rudimentary tab focus trap. - 422 Pydantic errors map per-field via the last loc segment; 401/5xx surface as a generic top-of-form alert. - On 201 invalidates ['engagements'] and closes.
2026-05-23 04:26:48 +02:00
import { useState, type ReactNode } from 'react';
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 { Link } from 'react-router-dom';
feat(frontend): wire LoginPage + EngagementsPage + create dialog to backend LoginPage - RT mode now POSTs /api/v1/auth/login with controlled username/password fields. Success seeds the session cache via queryClient.setQueryData and navigates to /engagements. 401 surfaces as the generic "Identifiants invalides" — no echo of the backend detail (avoids user enumeration leaks). - SOC mode kept visually for masthead continuity but disabled with a "sprint 2" placeholder pointing at the deferred POST /api/v1/auth/soc/session endpoint. - Removed the sprint-0 mock role-picker. EngagementsPage - MOCK_ENGAGEMENTS dropped. useQuery against fetchEngagements (handles both bare-array and { items: [] } envelope shapes — backend has not pinned this yet). - Distinct loading / empty / error states. Error row surfaces an HTTP code and a Retry button. Empty state offers the create dialog. - Column shape aligned with the real Engagement schema (snake_case: name, client_name, c2_type, start_date, end_date). Dropped mock-only columns (operators, socAnalysts) — those land when backend exposes /engagements/:id/members and /engagements/:id/soc-sessions counts. engagementsApi.ts - fetchEngagements + createEngagement, both bound to /api/v1/engagements. - ENGAGEMENTS_QUERY_KEY exported so the dialog can invalidate without re-knowing the key. EngagementCreateDialog (frontend-design skill — new non-trivial component) - "Arm engagement" mission-control dialog. Backdrop is a graphite dim with a faint scanline overlay (no soft blur) — reads as "cockpit paused while you issue a command", not as a SaaS modal. - Surface --surface-3 with corner-marks and an amber hairline accent under the title; underline-style inputs that light amber on focus; label-system uppercase microtypography throughout. - Esc + outside-click close (suspended while the mutation is in flight). - Rudimentary tab focus trap. - 422 Pydantic errors map per-field via the last loc segment; 401/5xx surface as a generic top-of-form alert. - On 201 invalidates ['engagements'] and closes.
2026-05-23 04:26:48 +02:00
import { useQuery } from '@tanstack/react-query';
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 { Panel } from '@/components/ui/Panel';
import { Pill } from '@/components/ui/Pill';
import { Button } from '@/components/ui/Button';
feat(frontend): wire LoginPage + EngagementsPage + create dialog to backend LoginPage - RT mode now POSTs /api/v1/auth/login with controlled username/password fields. Success seeds the session cache via queryClient.setQueryData and navigates to /engagements. 401 surfaces as the generic "Identifiants invalides" — no echo of the backend detail (avoids user enumeration leaks). - SOC mode kept visually for masthead continuity but disabled with a "sprint 2" placeholder pointing at the deferred POST /api/v1/auth/soc/session endpoint. - Removed the sprint-0 mock role-picker. EngagementsPage - MOCK_ENGAGEMENTS dropped. useQuery against fetchEngagements (handles both bare-array and { items: [] } envelope shapes — backend has not pinned this yet). - Distinct loading / empty / error states. Error row surfaces an HTTP code and a Retry button. Empty state offers the create dialog. - Column shape aligned with the real Engagement schema (snake_case: name, client_name, c2_type, start_date, end_date). Dropped mock-only columns (operators, socAnalysts) — those land when backend exposes /engagements/:id/members and /engagements/:id/soc-sessions counts. engagementsApi.ts - fetchEngagements + createEngagement, both bound to /api/v1/engagements. - ENGAGEMENTS_QUERY_KEY exported so the dialog can invalidate without re-knowing the key. EngagementCreateDialog (frontend-design skill — new non-trivial component) - "Arm engagement" mission-control dialog. Backdrop is a graphite dim with a faint scanline overlay (no soft blur) — reads as "cockpit paused while you issue a command", not as a SaaS modal. - Surface --surface-3 with corner-marks and an amber hairline accent under the title; underline-style inputs that light amber on focus; label-system uppercase microtypography throughout. - Esc + outside-click close (suspended while the mutation is in flight). - Rudimentary tab focus trap. - 422 Pydantic errors map per-field via the last loc segment; 401/5xx surface as a generic top-of-form alert. - On 201 invalidates ['engagements'] and closes.
2026-05-23 04:26:48 +02:00
import { ApiClientError } from '@/lib/api';
import type { Engagement, EngagementStatus } from '@/types/api';
import { ENGAGEMENTS_QUERY_KEY, fetchEngagements } from './engagementsApi';
import { EngagementCreateDialog } from './EngagementCreateDialog';
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
feat(frontend): wire LoginPage + EngagementsPage + create dialog to backend LoginPage - RT mode now POSTs /api/v1/auth/login with controlled username/password fields. Success seeds the session cache via queryClient.setQueryData and navigates to /engagements. 401 surfaces as the generic "Identifiants invalides" — no echo of the backend detail (avoids user enumeration leaks). - SOC mode kept visually for masthead continuity but disabled with a "sprint 2" placeholder pointing at the deferred POST /api/v1/auth/soc/session endpoint. - Removed the sprint-0 mock role-picker. EngagementsPage - MOCK_ENGAGEMENTS dropped. useQuery against fetchEngagements (handles both bare-array and { items: [] } envelope shapes — backend has not pinned this yet). - Distinct loading / empty / error states. Error row surfaces an HTTP code and a Retry button. Empty state offers the create dialog. - Column shape aligned with the real Engagement schema (snake_case: name, client_name, c2_type, start_date, end_date). Dropped mock-only columns (operators, socAnalysts) — those land when backend exposes /engagements/:id/members and /engagements/:id/soc-sessions counts. engagementsApi.ts - fetchEngagements + createEngagement, both bound to /api/v1/engagements. - ENGAGEMENTS_QUERY_KEY exported so the dialog can invalidate without re-knowing the key. EngagementCreateDialog (frontend-design skill — new non-trivial component) - "Arm engagement" mission-control dialog. Backdrop is a graphite dim with a faint scanline overlay (no soft blur) — reads as "cockpit paused while you issue a command", not as a SaaS modal. - Surface --surface-3 with corner-marks and an amber hairline accent under the title; underline-style inputs that light amber on focus; label-system uppercase microtypography throughout. - Esc + outside-click close (suspended while the mutation is in flight). - Rudimentary tab focus trap. - 422 Pydantic errors map per-field via the last loc segment; 401/5xx surface as a generic top-of-form alert. - On 201 invalidates ['engagements'] and closes.
2026-05-23 04:26:48 +02:00
const STATUS_TONE: Record<EngagementStatus, 'running' | 'soc' | 'success' | 'pending'> = {
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
active: 'running',
reporting: 'soc',
archived: 'pending',
planning: 'success',
};
export function EngagementsPage() {
feat(frontend): wire LoginPage + EngagementsPage + create dialog to backend LoginPage - RT mode now POSTs /api/v1/auth/login with controlled username/password fields. Success seeds the session cache via queryClient.setQueryData and navigates to /engagements. 401 surfaces as the generic "Identifiants invalides" — no echo of the backend detail (avoids user enumeration leaks). - SOC mode kept visually for masthead continuity but disabled with a "sprint 2" placeholder pointing at the deferred POST /api/v1/auth/soc/session endpoint. - Removed the sprint-0 mock role-picker. EngagementsPage - MOCK_ENGAGEMENTS dropped. useQuery against fetchEngagements (handles both bare-array and { items: [] } envelope shapes — backend has not pinned this yet). - Distinct loading / empty / error states. Error row surfaces an HTTP code and a Retry button. Empty state offers the create dialog. - Column shape aligned with the real Engagement schema (snake_case: name, client_name, c2_type, start_date, end_date). Dropped mock-only columns (operators, socAnalysts) — those land when backend exposes /engagements/:id/members and /engagements/:id/soc-sessions counts. engagementsApi.ts - fetchEngagements + createEngagement, both bound to /api/v1/engagements. - ENGAGEMENTS_QUERY_KEY exported so the dialog can invalidate without re-knowing the key. EngagementCreateDialog (frontend-design skill — new non-trivial component) - "Arm engagement" mission-control dialog. Backdrop is a graphite dim with a faint scanline overlay (no soft blur) — reads as "cockpit paused while you issue a command", not as a SaaS modal. - Surface --surface-3 with corner-marks and an amber hairline accent under the title; underline-style inputs that light amber on focus; label-system uppercase microtypography throughout. - Esc + outside-click close (suspended while the mutation is in flight). - Rudimentary tab focus trap. - 422 Pydantic errors map per-field via the last loc segment; 401/5xx surface as a generic top-of-form alert. - On 201 invalidates ['engagements'] and closes.
2026-05-23 04:26:48 +02:00
const [createOpen, setCreateOpen] = useState(false);
const query = useQuery<Engagement[]>({
queryKey: ENGAGEMENTS_QUERY_KEY,
queryFn: ({ signal }) => fetchEngagements(signal),
});
const engagements = query.data ?? [];
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
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>
feat(frontend): wire LoginPage + EngagementsPage + create dialog to backend LoginPage - RT mode now POSTs /api/v1/auth/login with controlled username/password fields. Success seeds the session cache via queryClient.setQueryData and navigates to /engagements. 401 surfaces as the generic "Identifiants invalides" — no echo of the backend detail (avoids user enumeration leaks). - SOC mode kept visually for masthead continuity but disabled with a "sprint 2" placeholder pointing at the deferred POST /api/v1/auth/soc/session endpoint. - Removed the sprint-0 mock role-picker. EngagementsPage - MOCK_ENGAGEMENTS dropped. useQuery against fetchEngagements (handles both bare-array and { items: [] } envelope shapes — backend has not pinned this yet). - Distinct loading / empty / error states. Error row surfaces an HTTP code and a Retry button. Empty state offers the create dialog. - Column shape aligned with the real Engagement schema (snake_case: name, client_name, c2_type, start_date, end_date). Dropped mock-only columns (operators, socAnalysts) — those land when backend exposes /engagements/:id/members and /engagements/:id/soc-sessions counts. engagementsApi.ts - fetchEngagements + createEngagement, both bound to /api/v1/engagements. - ENGAGEMENTS_QUERY_KEY exported so the dialog can invalidate without re-knowing the key. EngagementCreateDialog (frontend-design skill — new non-trivial component) - "Arm engagement" mission-control dialog. Backdrop is a graphite dim with a faint scanline overlay (no soft blur) — reads as "cockpit paused while you issue a command", not as a SaaS modal. - Surface --surface-3 with corner-marks and an amber hairline accent under the title; underline-style inputs that light amber on focus; label-system uppercase microtypography throughout. - Esc + outside-click close (suspended while the mutation is in flight). - Rudimentary tab focus trap. - 422 Pydantic errors map per-field via the last loc segment; 401/5xx surface as a generic top-of-form alert. - On 201 invalidates ['engagements'] and closes.
2026-05-23 04:26:48 +02:00
<h1
className="font-display text-fg-default"
style={{ fontSize: '22px', letterSpacing: '0.02em' }}
>
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
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>
feat(frontend): wire LoginPage + EngagementsPage + create dialog to backend LoginPage - RT mode now POSTs /api/v1/auth/login with controlled username/password fields. Success seeds the session cache via queryClient.setQueryData and navigates to /engagements. 401 surfaces as the generic "Identifiants invalides" — no echo of the backend detail (avoids user enumeration leaks). - SOC mode kept visually for masthead continuity but disabled with a "sprint 2" placeholder pointing at the deferred POST /api/v1/auth/soc/session endpoint. - Removed the sprint-0 mock role-picker. EngagementsPage - MOCK_ENGAGEMENTS dropped. useQuery against fetchEngagements (handles both bare-array and { items: [] } envelope shapes — backend has not pinned this yet). - Distinct loading / empty / error states. Error row surfaces an HTTP code and a Retry button. Empty state offers the create dialog. - Column shape aligned with the real Engagement schema (snake_case: name, client_name, c2_type, start_date, end_date). Dropped mock-only columns (operators, socAnalysts) — those land when backend exposes /engagements/:id/members and /engagements/:id/soc-sessions counts. engagementsApi.ts - fetchEngagements + createEngagement, both bound to /api/v1/engagements. - ENGAGEMENTS_QUERY_KEY exported so the dialog can invalidate without re-knowing the key. EngagementCreateDialog (frontend-design skill — new non-trivial component) - "Arm engagement" mission-control dialog. Backdrop is a graphite dim with a faint scanline overlay (no soft blur) — reads as "cockpit paused while you issue a command", not as a SaaS modal. - Surface --surface-3 with corner-marks and an amber hairline accent under the title; underline-style inputs that light amber on focus; label-system uppercase microtypography throughout. - Esc + outside-click close (suspended while the mutation is in flight). - Rudimentary tab focus trap. - 422 Pydantic errors map per-field via the last loc segment; 401/5xx surface as a generic top-of-form alert. - On 201 invalidates ['engagements'] and closes.
2026-05-23 04:26:48 +02:00
<Button variant="primary" onClick={() => setCreateOpen(true)}>
+ New engagement
</Button>
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
</header>
<Panel
title="Active and recent"
meta={
<span className="tabular">
feat(frontend): wire LoginPage + EngagementsPage + create dialog to backend LoginPage - RT mode now POSTs /api/v1/auth/login with controlled username/password fields. Success seeds the session cache via queryClient.setQueryData and navigates to /engagements. 401 surfaces as the generic "Identifiants invalides" — no echo of the backend detail (avoids user enumeration leaks). - SOC mode kept visually for masthead continuity but disabled with a "sprint 2" placeholder pointing at the deferred POST /api/v1/auth/soc/session endpoint. - Removed the sprint-0 mock role-picker. EngagementsPage - MOCK_ENGAGEMENTS dropped. useQuery against fetchEngagements (handles both bare-array and { items: [] } envelope shapes — backend has not pinned this yet). - Distinct loading / empty / error states. Error row surfaces an HTTP code and a Retry button. Empty state offers the create dialog. - Column shape aligned with the real Engagement schema (snake_case: name, client_name, c2_type, start_date, end_date). Dropped mock-only columns (operators, socAnalysts) — those land when backend exposes /engagements/:id/members and /engagements/:id/soc-sessions counts. engagementsApi.ts - fetchEngagements + createEngagement, both bound to /api/v1/engagements. - ENGAGEMENTS_QUERY_KEY exported so the dialog can invalidate without re-knowing the key. EngagementCreateDialog (frontend-design skill — new non-trivial component) - "Arm engagement" mission-control dialog. Backdrop is a graphite dim with a faint scanline overlay (no soft blur) — reads as "cockpit paused while you issue a command", not as a SaaS modal. - Surface --surface-3 with corner-marks and an amber hairline accent under the title; underline-style inputs that light amber on focus; label-system uppercase microtypography throughout. - Esc + outside-click close (suspended while the mutation is in flight). - Rudimentary tab focus trap. - 422 Pydantic errors map per-field via the last loc segment; 401/5xx surface as a generic top-of-form alert. - On 201 invalidates ['engagements'] and closes.
2026-05-23 04:26:48 +02:00
{query.isLoading
? 'loading …'
: query.isError
? 'error'
: `${String(engagements.length)} entries`}
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
</span>
}
>
feat(frontend): wire LoginPage + EngagementsPage + create dialog to backend LoginPage - RT mode now POSTs /api/v1/auth/login with controlled username/password fields. Success seeds the session cache via queryClient.setQueryData and navigates to /engagements. 401 surfaces as the generic "Identifiants invalides" — no echo of the backend detail (avoids user enumeration leaks). - SOC mode kept visually for masthead continuity but disabled with a "sprint 2" placeholder pointing at the deferred POST /api/v1/auth/soc/session endpoint. - Removed the sprint-0 mock role-picker. EngagementsPage - MOCK_ENGAGEMENTS dropped. useQuery against fetchEngagements (handles both bare-array and { items: [] } envelope shapes — backend has not pinned this yet). - Distinct loading / empty / error states. Error row surfaces an HTTP code and a Retry button. Empty state offers the create dialog. - Column shape aligned with the real Engagement schema (snake_case: name, client_name, c2_type, start_date, end_date). Dropped mock-only columns (operators, socAnalysts) — those land when backend exposes /engagements/:id/members and /engagements/:id/soc-sessions counts. engagementsApi.ts - fetchEngagements + createEngagement, both bound to /api/v1/engagements. - ENGAGEMENTS_QUERY_KEY exported so the dialog can invalidate without re-knowing the key. EngagementCreateDialog (frontend-design skill — new non-trivial component) - "Arm engagement" mission-control dialog. Backdrop is a graphite dim with a faint scanline overlay (no soft blur) — reads as "cockpit paused while you issue a command", not as a SaaS modal. - Surface --surface-3 with corner-marks and an amber hairline accent under the title; underline-style inputs that light amber on focus; label-system uppercase microtypography throughout. - Esc + outside-click close (suspended while the mutation is in flight). - Rudimentary tab focus trap. - 422 Pydantic errors map per-field via the last loc segment; 401/5xx surface as a generic top-of-form alert. - On 201 invalidates ['engagements'] and closes.
2026-05-23 04:26:48 +02:00
{query.isLoading ? (
<LoadingRow />
) : query.isError ? (
<ErrorRow error={query.error} onRetry={() => void query.refetch()} />
) : engagements.length === 0 ? (
<EmptyRow onCreate={() => setCreateOpen(true)} />
) : (
<EngagementsTable engagements={engagements} />
)}
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
</Panel>
feat(frontend): wire LoginPage + EngagementsPage + create dialog to backend LoginPage - RT mode now POSTs /api/v1/auth/login with controlled username/password fields. Success seeds the session cache via queryClient.setQueryData and navigates to /engagements. 401 surfaces as the generic "Identifiants invalides" — no echo of the backend detail (avoids user enumeration leaks). - SOC mode kept visually for masthead continuity but disabled with a "sprint 2" placeholder pointing at the deferred POST /api/v1/auth/soc/session endpoint. - Removed the sprint-0 mock role-picker. EngagementsPage - MOCK_ENGAGEMENTS dropped. useQuery against fetchEngagements (handles both bare-array and { items: [] } envelope shapes — backend has not pinned this yet). - Distinct loading / empty / error states. Error row surfaces an HTTP code and a Retry button. Empty state offers the create dialog. - Column shape aligned with the real Engagement schema (snake_case: name, client_name, c2_type, start_date, end_date). Dropped mock-only columns (operators, socAnalysts) — those land when backend exposes /engagements/:id/members and /engagements/:id/soc-sessions counts. engagementsApi.ts - fetchEngagements + createEngagement, both bound to /api/v1/engagements. - ENGAGEMENTS_QUERY_KEY exported so the dialog can invalidate without re-knowing the key. EngagementCreateDialog (frontend-design skill — new non-trivial component) - "Arm engagement" mission-control dialog. Backdrop is a graphite dim with a faint scanline overlay (no soft blur) — reads as "cockpit paused while you issue a command", not as a SaaS modal. - Surface --surface-3 with corner-marks and an amber hairline accent under the title; underline-style inputs that light amber on focus; label-system uppercase microtypography throughout. - Esc + outside-click close (suspended while the mutation is in flight). - Rudimentary tab focus trap. - 422 Pydantic errors map per-field via the last loc segment; 401/5xx surface as a generic top-of-form alert. - On 201 invalidates ['engagements'] and closes.
2026-05-23 04:26:48 +02:00
{createOpen && <EngagementCreateDialog onClose={() => setCreateOpen(false)} />}
</div>
);
}
function EngagementsTable({ engagements }: { engagements: Engagement[] }) {
return (
<table className="w-full" style={{ fontSize: 12.5 }}>
<thead>
<tr className="text-fg-subtle">
<Th>Name</Th>
<Th>Client</Th>
<Th>Status</Th>
<Th>C2</Th>
<Th>Window</Th>
<Th />
</tr>
</thead>
<tbody>
{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.name}
</div>
<div className="font-mono text-fg-faint" style={{ fontSize: '10.5px' }}>
{eng.id}
</div>
</Td>
<Td>{eng.client_name ?? <span className="text-fg-faint"></span>}</Td>
<Td>
<Pill tone={STATUS_TONE[eng.status]}>
<span className="status-dot" style={{ color: 'currentColor' }} />
{eng.status}
</Pill>
</Td>
<Td>
{eng.c2_type ? (
<span className="font-mono tabular">{eng.c2_type.toUpperCase()}</span>
) : (
<span className="text-fg-faint"></span>
)}
</Td>
<Td>
{eng.start_date || eng.end_date ? (
<span className="font-mono tabular text-fg-muted">
{eng.start_date ?? '—'} {eng.end_date ?? '—'}
</span>
) : (
<span className="text-fg-faint"></span>
)}
</Td>
<Td align="right">
<Link to="/runs">
<Button variant="ghost" size="sm">
Enter
</Button>
</Link>
</Td>
</tr>
))}
</tbody>
</table>
);
}
function LoadingRow() {
return (
<div className="px-4 py-12 flex items-center gap-3 text-fg-faint label-system">
<span className="status-dot text-fg-faint pulsing" />
<span>fetching engagements </span>
</div>
);
}
function ErrorRow({ error, onRetry }: { error: unknown; onRetry: () => void }) {
const message =
error instanceof ApiClientError
? `HTTP ${String(error.status)} · ${error.message}`
: 'Unable to reach the backend.';
return (
<div className="px-4 py-8 flex items-center justify-between gap-4">
<div>
<div className="label-system" style={{ color: 'var(--state-failed)' }}>
// Fetch failed
</div>
<div className="text-fg-muted mt-1" style={{ fontSize: 12.5 }}>
{message}
</div>
</div>
<Button variant="ghost" size="sm" onClick={onRetry}>
Retry
</Button>
</div>
);
}
function EmptyRow({ onCreate }: { onCreate: () => void }) {
return (
<div className="px-4 py-12 flex flex-col items-start gap-3">
<div className="label-system">// No engagements yet</div>
<p className="text-fg-muted" style={{ fontSize: 12.5 }}>
Create your first engagement to start composing scenarios and running them against client
infrastructure.
</p>
<Button variant="primary" size="sm" onClick={onCreate}>
+ New engagement
</Button>
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
</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>
);
}