Printer assignment end to end, and a container that keeps a site's files
Assign printers to a MACHINE and let whichever PC controls it install them, with the observed half kept strictly apart from the assigned half: a collector report can never write an assignment row. Adoption is explicit. Adds a singular relationship type so defaultprinter can mean exactly one, client scripts that converge a bay's queues and correct a drifted one in place, and a wave gate so a fleet-wide driver rollout cannot stampede its own share. On the container side: the compose stack never persisted the instance directory, so a rebuild discarded plugins.json and every upload. It is a volume now, the image ships instance/ owned by the app user so the mountpoint is writable, and the database moves to MySQL 8.4 LTS, which the Windows runbook has recommended since 8.0 went end of life. The image also could not build at all: the frontend stage never copied the plugin staging script it runs. Adds a topology migration guide covering IIS to Docker and back.
This commit is contained in:
148
CHANGELOG.md
148
CHANGELOG.md
@@ -10,6 +10,154 @@ ADR-007 and ADR-002.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- **Printer assignment has a form.** `PrinterAssignmentPicker` is the ONE picker
|
||||
for both ends: MachineForm gained it (the machine is where the assignment
|
||||
belongs and there was no way to set it except the relationships card), and
|
||||
PCForm now uses the same component instead of its own copy, so the two sides
|
||||
of an override cannot drift. PCForm also stopped reconciling row at a time
|
||||
through the generic relationship endpoints, which left a PC half-assigned on
|
||||
an HTTP failure; it calls
|
||||
`PUT /api/printers/assignments/for-asset/<id>` instead.
|
||||
- **A relationship type can declare itself singular**
|
||||
(`relationshiptypes.issingular`, migration 7d34), and `defaultprinter` does.
|
||||
Setting a second default REPLACES the first. The unique constraint is
|
||||
(source, target, type), so two different targets were two valid rows and the
|
||||
resolver took the OLDEST: a new default silently lost.
|
||||
- **Observed printer queues.** `POST /api/collector/printers` (the ADR-006 hook,
|
||||
no new transport and no new credential) records what a bay ACTUALLY has in a
|
||||
plugin-owned table, kept strictly apart from what it is assigned. Adoption is
|
||||
explicit via `POST /api/printers/assignments/seed-from-observed`, which routes
|
||||
through the same reconcile path as the editor and REFUSES a queue matching no
|
||||
known printer. New client script `Report-PrintersToShopDB.ps1`.
|
||||
- **Wave-gated driver rollout.** `Install-ShopdbPrinterDrivers.ps1` takes
|
||||
`-WaveStart`, `-Waves`, `-WaveUnit` and `-IgnoreWave`. GE-Enforce offsets each
|
||||
PC by SHA256(hostname) % 5 MINUTES, which was sized for a JSON check, not a
|
||||
100 MB driver set: ungated, ~300 bays pull ~30 GB inside one five-minute
|
||||
window on the share the whole floor depends on. Each bay derives its wave from
|
||||
its own hostname, the gate runs BEFORE the manifest is read (the manifest is
|
||||
on that share too), and it fails closed on an unparseable date.
|
||||
|
||||
### Changed
|
||||
|
||||
- **MySQL 8.4 LTS** in both compose files, and as the default in
|
||||
`build-offline-bundle.ps1`. 8.0 reached end of life in April 2026, which
|
||||
`INSTALL-WINDOWS-IIS.md` already said while the stack still pinned it; the two
|
||||
halves of the product disagreed about which server a site should run.
|
||||
Verified against a real 8.4.11 server rather than by changing a tag: the core
|
||||
chain and five plugin chains applied clean, 66 tables at a single
|
||||
`utf8mb4_unicode_ci` collation, and the image's PyMySQL authenticates against
|
||||
8.4's `caching_sha2_password` (which is why `cryptography` is pinned).
|
||||
Sites upgrading an EXISTING server should note that 8.4 removes
|
||||
`mysql_native_password`: an account created on 5.6 or 5.7 must be moved to
|
||||
`caching_sha2_password` before the upgrade or it cannot log in afterwards.
|
||||
Air-gapped sites need a fresh offline bundle, since the tarball carries the
|
||||
MySQL image.
|
||||
- **The database healthcheck probes over TCP** rather than the unix socket. The
|
||||
entrypoint's init pass answers on the socket while running the server with
|
||||
`--skip-networking`, so a socket ping reported healthy DURING init and
|
||||
`depends_on: service_healthy` released `api` and `migrate` against a server
|
||||
that was about to restart. Found by having it happen: a probe passed at 8s and
|
||||
the next query failed because the server was mid-restart.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **The image did not build.** `npm run build` fires a `prebuild` hook that runs
|
||||
`node ../scripts/stage-frontend.mjs`, which stages each plugin's frontend into
|
||||
the Vite tree and codegens `routes.gen.js`. The Dockerfile's frontend stage
|
||||
copied `frontend/` alone and flattened it to the stage root, so that path
|
||||
resolved to `/scripts` and every build since the staging script landed died on
|
||||
`Cannot find module`. The stage now keeps the repo-relative layout and copies
|
||||
`scripts/stage-frontend.mjs` and `plugins/` in beside it. Verified end to end:
|
||||
16 plugin frontends staged, `frontend/dist` in the final image.
|
||||
- **The compose stack did not persist `instance/`.** `db_data` was a volume and
|
||||
the instance directory was not, so `docker compose build api && up -d api`
|
||||
recreated the container and discarded `plugins.json` along with every upload:
|
||||
floor plans, branding, model and application images, employee photos,
|
||||
warranty proofs, slides, printed-part files and the Dell OAuth token. The
|
||||
visible symptom was a site coming back with its plugins disabled. Both compose
|
||||
files now mount an `instance_data` volume (the air-gap `migrate` service too,
|
||||
since `plugin upgrade-all` writes `plugins.json`), and DEPLOY.md carries the
|
||||
one-time rescue for a stack that predates it.
|
||||
- **A drifted print queue is corrected, not just a missing one.** Queues were
|
||||
matched by NAME alone, so a bay whose printer had moved or whose queue was
|
||||
built on a replaced driver looked converged and printed to the wrong device.
|
||||
`Set-ShopdbPrinters.ps1` now repoints a wrong port and swaps a wrong driver
|
||||
IN PLACE with `Set-Printer`, so the queue keeps its name, sharing, permissions
|
||||
and whoever holds it as their default. The driver is only swapped when the
|
||||
wanted one is staged, and there is still no removal path in the script.
|
||||
- **MachineForm's dropdowns all came up empty.** It read `.data.data` off
|
||||
`computersApi.listAll()`, which already resolves to the array, so the whole
|
||||
parallel load threw into the catch and the machine's own values never loaded.
|
||||
- **The legacy import loader dropped `machines.printerid`**, the classic
|
||||
system's record of each machine's default printer, so the production import
|
||||
would have lost every one.
|
||||
|
||||
## [0.11.3] - 2026-08-19
|
||||
|
||||
Fixes the last of the buildings-and-levels bugs, and brings the shop-floor
|
||||
clients into the product: the collector reporter, EventSaver and a printer
|
||||
assignment feature that lets a bay install its own printers.
|
||||
|
||||
**Anyone on 0.11.0-0.11.2 should take this.** The hover preview reported "this
|
||||
asset has a position but no level" for every asset, and the map PDF printed
|
||||
markers from every floor onto one sheet.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Every hover mini-map said "no level".** When 0.11.0 gave LocationMapTooltip a
|
||||
levelid, none of its seven call sites were taught to pass one - printer,
|
||||
machine and PC detail, the toner report, enforcement reports, the warranty chip
|
||||
and the dashboard cards. Two payloads behind them also emitted coordinates with
|
||||
no level.
|
||||
- **Map PDF export printed other floors' markers.** It plotted every filtered
|
||||
asset onto one sheet, which is worse than the on-screen version was: nobody can
|
||||
correct a sheet once it is printed and carried onto the floor.
|
||||
- **The legacy import loader created undrawable markers**, sending mapleft/maptop
|
||||
with no level at three call sites. That loader is the one still to run against
|
||||
production.
|
||||
- The build gate that should have caught all of this asked whether a FILE
|
||||
mentions `levelid`, not whether each position does - one module emitted `mapx`
|
||||
six times and `levelid` once and passed. It now checks per occurrence, covers
|
||||
`scripts/`, and fails any Vue file that binds tooltip coordinates without a
|
||||
level.
|
||||
|
||||
### Added
|
||||
|
||||
- **Printer assignment.** Printers belong to the MACHINE and reach whichever PC
|
||||
controls it, so a reimaged or swapped PC comes back with the bay's printers and
|
||||
nothing had to be saved off the old one. New `usesprinter` relationship type,
|
||||
propagating through `controls` at read time; `GET /api/printers/for-host/<host>`
|
||||
for the client; `PUT /api/printers/assignments/for-asset/<id>` to reconcile a
|
||||
whole set in one call.
|
||||
- **`printerdrivers.vendorid`**, so one row serves a make. HP and Xerox universal
|
||||
drivers cover 41 of the reference site's 44 printers; binding a driver to a
|
||||
single model meant 21 near-duplicate rows. Resolution is model, then vendor,
|
||||
then the pre-vendorid name convention.
|
||||
- **Client scripts** in `plugins/printers/client/`: stage a site's driver set
|
||||
(`Install-ShopdbPrinterDrivers.ps1`, with `-TestOnly` for DSC), converge a
|
||||
bay's queues (`Set-ShopdbPrinters.ps1`), and apply the per-user default
|
||||
(`Apply-ShopdbDefaultPrinter.ps1`).
|
||||
- **The collector reporter is in the repository** at
|
||||
`plugins/computers/client/`, with no site in it, and the server generates a
|
||||
copy carrying this site's URL and ranges: Settings > Computers > Asset
|
||||
reporter, or `GET /api/computers/client-script`. The collector key is
|
||||
deliberately never stamped into it.
|
||||
- **EventSaver source** at `plugins/slides/client/`. The site UNC that was
|
||||
compiled into it is gone; a missing ini now fails visibly rather than pointing
|
||||
a new site at the reference site's file server.
|
||||
- Settings: `computers_routableranges` (replaces two hardcoded VLANs) and the
|
||||
buildings-and-levels admin gains editable width AND height.
|
||||
|
||||
### Changed
|
||||
|
||||
- The map editor accepts a click. Its handler was bound only if the map was
|
||||
already a picker when it mounted, and the editor opens with nothing selected,
|
||||
so placing a marker by hand was impossible for the life of the page.
|
||||
- Global search orders every query before truncating, so the same search cannot
|
||||
return a different subset twice.
|
||||
|
||||
## [0.11.2] - 2026-08-17
|
||||
|
||||
Bug fixes for the buildings-and-levels work in 0.11.0, all found by using it.
|
||||
|
||||
Reference in New Issue
Block a user