Files
ui/_docs/02_document/modules/src__features__settings__SettingsPage.md
T
Oleksandr Bezdieniezhnykh 17d5bb45e7
ci/woodpecker/push/build-arm Pipeline was successful
[AZ-485] [AZ-486] Cycle 1 docs refresh (Step 13)
Phase B cycle 1 was a structural refactor only: F4 (barrel imports +
STC-ARCH-01) and F7 (endpoint builders + STC-ARCH-02). This commit
brings docs in line with source after the cycle, no code changes.

Module docs (12 consumers): swap every /api/<service>/... literal in
code snippets and integration tables for the matching endpoints.*
builder; note the barrel import migration in Dependencies.

New module doc: src__api__endpoints.md (public surface, F4 barrel
re-export note, STC-ARCH-02 enforcement, contract-test reference).

Architecture compliance baseline: mark F4 + F7 CLOSED with commit
hashes (23746ec, 8a461a2).

01_api-transport component description: add endpoints.ts + barrel to
Internal Interfaces, close the F7 caveat, extend Module Inventory.

ripple_log_cycle1.md: Task Step 0.5 reverse-dep analysis records the
import-graph closure (no extra docs needed beyond the direct set).

Carry-over reports landed alongside the docs:
- test_run_report_phase_b_cycle1.md (Step 11 outcome)
- implementation_report_refactor_phase_b_cycle1.md (cycle summary)

State file: trimmed to the autodev <30-line target; Steps 14 + 15
recorded as SKIPPED with rationale (no security or perf surface
changed in this cycle); pointer moved to Step 16 (Deploy).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 00:01:04 +03:00

8.4 KiB
Raw Blame History

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 from GET endpoints.annotations.settingsSystem() (= /api/annotations/settings/system). null until the GET resolves; the panel does not render until then ({system && (...)}).
    • dirs: DirectorySettings | null — analogous, from GET endpoints.annotations.settingsDirectories() (= /api/annotations/settings/directories).
    • aircrafts: Aircraft[] — from GET endpoints.flights.aircrafts() (= /api/flights/aircrafts).
    • saving: boolean — disables the two Save buttons during a PUT.
  • Bootstrap effect (useEffect([])):

    api.get<SystemSettings>(endpoints.annotations.settingsSystem()).then(setSystem).catch(() => {})
    api.get<DirectorySettings>(endpoints.annotations.settingsDirectories()).then(setDirs).catch(() => {})
    api.get<Aircraft[]>(endpoints.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():

    1. Guard: if (!system) return.
    2. setSaving(true).
    3. await api.put(endpoints.annotations.settingsSystem(), system).
    4. 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: true permanently (no try/finally). Flag for Step 4.

  • saveDirs() — analogous against PUT endpoints.annotations.settingsDirectories(). Same missing try/finally issue.

  • handleToggleDefault(a) — duplicate of the same handler in AdminPage: PATCH endpoints.flights.aircraft(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 to FlightContext in Step 8 (the legacy WPF had a single AircraftService.SetDefault(...)).

  • field(label, value, onChange, type='text') — local helper that renders a labeled <input>. Always controlled (value={value ?? ''}). Converts numeric inputs via parseInt(v) || 0 / parseFloat(v) || 0 at the call site. Note: parseInt / parseFloat on an empty string returns NaN, and NaN || 0 is 0 — so clearing a numeric field silently writes 0, not null. Flag for Step 4 against the SystemSettings type which permits null.

  • 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.

Dependencies

  • Internal:
    • ../../api (barrel) — api, endpoints. (Since AZ-485 / F4 + AZ-486 / F7.)
    • ../../typesSystemSettings, DirectorySettings, Aircraft.
  • External: react (useState, useEffect), react-i18next (useTranslation).

Consumers (intra-repo)

  • src/App.tsx — mounted at the /settings route inside the protected tree.

Data models

  • SystemSettings includes id, 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.
  • DirectorySettings has all 7 directory fields exposed as text inputs. Path validation is server-side only.
  • Aircraft (id, model, type, isDefault) — same shape as in AdminPage.

Configuration

  • i18n keys consumed: settings.tenant, settings.directories, settings.aircrafts, settings.save. (Confirmed in src/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 in src/index.css.

External integrations

Method Builder → Path Purpose
GET endpoints.annotations.settingsSystem()/api/annotations/settings/system Load tenant config
PUT endpoints.annotations.settingsSystem()/api/annotations/settings/system Save tenant config
GET endpoints.annotations.settingsDirectories()/api/annotations/settings/directories Load directory paths
PUT endpoints.annotations.settingsDirectories()/api/annotations/settings/directories Save directory paths
GET endpoints.flights.aircrafts()/api/flights/aircrafts Load aircraft list
PATCH endpoints.flights.aircraft(id)/api/flights/aircrafts/{id} Toggle isDefault

Path builders live in src/api/endpoints.ts (since AZ-486 / F7). 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 in security_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.
  • saving state can stick on PUT failure because there is no try/finally, leaving the Save button permanently disabled. Step 4 candidate (matches the AdminPage "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('') || 0 writes 0 for an empty input, not null. This silently overwrites a legitimate null (per the SystemSettings type, all four numeric fields are nullable). Step 4 fix: detect empty input and pass null.
  • Aircraft toggle handler is duplicated between AdminPage and SettingsPage — extract to a shared helper. Step 8.
  • No optimistic concurrency: two admins editing system settings simultaneously will overwrite each other. The id field is sent back on PUT but no version / etag / If-Match header. 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.
  • saving is 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/Border and generateAnnotatedImage, silentDetection in SystemSettings are 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.