# fLOGr A self-hosted web app for logging your fuel purchases and tracking vehicle fuel economy. Enter each refuel, and fLOGr calculates litres/100km, cost per 100 km, price trends, and per-vehicle comparisons over time. Built with **Flask** + **SQLite** + **Chart.js**, styled with **Bootstrap 5**. Developed by ThatButcherGuy. --- ## Features at a glance - **Quick log entry** — enter a refuel on the go (vehicle, fuel, litres, price/L, kilometres, location, optional comments). - **Edit / delete records** — fix typos or remove a mistaken entry; the vehicle odometer is adjusted automatically. - **Smart purchase locations** — store each retailer + suburb once and reuse it; rename a location and it updates every past log. - **Stats & Reports** — summary cards, global statistics, per-vehicle comparison, and interactive charts (economy, price/L, litres, spend by location, monthly spend, fuel price history). - **Vehicle garage** — manage your vehicles and view detailed per-vehicle stats. - **Full-tank accuracy** — a "Partial tank" flag lets you log top-ups that are excluded from fuel-economy calculations but still counted in litres/spend. - **Two-factor authentication (TOTP)** and optional **Authentik (OIDC) SSO**. - **Scoped API** — generate tokens to let programs (e.g. an AI agent) read your data. - **Logging & audit trail** — request logs to `docker logs`, plus a per-user **Activity Log** of every data/account change. - **CSV export** — export your log, optionally filtered. --- ## Quick start (Docker) ### docker-compose.yml ```yaml services: flogr: image: docker.io/thatbutcherguy/flogr-docker:latest container_name: flogr ports: - "15001:8080" # host : container volumes: - /path/to/flogr/data:/flask-app/data # persist the SQLite database environment: - TZ=Australia/Canberra - DATABASE_PATH=/flask-app/data/flogr.db - PUID=568 - PGID=568 - SECRET_KEY=change-me-to-a-long-random-value # REQUIRED for production - LOG_LEVEL=INFO # DEBUG / INFO / WARNING / ERROR # - LOG_FORMAT=json # uncomment for structured (JSON) logs # Optional: Authentik OIDC SSO # - AUTHENTIK_CLIENT_ID=******** # - AUTHENTIK_CLIENT_SECRET=******** # - AUTHENTIK_ISSUER_URL=https://auth.example.com/application/o/flogr/ restart: unless-stopped networks: - flogr networks: flogr: external: true ``` The app listens on **port 8080** in the container, mapped to `15001` on the host (behind a reverse proxy if desired). ### Environment variables | Variable | Default | Description | |---|---|---| | `DATABASE_PATH` | `data/flogr.db` | Where the SQLite database is stored. | | `SECRET_KEY` | | Flask session key. Set a long random value in production. | | `LOG_LEVEL` | `INFO` | Log verbosity: `DEBUG`, `INFO`, `WARNING`, `ERROR`. | | `LOG_FORMAT` | `plain` | `json` emits one JSON object per line for log viewers; `plain` is human-readable. | | `AUTHENTIK_CLIENT_ID` / `_SECRET` / `ISSUER_URL` | (unset) | When all three are set, Authentik OIDC login is enabled. | | `GUNICORN_*` | | Optional Gunicorn tuning (processes, threads, timeout, bind). | --- ## Using fLOGr ### Authentication Two login methods, switched per-user from **Settings**: - **Password login** — default; passwords are hashed with Werkzeug. - **Authentik (OIDC)** — optional SSO. `Settings → Authentik (OIDC) Login` toggles it per user. If Authentik is unreachable, the login page warns you and you can fall back to your password. **Two-factor authentication (TOTP)** — enable from Settings, scan the secret into an authenticator app, then enter a code to confirm. Save your **10 recovery codes** (each usable once). 2FA applies to password login; Authentik handles its own MFA. ### Enter a Record (home page) Once logged in, the home page is a fast refuel entry form. > **For accurate fuel-economy maths:** fill the tank to the **same level** each time — the fullest point is most repeatable — and log **every** refuel. If you forget one, a close guess is better than nothing. Mark a **partial top-up** as "Partial tank" so it's excluded from L/100km (but still counted in spend/litres). Fields: - **Vehicle** — required selection from your Garage. - **Fuel type** — defaults to the vehicle's fuel; changeable (handy for dual-fuel). - **Date** — defaults to today, cannot be in the future. - **Receipt number** — optional. - **Purchased at** — choose an existing location or add one inline. - **Litres** — to 2 decimal places. - **Price per litre** — to 3 decimal places; include any checkout discounts. - **Kilometres** — how far you drove on the previous tank; added to the vehicle's odometer. - **Partial tank** — check for a top-up that isn't a full tank. - **Comments** — optional free text. For each record fLOGr calculates **sale price** (`litres × price/L`) and **L/100km** (`litres / km × 100`). ### View Log All your records, newest first. Sortable by any column, searchable, and filterable by vehicle. From here you can **edit** or **delete** entries (odometer adjusts automatically), and **Export CSV** for all records or the selected vehicle. The page uses **server-side paging** — it loads instantly and fetches one page of records at a time, so it stays responsive even with thousands of entries. ### Locations Manage your reusable `Retailer Suburb` purchase locations. Renaming a location updates it across all past log entries; you can't delete a location still referenced by logs. ### Garage Your vehicles (registration, type, make/model, year, odometer). Add, edit or delete vehicles. Click a registration for full vehicle details (useful on small screens where the Garage table is trimmed). ### Stats A read-only **numbers** overview of your logs, fully driven by the **filter bar** at the top (vehicle, a custom date range, or a **quick period** — All / 7 / 30 / 90 days / 6 / 12 months / YTD / Financial year (AU)). A banner shows exactly what's being viewed (e.g. *"vehicle ABC · date: Year to date"*): - **Trend vs Previous Period** — when a date filter is active, compares this period against the equivalent prior window (total spend %, economy L/100km change, distance %, avg days/tank) with green/red change badges. - **Summary cards** — total spend, litres, distance, combined L/100km, cost/100km, avg cost/tank, best/worst economy (label-above-value). - **Most Recent Fill** — your latest entry, days since, economy, price, cost, location, and **avg days / tank**. - **Summary table** — totals and averages across the filtered set, plus **Average days per tank**. - **Vehicle Comparison** — each vehicle's economy, cost/100km, and range per tank. - **Location Insights** — most spent at, most frequent, cheapest & most expensive avg $/L, best-economy location, and distinct location count in the period. - **Vehicles** — links to per-vehicle statistics. All figures use thousands separators, and dollar values include `$`. ### Vehicle Stats Click a vehicle's registration on the Stats page (or Garage). Shows vehicle details, summary cards (including **days since last fill** and **avg days / tank**), and the last 20 records for that vehicle. Filter the summary to a date range. ### Reports & Charts The interactive, graphical analysis hub (also via **"Open Reports & Charts"** on the Stats page). Filter by vehicle, location, a custom date range, or a **quick period** (All / 7 / 30 / 90 days / 6 / 12 months / YTD / Financial year (AU)) — charts and summary cards update instantly, client-side: - Fuel economy (L/100km) and price/L over time (monthly line charts) - Litres purchased over time (bar) - Spend by location (doughnut) - Fuel price history — avg $/L by year (bar) - Monthly spend, last 12 months (bar) - Vehicle comparison — economy and cost/100km (bar) Data is served by `/api/stats`. > **Stats vs Reports:** Stats is the readable numbers/table view; Reports is the trend/chart view. They cross-link (Stats → "Open Reports & Charts"; Reports → "View Statistics"). ### Settings - **Appearance** — light / dark / auto theme (also the navbar 🌙/☀️🖥️ toggle). - **Account & Security** — username, email, Authentik toggle, 2FA enable/disable. - **API Access** — generate/revoke scoped tokens. - **Activity Log** — your audit trail (see below). --- ## API (token-authenticated) Generate a scoped token from **Settings → API Access**. Send it as: ``` Authorization: Bearer flogr_... ``` Endpoints: | Endpoint | Reads | Scope | |---|---|---| | `GET /api/logs` | your log entries | `logs` | | `GET /api/locations` | your locations | `locations` | | `GET /api/vehicles` | your vehicles | `vehicles` | Tokens are read-only by default. Each endpoint needs its named scope, or a token may carry `write` (grants access to all). Only a SHA-256 hash of the token is stored — the raw value is shown **once** at creation. Revoke a token anytime from Settings. --- ## Logging & Audit **Container logging** — the app writes structured logs to stdout, captured by `docker logs flogr` (alongside Gunicorn's access/error streams). Every request is logged with method, path, status, user ID, source IP and duration. `LOG_LEVEL=DEBUG` shows more detail; `LOG_FORMAT=json` emits one JSON object per line for dashboards. **Activity Log (audit)** — **Settings → Activity Log** keeps an immutable, per-user trail of data and account changes, kept indefinitely for troubleshooting and traceability: - **Fuel-log entries** capture full before/after field diffs on create and edit, and a full snapshot on delete. - **Locations, vehicles, API tokens, account settings, and auth events** (login, failed login, logout, registration, 2FA, OIDC) are recorded as concise summaries. - Each user sees only their **own** entries. --- ## Database schema A single SQLite database with these tables: | Table | Purpose | |---|---| | `users` | Accounts, password hash, 2FA secret/codes, OIDC flag. | | `fuel_types` | Lookup of fuel codes (`U91`, `P98`, `DL`, ...). | | `vehicles` | Each vehicle in a user's Garage (registration, type, make/model, year, odometer). | | `log` | Every refuel record (`log.location_id` → `locations.id`, `partial_tank` flag). | | `locations` | Reusable retailer + suburb purchase locations. | | `api_tokens` | Scoped API tokens (SHA-256 hashes only). | | `audit_log` | Per-user activity/audit trail. | --- ## Development - Single persistent clone; releases are git tags (`vX.Y.Z`). Semver: **minor** for features, **patch** for fixes/security. - Database schema lives in `static/schema.sql` (fresh installs); existing installs get new tables/columns via migrations run at app startup. A schema change needs **both**. - Run locally: `uv venv && source .venv/bin/activate && uv pip install -r requirements.txt`, then set `DATABASE_PATH`, `SECRET_KEY` and start with `python app.py`. --- ## Wish list - **User account management** — edit/delete users, email + `is_active`, reset / forgot-password. - **PDF export** of logs and of reports (charts + stats). - **Unit-of-measure support** — miles, gallons, MPG, other currencies. - **Vehicle service tracking** and richer **vehicle profiles** (mods with install dates, photo gallery for insurance). > ✅ **Done recently:** password strength on registration (min 8 chars), auto-rounding of kilometres on entry, container logging, per-user activity/audit log, and avg-days-per-tank + days-since-last-fill stats.