Overview
Implement a self-contained config_module/ directory that adds a gear icon (⚙) to the dashboard for editing station and display settings via the web. The module is designed to be fully removable: deleting config_module/ disables the feature completely with no residual errors.
Design Principles
- Secrets only in
.env: callsign, grid, and display settings move out of .env and into settings.json managed by this module
- Modular: all feature code in
config_module/; existing files minimally touched
- Removable:
rm -rf config_module/ is sufficient to disable the feature entirely ("ultimate security")
Settings Managed
- Admin username, receiver callsign, receiver grid square
- Default last interval, highlight threshold, contest bands
- Map initial view (lat/lon) and zoom level
Security Layers
- Filesystem lockfile (
config.lock, SSH-only): hard-disables all web config changes regardless of auth state — touch config.lock to lock, rm config.lock to unlock
- Flask-Login session auth: single admin account, hashed password in
.env
- CSRF protection on all POST forms (manual token, no extra dependency)
- HTTPS required in production (
SESSION_COOKIE_SECURE)
What Lives Where
| Data |
Location |
| MongoDB credentials |
.env |
| Flask secret key |
.env |
| Admin password hash |
.env |
| Admin username |
settings.json |
| Receiver callsign |
settings.json |
| Receiver grid square |
settings.json |
| Display defaults, bands, map view |
settings.json |
| Band colors, freq ranges, zone maps |
config.js (dev edit only) |
Module Structure
config_module/
├── __init__.py ← register(app) — called optionally via try/except in app.py
├── routes.py ← Blueprint: GET/POST /settings, GET/POST /login, GET /logout
├── auth.py ← Flask-Login setup, AdminUser model (no DB)
├── storage.py ← settings.json R/W, config.lock check
├── templates/config_module/
│ ├── gear_icon.html ← included by main templates via {% include ... ignore missing %}
│ └── login.html
└── static/
├── config_module.js
└── config_module.css
Minimal Changes to Existing Files
app.py: one try/except ImportError block to optionally load module
- Each template: one line —
{% include 'config_module/gear_icon.html' ignore missing %}
requirements.txt: add flask-login>=0.6.0
.env/.env.example: remove RECEIVER_CALLSIGN/RECEIVER_GRIDSQUARE; add FLASK_SECRET_KEY, ADMIN_PASSWORD_HASH
.gitignore: add config.lock, settings.json
- No changes to existing API or view routes
Acceptance Criteria
Overview
Implement a self-contained
config_module/directory that adds a gear icon (⚙) to the dashboard for editing station and display settings via the web. The module is designed to be fully removable: deletingconfig_module/disables the feature completely with no residual errors.Design Principles
.env: callsign, grid, and display settings move out of.envand intosettings.jsonmanaged by this moduleconfig_module/; existing files minimally touchedrm -rf config_module/is sufficient to disable the feature entirely ("ultimate security")Settings Managed
Security Layers
config.lock, SSH-only): hard-disables all web config changes regardless of auth state —touch config.lockto lock,rm config.lockto unlock.envSESSION_COOKIE_SECURE)What Lives Where
.env.env.envsettings.jsonsettings.jsonsettings.jsonsettings.jsonconfig.js(dev edit only)Module Structure
Minimal Changes to Existing Files
app.py: onetry/except ImportErrorblock to optionally load module{% include 'config_module/gear_icon.html' ignore missing %}requirements.txt: addflask-login>=0.6.0.env/.env.example: removeRECEIVER_CALLSIGN/RECEIVER_GRIDSQUARE; addFLASK_SECRET_KEY,ADMIN_PASSWORD_HASH.gitignore: addconfig.lock,settings.jsonAcceptance Criteria
rm -rf config_module/leaves app fully functional with default values, no errorsconfig.lockpresent →POST /settingsreturns 423; modal is read-only with lock message/loginsettings.json; updates live without restartRECEIVER_CALLSIGNandRECEIVER_GRIDSQUAREremoved from.envsettings.jsonandconfig.lockin.gitignoreset_password.pyhelper script included for generating password hashignore missinginclude)