V2: Added Webserver for faster formatting
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
# Roster Formatter
|
||||
|
||||
A local browser app that turns the current one-page roster PDF into the established formatted PNG. The web app and command-line tool use the same renderer.
|
||||
|
||||
## Start with Docker Compose
|
||||
|
||||
From this folder:
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Open <http://127.0.0.1:8787>, upload the roster PDF, choose its month, and select **Format roster**.
|
||||
|
||||
Common commands:
|
||||
|
||||
```bash
|
||||
docker compose restart # restart it later
|
||||
docker compose logs -f app # watch logs
|
||||
docker compose down # stop it
|
||||
docker compose up -d --build # rebuild after code changes
|
||||
```
|
||||
|
||||
The Compose service restarts automatically unless you explicitly stop it. Its direct browser port binds only to `127.0.0.1`.
|
||||
|
||||
The service also joins the existing external `tabernet` Docker network, so Caddy can reach it at `roster-formatter:8787`. For a Caddy hostname, add that hostname to `.env`:
|
||||
|
||||
```dotenv
|
||||
ROSTER_ALLOWED_HOSTS=roster.example.com
|
||||
```
|
||||
|
||||
Then a minimal Caddy route is:
|
||||
|
||||
```caddyfile
|
||||
roster.example.com {
|
||||
reverse_proxy roster-formatter:8787
|
||||
}
|
||||
```
|
||||
|
||||
The `tabernet` network must already exist. Because roster images contain personal information, add authentication in Caddy before exposing this service outside your private network.
|
||||
|
||||
## Saved files and automatic cleanup
|
||||
|
||||
Every successful PNG is returned to the browser with a **Download PNG** button and saved in [`output/`](output). The Compose container mounts that folder, so saved PNGs survive rebuilds and restarts.
|
||||
|
||||
Layout defaults are persistent too. Change values under **Advanced adjustments**, then select **Save as defaults** to prefill them for future rosters. **Reset changes** restores the currently saved values, while **Use original values** loads the production geometry without saving it. The server stores these defaults in `output/.roster-layout-defaults.json`, inside the same mounted volume, and the roster cleanup never removes that settings file.
|
||||
|
||||
On startup, every six hours, and after each successful format, the app removes only filenames matching its roster pattern, such as `MARCH 2026 Roster.png`, when either its roster month or saved-file age is outside the three-calendar-month window. The file-age check prevents an accidentally future-dated filename from bypassing cleanup. Other files are not removed. Cleanup is permanent.
|
||||
|
||||
Change the retention window or browser port with a `.env` file beside `compose.yaml`:
|
||||
|
||||
```dotenv
|
||||
ROSTER_RETENTION_MONTHS=6
|
||||
ROSTER_PORT=8788
|
||||
TZ=America/Chicago
|
||||
```
|
||||
|
||||
Set `ROSTER_RETENTION_MONTHS=0` to disable cleanup.
|
||||
|
||||
The image runs as UID/GID `1000:1000`, matching this machine. On a host with a different checkout owner, set `ROSTER_UID` and `ROSTER_GID` in `.env`, then rebuild so the container can write to `output/`:
|
||||
|
||||
```dotenv
|
||||
ROSTER_UID=1001
|
||||
ROSTER_GID=1001
|
||||
```
|
||||
|
||||
## Layout guarantees
|
||||
|
||||
With the advanced fields untouched, the renderer preserves the original production values:
|
||||
|
||||
| Setting | Default |
|
||||
| --- | ---: |
|
||||
| Canvas | 792 × 612 PDF points |
|
||||
| PNG export | 1650 × 1275 pixels at 150 DPI |
|
||||
| Roster vertical shift | 100 |
|
||||
| Bottom trim | 60 |
|
||||
| Logo position | X 50, Y -10 |
|
||||
| Logo scale | 0.27 |
|
||||
| Date position | X 460, Y 88 |
|
||||
| Date font size | 48 |
|
||||
|
||||
The web page exposes the layout values in a collapsed **Advanced adjustments** section. Only an intentional field change—or explicitly saving that change as the new default—alters logo/date scale or placement. The canvas size and PNG DPI are not editable. The original production values remain available through **Use original values**.
|
||||
|
||||
The page theme defaults to **System** and follows the browser/operating-system color preference. The **System / Light / Dark** control remembers the choice in that browser. Theme changes affect only the webpage and never the generated PNG.
|
||||
|
||||
Input is deliberately limited to a one-page, unrotated, 11 × 8.5 inch landscape PDF. This prevents a differently sized upload from silently changing the established geometry.
|
||||
|
||||
## Run without Docker
|
||||
|
||||
The existing `venv` already contains the formatter dependencies. Start the web app with:
|
||||
|
||||
```bash
|
||||
venv/bin/python server.py
|
||||
```
|
||||
|
||||
Or create a fresh environment:
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
.venv/bin/pip install -r requirements.txt
|
||||
.venv/bin/python server.py
|
||||
```
|
||||
|
||||
The original CLI workflow is still available and now accepts an explicit input path:
|
||||
|
||||
```bash
|
||||
venv/bin/python main.py --input input/original.pdf --month "July 2026"
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
Run the standard-library test suite with:
|
||||
|
||||
```bash
|
||||
PYTHONDONTWRITEBYTECODE=1 venv/bin/python -m unittest discover -v
|
||||
```
|
||||
|
||||
Uploads and intermediate PDFs are created in isolated temporary directories and removed immediately after formatting. Only the final PNG is retained in `output/`.
|
||||
Reference in New Issue
Block a user