MAESTRO: add analysis controls panel

This commit is contained in:
Mariusz Banach
2026-02-18 01:12:21 +01:00
parent af0c4bebcb
commit 426ea49393
2 changed files with 106 additions and 2 deletions

View File

@@ -24,13 +24,13 @@ This phase implements the test selection panel and analysis configuration contro
- [x] T021 [US2] Create `backend/app/schemas/tests.py` (response schema) and `backend/app/routers/tests.py` — FastAPI router with `GET /api/tests` returning all available tests (id, name, category) from scanner registry. Register router in `backend/app/main.py`. Verify `test_tests_router.py` passes (TDD Green) - [x] T021 [US2] Create `backend/app/schemas/tests.py` (response schema) and `backend/app/routers/tests.py` — FastAPI router with `GET /api/tests` returning all available tests (id, name, category) from scanner registry. Register router in `backend/app/main.py`. Verify `test_tests_router.py` passes (TDD Green)
- [x] T022 [US2] Write failing tests (TDD Red) in `frontend/src/__tests__/TestSelector.test.tsx` (render with mocked list, select/deselect all, search filtering) and `frontend/src/__tests__/AnalysisControls.test.tsx` (render, toggle states, keyboard accessibility) - [x] T022 [US2] Write failing tests (TDD Red) in `frontend/src/__tests__/TestSelector.test.tsx` (render with mocked list, select/deselect all, search filtering) and `frontend/src/__tests__/AnalysisControls.test.tsx` (render, toggle states, keyboard accessibility)
- [x] T023 [P] [US2] Create `frontend/src/components/TestSelector.tsx` — dropdown with checkboxes per test, fetches list from `GET /api/tests`, "Select All"/"Deselect All" buttons (FR-04), text search/filter (FR-23), grouped by vendor/category (FR-24), FontAwesome icons. Verify `TestSelector.test.tsx` passes (TDD Green) - [x] T023 [P] [US2] Create `frontend/src/components/TestSelector.tsx` — dropdown with checkboxes per test, fetches list from `GET /api/tests`, "Select All"/"Deselect All" buttons (FR-04), text search/filter (FR-23), grouped by vendor/category (FR-24), FontAwesome icons. Verify `TestSelector.test.tsx` passes (TDD Green)
- [ ] T024 [P] [US2] Create `frontend/src/components/AnalysisControls.tsx` — panel containing TestSelector, DNS resolution toggle (off by default, FR-18), decode-all toggle (FR-19), FontAwesome toggle icons, keyboard accessible (NFR-02). Verify `AnalysisControls.test.tsx` passes (TDD Green) - [x] T024 [P] [US2] Create `frontend/src/components/AnalysisControls.tsx` — panel containing TestSelector, DNS resolution toggle (off by default, FR-18), decode-all toggle (FR-19), FontAwesome toggle icons, keyboard accessible (NFR-02). Verify `AnalysisControls.test.tsx` passes (TDD Green)
## Completion ## Completion
- [ ] `pytest backend/tests/api/test_tests_router.py` passes - [ ] `pytest backend/tests/api/test_tests_router.py` passes
- [ ] `GET /api/tests` returns all 106+ tests with id, name, and category - [ ] `GET /api/tests` returns all 106+ tests with id, name, and category
- [ ] All vitest tests pass: `npx vitest run src/__tests__/TestSelector.test.tsx src/__tests__/AnalysisControls.test.tsx` - [x] All vitest tests pass: `npx vitest run src/__tests__/TestSelector.test.tsx src/__tests__/AnalysisControls.test.tsx`
- [ ] Test selector renders all 106+ tests with checkboxes - [ ] Test selector renders all 106+ tests with checkboxes
- [ ] Select All / Deselect All buttons work correctly - [ ] Select All / Deselect All buttons work correctly
- [ ] Search/filter narrows visible tests by name - [ ] Search/filter narrows visible tests by name

View File

@@ -0,0 +1,104 @@
"use client";
import type { KeyboardEvent } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faToggleOff,
faToggleOn,
faGlobe,
faCode,
} from "@fortawesome/free-solid-svg-icons";
import type { AnalysisConfig } from "../types/analysis";
import TestSelector from "./TestSelector";
type AnalysisControlsProps = {
config: AnalysisConfig;
onChange: (next: AnalysisConfig) => void;
};
const handleToggleKeyDown = (
event: KeyboardEvent<HTMLButtonElement>,
onToggle: () => void,
): void => {
if (event.key === "Enter" || event.key === " " || event.key === "Spacebar") {
event.preventDefault();
onToggle();
}
};
export default function AnalysisControls({ config, onChange }: AnalysisControlsProps) {
const updateTests = (nextTestIds: number[]) => {
onChange({ ...config, testIds: nextTestIds });
};
const toggleResolve = () => {
onChange({ ...config, resolve: !config.resolve });
};
const toggleDecodeAll = () => {
onChange({ ...config, decodeAll: !config.decodeAll });
};
return (
<section className="flex flex-col gap-6">
<div className="rounded-2xl border border-info/10 bg-surface p-6 shadow-[0_0_40px_rgba(15,23,42,0.25)]">
<div className="flex items-center justify-between text-xs uppercase tracking-[0.2em] text-info/90">
<span>Analysis Controls</span>
<span className="font-mono text-[10px] text-text/50">US2</span>
</div>
<div className="mt-4 grid gap-3 sm:grid-cols-2">
<div className="flex items-center justify-between gap-4 rounded-xl border border-info/10 bg-background/40 p-4">
<div className="flex items-start gap-3">
<div className="mt-1 rounded-full border border-info/20 bg-background/60 p-2 text-xs text-info/80">
<FontAwesomeIcon icon={faGlobe} />
</div>
<div className="flex flex-col">
<span className="text-sm font-semibold text-text/80">DNS Resolution</span>
<span className="text-xs text-text/50">Resolve hostnames while analyzing.</span>
</div>
</div>
<button
type="button"
role="switch"
aria-checked={config.resolve}
aria-label="Toggle DNS resolution"
data-testid="toggle-resolve"
className="inline-flex items-center gap-2 rounded-full border border-info/20 bg-background/60 px-3 py-2 text-xs text-text/70 transition hover:border-info/40 hover:text-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-info"
onClick={toggleResolve}
onKeyDown={(event) => handleToggleKeyDown(event, toggleResolve)}
>
<FontAwesomeIcon icon={config.resolve ? faToggleOn : faToggleOff} />
{config.resolve ? "On" : "Off"}
</button>
</div>
<div className="flex items-center justify-between gap-4 rounded-xl border border-info/10 bg-background/40 p-4">
<div className="flex items-start gap-3">
<div className="mt-1 rounded-full border border-info/20 bg-background/60 p-2 text-xs text-info/80">
<FontAwesomeIcon icon={faCode} />
</div>
<div className="flex flex-col">
<span className="text-sm font-semibold text-text/80">Decode All</span>
<span className="text-xs text-text/50">Decode every encoded header value.</span>
</div>
</div>
<button
type="button"
role="switch"
aria-checked={config.decodeAll}
aria-label="Toggle decode all"
data-testid="toggle-decode-all"
className="inline-flex items-center gap-2 rounded-full border border-info/20 bg-background/60 px-3 py-2 text-xs text-text/70 transition hover:border-info/40 hover:text-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-info"
onClick={toggleDecodeAll}
onKeyDown={(event) => handleToggleKeyDown(event, toggleDecodeAll)}
>
<FontAwesomeIcon icon={config.decodeAll ? faToggleOn : faToggleOff} />
{config.decodeAll ? "On" : "Off"}
</button>
</div>
</div>
</div>
<TestSelector selectedTestIds={config.testIds} onSelectionChange={updateTests} />
</section>
);
}