feat(ui): rebuild the interface with a proper design system
The old UI was two pages of raw pandas tables with no navigation, no styling and no way to see a run's comments. This replaces it with a small, testable application and a beer-themed design. What changed ------------ Restructure - The domain logic now lives in `brewing.py` as pure functions over plain data, with no Flask or Mongo dependency. The gravity maths, the ABV formula and the run-aggregation rules are unit-testable on their own. - `app_factory.py` replaces the module-level app, and the routes move into blueprints under `views/`. A request-scoped Mongo repository is created per request and closed at teardown. - `mongo/repository.py` replaces `build_db.py` / `query_db.py` / `get_conn.py`. Reading a document no longer requires a second query to fetch the fields. Interface - A dark, beer-themed design system: warm amber `#be7f39` on near-black, a condensed display face (Anton) for headings and Inter for body text. Both fonts and the chart library are vendored into `static/`, so the container needs no outbound network at runtime — the old templates pulled Bootstrap from a CDN. - A real navigation bar, a login page, a brew-process page with a proper form, and a run-detail page listing every reading with its comment. - The run summary tables now show the beer name and the latest comment, which the old pandas tables did not. - Native form controls (checkbox, number spinners, date icon, scrollbars) are restyled for the dark theme. Correctness - The alcohol calculation now uses the standard homebrew formula, `(OG - FG) x 131.25`. The old code used `(max - min) / 7.36 + 0.5`, which is not a recognised brewing formula and overstated every result. - Gravity points and decimal readings now convert correctly in both directions (`1039` <-> `1.039`). - A run's display name resolves to the most-used label across its readings, because `beer_type` is stored per-reading and drifts across a run. - Re-submitting a reading for the same run and day updates it in place rather than creating a duplicate. Security / production - The container ran the Werkzeug debugger with `FLASK_DEBUG=1`, exposing an interactive console on any unhandled error. It now runs waitress with debugging off. - `SECRET_KEY` comes from the environment. - The app secret is no longer a hard-coded string in source. Housekeeping - Removes the bokeh/duckdb/pandas dependency chain and the static file it generated at import time; removes the dead `add_user.py` and `test.py`. - Splits `requirements.txt` (runtime) from `requirements-dev.txt` (tests). - Adds a pytest suite (64 tests) and a `test` job to the CI workflow so the image is only built when the tests pass. - Adds a README describing the data model and a RUNBOOK for the cluster side. Data compatibility ------------------ The existing `beer_db.beer_data` documents are read as they are and are not migrated or rewritten. No collection is dropped, and no document is modified by this change other than through the explicit "record a reading" form, which keeps the original `(date, beer_run_id)` uniqueness rule.
This commit is contained in:
+119
@@ -0,0 +1,119 @@
|
||||
# RUNBOOK — Brew Log (beer_data)
|
||||
|
||||
Operational notes for the `beer` namespace deployment.
|
||||
|
||||
## What runs where
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Namespace | `beer` |
|
||||
| Deployment | `beer-deployment` (3 replicas) |
|
||||
| Image | `git.aridgwayweb.com/armistace/beer-data:latest` |
|
||||
| Service | `beer-service`, NodePort **30015** → container port 5000 |
|
||||
| Database | MongoDB at `192.168.178.229`, database `beer_db` |
|
||||
| Secret | `beercred` (registry pull credentials) |
|
||||
|
||||
Access: `http://<any-cluster-node>:30015/`
|
||||
|
||||
The pods are pinned to `archlinux-k3s-3` (`.161`) via `nodeSelector`.
|
||||
|
||||
## Environment variables
|
||||
|
||||
| Variable | Value | Source |
|
||||
|---|---|---|
|
||||
| `MONGO_HOST` | `192.168.178.229` | Deployment manifest |
|
||||
| `MONGO_USER` | `root` | Deployment manifest |
|
||||
| `MONGO_PASS` | *(see below)* | Deployment manifest |
|
||||
| `SECRET_KEY` | *(see below)* | Deployment manifest — Flask session signing key |
|
||||
| `FLASK_ENV` | `production` | Dockerfile |
|
||||
| `FLASK_DEBUG` | `0` | Dockerfile |
|
||||
|
||||
`SECRET_KEY` and `MONGO_PASS` must be set in the live Deployment. They are
|
||||
**not** committed to this repo — read them from the running cluster:
|
||||
|
||||
```bash
|
||||
kubectl -n beer get deploy beer-deployment -o jsonpath='{.spec.template.spec.containers[0].env}'
|
||||
```
|
||||
|
||||
> The app refuses to start a *usable* session without a `SECRET_KEY`; the
|
||||
> Dockerfile ships a placeholder so the container still boots if the variable
|
||||
> is missing. Treat the placeholder as a misconfiguration.
|
||||
|
||||
## Deploying
|
||||
|
||||
Pushing to `master` triggers `.gitea/workflows/build_push.yml`, which runs the
|
||||
test suite and then builds and pushes the image. **The workflow does not
|
||||
currently deploy to the cluster** — apply the manifests yourself:
|
||||
|
||||
```bash
|
||||
kubectl -n beer rollout restart deployment/beer-deployment
|
||||
kubectl -n beer rollout status deployment/beer-deployment --timeout=180s
|
||||
```
|
||||
|
||||
### Verify the deploy actually took
|
||||
|
||||
CI green and pods `Ready` do **not** prove the new code is running. Check the
|
||||
running digest against the registry:
|
||||
|
||||
```bash
|
||||
# what the pod is running
|
||||
kubectl -n beer get pod -l app=beer-data \
|
||||
-o jsonpath='{.items[0].status.containerStatuses[0].imageID}'
|
||||
|
||||
# what the registry has as :latest
|
||||
curl -s -u "armistace:$REG_PASSWORD" -I \
|
||||
"https://git.aridgwayweb.com/v2/armistace/beer-data/manifests/latest" \
|
||||
-H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
|
||||
| grep -i docker-content-digest
|
||||
```
|
||||
|
||||
If they differ, the registry `:latest` is stale — re-run the build rather than
|
||||
re-applying the manifest.
|
||||
|
||||
### Check the app is alive
|
||||
|
||||
```bash
|
||||
kubectl -n beer get pods
|
||||
kubectl -n beer logs deploy/beer-deployment --tail=50
|
||||
curl -s http://<node>:30015/healthz # -> {"status":"ok"}
|
||||
```
|
||||
|
||||
`/healthz` never touches Mongo, so it proves the process is up but not that the
|
||||
database is reachable.
|
||||
|
||||
## Data safety
|
||||
|
||||
The `beer_db.beer_data` collection holds the actual brewing records. There is
|
||||
no migration step and no schema version — documents are read as they are
|
||||
written, and every field is treated as optional.
|
||||
|
||||
To take a backup before any risky change:
|
||||
|
||||
```bash
|
||||
mongodump --uri="mongodb://root:$MONGO_PASS@192.168.178.229:27017/beer_db" \
|
||||
--out=/tmp/beer_db_backup_$(date +%Y%m%d)
|
||||
```
|
||||
|
||||
## Common problems
|
||||
|
||||
### The login page loads but nothing else does
|
||||
|
||||
The Mongo connection failed. Check the app logs for an auth error and confirm
|
||||
the credentials:
|
||||
|
||||
```bash
|
||||
kubectl -n beer logs deploy/beer-deployment --tail=100 | grep -i mongo
|
||||
```
|
||||
|
||||
### A run shows a negative alcohol figure
|
||||
|
||||
That was the old formula (`(max - min) / 7.36 + 0.5`) applied to a run whose
|
||||
current gravity is above the assumed original gravity of 1012. Fixed in the
|
||||
current version: the standard `(OG - FG) × 131.25` formula is used, and a run
|
||||
with no known OG shows a dash instead of a figure.
|
||||
|
||||
### The chart on the run page is blank
|
||||
|
||||
The chart library is served from `/static/js/chart.umd.js` — it is vendored in
|
||||
the repo, not loaded from a CDN, so the container works with no outbound
|
||||
network. Check the browser console for a 404 on that path.
|
||||
Reference in New Issue
Block a user