Captures the full output of autodev existing-code Phase A through Step 4 (Code Testability Revision) for the Azaion UI workspace: - Step 1 Document: _docs/02_document/ (FINAL_report, architecture, glossary, components/, modules/, diagrams/, system-flows, module-layout) plus _docs/00_problem/ + _docs/01_solution/ + _docs/legacy/ + _docs/how_to_test + README. - Step 2 Architecture Baseline: architecture_compliance_baseline.md. - Step 3 Test Spec: _docs/02_document/tests/ (environment, test-data, blackbox/performance/resilience/security/ resource-limit tests, traceability-matrix), enum_spec_snapshot, expected_results/results_report.md (98 rows), plus the run-tests.sh + run-performance-tests.sh runners. - Step 4 Code Testability Revision: 01-testability-refactoring/ run dir (list-of-changes C01-C07, deferred_to_refactor, analysis/research_findings + refactoring_roadmap) and the 7 child task specs AZ-448..AZ-454 under _docs/02_tasks/todo/ plus _dependencies_table.md. - _docs/_autodev_state.md pins the cursor at Step 4 / refactor Phase 4 entry so /autodev resumes cleanly. Epic AZ-447 (UI testability gates) tracks the 7 child tasks that will land in subsequent commits. Co-authored-by: Cursor <cursoragent@cursor.com>
7.9 KiB
Module: src/features/settings/SettingsPage.tsx
Source:
src/features/settings/SettingsPage.tsx(107 lines) Topo batch: B4 (depends on B3:api/client,types/index)
Purpose
The per-tenant configuration screen. Three side-by-side panels:
Tenant (system settings: military unit, name, default camera
width and FoV), Directories (server-side filesystem paths for
videos, images, labels, results, thumbnails, GPS sat / route),
and Aircrafts (read-only list with star-toggle for default
selection). Replaces the legacy WPF SettingsWindow.xaml plus the
"system" tab (_docs/legacy/wpf-era.md §4).
Available to every authenticated user — Header does not gate
/settings behind a permission check. The backend is the authority
for who is allowed to write.
Public interface
export default function SettingsPage(): JSX.Element
No props.
Internal logic
-
State:
system: SystemSettings | null— loaded fromGET /api/annotations/settings/system.nulluntil the GET resolves; the panel does not render until then ({system && (...)}).dirs: DirectorySettings | null— analogous, fromGET /api/annotations/settings/directories.aircrafts: Aircraft[]— fromGET /api/flights/aircrafts.saving: boolean— disables the two Save buttons during a PUT.
-
Bootstrap effect (
useEffect([])):api.get<SystemSettings>('/api/annotations/settings/system').then(setSystem).catch(() => {}) api.get<DirectorySettings>('/api/annotations/settings/directories').then(setDirs).catch(() => {}) api.get<Aircraft[]>('/api/flights/aircrafts').then(setAircrafts).catch(() => {})Three independent calls, all silently swallowed on error. Empty UI on failure (no error banner). Flag for Step 4.
-
saveSystem():- Guard:
if (!system) return. setSaving(true).await api.put('/api/annotations/settings/system', system).setSaving(false).
No optimistic update needed (the PUT body is the local state). No re-fetch — assumes the server echoes the same values. Error path is missing: a thrown PUT leaves
saving: truepermanently (notry/finally). Flag for Step 4. - Guard:
-
saveDirs()— analogous againstPUT /api/annotations/settings/directories. Same missingtry/finallyissue. -
handleToggleDefault(a)— duplicate of the same handler inAdminPage:PATCH /api/flights/aircrafts/${a.id}with{ isDefault: !a.isDefault }then optimistic local flip. Two copies of the same logic in two pages — extract to a shared helper or toFlightContextin Step 8 (the legacy WPF had a singleAircraftService.SetDefault(...)). -
field(label, value, onChange, type='text')— local helper that renders a labeled<input>. Always controlled (value={value ?? ''}). Converts numeric inputs viaparseInt(v) || 0/parseFloat(v) || 0at the call site. Note:parseInt/parseFloaton an empty string returnsNaN, andNaN || 0is0— so clearing a numeric field silently writes0, notnull. Flag for Step 4 against theSystemSettingstype which permitsnull. -
Layout — three independent flex children:
- Tenant (
w-[300px]shrink-0):field()× 4 + Save. - Directories (
w-[300px]shrink-0):field()× 7 + Save. - Aircrafts (
flex-1 max-w-sm): list with star toggle.
- Tenant (
Dependencies
- Internal:
../../api/client—api.../../types—SystemSettings,DirectorySettings,Aircraft.
- External:
react(useState,useEffect),react-i18next(useTranslation).
Consumers (intra-repo)
src/App.tsx— mounted at the/settingsroute inside the protected tree.
Data models
SystemSettingsincludesid, name, militaryUnit, defaultCameraWidth, defaultCameraFoV, thumbnailWidth, thumbnailHeight, thumbnailBorder, generateAnnotatedImage, silentDetection. The page exposes only the first four — every other field is read on GET and echoed back on PUT untouched. Flag if a future cycle needs to expose thumbnails / silent-detection toggles.DirectorySettingshas all 7 directory fields exposed as text inputs. Path validation is server-side only.Aircraft(id, model, type, isDefault) — same shape as inAdminPage.
Configuration
- i18n keys consumed:
settings.tenant,settings.directories,settings.aircrafts,settings.save. (Confirmed insrc/i18n/en.json.) Hardcoded English labels for every form field ("Military Unit", "Name", "Default Camera Width", "Default Camera FoV", "Videos Dir", "Images Dir", "Labels Dir", "Results Dir", "Thumbnails Dir", "GPS Sat Dir", "GPS Route Dir"). Flag for Step 4. - Tailwind tokens:
bg-az-panel,bg-az-bg,bg-az-orange,text-az-{text,muted,orange},bg-az-blue/20,bg-az-green/20,text-az-{blue,green},border-az-border. Defined insrc/index.css.
External integrations
| Method | Path | Purpose |
|---|---|---|
GET |
/api/annotations/settings/system |
Load tenant config |
PUT |
/api/annotations/settings/system |
Save tenant config |
GET |
/api/annotations/settings/directories |
Load directory paths |
PUT |
/api/annotations/settings/directories |
Save directory paths |
GET |
/api/flights/aircrafts |
Load aircraft list |
PATCH |
/api/flights/aircrafts/{id} |
Toggle isDefault |
Routed by nginx.conf to annotations/ and flights/ backends.
Security
- No client-side write authorization check — the page renders the
Save buttons for every user. The backend (
annotations/service) is the authority. Document insecurity_approach.md(Step 6). - Hardcoded internal directory paths are not present in this
file (unlike
AdminPage's hardcoded GPS device IP) — every value is server-supplied. Good. - Path inputs are free-text — server-side path traversal
validation is mandatory. Verify the
annotations/service rejects e.g.../../etc/passwd. Flag for Step 6. savingstate can stick on PUT failure because there is notry/finally, leaving the Save button permanently disabled. Step 4 candidate (matches theAdminPage"AI/GPS save buttons do nothing" pattern — there is a clear lack of UX-level error handling across both admin/settings pages).
Tests
None.
Notes / open questions
- Numeric clear-to-zero pitfall:
parseInt('') || 0writes0for an empty input, notnull. This silently overwrites a legitimatenull(per theSystemSettingstype, all four numeric fields are nullable). Step 4 fix: detect empty input and passnull. - Aircraft toggle handler is duplicated between
AdminPageandSettingsPage— extract to a shared helper. Step 8. - No optimistic concurrency: two admins editing system settings
simultaneously will overwrite each other. The
idfield is sent back on PUT but noversion/etag/If-Matchheader. Flag for Step 6 problem-extraction. Backend may not support optimistic concurrency yet. - The Aircrafts panel is read-only-ish here but allows the
star-toggle, same as
AdminPage. The duplication suggests an open question: is this intentional (settings is "personal preferences", admin is "global config", but default-aircraft is a global setting) or accidental? Surface to the user in Step 6 problem extraction. savingis a single global flag even though the page has two independent Save buttons (system / dirs). A user who clicks "Save System" then quickly clicks "Save Dirs" while the first PUT is in flight will see the Dirs button disabled too. Acceptable given the latency budget; flag if both saves become slow.thumbnailWidth/Height/BorderandgenerateAnnotatedImage,silentDetectioninSystemSettingsare not exposed in the UI but are echoed back on PUT. If a future cycle adds them, ensure the GET → PUT round-trip preserves any concurrent change made by another client between the GET and the PUT.