34 Commits

Author SHA1 Message Date
Knacky
8b5b5d94d8 fix(frontend): floating dropdowns and matrix modal frame use bg-paper
MitreTechniquePicker dropdown, SimulationList overflow menu,
ExportEngagementButton format menu, and MitreMatrixModal dialog frame
all used bg-canvas as their surface color. With the tinted canvas
(#f3f5f8), these floating surfaces appeared slightly grey instead of
clean white. Switched to bg-paper (#ffffff light / #1f2937 dark).
MitreMatrixModal cell hover (bg-canvas) intentionally preserved —
matrix cells sit on canvas, not on paper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 11:12:07 +02:00
Knacky
76bcb04c8f style(frontend): inputs + outline buttons inside cards use bg-paper
.text-input, .btn-outline, .btn-outline-ink were using bg-canvas which
now resolves to the tinted #f3f5f8 instead of white, making inputs and
Cancel/outlined buttons visually gritty on white paper cards. Switching
all three to bg-paper restores white surfaces inside cards in light mode.
Dark mode unaffected (canvas/paper both resolve correctly there).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 11:11:59 +02:00
Knacky
88b97cef2e feat(frontend): 2-column layout for EngagementFormPage in edit mode
In edit mode with canEditEngagements, wraps [form | C2ConfigCard] in a
lg:grid-cols-2 responsive grid with items-start alignment. Stacks to
single column on screens narrower than lg. In create mode, retains the
existing max-w-2xl single-column layout. No logic changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 11:01:09 +02:00
Knacky
e4b1d6cb57 style(frontend): tint canvas light to #f3f5f8 for cards-on-canvas contrast
Canvas and paper were both #ffffff in light mode — cards only separated
by a 1px hairline, causing eye fatigue. Tints the canvas token to a
very pale cool neutral (#f3f5f8) so paper cards lift naturally without
shadow or radius, preserving brutalism. Dark mode tokens unchanged.
Updates DESIGN.md Surface section with rationale.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 11:01:00 +02:00
Knacky
184a2a16c9 fix(frontend): a11y on clickable rows + correct c2 source field + pill metric alignment (sprint 8 design-review)
F1: add tabIndex/role/onKeyDown/aria-expanded to C2TasksPanel expander rows and
    C2CallbackPicker callback rows; focus-visible ring via Tailwind utilities
F2: add source:'mimic'|'import' to C2TaskListItem; C2TasksPanel reads task.source
    instead of mapping_applied for the Source badge label
F3: align C2TaskStatusBadge and C2CallbackPicker Active/Inactive pill metrics to
    py-[6px] text-[14px] font-medium (matches SimulationStatusBadge / StatusBadge)
F4: replace hand-rolled Source pill class string with badge-pill-outline recipe
Tests: 212/212 passing (+3 new: Enter/Space key on expander, Enter key on callback row)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 20:22:45 +02:00
Knacky
7ff153905b feat(frontend): c2 tasks panel + history import (sprint 8 phase 2)
- Add getC2Tasks / listCallbackHistory / importC2 API functions + types
- useC2Tasks with 2500ms polling (stops when all tasks completed)
- useC2CallbackHistory, useImportC2 hooks
- C2TaskStatusBadge, C2TasksPanel (expandable output rows, polling indicator)
- C2CallbackPicker extracted as shared component (reused in both modals)
- ImportC2HistoryModal: 2-step callback picker → paginated history table
- SimulationFormPage: RT card + tasks panel share left grid column; Import C2 history button
- 37 new tests (api/c2, C2TasksPanel, ImportC2HistoryModal, SimulationFormPage panel visibility)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 20:11:12 +02:00
Knacky
5ff6ae8940 feat(frontend): c2 config card + execute modal (sprint 8 phase 1)
- frontend/src/api/c2.ts: 6 typed API calls (getC2Config, putC2Config,
  deleteC2Config, testC2Config, listCallbacks, executeC2) following the
  frozen M1+M2 backend contracts
- frontend/src/api/types.ts: C2Config, C2ConfigInput, C2TestResult,
  C2Callback, C2CallbacksResponse, C2Task, C2ExecuteInput, C2ExecuteResponse
- frontend/src/hooks/useC2.ts: useC2Config, useUpdateC2Config,
  useDeleteC2Config, useTestC2Config, useC2Callbacks, useExecuteC2
- frontend/src/components/C2ConfigCard.tsx: engagement-scoped C2 config
  card (url + write-only token + verify-tls + save/delete/test-connection),
  503 disabled state, ConfirmDialog on delete
- frontend/src/components/ExecuteViaC2Modal.tsx: callback picker table
  (mono data cells), commands textarea pre-filled from rt.commands,
  Launch disabled until row selected + non-empty commands
- frontend/src/pages/EngagementFormPage.tsx: embed C2ConfigCard in edit
  mode only, admin+redteam only (canEditEngagements gate)
- frontend/src/pages/SimulationFormPage.tsx: Execute via C2 button in RT
  card, visible only when !isDone && canEditRT && hasC2Config; opens modal
- Tests: 33 new tests across api/c2, components/C2ConfigCard,
  components/ExecuteViaC2Modal, EngagementFormPage, SimulationFormPage
  (172 total, 139 baseline + 33 new, all passing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 19:50:11 +02:00
Knacky
1997a8c621 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>
2026-06-10 18:17:54 +02:00
Knacky
573281f454 fix(design): code-reviewer polish — dedupe tag-mitre recipe, drop dead boxShadow tokens, cover StatusBadge classes
F1: MitreTechniqueTag inline classes replaced with className="tag-mitre gap-xxs"
    (index.css .tag-mitre is now the single source of truth for technique tags).
F2: boxShadow block removed from tailwind.config.ts — no component referenced
    shadow-soft-lift / shadow-floating / *-dark; DESIGN.md §Don'ts prohibits shadows.
F4: StatusBadge.test.tsx gains 3 class assertions (bg-warn-soft / bg-primary-soft / bg-cloud)
    mirroring SimulationStatusBadge.test.tsx pattern. Test count: 136 → 139.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 05:53:53 +02:00
Knacky
6995c4c860 fix(design): address design-reviewer findings F1-F6 — nav slab, spinner, badge coverage, mono discipline
F3: nav-bar-top bg-paper → bg-slab text-slab-text (3-band slab anchoring restored).
    NavLinks: text-slab-muted default, text-slab-text + border-primary active.
    Logo span: text-ink → text-slab-text.
F2: (you) label extracted from font-mono td into adjacent font-sans span.
F1: Loader2 circular spinner → EXPORTING… text with animate-pulse (terminal-SOC compatible).

Screenshots regenerated:
- All 8 pages light+dark (01→10)
- 05-simulation-form-edit light+dark (F6)
- 11-mitre-matrix-modal light+dark (F6)
- 12-toast-success light+dark (F6)
- 13-confirm-dialog light+dark (F6)
- admin-light/dark-open/closed regenerated from HEAD (F4)

F4: StatusBadge.tsx confirmed single code path — planned → bg-warn-soft (no divergence in code).
    Divergence in prior captures was stale cache; regenerated admin-* confirm consistency.
F5: Simulations seeded (pending/in_progress/review_required/done) via API;
    10-sim-list-badges shows all 4 semantic badge colors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 19:43:08 +02:00
Knacky
0e69eb901c test(frontend): update vitest assertions for new design tokens 2026-06-09 18:46:03 +02:00
Knacky
5cc830554c refactor(pages): apply terminal-SOC aesthetic to all 8 pages 2026-06-09 18:44:47 +02:00
Knacky
ec7800ae38 refactor(components): squared shapes + mono data cells across all components 2026-06-09 18:42:26 +02:00
Knacky
c791e50108 feat(design): bundle JetBrains Mono for data cells 2026-06-09 18:39:13 +02:00
Knacky
64da94f10d feat(design): refresh tailwind tokens for brutalist terminal aesthetic 2026-06-09 18:38:43 +02:00
Knacky
123d9812bc test: cover Content-Disposition fallback in ExportEngagementButton 2026-06-08 18:20:29 +02:00
Knacky
25877c4092 test: ExportEngagementButton + EngagementDetailPage RBAC tests
9 tests for ExportEngagementButton (render, open, close-outside,
Escape, per-format trigger, loading state, error toast).
3 RBAC tests for EngagementDetailPage (admin/redteam see Export,
soc does not). Total: 121 → 133 vitest passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 18:04:56 +02:00
Knacky
100441bdeb feat: ExportEngagementButton + exports API client
Add split-button dropdown [Export ▼] on EngagementDetailPage that
downloads engagement as Markdown, CSV, or PDF via
GET /api/engagements/<id>/export?format=md|csv|pdf.

Both halves open the dropdown (no default left-click action).
RBAC-gated with canEditEngagements (admin + redteam only).
Loading state per item, toast on error, click-outside + Escape close.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 18:04:49 +02:00
Knacky
33a0ca30bb fix(frontend): sprint 5 post-code-review — dropdown close-on-outside + empty-state dropdown
- useEffect pointerdown + Escape listeners when dropdown open (NIT 1)
- empty state now renders NewSimulationDropdown instead of plain Link (NIT 2)
- 3 new Vitest: close-on-outside, close-on-Escape, empty-state has dropdown

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 07:02:34 +02:00
Knacky
20783118ee fix(frontend): sprint 5 design-review — dropdown dark + Plus icon + re-shoots
- shadow-floating dark:shadow-floating-dark on dropdown menu (Fix 1)
- hover:bg-cloud dark:hover:bg-fog on dropdown items (Fix 2)
- Plus icon + "New" label on split-button primary half (Fix 3)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 06:50:19 +02:00
Knacky
2b700115e8 fix(frontend): sprint 5 — correct API path /simulation-templates → /templates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 06:43:33 +02:00
Knacky
90fc5bab6c feat(frontend): sprint 5 — templates CRUD pages + nav + picker modal + dropdown
- types.ts: SimulationTemplate, SimulationTemplateCreateInput, SimulationTemplatePatchInput,
  extend SimulationCreateInput with template_id
- api/templates.ts: listTemplates, getTemplate, createTemplate, updateTemplate, deleteTemplate
- hooks/useTemplates.ts: useTemplates, useTemplate, useCreateTemplate, useUpdateTemplate,
  useDeleteTemplate (TanStack Query, invalidates ["templates"])
- TemplatesListPage: /admin/templates — table (name, MITRE count, created by, updated),
  New/Edit/Delete actions, loading/error/empty states
- TemplateFormPage: /admin/templates/new + /admin/templates/:id/edit — controlled form
  with inline MITRE field (picker + matrix modal), ConfirmDialog for delete
- TemplatePickerModal: reusable modal listing templates with empty state (AC-27.6)
- SimulationList: replace "New simulation" link with split-button dropdown
  (Blank → /simulations/new | From template… → TemplatePickerModal + POST template_id)
- Layout: "Templates" nav link (admin | redteam, before "Users")
- App.tsx: /admin/templates routes gated roles=["admin","redteam"]
- 26 new Vitest tests (118 total, 92 original preserved)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 06:36:10 +02:00
Knacky
e99286ef8e fix(frontend): sprint 4 post-code-review — btn-ink uses slab token + unify New CTA label
- btn-ink hover: bg-slab-hover (unnecessary new token) → bg-paper (existing token,
  same #1f2937 value in dark, avoids token sprawl)
- tailwind.config.ts: remove slab-hover token added in fc530af
- EngagementsListPage: both CTAs unified to "+ New" (≤8 chars convention, AC-17.2)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 20:40:06 +02:00
Knacky
fc530af78b fix(frontend): post-code-review NITs — btn-ink uses @apply bg-slab, unify New engagement label
- btn-ink: replace inline background-color #111827 with @apply bg-slab (and add
  slab-hover token #1f2937 for the hover state) so the token system is consistent
- EngagementsListPage: header button "+ New" → "+ New engagement" to match
  empty-state CTA label

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 20:39:01 +02:00
Knacky
9964d058f4 fix(frontend): sprint 4 design-review — slab token + UsersAdmin alignment + dark hairlines + badge contrast
- bump dark hairline from #374151 → #4b5563 for visible table borders
- topbar header bg-canvas → bg-paper for dark-mode lift vs canvas body
- UsersAdminPage create-form: Option A structural 3-row grid (labels / inputs / hints)
  to fix AC-17.3 alignment; removes FormField wrapper that caused row-height misalignment
- EngagementsListPage: replace text "+ New" with lucide Plus icon per design spec

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 20:28:32 +02:00
Knacky
892692f3b8 fix(frontend): post-design-review — slab token split + badge contrast + modal backdrop + dark shadows
- Add fixed slab/slab-text/slab-muted tokens so utility strip and footer never
  invert to near-white in dark mode (root token split: ink is themed text,
  slab is fixed dark surface)
- btn-ink uses fixed #111827 so confirm dialogs stay dark-on-dark readable
- Toast error surface switched to slab; success uses text-white (not text-ink-on)
- StatusBadge active and SimulationStatusBadge review_required/done use text-white
  instead of text-canvas/text-ink-on (prevents near-black text on colored pill
  in dark mode)
- Modal backdrops (MitreMatrixModal, ConfirmDialog) switched to .modal-backdrop
  class (fixed rgba(0,0,0,0.6)) instead of bg-ink/60 which turned near-white
- Card shadow lifted in dark mode via .dark .card-product override
- MitreMatrixModal panel uses shadow-floating-dark in dark mode
- UsersAdminPage form: items-start + explicit label-height spacer on button
  column for pixel-perfect baseline alignment (AC-17.3 structural fix)

92/92 tests passing, typecheck and lint clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 20:19:16 +02:00
Knacky
f5ea9d16af feat(frontend): sprint 4 — dark mode + matrix overhaul + tactic selection + done read-only + UI polish
US-17: fix duplicate "Create engagement" button, icon conventions (Save/RotateCcw/Grid2x2), UsersAdminPage form baseline alignment
US-18: done status fully read-only + Reopen button (done → review_required) for all roles
US-19: invalidate engagement queries on simulation PATCH/transition for auto-status propagation
US-20: MitreMatrixModal rewritten — CSS grid 12-column layout, no horizontal scroll, attack.mitre.org compact look
US-21: tactic header clickable in matrix, tactic chips (MitreTacticTag) in field, single atomic PATCH with technique_ids + tactic_ids
US-22: MitreTechniquesField chips-only area + inline search input + matrix icon button; chips show ID-only (name in title=)
US-23: useTheme hook — 3-state light/dark/system, CSS variables, Tailwind darkMode class, localStorage persistence

92/92 tests passing, typecheck and lint clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 20:06:01 +02:00
Knacky
39f4076a81 fix(frontend): sprint 3 post-review — real dedup test + Apply 0 guard + Link stopPropagation
- MitreTechniquesField test: rewrite dedup test to actually exercise picker
  selection path — types query, waits for option, fires pointerDown,
  asserts no PATCH sent (dedup guard in handleSelect now truly covered)
- MitreMatrixModal: Apply button disabled only when totalSelected === 0
  AND initialSelection.length === 0 (no-op case); when totalSelected === 0
  but initialSelection was non-empty, shows "Clear all" and stays enabled
  so user can explicitly wipe the list
- MitreMatrixModal tests: update disabled test to match "Clear all" label,
  add "Clear all" enabled + onApply([]) path test
- SimulationList: stopPropagation on Name <Link> to prevent double-navigate
  with row onClick handler

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:23:46 +02:00
Knacky
771483f3b0 feat(frontend): sprint 3 — multi-technique MITRE selection + matrix modal
- types: replace mitre_technique_id/name scalars with techniques:MitreTechnique[]
  on Simulation; add MitreTactic/MitreMatrixTechnique/MitreMatrixSubtechnique;
  SimulationPatchInput now uses technique_ids:string[]
- api/mitre.ts: add getMitreMatrix() → GET /api/mitre/matrix
- hooks/useMitre: add useMitreMatrix(enabled) with staleTime:Infinity
- MitreTechniquePicker: clean rewrite — onSelect(technique) one-shot, resets
  input after selection, no incoming value props
- MitreTechniqueTag: chip component with id+name and × remove button
- MitreMatrixModal: tactic columns (220px fixed), expand/collapse subtechniques,
  search filter (auto-expands parent on sub match), selection state, focus trap
  (Tab wrap, Escape, search autofocus), backdrop click cancel, Apply N techniques
- MitreTechniquesField: orchestrates tags+picker+matrix with auto-save PATCH on
  every add/remove/Apply, dedup guard, disabled read-only mode for SOC
- SimulationFormPage: swap MitreTechniquePicker for MitreTechniquesField; remove
  technique state from RT form (techniques have independent auto-save cycle)
- SimulationList: MITRE column → T1059 +2 counter format, — when empty
- Tests: 84 passing (13 test files); new suites for Tag, Field, Modal;
  MitreTechniquePicker + SimulationFormPage + SimulationList adapted to new API

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:04:23 +02:00
Knacky
2a7d27bf02 fix(frontend): post-QA sprint 2 — i18n + alignment + textarea + action bar layout
- Translate all remaining French strings to English (toasts, buttons, banner)
- Fix UsersAdminPage create-form grid alignment: items-start + self-end on button wrapper
- Change execution_result from TextInput to TextArea (5 rows, multiline)
- Replace split Save RT / Save SOC footers + workflow div with a single sticky
  action bar (Save Red Team | Save SOC | Mark for review | Close | Delete)
- Update Vitest assertions to use English button labels

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 16:08:46 +02:00
Knacky
cf0e8a8a6b fix(frontend): sprint 2 review fixes — MitrePicker input retention + SPA navigation
- MitreTechniquePicker: use hasHydratedFromProps ref so onChange(null,null) on
  keystrokes does not propagate back and wipe inputValue mid-stroke
- SimulationList: replace window.location.href with useNavigate(); drop
  redundant stopPropagation on inner Link
- SimulationFormPage: hoist canSaveSoc flag; replace duplicated ternary
  expressions at onSubmit and button visibility guard
- SimulationFormPage: drop dead .replace(' ', 'T') on executed_at (isoformat
  always emits 'T')
- Tests: add regression for MitrePicker input retention and SimulationList
  SPA navigation (63 tests total)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:22:48 +02:00
Knacky
c9032a9057 fix(frontend): post-review fixes sprint 2
- MitreTechniquePicker: guard sync effect while dropdown open to prevent
  mid-stroke wipe when onChange(null,null) propagates back as null props
- SimulationList: replace window.location.href with navigate() to keep
  TanStack Query cache intact on row click
- SimulationFormPage: simplify redundant ternary on SOC form onSubmit
- SimulationFormPage: remove dead .replace(' ','T') on executed_at
  (backend already returns ISO 8601 with T separator)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:22:05 +02:00
Knacky
765bb5a1a4 feat(frontend): sprint 2 — simulations UI + MITRE picker
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:13:14 +02:00
Knacky
5104f7c429 feat: sprint 1 — auth + CRUD engagements
Ship the first feature end-to-end on the UI: users log in with JWT,
admins manage user accounts, and any authenticated user (per RBAC)
can create, list, view, edit, and delete engagements.

Backend (Flask + SQLAlchemy + SQLite, 63 pytest)
- User / Engagement models, Alembic 0001 initial schema
- argon2 password hashing, JWT bearer (60-min TTL), @login_required
  and @role_required decorators
- 13 API endpoints under /api/*, including last-admin protection on
  DELETE/PATCH user and JSON 404 on unknown /api/* paths
- `flask create-admin` CLI with duplicate / short-password handling

Frontend (React + Vite + Tailwind + TanStack Query, 20 vitest)
- Inter font bundled locally (no CDN), DESIGN.md tokens in Tailwind
- LoginPage / EngagementsList / EngagementForm / EngagementDetail /
  UsersAdmin pages with role-aware UI
- Layout, ProtectedRoute, StatusBadge, FormField, LoadingState,
  ErrorState, EmptyState, Toast + provider
- Axios client: Bearer interceptor, 401 → purge + /login + "Session
  expirée" toast, 403 → "Accès refusé" toast (declarative <Navigate>
  for already-authed users, Fragment-keyed admin user rows)

Deployment
- Single multistage Dockerfile (node:20-alpine → python:3.12-slim)
- docker/entrypoint.sh runs `flask db upgrade` before `flask run`
- Makefile: build/start/stop/restart/update/logs/create-admin/
  update-mitre/test-{backend,frontend,e2e}/clean
- .env.example documenting MIMIC_JWT_SECRET / MIMIC_DB_PATH / MIMIC_PORT
- SQLite at /data/mimic.sqlite on named volume mimic-data

Acceptance suite (Playwright, 36 tests, all 27 ACs)
- e2e/ scaffold with playwright.config + auth/api fixtures
- One spec per user story (us1-bootstrap through us6-deployment)
- Portable via MIMIC_CONTAINER_CMD / MIMIC_BASE_URL (docker or podman)

Docs
- README.md with quick-start and architecture overview
- CHANGELOG.md updated with Sprint 1 deliverables
- pyrightconfig.json so the Python LSP sees backend/.venv and
  resolves the `backend.app.*` absolute imports

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 09:37:53 +02:00