Penalty Tracker
A small, self-contained system for tracking competition penalties (e.g. for hot-air balloon competitions).
Features
- System Admin can create competitions and manage all users.
- Chief Scorer (per competition) manages pilots, members, settings, and can export penalties as CSV. Chief Scorers can promote others to Chief Scorer or Scorer.
- Scorer (per competition) adds penalties. Scorers can only edit/delete their own entries unless the competition setting "allow any scorer to edit" is on.
- Pilots can be added manually or imported from CSV (
number,last_name,first_name,country,balloon_id). - Rule catalogues per language are loaded from
rules/*.csv. The penalty form lets you search rules by number or text and shows the suggested penalty plus repeat-behaviour (same / doubled / escalates). - All open clients receive live updates for a competition via WebSocket — works for 30+ concurrent users on a single SQLite process.
- The penalty table is client-side sortable by every column and updates in-place without flicker when new rows arrive.
- Multi-lingual UI: English, German, Polish, Russian, French, Spanish, Portuguese (set per user in user settings).
- Clean black / white design with
#2b6cb0accent, max border-radius0.375rem.
Build & run
go build -o penaltytracker .
./penaltytracker
Then open http://localhost:8080.
Default login (created automatically on first start):
username: admin
password: admin
Change the password immediately under the user menu.
Configuration (env vars)
| Variable | Default | Description |
|---|---|---|
ADDR |
:8080 |
HTTP listen address |
DB_PATH |
penaltytracker.db |
SQLite database path |
RULES_DIR |
rules |
Directory containing per-language rule CSV files |
WEB_DIR |
web |
Directory containing the frontend static files |
CORS_ORIGINS |
(empty) | Comma-separated allowed frontend origins (e.g. http://127.0.0.1:36823,https://app.example.com). When set, CORS headers are added. Use * to allow any origin. |
COOKIE_CROSS_SITE |
(empty) | Set to true only when the frontend runs on a different site (different registrable domain) than the API and you need cross-site cookies. Switches the session cookie to SameSite=None; Secure — requires HTTPS on both sides. Leave unset for same-host different-port setups (e.g. 127.0.0.1:36823 → 127.0.0.1:8080); SameSite=Lax already works there. |
Frontend backend URL
If you host the frontend on a different origin than the API, open the login screen and fill in Backend URL (e.g. http://192.168.0.10:8080 or https://api.example.com). It is stored in localStorage (api_base). Leave it empty to use the same origin as the page. You can also pre-set it by editing web/config.js (DEFAULT_API_BASE).
Cross-origin cookies require HTTPS on both sides (so the Secure cookie flag is accepted). For LAN testing on plain HTTP, host the frontend and backend on the same origin (the default).
Rules CSV format
rules/<lang>.csv — one file per language code (en, de, pl, ru, fr, es, pt).
rule_number,rule_text,suggested_penalty,escalation_mode
R1.1,Late at briefing,Warning,escalate:Warning|50 CP|100 CP|DSQ
R3.1,Unsafe flying,200 CP,doubled
R4.2,Incorrect declaration,No Result,same
escalation_mode:
same— penalty stays the same on repeat.doubled— penalty doubles each time.escalate:tier1|tier2|tier3|...— penalty climbs through the listed tiers.
To reload rules without restart, POST /api/rules/reload as a system admin.
Pilot import CSV format
number,last_name,first_name,country,balloon_id
1,Doe,John,DE,D-OABC
2,Müller,Anna,DE,
Header row is optional and auto-detected.
Notes
- Storage is a single SQLite file in WAL mode — easy to back up by copying the
.dbfile (along with-wal/-shmif present) while the server is briefly stopped. - The frontend is pure HTML/CSS/JS, no build step.
- The Go binary embeds nothing — it serves
./weband./rulesfrom the working directory.