The map was one picture of one floor. A second floor was added, the blueprint changed size, and machines moved, so a position now records WHICH DRAWING its coordinates belong to. Buildings and levels (ADR-017). Each level owns its blueprint per theme and its own native pixel size; assets.mapx/mapy are pixels of assets.levelid, not of the site. A position whose level is unknown renders "level unknown" and is never drawn on the default level, because a marker on the wrong floor plan looks entirely correct while pointing at the wrong place. Repositioning in bulk: filter by unplaced, needs-review or level, search, place, confirm. Landmark recalibration solves the transform PER AXIS from landmark pairs and never from image dimensions - the canvas grew taller without rescaling, so a dimension-derived scale would stretch Y by 1.57 and be wrong everywhere. It defaults to a dry run, reports what would land off the drawing, snapshots before applying, and clears mapverifiedat because a transform is a guess awaiting review. Snapshots restore, including the level and the review state, and a restore snapshots first so an undo is undoable. Search: gaugelabreference was matched only for measuring tools and maintenancereference was matched nowhere at all, for any asset type, while Settings happily offers both identifiers on machines and PCs. A tag an operator is told to record has to be findable or it is a write-only field. USB devices and printed items were unreachable from search entirely - neither is an asset, so the generic asset search could not see them and no searcher existed; they now match on serial, asset tag, label, bin code and gage-lab tag, honouring isactive, with Settings toggles and result labels to match. The retired-application rule was half a rule: GET /api/knowledgebase hid articles whose topic application is retired while global search still returned them and printed the retired application as the subject. A filter is only real if every path that reaches the row applies it. Contract to 0.20.0 (additive): Asset gained levelid and mapverifiedat, Location gained levelid, and resolve_asset_position returns the levelid belonging to whichever source supplied the coordinates. The five plugins that write a map position are re-pinned. The install-list text format gained levelid as a NINTH field, appended, because the shipped Pascal installer reads fields 0-7 by index. That installer still compiles in one drawing's dimensions and bundles one blueprint, so its map is accurate for the default level only; /api/maplevels is deliberately unauthenticated so it can read both at runtime once rebuilt. Recorded in PRINTER-INSTALLER.md section 6 along with the other known gaps. Migration 7d33 converts an existing single-map site into one building and one default level carrying the old map_* settings, then assigns every placed asset and location to it. Nothing moves on screen. Old settings rows are kept so a rollback still finds them. Verified end to end on MySQL 5.6 from a production-shaped database.
140 lines
5.8 KiB
Markdown
140 lines
5.8 KiB
Markdown
# Printer installer map and install endpoints
|
|
|
|
How the shop-floor fleet installs network printers from shopdb-flask, replacing
|
|
the classic ASP `apiprinters.asp` / `apipcdefaultprinter.asp` / `installprinter.asp`
|
|
contract. Shopfloor 2.0 PCs cannot run unsigned `.bat` maps, so a signed
|
|
installer EXE (and the public web map page) drives installs from three endpoints
|
|
in the printers plugin.
|
|
|
|
- Server code: `plugins/printers/api/asset_routes.py`
|
|
(`printer_install_list`, `pc_default_printer`, `printer_install_batch`)
|
|
- Consumed as a fleet manifest entry: the `common` scope's `printer map`
|
|
entry (see `GE-ENFORCE-DISPLAY.md`).
|
|
|
|
All three endpoints are `@jwt_required(optional=True)`: an anonymous fleet
|
|
client works, and a logged-in browser (the public map page) works too.
|
|
|
|
---
|
|
|
|
## 1. The public map page
|
|
|
|
`PrinterInstallerMap` is a public (no-login) frontend page: the floor map with
|
|
printer hotspots positioned at each printer's `mapx` / `mapy`. The user clicks
|
|
the printers they want, and the page requests an install batch. The PC's default
|
|
printer is preselected via `pc-default`.
|
|
|
|
---
|
|
|
|
## 2. `GET /api/printers/install-list`
|
|
|
|
Flat, unpaginated list of active NETWORK printers. A printer counts as network
|
|
only if it has a hostname or a non-USB IP; USB-only printers are excluded.
|
|
|
|
Fields per row:
|
|
|
|
| Field | Notes |
|
|
|---|---|
|
|
| `printerid` | Printer id (the token `install-batch` takes). |
|
|
| `name` | Asset name, else asset number. |
|
|
| `machinenumber` | The asset number. |
|
|
| `windowsname` | Standardized Windows printer name. |
|
|
| `sharename` | Share / CSF name. |
|
|
| `hostname` | Print-queue host. |
|
|
| `ipaddress` | Primary IP (falls back to any communication row). |
|
|
| `vendorname` | Direct vendor, else the model's vendor. |
|
|
| `modelnumber` | Model name. |
|
|
| `installpath` | Installer path for this printer (see install-batch). |
|
|
| `iscsf` | CSF flag. |
|
|
| `locationname` | Location name, if the asset has one. |
|
|
| `mapx` / `mapy` | Floor-map hotspot position, in the native pixels of `levelid`. |
|
|
| `levelid` | Which level (drawing) those pixels belong to. Null when unplaced. |
|
|
|
|
`?format=text` returns a pipe-delimited line per printer, one printer per line,
|
|
with a fixed field order so the Inno / Pascal installer does a `split()` instead
|
|
of parsing JSON:
|
|
|
|
```
|
|
printerid|windowsname|vendorname|modelnumber|hostname|ipaddress|mapx|mapy|levelid
|
|
```
|
|
|
|
Any pipe or newline inside a value is neutralized to a space so the field count
|
|
stays fixed. The web map uses the default JSON.
|
|
|
|
**Fields 0-7 are frozen.** The shipped installer reads them positionally
|
|
(`GetField(Line, 6)` is mapx), so a new field goes on the END and nowhere else -
|
|
inserting one shifts every later field and the installer keeps running while
|
|
reading a model number as a coordinate. `levelid` is field 8 for that reason, and
|
|
installers built before levels existed ignore it.
|
|
|
|
An installer that ignores `levelid` draws every printer on whichever single
|
|
blueprint it ships, which is correct only while a site has one level. See
|
|
ADR-017 and section 6.
|
|
|
|
---
|
|
|
|
## 3. `GET /api/printers/pc-default?machine=NNNN`
|
|
|
|
The PC's default printer, by machine (asset) number persisted at PXE enrollment.
|
|
Parity with classic `apipcdefaultprinter.asp`: the installer preselects a PC's
|
|
default-printer hotspot on the site-map wizard. The link is resolved through the
|
|
`defaultprinter` asset relationship (PC asset -> printer asset), so it stays
|
|
inside the contract surface (no cross-plugin model import).
|
|
|
|
Returns `{printerid, windowsname}`, or `{}` when the machine is unknown or has no
|
|
active default printer set.
|
|
|
|
`?format=text` returns one pipe-delimited line (`printerid|windowsname`), or an
|
|
EMPTY body when there is no default (so the installer's split yields nothing).
|
|
|
|
---
|
|
|
|
## 4. `GET /api/printers/install-batch?printerids=1,2,3`
|
|
|
|
Returns a self-deleting Windows `.bat` that installs the selected printers,
|
|
grouped the same way classic `installprinter.asp` grouped them:
|
|
|
|
- HP / Xerox: one universal `PrinterInstaller.exe /PRINTER="a,b,c"` call.
|
|
- Printers with a `.exe` `installpath`: a PowerShell `Invoke-WebRequest` download
|
|
(using the caller's Windows credentials, against the site base URL + the
|
|
IIS-served `/installers` folder) followed by running it `/SILENT`.
|
|
- No `installpath`, or a non-`.exe` payload (e.g. a `.zip`): listed as a manual
|
|
install rather than run blindly.
|
|
|
|
`printerids` is required, comma-separated; non-numeric tokens are ignored. An
|
|
empty / missing list is a validation error.
|
|
|
|
The install name preference is `windowsname`, else `sharename`, else the asset
|
|
name / number.
|
|
|
|
---
|
|
|
|
## 5. Fleet wiring
|
|
|
|
The `common` scope's `printer map` manifest entry (see `GE-ENFORCE-DISPLAY.md`)
|
|
lays down the signed installer that consumes these endpoints. The web map page
|
|
covers the same install flow for a human at a browser.
|
|
|
|
---
|
|
|
|
## 6. The shipped installer predates levels (known gap)
|
|
|
|
The Pascal installer in the `inno` repo (`PrinterInstallerMap`) still assumes one
|
|
drawing per site, in two places that must change together:
|
|
|
|
- `MAP_SOURCE_W = 3300` / `MAP_SOURCE_H = 2550` are compiled-in constants, and
|
|
every hotspot is scaled by them. They are the dimensions of ONE level.
|
|
- A downsized copy of that level's blueprint is bundled into the installer
|
|
(`880x680`), so the picture is fixed at build time.
|
|
|
|
Two consequences, neither of which the installer can detect:
|
|
|
|
1. It ignores field 8, so printers on any level are drawn on the bundled image.
|
|
Coordinates from a different drawing land somewhere plausible and wrong.
|
|
2. When a level's blueprint is replaced with one of different dimensions, the
|
|
constants and the bundled image are both stale and every hotspot shifts.
|
|
|
|
The API side is ready: `GET /api/maplevels` is deliberately unauthenticated so
|
|
the installer can fetch level dimensions and blueprint URLs at runtime rather
|
|
than compiling them in, which is what fixes both. Until the installer is
|
|
rebuilt against it, treat its map as accurate for the default level only.
|