Commit graph

8 commits

Author SHA1 Message Date
Matt Johnson
e8019a32b7 fix(wizard): eliminate all hardcoded field.name branches
Change 5: Move contact_email validation to Pydantic schema
- NWSSettings now uses Field(pattern=...) for email validation
- Pydantic pattern validation catches invalid emails
- No special handler branch needed in routes.py

Change 6: Generic api_key_field mechanism
- Add api_key_field attribute to SourceAdapter base class
- FIRMSAdapter sets api_key_field="api_key_alias"
- GET handlers swap widget to "api_key_select" when field matches
- POST handlers validate against state.api_keys generically
- Templates use new api_key_select widget branch
- adapters_edit handlers now fetch and pass api_keys to context

Tests added:
- test_invalid_contact_email_via_pydantic_pattern
- test_invalid_api_key_alias_generic
- test_api_key_field_none_no_check
- test_adapters_edit_fetches_api_keys_into_context

Zero field.name hardcoded branches remain in routes.py or templates.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-19 01:01:56 +00:00
Matt Johnson
91f1d67abd refactor(gui): clean up flagged issues before merge
1. Make migration 015 idempotent with IF NOT EXISTS

2. Remove hardcoded cadence range from routes.py and template:
   - Added ge=10 constraint to AdapterConfig.cadence_s field
   - Removed manual 60-3600 check from routes.py POST handler
   - Validate cadence using AdapterConfig field metadata
   - Removed min/max attributes from template input

3. Move discover_adapters to its own module:
   - Created src/central/adapter_discovery.py
   - Updated supervisor.py to import from adapter_discovery
   - Updated routes.py to import from adapter_discovery
   - GUI no longer transitively imports nats or stream_manager

4. Remove dead code branch in form_descriptors.py:
   - Removed unreachable RegionConfig check (already handled earlier)
   - Improved error message for unsupported nested types

5. Updated test_adapters.py:
   - Changed invalid cadence test from 30 to 5 (below ge=10)
   - Updated assertion to check for "10" in error message

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-18 23:55:34 +00:00
Matt Johnson
966661305f feat(gui): generic adapter edit form
Implement Central 2-A2: generic adapter edit form feature.

- Add form_descriptors.py with describe_fields() and FieldDescriptor
  - Maps Pydantic types to HTML widgets (text, number, checkbox, csv, region)
  - Handles Optional types by recursively resolving inner type
  - Uses PydanticUndefined handling for proper default values

- Update routes.py GET/POST handlers:
  - Use cached _adapter_classes() for adapter class lookup
  - Generate field descriptors from adapter settings_schema
  - Parse form values based on widget type in POST handler
  - Validate settings via Pydantic ValidationError

- Update adapters_edit.html template:
  - Render form dynamically from field descriptors
  - Support all widget types (text, number, checkbox, csv, region)
  - Use adapter.display_name and adapter.description from class

- Delete per-adapter templates:
  - adapters_edit_nws.html
  - adapters_edit_firms.html
  - adapters_edit_usgs_quake.html

- Add tests/test_form_descriptors.py with comprehensive coverage
- Update tests/test_adapters.py to include last_error in mock rows
- Update tests/test_region_picker.py to include last_error in mock rows

Adding a new adapter no longer requires GUI template work.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-18 23:16:37 +00:00
494ad1c799
feat(gui): implement first-run setup wizard (1b-8) (#24)
* feat(gui): implement first-run setup wizard (1b-8)

Add a 5-step setup wizard that replaces the single-step /setup:
1. Create Operator - create initial operator account
2. System Settings - configure map tile URL and attribution
3. API Keys - optionally add API keys for adapters
4. Configure Adapters - enable/disable adapters with region picker
5. Finish Setup - review and complete setup

Key changes:
- Update middleware to handle wizard URL structure and step routing
- Add wizard routes for each step with proper auth checks
- Create new templates using base_wizard.html for consistent styling
- Add audit events for system.update and setup.complete
- Update tests for new middleware behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(gui): handle CSRF errors on wizard paths

Update csrf_exception_handler to re-render wizard forms with error
message instead of redirecting to /login when CSRF validation fails.

- /setup/operator: re-render with error
- /setup/system: re-render with current system values + error
- /setup/keys: re-render with current keys list + error
- /setup/adapters: re-render with current adapter config + error
- /setup/finish: re-render with summary data + error
- /setup: redirect to /setup (middleware routes to appropriate step)

Add error display to setup_keys.html and setup_finish.html templates.
Add 7 new CSRF handler tests for wizard paths.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(gui): region picker render + click-to-draw

Bug A: Maps render blank on /setup/adapters for FIRMS and USGS
because Leaflet computed zero dimensions before container layout
settled. Fix: add setTimeout invalidateSize() after map creation.

Bug B: No click-to-draw functionality - only drag corners. Fix:
add L.Control.Draw for rectangle drawing with CREATED event handler
to replace existing rectangle.

Both fixes applied to:
- setup_adapters.html (wizard inline JS)
- _region_picker.html (standalone edit page)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(gui): handle revisiting /setup/operator after operator created

When an operator already exists, /setup/operator now shows a
confirmation page instead of the create form. This prevents:
- Unique constraint violations on duplicate username
- Silent creation of duplicate operators

GET /setup/operator: queries config.operators; if any exist,
renders confirmation state with existing_operator context.

POST /setup/operator: checks operator count before INSERT; if
non-zero, renders confirmation state without inserting.

Template updated with conditional to show "Operator Already
Configured" message when existing_operator is set.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(csrf): replace fastapi-csrf-protect with session-bound CSRF

Fixes CSRF race condition where every GET rotated the CSRF token,
causing POST failures when users had multiple tabs or slow connections.

Changes:
- Remove fastapi-csrf-protect dependency
- Add session-bound CSRF tokens stored in config.sessions table
- Add pre-auth CSRF for unauthenticated routes (/login, /setup/operator)
- Add csrf.py module for pre-auth token generation/validation
- Update routes to use new CSRF token handling
- Add migration 013 to add csrf_token column to sessions

The session-bound approach ensures CSRF tokens remain stable for the
duration of a session, eliminating the race condition.

Note: Route tests (test_wizard.py, test_adapters.py, etc.) need
refactoring to mock get_settings() instead of CsrfProtect dependency.
Core auth/CSRF handler tests pass (74 tests).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test(csrf): update test suite for session-bound CSRF tokens

- Add CSRF fixtures to conftest.py for pre-auth and session CSRF
- Update test_wizard.py: use bypass_pre_auth_csrf and patch_route_settings
- Update test_adapters.py: set request.state.csrf_token and form mock data
- Update test_api_keys.py: add CSRF token to form data for POST routes
- Update test_streams.py: change return_value to side_effect for CSRF support
- Update test_region_picker.py: add CSRF token handling
- Update test_config_store.py: set CENTRAL_CSRF_SECRET env var in fixture

All 285 tests now pass with session-bound CSRF validation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Matt Johnson <mj@k7zvx.com>
2026-05-17 22:06:22 -06:00
1dbc54e182
feat(gui): Leaflet region picker (1b-5) (#19)
* feat(gui): add Leaflet region picker to adapter edit (1b-5)

- Add _region_picker.html template with Leaflet map and editable rectangle
- Add Leaflet 1.9.4 and Leaflet.draw 1.0.4 CDN deps to adapters_edit.html
- Update GET /adapters/{name} to fetch map_tile_url from config.system
- Update POST /adapters/{name} to validate and save region coordinates
- Validation: -90 <= south < north <= 90, -180 <= west < east <= 180
- Region changes flow through to audit log via existing settings capture

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(tests): update adapter tests for region picker mocks

Add region coordinates to form data mocks and system settings rows
to fetchrow.side_effect for tests that re-render on validation errors.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Ubuntu <zvx@cortex.echo6.co>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Matt Johnson <mj@k7zvx.com>
2026-05-17 16:53:27 -06:00
Ubuntu
5be002cb03 test(adapters): fix mock settings to use dicts instead of JSON strings
Now that routes.py no longer calls json.loads() on settings, the test
mocks must return dicts directly (as asyncpg does with jsonb).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-17 21:35:51 +00:00
Ubuntu
0f127399b3 fix(gui): remove JSONB double-encoding in adapter updates
The GUI pool has init=_setup_json_codec registered, which makes asyncpg
auto-serialize Python dicts to JSONB. Calling json.dumps() on a dict
before passing it to asyncpg double-encodes - the value gets stored as
a JSON-encoded string rather than a JSON object.

Changes:
- Remove json.dumps() from UPDATE statement in adapters_edit_submit
- Remove defensive isinstance(settings, str) checks that masked the bug
- Add regression tests to verify settings is passed as dict, not string

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-17 21:33:48 +00:00
Matt Johnson
dec8ce8545 feat(gui): add adapters list and edit UI (1b-4)
- Add GET /adapters route for listing all adapters
- Add GET /adapters/{name} for edit form with per-adapter fields
- Add POST /adapters/{name} for validation, update, and audit
- Add ADAPTER_UPDATE audit constant
- Add Adapters nav link to base.html
- Server-side validation for cadence (60-3600), email format,
  api_key_alias existence, satellites, and feed values
- Region displayed read-only with 1b-5 placeholder
- Hot reload via existing NOTIFY trigger (no new mechanism)
- Add comprehensive tests (9 tests)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-17 21:19:40 +00:00