Commit Graph

237 Commits

Author SHA1 Message Date
cproudlock
4f3ea2848a GE monogram avatar fallback + per-page document titles
Some checks failed
CI / backend (push) Successful in 1m44s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
CI / migrations-mysql (push) Failing after 7s
Users without a profile photo (and broken photo URLs) show the GE
monogram instead of nothing/initials - sidebar identity, employee
detail hero, and the directory list thumbs; the shopfloor cards
already did this. Document titles become
'<Facility> ShopDB - <Page>' via a router afterEach (facility from
public settings, page label from meta.title or a prettified route
name with spellings for PCs/USB/GE-Enforce/3D Printed Parts/...), so
copied links and browser tabs identify the page.
2026-07-17 11:31:38 -04:00
cproudlock
5625608bd0 Employees: external photo base URL is a setting
Some checks failed
CI / backend (push) Successful in 1m45s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
External HR Picture values are relative paths; the resolver hardcoded
/static/employees/ (which the SPA then mounts under the subpath, e.g.
/ops/static/...), but sites like WJ serve those photos from the
classic EmployeeDBAPP on another URL entirely. New setting
employee_photo_base_url (blank keeps the old behavior; a full URL like
https://host/EmployeeDBAPP/images/ passes through withBase untouched),
declared in the plugin config schema.
2026-07-17 11:16:04 -04:00
cproudlock
0cc205d25e Users: deleting a user clears their API tokens and detaches audit rows
Some checks failed
CI / backend (push) Successful in 1m45s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
Deleting any user who owned an API token or appeared in the audit log
hit the users FK and 500ed - the import's 'importer' account being the
guaranteed case (its PAT plus every audit row the import wrote).
Tokens are revoked outright; audit history is kept but detached
(userid NULL), so the trail survives the account.
2026-07-17 10:54:47 -04:00
cproudlock
bb5308bae0 printedparts: badge resolution honors the employee directory mode
Some checks failed
CI / backend (push) Successful in 1m44s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
The resolver only read the self-hosted directory table, which is empty
at sites running the external HR directory - every kiosk badge fell to
the deny policy. It now branches on employee_directory_mode like the
usb plugin: selfhosted looks up DirectoryEmployee by SSO; external
queries the HR directory via employee_connection, resolving PayNo
badges by their real PayNo column and recovering the employee's SSO.
2026-07-17 10:39:11 -04:00
cproudlock
d297c5b75d IIS runbook: app pool needs Modify on instance/
Some checks failed
CI / backend (push) Successful in 1m44s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
The plugin registry (instance/plugins.json), uploaded logos, floor
plans, item photos, and print files all write under instance/; with
the app pool at read-only, toggling a plugin in Settings surfaces as
an internal error and every upload fails. Grant Modify in step 7.3
and add the troubleshooting row.
2026-07-17 10:34:52 -04:00
cproudlock
deb6dd2162 Ignore the publication clone's _transfer bundle folder
Some checks failed
CI / backend (push) Successful in 1m43s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
CI / migrations-mysql (push) Failing after 7s
2026-07-17 10:01:34 -04:00
cproudlock
02ed88c7c5 Merge printedparts: 3D-printed parts storefront, kiosk, labels, alerts
Some checks failed
CI / backend (push) Successful in 1m44s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
The plugin-lab exemplar built end to end: catalog with photos and
print-file revisions, badge-attributed stock ledger, touch kiosk with
an open decrement-only take endpoint (decision record), 1x0.5in bin
labels, low-stock alerts to users/roles/emails, reports with a
reconcile check, per-plugin migrations 0001+0002, contract 0.13.0
(mailer + User/Role on the plugin surface).
2026-07-17 09:20:43 -04:00
cproudlock
d1357defc4 printedparts: catalog access is printedparts.view-gated
Some checks failed
CI / backend (push) Successful in 1m43s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 9s
CI / migrations-mysql (push) Failing after 8s
Browsing the catalog (item list, detail, file listings) now requires
authentication plus the view permission, and the /printedparts pages
and the label print page require login. Still deliberately open: the
kiosk endpoints per the decision record, the image serve and file
download (img tags and anchor downloads cannot carry a JWT), and the
reports (product-wide jwt-optional convention). Grant
printedparts.view to the roles that should see the catalog.
2026-07-17 09:19:30 -04:00
cproudlock
96e48e0f50 printedparts kiosk: keypad and entry-panel visual polish
Some checks failed
CI / backend (push) Successful in 1m44s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 7s
The keypad becomes a proper terminal pad: fixed 3-column grid of
rounded square buttons with tabular numerals, press feedback, and
muted Clear/backspace actions. Each manual step (item number, SSO,
quantity) shares one card panel - boxed entry display with placeholder
styling, keypad, and a full-width action button.
2026-07-17 09:13:27 -04:00
cproudlock
4dfdb167d5 printedparts stage 16: kiosk touch fixes from first hands-on use
Some checks failed
CI / backend (push) Successful in 1m44s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
The tap-anywhere wedge refocus stole focus from the manual-entry field
the moment it was tapped - the handler now only reclaims focus from
dead space, never from a real control. Manual entry works without a
physical keyboard: badge entry uses the TouchKeypad (an SSO is
digits), and item lookup accepts bare digits resolved by row id - the
digits in a minted code are the id, which also keeps labels printed
under an older prefix scannable after the prefix changes.
lab-stage-16
2026-07-17 09:04:52 -04:00
cproudlock
aa4bfcd41c printedparts stage 15: print-file revision history + role-based alerts
Some checks failed
CI / backend (push) Successful in 1m44s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 7s
printeditemfiles lands as the plugin's first incremental migration
(0002 on the plugin chain - the ADR-008 payoff). Revisions are
append-only per item: upload assigns the next number, records the
uploader from the JWT, enforces an extension allowlist and a 100 MB
cap; download serves the original filename; a permission-gated delete
covers wrong-file mistakes. The detail page gains the revision table
with a current badge. Unique storedfilename is sized 191 so the index
fits MySQL's 767-byte prefix - the per-plugin chain does not apply the
core env's ROW_FORMAT hook.

Alert recipients gain roles: Role joins the 0.13.0 surface, a role
picker on the settings page, and every active member of the selected
roles is folded into the deduped recipient list.
lab-stage-15
2026-07-17 08:47:41 -04:00
cproudlock
26b6b6b32f printedparts: Parts Kiosk link in the sidebar Displays section
Beside Shopfloor Dashboard and TV Slideshow, opening in a new tab and
shown only while the plugin is enabled - kiosk-style pages get
launched from the Displays group, not the Information nav.
2026-07-17 08:38:57 -04:00
cproudlock
eab225e1e6 printedparts stage 14: retire/restore in the UI, dashless item codes
Some checks failed
CI / backend (push) Successful in 1m42s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
CI / migrations-mysql (push) Failing after 7s
Retire button with confirmation on the detail page (item leaves the
storefront and the kiosk rejects its code; ledger history and label
survive), Restore on retired items, and an Include-retired list toggle
with a badge. Restore is its own permission-gated POST - the generic
update still cannot flip isactive. New codes mint as WJRP0042 style
without the dash; existing codes are immutable bin labels and keep
their form.
lab-stage-14
2026-07-17 08:35:31 -04:00
cproudlock
a8a6baf979 printedparts stage 13: pick alert recipients from shopdb users
Some checks failed
CI / backend (push) Successful in 1m42s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
Contract 0.13.0 puts the User model on the plugin surface. The
settings page gains a checkbox picker over the user list; selected
users receive low-stock alerts at their account email, merged and
deduped with the free-text address list, inactive accounts skipped,
site alert_recipients still the fallback when both are empty.
lab-stage-13
2026-07-17 08:30:04 -04:00
cproudlock
427eb0de8c printedparts stage 12: admin settings page + settings-rail card
Some checks failed
CI / backend (push) Successful in 1m42s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
CI / migrations-mysql (push) Failing after 7s
PrintedPartsSettings edits the four plugin settings (code prefix,
default threshold, kiosk badge policy, alert recipients) through the
core settings API; the route rides the plugin's router file and the
settings shell nests it into the rail; get_settings_cards contributes
the catalog card while the plugin is enabled.
lab-stage-12
2026-07-17 08:25:33 -04:00
cproudlock
df918ed38f printedparts stage 11: low-stock email alerts on threshold crossing
Some checks failed
CI / backend (push) Successful in 1m43s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
Contract 0.12.0: send_email/send_alert join the plugin surface (the
mailer was core-only), PLUGIN-HOOKS and status docs updated, manifest
pins the new floor. The alert fires inside _ledger_write only when a
decrement CROSSES the item's threshold - one alert per depletion,
rearmed by restocking above - and is best-effort after the commit so
mail trouble can never fail a take. Recipients come from
printedparts_alert_email, falling back to the site alert_recipients.
on_enable re-seeds settings idempotently so existing installs pick up
new keys. Crossing/rearm semantics proven by test.
lab-stage-11
2026-07-17 08:15:50 -04:00
cproudlock
fc0d48a6a7 printedparts stage 10: closeout - lab guide rewritten from the real build
Some checks failed
CI / backend (push) Successful in 1m42s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
The lab is now a build-along mirroring what actually happened: ten
stages, each with the goal, the divergences, a see-it-work check, and
the errors genuinely hit while building (empty Migration error from a
broken model import, the migration-guard KeyError, the missing Lucide
icon, nested-app-context test writes, Decimal sums, and the authz
sweep catching the deliberately open kiosk take). That last one gets
its explicit EXEMPT_ENDPOINTS entry with a pointer to the decision
record - the net stays, the exception is reviewable. Full suite: 993
backend tests, 49 vitest, frontend build, naming hook, all green.
lab-stage-10
2026-07-17 08:11:36 -04:00
cproudlock
b68e927ef6 printedparts stage 9: reports - stock w/ reconcile, consumption, by-person
Some checks failed
CI / backend (push) Failing after 1m42s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
Three jwt-optional endpoints with ?format=csv, merged into the reports
hub via get_reports while the plugin is enabled. The stock report's
ledgerdelta column is the reconcile check: 0 for every item whose
stock moved through the ledger, nonzero for anything that bypassed it
(the hand-seeded dev rows demonstrate the catch). MySQL SUM returns
Decimal - cast to int or the delta serializes as a string.
lab-stage-09
2026-07-17 08:04:09 -04:00
cproudlock
6439d1ccd9 printedparts stage 8: 1x0.5in bin labels
Some checks failed
CI / backend (push) Failing after 1m42s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
CI / migrations-mysql (push) Failing after 7s
New public print view at /print/printedparts-labels following the
plugin-owned USB label precedent: multi-select with per-item copies,
CODE128 of the item code via JsBarcode (a QR at this size is at the
edge of scanner tolerance), one label per page on 1in x 0.5in roll
stock via a new @page size. The Detail page's Bin Label button
preselects its item through ?item=<id>; the list header gains a batch
Print Labels button.
lab-stage-08
2026-07-17 08:00:35 -04:00
cproudlock
6ed3da1b64 printedparts stage 7: the kiosk - scan bin, scan badge, keypad, take
Some checks failed
CI / backend (push) Failing after 1m43s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
Two open endpoints: an item lookup by scanned code and the take POST -
the product's first unauthenticated write, held to the decision
record's bar (decrement-only, badge-attributed server-side, bounded,
physically rate-limited; justification in the plugin README). The
/parts-kiosk route is a full-screen no-auth view beside /shopfloor: a
hidden always-focused input consumes keyboard-wedge scans for
whichever step is active, TouchKeypad (net-new 3x4 grid) takes the
quantity, and a success screen resets after a few seconds. Manual
type-in fallbacks cover damaged labels. Kiosk test proves open access,
the over-take guard, the badge policy, and cache==ledger afterward.
lab-stage-07
2026-07-17 07:49:13 -04:00
cproudlock
d6a78a72ff printedparts stage 6: RBAC - declared permissions gate every mutation
Some checks failed
CI / backend (push) Successful in 1m42s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
get_permissions declares view/create/edit/delete/restock (seeded on
install/enable and by flask seed permissions); every write route adds
require_permission on top of jwt_required. New test proves
authentication alone is not authorization: a role-less member gets
403 where an admin succeeds.
lab-stage-06
2026-07-17 07:42:07 -04:00
cproudlock
6dfc8906c4 printedparts stage 5: the ledger - restock/adjust with badge attribution
Some checks failed
CI / backend (push) Failing after 1m42s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
Badge resolver copied from the USB contract (SSO digits, 0<digits>BZ
PayNo wrap) with names from the employees directory and the
unknown-badge policy setting; deliberately copied rather than
cross-imported so the contract test stays green. Restock and adjust
write the ledger row and move the cached quantity in one commit -
the single-commit invariant every write path must use. Adjust
requires a reason and refuses to drive stock below zero. Detail page
gains Restock/Adjust modals. Seven tests cover minting, the
cache==ledger invariant, badge shapes, policy toggle, and auth.
lab-stage-05
2026-07-17 07:41:18 -04:00
cproudlock
cb367a38f9 printedparts stage 4: catalog mutations, item photos, detail + form
Some checks failed
CI / backend (push) Failing after 1m40s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
POST/PUT/DELETE for items: create mints the itemcode from the
configured prefix plus the flushed row id, update refuses
quantityonhand (ledger-managed - restock/adjust arrive next stage),
delete soft-retires. The image upload/serve/delete trio replicates the
models.py pattern into instance/printedpartsimages/ with a public GET.
PrintedItemDetail follows the unified detail skeleton (hero photo,
info list, transaction history table); PrintedItemForm covers
create/edit plus photo management on edit.
lab-stage-04
2026-07-17 07:36:54 -04:00
cproudlock
d1c844d533 printedparts stage 3: read API + list page (first visible win)
Some checks failed
CI / backend (push) Successful in 1m39s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
GET /items (paginated, search across code/name/description/bin,
lowstock filter) and GET /items/<id> with recent transactions, both
open reads. printedpartsApi client, router file repointed at the
renamed views, PrintedItemsList with image thumbs and a red/green
quantity badge against the per-item threshold. Nav entry '3D Parts'
with a new 'box' Lucide icon mapping (the sidebar renders nothing for
unknown icon names - lab gotcha).
lab-stage-03
2026-07-16 17:10:42 -04:00
cproudlock
f5cfac33b4 printedparts stage 2: models, real 0001 baseline, tables live
Some checks failed
CI / backend (push) Successful in 1m39s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
PrintedItem (catalog: code, name, image, cached quantityonhand,
per-item threshold, bin) and PrintedItemTransaction (the ledger:
signed quantity change attributed to a badge-resolved employee).
Both registered in PLUGIN_TABLE_OWNERS; 0001 is a post-cutover real
baseline. The migration-guard test learns the new expected head.
Routes are a placeholder ping until the next stage - the scaffold's
list route imported the deleted scaffold model, which surfaces as an
empty 'Migration error' because the alembic env imports the models
package.
lab-stage-02
2026-07-16 16:57:21 -04:00
cproudlock
8dd1fadeca printedparts stage 1: scaffold, no AssetType, manifest per spec
Some checks failed
CI / backend (push) Failing after 1m38s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
CI / migrations-mysql (push) Failing after 8s
flask plugin new output, minus the scaffold's AssetType seeding:
printed parts are quantity-based consumables, not ADR-001 assets.
on_install seeds the three plugin settings instead. Manifest pins
core >=0.11.0, depends on employees (badge name resolution), ships
disabled until a site opts in.
lab-stage-01
2026-07-16 16:44:54 -04:00
cproudlock
a0b92f7b5e printedparts lab: set expectations up front
Some checks failed
CI / backend (push) Successful in 1m39s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 7s
State that this is a bundled plugin whose frontend and three small core
edits land in this repo, list the three deliberate divergences from the
scaffold before the learner hits them, suggest a per-milestone solution
branch for instructors, and point out the earliest visible win (wire
the bare list page as soon as the GET endpoint works).
2026-07-16 16:37:28 -04:00
cproudlock
6362cef699 printedparts docs: record the open-write kiosk decision; defer the dashboard widget
Some checks failed
CI / backend (push) Successful in 1m38s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 7s
The kiosk take endpoint is the product's first unauthenticated
mutation; spell out the acceptance criteria (decrement-only, badge
attributed, bounded, physically rate-limited) so future open-write
endpoints meet the same bar. The dashboard-widget milestone is marked
optional: get_dashboard_widgets predates the ADR-010 data-only
renderers and needs a core component to render.
2026-07-16 16:35:17 -04:00
cproudlock
d99de002bf printedparts plugin: design proposal + hands-on development lab
Some checks failed
CI / backend (push) Successful in 1m39s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
Design for a 3D-printed-parts storefront: item catalog with images and
quantity on hand, a transaction ledger attributing every take/restock/
adjust to a badge-scanned employee, an unauthenticated touch kiosk
(scan bin barcode, scan badge, keypad quantity), 1x0.5in CODE128 bin
labels, and stock/consumption/by-person reports.

The lab guide walks a developer through building it in seven
checkpointed milestones, reusing the USB badge contract, the
measuringtools migration baseline, the models-image upload trio, and
the open kiosk-endpoint precedents.
2026-07-16 16:33:19 -04:00
cproudlock
eed947b207 Forward real client IPs through waitress trusted-proxy flags
Some checks failed
CI / backend (push) Successful in 1m39s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
The X-Forwarded-For rewrite rule alone is not enough: waitress 2+
strips forwarded headers from untrusted proxies by default, so the app
still saw 127.0.0.1 with the rule active. Trust the loopback proxy and
consume x-forwarded-for on the waitress command line; waitress then
rewrites remote_addr to the real client. Runbook gains the
allowedServerVariables unlock (500.52) and both troubleshooting rows.
2026-07-16 15:27:45 -04:00
cproudlock
f5f67172aa Scope dark-mode form and notification styles to the explicit theme
Some checks failed
CI / backend (push) Successful in 1m39s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
The form-control and notification-tint blocks applied on the OS
prefers-color-scheme alone, so a machine in OS dark mode leaked dark
widget styling into the app's explicit light theme - dropdown options
rendered near-black on black. The theme store always stamps data-theme
at startup, so scope these rules to [data-theme=dark]. Dropdown options
in dark mode use the solid card background instead of the text color.
2026-07-16 15:11:35 -04:00
cproudlock
97f7dfb0de Export script: emit bundle into the pxe-images github transfer folder
Some checks failed
CI / backend (push) Successful in 1m39s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
CI / migrations-mysql (push) Failing after 7s
2026-07-16 14:14:44 -04:00
cproudlock
3ba09ac9f3 Fix bit(1) import coercion and subpath login redirect; add GitHub export script
Some checks failed
CI / backend (push) Successful in 1m40s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 9s
CI / migrations-mysql (push) Failing after 7s
Loader: bool() on pymysql bit(1) bytes is always true - isinstallable
and isshopfloor imported as 1 for every row; route through _truthy_bit.
The employee source DB is now optional (shopdb-only imports).

Frontend: under a subpath mount the 401 interceptor stored the browser
path (mount base included) as the login redirect and the router applied
its base again (/ops/ops). New stripBase() keeps redirects base-free.

tools/export-github.sh automates the publication flow: prune + scrub +
commit into ~/projects/shopdb-flask-pub and emit a transfer bundle.
2026-07-16 14:08:43 -04:00
cproudlock
f16d2289ff Remove stray import_apps.py (superseded by the import API + wjf loader)
Some checks failed
CI / backend (push) Successful in 1m44s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
2026-07-13 19:53:51 -04:00
cproudlock
99cac87d9a README: refresh to current product surface; retire direct-DB migration guide
Some checks failed
CI / backend (push) Successful in 1m41s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
Overview and plugin list cover all twelve plugins (geenforce, network
subnets, measuring tools, warranty, USB); naming examples use living
tables/columns instead of retired pctypes/isvnc; API params match the
implementation (perpage, dir, assettype); import section points at the
IMPORT-API surface and the wjf reference loader. DATA_MIGRATION_GUIDE
is now a pointer stub (its direct-DB approach is superseded).
2026-07-13 19:49:34 -04:00
cproudlock
7d7862f4b5 Neutral wording in import docs; portable tool output paths
Some checks failed
CI / backend (push) Successful in 1m41s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
Import-surface docs and docstrings describe the automation as a
migration script; status-doc references in CHANGELOG/ADR-009/ROADMAP
point at repo files. Screenshot/verify tools write to /tmp/shopdb-shots
(created on import) instead of a machine-specific directory.
2026-07-13 16:23:24 -04:00
cproudlock
6010f01de1 Support subpath IIS deployment as a second install method
Some checks failed
CI / backend (push) Successful in 1m41s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
The app can run as an IIS Application under an existing site
(e.g. https://host/ops/) instead of its own site + port:

- frontend: vite base via VITE_BASE_PATH; router history, axios
  baseURL, and root-absolute asset/route paths resolve through
  utils/basePath.js withBase()
- backend: MOUNT_PATH (env or .env) wraps the app in a WSGI
  middleware that shifts the prefix into SCRIPT_NAME, so one knob
  serves API + SPA under the mount
- docs: INSTALL-WINDOWS-IIS.md section 7b runbook + troubleshooting
  rows; DEPLOY-WINDOWS-IIS.md pointer; commented examples in
  deploy/windows/web.config and .env.example

Root deployment unchanged (MOUNT_PATH unset, base '/'). Also folds
two stray root-absolute callers into the shared plumbing
(MachineForm relationship-types fetch, reports CSV window.open).
2026-07-13 16:11:12 -04:00
cproudlock
69dd6d0abe Warranty form: make Vendor a strict dropdown of the vendor catalog
Some checks failed
CI / backend (push) Successful in 1m39s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
Replace the free-text/datalist vendor field with a plain select populated from
/api/vendors, so a warranty vendor is always one of the site's known vendors. An
existing warranty's vendor is kept selectable even if it is absent from the
catalog, so editing never blanks it.
2026-07-13 15:02:59 -04:00
cproudlock
fba2fa05b4 Warranty form: vendor combobox + clearer "Lookup source" label
Some checks failed
CI / backend (push) Successful in 1m38s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Has been cancelled
- Vendor is now a datalist combobox seeded from the site vendor catalog
  (/api/vendors), so users pick a known vendor instead of retyping it, while
  still allowing free text. Kept as a string, not an FK, to keep the warranty
  plugin decoupled from core.
- Renamed the "Provider" select to "Lookup source" with helper text. It was
  confusingly synonymous with Vendor; it actually means where coverage data
  comes from (manual entry vs a maker's warranty API that supports Refresh).
2026-07-13 15:00:53 -04:00
cproudlock
9e2544a687 wjf loader: import active machines only (isactive=1)
Classic ASP keeps retired machines in the machines table as history (isactive=0);
every other stage already filters isactive=1, but the assets hub, metrology,
locations and the verify count read machines unfiltered, so ~240 retired units
(incl. all G-prefix hostnames and 61 dead metrology PCs that each synthesized a
phantom measuring tool) landed as live assets. Add the isactive=1 filter to
those four queries. Downstream stages resolve via the id crosswalk, so warranties
/comms/relationships/installs for retired machines now drop automatically.
2026-07-13 15:00:45 -04:00
cproudlock
760b00f4d1 Warranties: fix N+1 slowness, filter alignment, and Covers hover
Some checks failed
CI / backend (push) Successful in 1m39s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 7s
- Perf: list_warranties did a db.session.get(Asset) per link per warranty
  (~1.8s for the full list). Eager-load the links and batch-fetch every linked
  asset in one query -> ~0.19s.
- Filters: the "Status" label wrapped its select onto a second line, so the
  dropdown sat above the search box; keep the label inline so they align.
- Covers: each asset chip now shows the asset name (often the hostname/alias) on
  hover, keeping the machine number as the label.
2026-07-13 14:49:36 -04:00
cproudlock
4a74a1a405 WJ import loader: mark setup_complete (a full import is the setup)
Some checks failed
CI / backend (push) Successful in 1m39s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
A freshly-imported DB left setup_complete unset, so the admin was bounced into
the first-run wizard even though the instance is fully populated. The harness now
sets setup_complete=true (it already mints the admin), so an imported instance
goes straight to the app.
2026-07-13 14:41:23 -04:00
cproudlock
9c909af66d WJ import loader: metrology PCs are computers that control a synthesized tool
Some checks failed
CI / backend (push) Successful in 1m39s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
Routing by pctype wrongly swept ~105 metrology PCs (CMM/Genspect/Keyence/Wax)
into measuringtools - but a PC that drives an instrument is still a computer;
the physical CMM/gauge is the tool. Dropped the pctype override: those PCs now
import as computers (measuringtools drops to the 48 real instrument rows).

Classic has no separate tool row for a metrology PC, so they'd be orphaned. New
metrology stage synthesizes a measuring-tool asset per metrology PC (typed by
its pctype: CMM / Form Tracer / Vision System / Genspect) and a Controls
relationship PC -> tool, mirroring what the runtime collector does.

Result: computers 663->751, measuringtools = 48 real + 88 synthesized (each
linked to its controlling PC), 88 new Controls relationships, no orphans.
2026-07-13 14:38:00 -04:00
cproudlock
c9641a3c62 CLAUDE.md: refresh Current State (tests, plugins, migration head, import docs)
Some checks failed
CI / backend (push) Successful in 1m39s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
808->966 tests (+ the migrations-mysql CI job), 11->12 bundled plugins (add
geenforce), Alembic head 7d24->7d25 (32 migrations; env.py sql_mode note for
strict MySQL 8), date to 2026-07-13, and a legacy-import pointer
(IMPORT-API/ADOPTION/PILOT-DEPLOY + the WJ reference loader).
2026-07-13 14:30:00 -04:00
cproudlock
a736ed541e Fix Actions column border: keep td.actions as a table-cell
Some checks failed
CI / backend (push) Successful in 1m38s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 9s
CI / migrations-mysql (push) Failing after 8s
.actions sets display:inline-flex (correct for a button container), but it was
applied directly to <td class="actions">, pulling the cell out of the table row
box so its bottom border rendered ~1px off from the other columns. Override
td.actions back to display:table-cell and space multiple buttons with a margin
instead of the flex gap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 14:23:28 -04:00
cproudlock
58af5afda2 List rows click through to the item; drop the redundant VLANs tab
Some checks failed
CI / backend (push) Successful in 1m39s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Has been cancelled
Row-click: the whole table row now navigates to the item detail (machines, PCs,
printers, network devices, measuring tools, applications), matching the Networks
list. The actions cell is @click.stop so View/Edit/Delete still work
independently; a shared .clickable-row style gives the cursor + hover.

Network hub: drop the VLANs tab - a subnet belongs to a VLAN (each Networks row
already shows its VLAN) so a sibling tab was redundant; VLAN naming stays in
Settings. Hub is now Devices | Networks.

Verified: row-click navigates on machines/pcs/network; hub shows two tabs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 14:21:34 -04:00
cproudlock
8528617037 Network: consolidate into one tabbed hub; subnet devices span all asset types
Some checks failed
CI / backend (push) Successful in 1m39s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
Replaces the two flat "Network Devices" + "View Networks" nav entries with a
single "Network" entry opening a tabbed hub: Devices | Networks | VLANs
(NetworkHub renders the existing device list, the subnet browse, and the VLAN
list; VLANs is now reachable outside Settings). /network -> hub; /networks
redirects to the Networks tab; subnet detail stays at /networks/:id.

Subnet "Devices on this network" now matches ANY asset whose primary IP falls in
the CIDR (PCs, printers, machines, measuring tools - not just network devices),
computed on the core Communication + Asset tables; each row links to its typed
detail (extension id resolved lazily/guarded per plugin). Fixes the empty list -
printers and PCs carry IPs and now appear (e.g. 35 devices on 10.80.92.0/24).

Also: subnet-browse search uses the standard form-control styling; dropped the
redundant per-tab page header.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 14:15:51 -04:00
cproudlock
dd541fba0a Add "View Networks": front-facing subnet browse + detail with attached devices
Some checks failed
CI / backend (push) Successful in 1m38s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
CI / migrations-mysql (push) Failing after 7s
Subnets previously lived only under Settings, easily confused with the Network
Devices asset list. Add a front-facing browse + detail:

- Nav: rename "Network" -> "Network Devices"; add "View Networks" (subnets), both
  under Assets (network plugin get_navigation_items; frontend fallback matched).
- /networks (SubnetsBrowse): all subnets with name / CIDR / type / VLAN / notes,
  searchable, row-click to detail.
- /networks/:id (SubnetDetail): the subnet (CIDR, network address, type, VLAN,
  gateway, notes) plus the network devices whose primary IP falls inside its
  CIDR - get_subnet now computes that membership in Python (a device's IP lives
  in a Communication row, so it is not a plain SQL join).

Verified on the import DB: 37 networks list (real WJ subnets), detail renders.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 14:06:47 -04:00
cproudlock
5f51aa2383 Detail pages: give the relationships card bottom spacing (was merging with Notes)
.relationships-section had card styling but lacked the margin-bottom +
break-inside:avoid that .section-card has, so it sat flush against the Notes
card below it and looked like one merged card (and could split across a multicol
break). Add both to match section-card. Affects every asset detail page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 13:53:37 -04:00
cproudlock
a6ebec5118 Employee profile: cap USB checkout history with a show-more toggle
Some checks failed
CI / backend (push) Successful in 1m39s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
USB Checkout History rendered every record unbounded (long table for heavy
users), inconsistent with the recognitions list right above it. Add the same
limit (10) + "Show N more" / "Show less" toggle recognitions use. Client-side
only; no API change (per-user history is small at current scale).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 13:49:33 -04:00