Findings the reviewers verified, fixed here. The larger cluster they found is NOT fixed and is listed at the end. Frontend. reportImageUrl is called from the template, so it ran on every render with no in-flight guard: a report whose fetch was pending was re-requested, and because each resolution reassigns the map the whole grid re-rendered and re-fired the rest, costing up to N-squared requests for N thumbnails. Two blobs resolving for one key orphaned the first object URL where revoke could not reach it, and a failure was never recorded so a broken image retried forever. Guarded, deduplicated, failures remembered. Three CSS variables did not exist, so their hardcoded fallbacks always won: --bg-alt (the "No screenshot" tile, near-black in light mode), and --warning-text/-bg/-border (dark khaki on a dark modal). Replaced with real tokens. The DEV badge's literal #c62828 is var(--danger). Installer and launcher. The generated site installer ran docker compose with no -p, so the project name defaulted to the package folder - which carries the version. Installing the next version created a new project with new empty volumes and the site came up looking wiped, old data stranded under the old project. Pinned to shopdb-<channel>. Start-ShopDBDev deleted any existing -TargetDir that lacked a .git, recursively and without asking. For a script handed round as "save it anywhere and run it", a mistyped -TargetDir pointed at Documents would have destroyed it. It now only reclaims a directory that is empty or carries this launcher's own marker, and refuses otherwise. Dev secrets came from Get-Random, which is System.Random: a 32-bit seed, and all four values off one stream, so recovering one recovers the rest. They sign JWTs for a stack a pilot may point at real imported data. CSPRNG now, and the API port binds to loopback like the DB rather than every interface. Two of my own from earlier in this session. The operator-report size check sat below request.form, and the first access to that parses and spools the whole multipart - so it ran after the thing it prevents. Moved above. And the channel remap missed build-channel-installer.ps1 entirely, where it set BOTH channels to main: a "stable" package built from the trunk, the exact mislabel the remap was for. stable now resolves the newest v* tag. Also removed a zero-byte docs/ShopDB-Planner-Tasks-v2.csv that rode in. NOT FIXED - needs a decision, and it is the biggest thing the review found. Migrations 0006/0007 freeze the originating site's fleet convention into product seed: a shipped policy literally named "RPCA Shopfloor App Monitors", a hardcoded 'Shopfloor PC' type deciding who gets the shipped watch-list, and shipped rows the API refuses to edit or deactivate - including their launch paths. A site whose eDNC is on another drive cannot correct it and gets permanent false drift. Those are stamped migrations, so this gets harder to undo at every install. See the review notes before anyone upgrades a second site through 0006-0007. Suite 2314 passing, frontend 316, all three PowerShell files parse. Scope: product
96 lines
3.4 KiB
YAML
96 lines
3.4 KiB
YAML
# shopdb-flask PULL stack for dev workstations (stable / edge channels).
|
|
#
|
|
# WHY this exists: pilots behind an inspecting proxy cannot reliably BUILD the
|
|
# image from source (npm's many small fetches get reset mid-install). Instead
|
|
# CI (.github/workflows/publish-images.yml) builds the image on clean runners
|
|
# and publishes it to GHCR; this file just PULLS and RUNS it. One resumable
|
|
# image pull survives the proxy where hundreds of npm requests do not.
|
|
#
|
|
# Nothing is built here (no `build:`), mirroring docker-compose.airgap.yml:
|
|
# - api/migrate use `image:` (pulled from GHCR), never `build: .`.
|
|
# - NO ./plugins bind mount; the image carries every plugin baked in.
|
|
# - a one-shot `migrate` runs db upgrade + plugin upgrade-all + seed before
|
|
# api starts, so `up -d` alone brings up a working site.
|
|
#
|
|
# Driven by scripts/dev-bootstrap.ps1, which sets SHOPDB_IMAGE (the channel
|
|
# image ref), SHOPDB_ENV_LABEL, API_PORT and MYSQL_PORT per channel and picks a
|
|
# compose project name (-p shopdb-stable / -p shopdb-edge) so the two channels
|
|
# run side by side without colliding.
|
|
|
|
x-app-env: &app-env
|
|
FLASK_APP: wsgi.py
|
|
FLASK_ENV: production
|
|
DATABASE_URL: mysql+pymysql://shopdb:${MYSQL_PASSWORD}@db:3306/shopdb_flask?charset=utf8mb4
|
|
SECRET_KEY: ${SECRET_KEY:?SECRET_KEY must be set}
|
|
JWT_SECRET_KEY: ${JWT_SECRET_KEY:?JWT_SECRET_KEY must be set}
|
|
CORS_ORIGINS: ${CORS_ORIGINS:?CORS_ORIGINS must be set}
|
|
LOG_LEVEL: ${LOG_LEVEL:-INFO}
|
|
# Empty on stable; 'DEV' on edge, which lights the sidebar badge.
|
|
SHOPDB_ENV_LABEL: ${SHOPDB_ENV_LABEL:-}
|
|
|
|
services:
|
|
db:
|
|
image: mysql:8.4
|
|
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
|
restart: unless-stopped
|
|
environment:
|
|
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD must be set}
|
|
MYSQL_DATABASE: shopdb_flask
|
|
MYSQL_USER: shopdb
|
|
MYSQL_PASSWORD: ${MYSQL_PASSWORD:?MYSQL_PASSWORD must be set}
|
|
volumes:
|
|
- db_data:/var/lib/mysql
|
|
ports:
|
|
- "127.0.0.1:${MYSQL_PORT:-3306}:3306"
|
|
healthcheck:
|
|
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
# One-shot schema + seed. Runs to completion and exits; api waits for it.
|
|
# Every step is idempotent, so it is safe to run on every `up`.
|
|
migrate:
|
|
image: ${SHOPDB_IMAGE:?SHOPDB_IMAGE must be set to the channel image ref}
|
|
pull_policy: always
|
|
restart: "no"
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
environment:
|
|
<<: *app-env
|
|
command:
|
|
- sh
|
|
- -c
|
|
- >
|
|
flask db upgrade &&
|
|
flask plugin upgrade-all &&
|
|
flask seed permissions &&
|
|
flask seed settings &&
|
|
flask seed reference-data
|
|
volumes:
|
|
- instance_data:/app/instance
|
|
|
|
api:
|
|
image: ${SHOPDB_IMAGE:?SHOPDB_IMAGE must be set to the channel image ref}
|
|
pull_policy: always
|
|
restart: unless-stopped
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
migrate:
|
|
condition: service_completed_successfully
|
|
environment:
|
|
<<: *app-env
|
|
ports:
|
|
# Loopback only, same as the DB below. This stack generates its own
|
|
# dev secrets and may hold a real imported dataset; it has no business
|
|
# listening on every interface of a laptop on a shop-floor network.
|
|
- "127.0.0.1:${API_PORT:-5001}:5001"
|
|
volumes:
|
|
- instance_data:/app/instance
|
|
|
|
volumes:
|
|
db_data:
|
|
instance_data:
|