Commit Graph

37 Commits

Author SHA1 Message Date
69c2ddae29 feat: sort flights by date and add sortable date/price columns
All checks were successful
Deploy / deploy (push) Successful in 36s
- Change API ORDER BY from price/date to date/price (chronological default)
- Add flightSortField/flightSortDir state to ScanDetails
- Make Date and Price sub-table headers clickable with sort icons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 15:27:21 +01:00
3cad8a8447 fix: commit missing ScanTimer component and useScanTimer hook
All checks were successful
Deploy / deploy (push) Successful in 31s
These files were referenced by ScanDetails.tsx but never committed,
breaking the Docker build (tsc could not resolve the imports).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 18:12:47 +01:00
9a76d7af82 feat: add cancel, pause, and resume flow control for scans
Some checks failed
Deploy / deploy (push) Failing after 18s
Users running large scans can now pause (keep partial results, resume
later), cancel (stop permanently, partial results preserved), or resume
a paused scan which races through cache hits before continuing.

Backend:
- Extend scans.status CHECK to include 'paused' and 'cancelled'
- Add _migrate_add_pause_cancel_status() table-recreation migration
- scan_processor: _running_tasks/_cancel_reasons registries,
  cancel_scan_task/pause_scan_task/stop_scan_task helpers,
  CancelledError handler in process_scan(), start_resume_processor()
- api_server: POST /scans/{id}/pause|cancel|resume endpoints with
  rate limits (30/min pause+cancel, 10/min resume); list_scans now
  accepts paused/cancelled as status filter values

Frontend:
- Scan.status type extended with 'paused' | 'cancelled'
- scanApi.pause/cancel/resume added
- StatusChip: amber PauseCircle chip for paused, grey Ban for cancelled
- ScanDetails: context-aware action row with inline-confirm for
  Pause and Cancel; Resume button for paused scans

Tests: 129 total (58 new) across test_scan_control.py,
test_scan_processor_control.py, and additions to existing suites

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 18:11:23 +01:00
d494e80ff7 fix: force Traefik to use domverse network for routing
All checks were successful
Deploy / deploy (push) Successful in 11s
When a container is attached to multiple networks, Traefik picks an IP
from any of them. The frontend is on both domverse and flight-radar_default;
Traefik isn't on the latter, so ~50% of requests 504 after 30s.

Adding traefik.docker.network=domverse pins routing to the shared network.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 16:05:17 +01:00
cde496ad48 infra: fix CORS, add monitoring and dashboard labels
All checks were successful
Deploy / deploy (push) Successful in 11s
- Set ALLOWED_ORIGINS to production domain (fixes CORS for the web app)
- Add LOG_LEVEL=INFO to backend
- Add AutoKuma monitoring labels for Uptime Kuma auto-discovery
- Add Homepage dashboard labels (Productivity group)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:59:28 +01:00
7b07775845 fix: replace wget with curl in frontend healthcheck
All checks were successful
Deploy / deploy (push) Successful in 12s
nginx:alpine is a minimal image that does not include wget.
Install curl explicitly and use curl -f for the healthcheck.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 14:17:56 +01:00
6c1cffbdd4 fix: add missing vite-env.d.ts for CSS import type declarations
All checks were successful
Deploy / deploy (push) Successful in 36s
Without this file TypeScript errors on plain CSS side-effect imports
(e.g. import './index.css') because it lacks Vite's built-in type shims.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 14:06:10 +01:00
442e300457 fix: add missing tsconfig files and restore npm run build
Some checks failed
Deploy / deploy (push) Failing after 19s
tsconfig.json, tsconfig.app.json, and tsconfig.node.json were never
committed because *.json was gitignored without exceptions for them.
Added whitelist entries and restored Dockerfile to use npm run build
(tsc -b + vite) now that the config files are present.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 14:02:21 +01:00
8eeb774d4e fix: replace wget --spider with wget -qO /dev/null for healthcheck
All checks were successful
Deploy / deploy (push) Successful in 11s
--spider sends a HEAD request which newer BusyBox builds in nginx:alpine
may not handle consistently. A plain GET to /dev/null is more reliable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 12:40:31 +01:00
000391f7fc fix: guard migrations against fresh-database installs
All checks were successful
Deploy / deploy (push) Successful in 36s
On a fresh DB, migrations ran before the schema was applied and tried to
operate on tables that didn't exist yet (routes, scans), causing:
  "no such table: routes" on _migrate_add_routes_unique_index
  "no such table: scans" on _migrate_add_scheduled_scan_id_to_scans

Added table-existence checks so both migrations bail out when the table
isn't there yet. The schema's CREATE TABLE IF NOT EXISTS handles fresh
installs correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 12:35:45 +01:00
9b982ad9a5 fix: bypass tsc -b in Docker build, use vite directly
Some checks failed
Deploy / deploy (push) Failing after 54s
tsc -b with project references fails in the Alpine Docker environment
(TypeScript 5.9 + no composite:true on referenced configs). Vite uses
esbuild for TS compilation anyway, so tsc -b only served as a type-check
which is redundant in a production build.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 12:32:40 +01:00
7c125dbaeb fix: add domverse network to backend service
Some checks failed
Deploy / deploy (push) Failing after 37s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 12:07:54 +01:00
65b0d48f9d feat: add Traefik + Authentik integration to docker-compose
- Route https://flights.domverse-berlin.eu via Traefik on the domverse network
- Protect with Authentik (authentik@docker ForwardAuth middleware)
- Remove host port bindings (80, 8000) — Traefik handles all ingress
- Frontend joins both default compose network (nginx→backend) and domverse (Traefik)
- Backend stays internal-only, no external exposure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 12:07:31 +01:00
cdb8c20e82 ci: switch from GitLab CI to Gitea Actions, fix Dockerfile.backend
- Replace .gitlab-ci.yml with .gitea/workflows/deploy.yml
- Fix Dockerfile.backend: add scan_processor.py and searcher_v3.py to
  COPY command (they were missing, would cause runtime ImportError)
- Update docker-compose.yml comment to reference Gitea workflow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 11:42:02 +01:00
717b976293 ci: add GitLab CI/CD pipeline for Docker deploy
On every push to main: builds both Docker images on the server via
docker compose up --build -d, prunes dangling images, and prints the
running container list. No registry required — shell executor runner
on the deployment server is all that's needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 11:32:18 +01:00
836c8474eb feat: add scheduled scans (cron-like recurring scans)
- New `scheduled_scans` table with daily/weekly/monthly frequencies
- asyncio background scheduler loop checks for due schedules every 60s
- 6 REST endpoints: CRUD + toggle enabled + run-now
- `scheduled_scan_id` FK added to scans table; migrated automatically
- Frontend: Schedules page (list + create form), Schedules nav link,
  "Scheduled" badge on ScanDetails when scan was triggered by a schedule

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 10:48:43 +01:00
ef5a27097d fix: enrich route destination names from airport DB when not stored
Specific-airports mode scans never resolved full airport names — they
stored the IATA code as destination_name. Fixed in two places:

- airports.py: add lookup_airport(iata) cached helper
- api_server.py: enrich destination_name/city on the fly in the routes
  endpoint when the stored value equals the IATA code (fixes all past scans)
- scan_processor.py: resolve airport names at scan time in specific-airports
  mode using lookup_airport (fixes future scans at the DB level)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 21:04:46 +01:00
0a2fed7465 feat: add info icon tooltip for airport names in routes table
Replaces the non-functional title attribute with a small Info icon
next to the IATA code badge. Hovering shows a dark tooltip with the
full airport name and city. Only rendered when useful name data exists.
Clicking the icon stops propagation so it doesn't expand the flights row.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 20:59:37 +01:00
ce1cf667d2 feat: write routes live during scan instead of bulk-insert at completion
Routes and individual flights are now written to the database as each
query result arrives, rather than after all queries finish. The frontend
already polls /scans/:id/routes while status=running, so routes appear
progressively with no frontend changes needed.

Changes:
- database/schema.sql: UNIQUE INDEX uq_routes_scan_dest(scan_id, destination)
- database/init_db.py: _migrate_add_routes_unique_index() migration
- scan_processor.py: _write_route_incremental() helper; progress_callback
  now writes routes/flights immediately; Phase 2 bulk-write replaced with
  a lightweight totals query
- searcher_v3.py: pass flights= kwarg to progress_callback on cache_hit
  and api_success paths

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 20:53:04 +01:00
4926e89e46 feat: re-run and delete scan from detail page
Backend:
- DELETE /api/v1/scans/{id} — 204 on success, 404 if missing,
  409 if pending/running; CASCADE removes routes and flights

Frontend (api.ts):
- scanApi.delete(id)

Frontend (ScanDetails.tsx):
- Re-run button: derives window_months from stored dates, detects
  country vs airports mode via comma in scan.country, creates new
  scan and navigates to it; disabled while scan is active
- Delete button: inline two-step confirm (no modal), navigates to
  dashboard on success; disabled while scan is active

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 16:33:45 +01:00
f9411edd3c remove: docker-compose.dev.yml — develop locally, deploy with docker compose
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 16:24:12 +01:00
06e6ae700f feat: add docker-compose.dev.yml for local development
- docker-compose.dev.yml: backend on 8000, frontend (Vite) on 5173
- Backend mounts source files + uvicorn --reload for hot reload
- Frontend uses node:20-alpine, mounts ./frontend, runs npm run dev --host
- vite.config.ts: proxy target reads from API_TARGET env var
  (defaults to localhost:8000 for plain npm run dev,
   set to http://backend:8000 by docker-compose.dev.yml)

Usage: docker compose -f docker-compose.dev.yml up

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 16:08:53 +01:00
6d168652d4 refactor: split back into two containers (backend + frontend)
Single-container supervisord approach added unnecessary complexity.
Two containers is simpler and more standard:

- Dockerfile.backend: python:3.11-slim, uvicorn on port 8000
- Dockerfile.frontend: node build → nginx:alpine on port 80
- nginx.conf: proxy_pass restored to http://backend:8000
- docker-compose.yml: two services with depends_on
- Removed combined Dockerfile and supervisord.conf

Each container does one thing; logs are separate; either can be
restarted independently.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 16:06:26 +01:00
8bd47ac43a fix: improve airport search ranking and add missing modern airports
- Rewrite airport search to use priority buckets instead of simple
  append: exact IATA → IATA prefix → city prefix → city contains →
  name prefix → name contains → country match. This ensures BER
  appears before Berlin-Schönefeld when typing "BER".
- Add _MISSING_AIRPORTS patch list to get_airport_data() so airports
  absent from the OpenFlights dataset (e.g. BER opened Nov 2020,
  IST new Istanbul airport) are included at runtime.
- Deduplicate results via seen-set to avoid duplicate entries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 15:48:43 +01:00
260f3aa196 fix: add web app deps and git to Docker build
- Added fastapi, uvicorn, pydantic, requests to requirements.txt
  (were missing — only CLI deps were present)
- Changed fast-flights entry to git+GitHub URL (v3 not on PyPI)
- Added git to apt-get install in Dockerfile (needed for pip git+ URL)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 15:42:11 +01:00
3eed32076b chore: consolidate to single Docker container
Replace two-container setup (separate backend + nginx frontend) with a
single image that runs both via supervisord:

- New Dockerfile: Node stage builds React, Python+nginx stage is the runtime
- supervisord.conf: manages uvicorn (api_server.py) + nginx as sibling procs
- nginx.conf: proxy_pass updated to localhost:8000 (same container)
- docker-compose.yml: simplified to one service on port 80

Deploy:
  docker-compose up -d        # or
  docker build -t flight-radar . && docker run -p 80:80 flight-radar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 15:30:49 +01:00
81dd5735ea feat: add weekday abbreviation to flight results table
Each date row now shows e.g. "WED 2026-04-01" — the 3-letter weekday
prefix is rendered in muted monospace before the date string.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 15:24:55 +01:00
71db3cc305 feat: implement Phase 7 — Logs page redesign
- Auto-refresh toggle (RefreshCw icon, animates when active, polls every 5s)
- Horizontal filter bar: level select (140px) + search input + Clear button
- Clear button only rendered when level or search is active
- Level badges: INFO=blue, WARNING=amber, ERROR=red, CRITICAL=dark red, DEBUG=grey
- Row background tints: ERROR=#FFF5F5, WARNING=#FFFBF0, CRITICAL=#FFF0F0
- Message text in font-mono, metadata line with · separators
- Right-aligned timestamp: time on first line, date below
- Skeleton loading (8× SkeletonTableRow) while fetching

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 15:04:29 +01:00
9693fa8031 feat: implement Phase 6 — Airports page redesign
- Search icon visually inside input (pl-11 + absolute positioned icon)
- No search button; debounced search on keystroke, Enter to force-search
- Desktop: data table with IATA mono chip, Copy button → Check + "Copied!" for 2s
- Mobile: compact list view (md:hidden / hidden md:block responsive split)
- Initial EmptyState (Search icon) and no-results EmptyState (MapPin icon)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 15:02:15 +01:00
d87bbe5148 feat: implement Phase 5 — Scan Details redesign
- Breadcrumb: ← Dashboard / Scan #N with ArrowLeft icon
- Header card: PlaneTakeoff icon + route, StatusChip, metadata row
  (Calendar, Users, Armchair icons), created-at timestamp
- StatCards for Total Routes / Routes Scanned / Flights Found
- Progress card with Loader2 spinner + % bar (running/pending only)
- Routes table: sort indicators, IATA chips (font-mono + primary-container),
  ChevronRight rotates 90° on expand, min price green / avg+max muted
- Animated expand: max-height 0→600px CSS transition (no snap)
- Sub-table light green background (#F8FDF9) with nested indent
- EmptyState for completed 0 routes and failed scans

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 15:00:30 +01:00
45aa2d9aae feat: implement Phase 4 — Create Scan form redesign
- Three card sections: Origin, Destination, Parameters
- SegmentedButton replaces plain toggle buttons (Globe / PlaneTakeoff icons)
- AirportSearch updated to design tokens; hasError prop for red border state
- AirportChip tags for airports mode
- +/− stepper buttons for Search Window and Passengers (no spin arrows)
- Inline field-level validation errors (replaces browser native popups)
- Toast on success / error; useNavigate replaces window.location.href

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 14:57:21 +01:00
1021664253 feat: implement Phase 2 (component library) + Phase 3 (dashboard)
Phase 2 - New shared components:
- Button: filled/outlined/text/icon variants, loading state
- StatusChip: colored badge with icon per status (completed/running/pending/failed)
- StatCard: icon circle with tinted bg, big number display
- EmptyState: centered icon, title, description, optional action
- SkeletonCard: shimmer loading placeholders (stat, list item, table row)
- SegmentedButton: active shows Check icon, secondary-container colors
- AirportChip: PlaneTakeoff icon, error hover on remove
- Toast: updated to Lucide icons + design token colors

Phase 3 - Dashboard redesign:
- 5 stat cards with skeleton loading
- Status chip separated from destination text (fixes "BDS→DUScompleted" bug)
- Hover lift effect on scan cards
- Relative timestamps (Just now, Xm ago, Today, Yesterday, N days ago)
- EmptyState when no scans exist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 14:53:16 +01:00
7417d56578 Phase 1: Design system foundation — tokens, fonts, sidebar layout
- index.css: @import "tailwindcss" + @theme block with full colour
  palette, shadows, radii, typography tokens, skeleton animation
- index.html: Google Sans + Roboto Mono fonts, title → Flight Radar
- src/lib/utils.ts: cn() helper (clsx + tailwind-merge)
- Layout.tsx: 256px fixed sidebar on desktop (active pill nav, logo,
  Developer section divider), sticky top bar with page title + New Scan
  CTA (hidden on /scans), bottom nav bar on mobile with pill indicator
- package.json/lock: add lucide-react, clsx, tailwind-merge
- .gitignore: unblock frontend/package*.json and frontend/src/lib/

Build: 0 TypeScript errors · 0 console errors · all 6 criteria pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 14:44:44 +01:00
5d08d9353d Add PRD for design system implementation
7-phase implementation plan: foundation tokens → shared components →
all 5 pages. Defines acceptance criteria per phase, exact file list
(17 files changed, 7 new components), dependency notes (lucide-react,
clsx, tailwind-merge), and explicit non-goals. No implementation yet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 17:30:08 +01:00
b0a93bf824 Add comprehensive Material Design 3 design system document
Covers: color tokens, typography scale, spacing grid, 13 components
(nav sidebar/bottom, buttons, status chips, stat cards, data table,
forms, segmented toggle, toasts, empty states, skeletons, progress),
page-by-page specs for all 5 views, motion guidelines, Lucide icon
catalogue, and Tailwind v4 implementation checklist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 17:23:55 +01:00
3da6ce2b22 Add workflow rule: commit and push after every successful test run
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 17:16:28 +01:00
6421f83ca7 Add flight comparator web app with full scan pipeline
Full-stack flight price scanner built on fast-flights v3 (SOCS cookie bypass):

Backend (FastAPI + SQLite):
- REST API with rate limiting, Pydantic v2 validation, paginated responses
- Scan pipeline: resolves airports, queries every day in the window, saves
  individual flights + aggregate route stats to SQLite
- Background async scan processor with real-time progress tracking
- Airport search endpoint backed by OpenFlights dataset
- Daily scan window (all dates, not monthly samples)

Frontend (React 19 + TypeScript + Tailwind CSS v4):
- Dashboard with live scan status and recent scans
- Create scan form: country mode or specific airports (searchable dropdown)
- Scan detail page with expandable route rows showing individual flights
  (date, airline, departure, arrival, price) loaded on demand
- AirportSearch component with debounced live search and multi-select

Database:
- scans → routes → flights schema with FK cascade and auto-update triggers
- Migrations for schema evolution (relaxed country constraint)

Tests:
- 74 tests: unit + integration, isolated per-test SQLite DB
- Confirmed flight fixtures in tests/confirmed_flights.json (50 real flights,
  BDS→FMM Ryanair + BDS→DUS Eurowings, scraped Feb 2026)
- Integration tests parametrized from confirmed routes

Docker:
- Multi-stage builds, Compose orchestration, Nginx reverse proxy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 17:11:51 +01:00