listAll goes through fetchAllPages, which pages past the server's 100-row cap
and returns the rows themselves. Three asset forms read .data.data off that
array. An array has no .data, so the second access threw and the surrounding
Promise.all rejected, taking the whole load into the catch.
On the PC edit page the throw landed before the record fetch, so no request was
made for the computer at all. The form rendered with its reference data and no
record, and the banner said only "Failed to load data" - which reads like a
backend or schema fault and was twice diagnosed as one. The server logs settled
it: every reference endpoint answered 200 and the record was never requested.
MachineForm already carried a comment describing this exact trap, added when
pcsRes hit it. Two adjacent lines had the same defect and were missed, so the
comment now sits above all of them.
Report the failure honestly while here. apiError only reads err.response, so a
client-side TypeError produced nothing but the fallback string no matter how
many times the page was reloaded. Fall back to err.message when there is no
response, which is what identified this.
Default fuzzy reconcile threshold to 0 and refuse merges when digit runs differ so numbered labels like Bay 311 never bind to Bay 310. Keep edit forms from clearing saved model fields when category filtering hides untyped rows. Includes in-progress computers policy and endpoints command feature work.
The backend clamps perpage to MAX_PAGE_SIZE (100), so location selectors that requested a large page received only the first page and still got a 200. Late-alphabet locations (past ~C5W) simply went missing from every module's dropdown, and no error surfaced.
Add locationsApi.listAll, which pages through the full set via fetchAllPages, matching the established machines/printers pattern, and switch every location selector consumer to it.
The Add Computer, Add Machine, and Add Printer forms offered the entire model catalog and vendor list regardless of what was being added, so a PC could be pointed at a printer model and every vendor showed up whether or not it made that kind of thing. Scope each form to its own asset category so the selectors only present sensible choices.
Add a ?category= filter to GET /api/models that joins modeltypes and matches on category (Equipment, PC, Network, Printer), and include the model's category in the list response so the forms can distinguish types client-side. Untyped models are excluded, since a null category cannot satisfy the filter.
Each subtype form now loads only its category: PCForm requests category PC (servers, which are Network, stay out); MachineForm requests category Equipment, with the main Model dropdown hiding Controller-type models and the Controller dropdown showing only those; PrinterForm requests category Printer, which also fixes its Model list that previously showed the whole catalog.
Vendor dropdowns are derived from the loaded models: a form only offers vendors that actually have a model in its category. Each dropdown keeps the record's own saved vendor even if it falls outside that set, so editing an existing asset never drops its vendor.
PC records now carry Entra/Azure device identity so imported machines keep a durable link back to their directory object instead of matching on hostname alone. Adds azuredeviceid, azureobjectid, and azuredevicecategory to the Computer model with matching migrations, surfaces them read-only in PCDetail as an Entra Device Info card, and lets PCForm edit the device id directly. The computers plugin registers a machineassetnumber import hook that reconciles PC-to-machine links during bulk import and warns on unresolved or conflicting relationships.
Adds importsources adapters that normalize Azure device-group and Thor PC inventory exports into canonical ShopDB CSVs via hostname alignment, exposes register_import_hook and RowWarning on the shopdb.api contract surface (contract 0.21.0), and refreshes the Import Site Assets page with clearer source instructions. Includes reconcile_locations maintenance script and coverage for the new import paths.
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.