Files
shopdb-flask/CHANGELOG.md
cproudlock 848a8fb34f
All checks were successful
CI / backend (push) Successful in 1m19s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
Add optional permission scopes to API tokens
A token may carry a scopes list: it then grants only those permissions,
intersected with what the owner holds at use time, with the admin role
bypass suspended and role-gated routes denied - a scoped token from an
admin account is genuinely limited. Scope ceiling enforced at
create/update too (only permissions the owner holds; 400 lists
violations) and the picker only offers what you hold. Token management
itself now requires the new apitokens.create permission (admin by
default, grantable via roles). Unscoped tokens keep the exact prior
act-as-owner behavior; imports need an unscoped admin token.
Migration 7d22.

756 tests pass; live-verified scoped 201/403 matrix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 08:58:31 -04:00

18 KiB

Changelog

All notable changes to shopdb-flask are recorded here.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning. The product version (__version__) and the plugin-contract version (__contract_version__) are distinct series with independent bump rules; see ADR-007 and ADR-002.

Unreleased

Added

  • Personal API tokens (PATs) so scripts and integrations authenticate without the hourly-expiring login JWT (immediate consumer: long legacy-import runs that die when the JWT expires mid-run). New core apitokens table + migration 7d21_apitokens (stores only the sha256 hash of each secret; the full secret shopdb_pat_<40 hex> is shown ONCE at creation). New core blueprint /api/apitokens (list own / admin ?all=true; create; rename or deactivate; revoke). A Bearer shopdb_pat_... header is recognized before any JWT decode by a before_request shim that mints a request-scoped JWT for the token's owner, so the entire existing auth+authz stack (jwt_required, require_permission, require_role, import mode, current_user) authenticates the PAT as its owner with zero decorator changes; an invalid, revoked, or expired PAT gets a clean 401. lastusedat is stamped on use (throttled to at most one write per 60s). Any authenticated user manages their own tokens; admins may list or revoke anyone's. New Settings > API Tokens page (ApiTokensList.vue) with a create modal that reveals the secret once (copy button) and an admin All Tokens section. Docs: docs/IMPORT-API.md and docs/CONFIG.md updated to recommend a PAT for imports. Core feature; no plugin-contract change.

  • Optional permission scopes on personal API tokens. A token MAY carry a scopes list (permission names, migration 7d22_apitokens_scopes adds the nullable apitokens.scopes JSON column); NULL keeps the original behavior (acts as its owner). A scoped token grants ONLY the listed permissions, intersected with what the owner actually holds at use time, and SUSPENDS the admin-role bypass, so a scoped token minted by an admin is genuinely limited: it is denied on role-gated (require_role) endpoints and gets no import mode. The shim mints the request JWT with a patscopes claim that require_permission, require_role, and import_mode_active read; normal login JWTs carry no such claim and are unaffected (zero regression). Scopes are validated at write time against the token OWNER's permissions (the scope ceiling - a token can never grant more than its owner holds; when an admin edits another user's token the ceiling is that owner's permissions), rejecting unknown or unheld names 400. Minting/managing tokens now requires the new apitokens.create permission (category apitokens; admins hold it by default, grantable via the roles UI) rather than being open to any authenticated user. The Settings > API Tokens create/edit modals gain a "Restrict permissions" section (a category-grouped checkbox grid limited to the permissions the creator holds) and the token lists show a full-access / N-permissions access chip.

  • Vendor-model photos on asset detail heroes: computers and printers now surface the linked model's imageurl in their extension payloads (the field machines already exposed), and the machine, PC, printer, network device, and measuring tool detail pages render the photo in the hero card when present (hidden cleanly when absent). Network devices and measuring tools have no model link yet, so their heroes stay photo-less until one is added.

  • Dualpath "single machine" site toggle (dualpath_single_machine, default on). A Dualpath relationship pair is one physical dual-bay machine (single controller, bay-selector switch); when on, the machines list, dashboard and machines-by-type counts, and the floor map collapse each pair to one entry (the lower natural-sort assetnumber is PRIMARY; the SECONDARY bay is hidden) and show a combined 2007 / 2008 label. The data model is unchanged (both bay records always exist); detail pages stay per-bay and always show a sibling-bay banner regardless of the toggle. Contract surface (plugin contract bumped 0.8.0 -> 0.9.0, additive): new shopdb.api helpers resolve_dualpath_pairs and dualpath_single_machine_enabled, consumed by the machines plugin to collapse pairs contract-purely.

  • Relationship propagation, wired and data-driven: relationship types declare propagation-through pairs (relationshiptypepropagations M:N, replacing the never-consumed single column); creating a controls link on one Dualpath bay auto-creates it on the partner bay, and flask relationships propagate backfills existing data.

  • Employee photos, mode-aware: self-hosted directory employees support upload/replace/delete (admin), served publicly for kiosk cards; external directory mode passes the HR-supplied picture URL through read-only. One resolver feeds the shopfloor recognition/recert cards and the employee detail hero in either mode.

  • Vendor-model photo management. New admin-gated core endpoints POST /api/models/<modelid>/image (multipart file, png/jpg/jpeg/gif/webp/svg, one image per model, replace semantics) and DELETE /api/models/<modelid>/image, plus the public GET /api/models/image/<filename> serve route. Uploads land in instance/modelimages/ (survives upgrades, backed up with the rest of instance/) and set models.imageurl to the served URL; the manual Image URL field still accepts external URLs and the shipped /images/models/* assets (upload is additive). Delete only removes files we own under the instance dir. The Models settings page grows a thumbnail, Upload/Replace, and Remove controls in the edit modal. Asset hero images (e.g. the machine badge) read imageurl unchanged, so uploaded photos render with no consumer changes.

  • Application support teams with contacts, replacing the legacy supportteams/appowners pair. New core supportteamcontacts table (multiple named contacts per team, ordered by sortorder); supportteams keeps teamname (now unique) and teamurl (a ServiceNow group deep link) and sheds the single-owner appownerid FK. New core blueprint at /api/supportteams (team + nested contact CRUD, admin-gated; ?teamname exact-match lookup for import; delete a team 409s while any application still references it). Migration 7d18_supportteamcontacts migrates each legacy team's app owner into one contact. Application payloads now flatten supportteamname, teamurl, and the team's active contacts; a Support card on the application detail page and a new settings/supportteams management page render them.

  • Import mode: a complete, idempotent HTTP migration surface so a script or LLM can import the classic ASP shopdb through the API alone (no direct DB writes).

    • Contract surface (plugin contract bumped 0.7.0 -> 0.8.0, additive): new shopdb.api helpers apply_import_timestamps, import_mode_active, parse_import_datetime in shopdb/utils/import_mode.py. When the caller is an admin AND sends header X-Import-Mode: true, create/update endpoints accept optional createddate / modifieddate (ISO or legacy YYYY-MM-DD HH:MM:SS, naive-UTC) and preserve them instead of stamping now. Non-admin or missing header: the fields are ignored exactly as before. Wired into every timestamped import target: assets (all five type plugins), vendors, models, modeltypes, businessunits, locations, operating systems, applications, knowledge base, USB devices, and asset relationships.
    • Natural-key exact-match lookup filters for the documented lookup-then-upsert idempotency recipe: assetnumber on all five asset plugin list endpoints; vendor, modelnumber, modeltype, businessunit, locationname, osname/osversion, appname, knowledge base linkurl/shortdescription, warranty servicetag/vendor, and notification ticketnumber.
    • Backdated event history: in import mode the selfhosted USB checkout/checkin endpoints accept optional checkouttime / checkintime overrides so migrated usbcheckouts rows keep their real event times.
    • New operator manual docs/IMPORT-API.md grounded in the real prodscratch legacy schema: order of operations, a full table-by-table mapping, honest no-target list with dispositions, a worked idempotent Python importer, and row-count parity checks.

Changed

  • System Settings is no longer one tabbed page. The inner tab bar is gone and each section is its own routed settings page reached through the settings rail: ServiceNow, Zabbix Supplies, Dell Warranty, and Collector PC Types (the former Integrations dumping ground, now split three-plus ways), plus Branding, Floor Map, Printing & Labels, Email / SMTP, Authentication, Audit & Logging, Asset Identifiers, and Global Search. The rail regroups these under Site & Facility, Integrations, Communication, Search & Identity, and Access & Security. Shared load/save/upload plumbing moved into a useSystemSettings composable so the pages stay thin. Old bookmarks keep working: /settings/system and every /settings/system?tab=<key> redirect to the matching new page.

Fixed

  • Measuring tools wired into the remaining cross-cutting surfaces an audit found them missing from: generic asset serialization (typedata + pluginid, which also fixes relationship-card links to tools), map subtype filtering/coloring and the MapEditor filter, dashboard totals, warranty asset links (via a new by-asset detail route), and the two ADR-010 hook declarations (presentation route token corrected; the calibration map-overlay endpoint now actually exists). The login avatar also resolves through the employee-photo helper, so self-hosted uploads show.
  • Measuring tools are now wired into the asset-identifier matrix and global search. The Settings identifier matrix gains a Measuring Tool column and the gauge-lab and maintenance reference inputs/rows on the measuring-tool form and detail pages honor those per-type toggles (a maintenance-reference field was added, matching the other asset types). Global search gains a Measuring Tools domain toggle and filter chip, routes measuring-tool hits to /measuringtools/<id> (previously the generic /assets/<id> fallback), and matches on gaugelabreference so a gage-tag lookup resolves the tool.
  • Site & Facility settings page renders booleans as toggles and the directory-mode settings as dropdowns, with labels and help text for every field (no more raw keys or type-true/false boxes).
  • System Settings tabs follow the URL: clicking a settings-rail link that only changes the ?tab= query (Branding, Floor Map) now switches the right panel, tab clicks update the URL, and browser back/forward restore tabs.
  • Following a relationship link between two assets of the same type now loads the destination page instead of stale content (router-view keyed on path; query-only URL changes still avoid a remount).
  • Asset relationships card no longer lists a symmetric peer twice. Relationship types gain relationshiptypes.isdirectional (migration 7d19_relationshiptype_directional; seeded false for the connection-like types Dualpath, connectedto, Cluster Member, Serial Cable, Direct Ethernet, USB, WiFi, true for controls/Controlled By/Backup For/Master-Slave/partof/ defaultprinter). The card now collapses every stored direction row of a symmetric type into one direction-blind "Connected" entry per peer (deleting it removes all collapsed rows), while directional types drop the Outgoing/Incoming headers for inline Type -> peer / <- Type from peer phrasing. The type CRUD and the per-asset relationships endpoint carry isdirectional; the Relationship Types settings page gains a Directional toggle.

[0.6.0] - 2026-07-11

Added

  • measuringtools plugin (ADR-005): gage-lab instruments as Asset extensions with type lookup (color-coded), calibration tracking (derived overdue/due-soon/current status), calibration report, and full frontend. Built as the framework exemplar; docs/PLUGIN-GUIDE.md walks through its construction step by step as the plugin-system tutorial.
  • CSV export on the Warranty and Toner report pages; per-report filter controls (business unit, asset type, location, application, limit) on the inline core reports; report open-state is URL-backed and deep-linkable.
  • Per-plugin Alembic migration chains (ADR-008): every bundled plugin now carries its own chain with a stamp-only anchor; new plugin schema changes land in plugins/<name>/migrations/, never the core chain. Deploys run flask plugin upgrade-all after flask db upgrade.
  • Frontend plugin route gating (ADR-009): a disabled backend plugin's pages redirect to the dashboard; new public GET /api/plugins/enabled.
  • get_reports() plugin hook (plugin contract 0.5.0 -> 0.6.0): plugins contribute their own report cards; warranty and toner cards moved off the hardcoded frontend list.
  • Reports hub grouped by category with a search filter.
  • Configurable QR label targets: qr_target_printer / qr_target_usb settings (blank = the asset's own page, else a URL template with placeholders) and a usb_label_style barcode/QR toggle for USB mini-labels. New Settings > Printing & Labels section.
  • Site palette theming: optional brand_primary_dark_color, brand_accent_color, brand_sidebar_color settings applied at bootstrap.
  • Collector integration guide rewrite: header-only auth reference and a paste-ready GE-Enforce PowerShell reporting function.

Changed

  • Equipment -> machines rename (backend). The equipment plugin is now the machines plugin: /api/equipment -> /api/machines, tables equipment/equipmenttypes -> machines/machinetypes (columns equipmentid -> machineid, equipmenttypeid -> machinetypeid, equipmenttype -> machinetype), permissions equipment.* -> machines.*, assettype value equipment -> machine. The legacy core machinetypes lookup (it types the vendor MODELS catalog, not machine instances) is renamed to modeltypes (/api/machinetypes -> /api/modeltypes, models.machinetypeid -> models.modeltypeid) to free the name. Data flips migrate assettypes, auditlog entitytype, settings keys (identifier_*_equipment_enabled -> identifier_*_machine_enabled, search_equipment_enabled -> search_machine_enabled), and permission rows in place; plugins.json registry entries carry over automatically. Upgrade: run flask db upgrade then flask plugin upgrade-all.

  • Inter (variable) replaces Roboto, bundled locally - no Google Fonts fetch, so air-gapped installs render correctly. Tables use tabular numerals.

  • ServiceNow defaults point at the current geaerospaceqa.service-now.com global search (search, incident, and change links).

Fixed

  • USB frontend remapped to the actual API shape (device_id / device_desc): device list, detail, form, label batch, and the employee profile's checked-out/history panels were all reading dead legacy fields.
  • External-mode GET /api/usb/checkouts/active now honors the badge filter.
  • Warranties list page no longer demands login (matches every other list page; reads were already public).
  • Removed the dead legacy Warranty Status report (always-zero buckets from a retired column); the warranty plugin's report is the real one.
  • Pruned dead usbApi client methods that had no backend routes.

0.5.0 - 2026-07-10

First release cut with a version, tag, changelog, and CI. Focused on letting other GE Aerospace sites stand up their own self-hosted instance (single-tenant per ADR-004).

Added

  • First-run setup wizard (/setup): creates the initial superadmin in-app, configures each plugin (create tables here vs connect your own database), uploads light/dark floor-map blueprints, and seeds starter reference data.
  • Self-hosted employee directory and USB plugins: in-app management plus CSV import, no external database required. Both ship default-disabled with an enable-time provisioning note.
  • Dell warranty plugin: real Dell provider, bulk warranty sync, add-warranty from asset pages, PC hero warranty badge, disk-cached Dell API token.
  • Custom fields, and a two-pane settings shell with tabbed, searchable System Settings and Settings index pages.
  • Dashboard defaults (visitor-IP to business-unit mapping) for kiosk displays; printer installer endpoint (data plus floor-map positions).
  • Global toast notifications replacing alert() calls.
  • Multi-stage Docker build that compiles the Vue frontend and ships frontend/dist, which Flask serves.
  • Documentation overhaul: new CONFIG, UPGRADE, and BACKUP-RESTORE guides; reconciled README, DEPLOY, CLAUDE, and ROADMAP.
  • ADR-007 (product versioning and releases), CHANGELOG, and best-effort Gitea Actions CI (backend tests, naming/style gate, frontend build).

Changed

  • Plugin contract (__contract_version__) settled at 0.5.0: full plugin import surface exposed via shopdb.api, dead search hook removed, and the dashboard-widgets hook wired to a real consumer.
  • Role-based access control now enforced on write routes, including admin-only guards on dashboard-defaults writes.
  • Branding, ServiceNow integration, employee-ID pattern, printer hostname template, and floor-plan blueprints are settings-driven and per-site configurable, with GE defaults preserved as shipped fallbacks (branding and floor-plan configurability landed in this release; some consumer wiring continues under Unreleased).

Security

  • Dashboard-defaults writes now require admin authorization instead of any authenticated user.
  • Collector error responses no longer leak exception detail; failures are logged server-side with generic client-facing messages.
  • Login rate limiting added (IP-based fixed window) on top of the existing account lockout.

BREAKING

  • Collector API key must now be sent in the X-API-Key header. The api-key-in-querystring fallback has been removed. Update any collector integration that passed the key as a query parameter. See docs/COLLECTOR-INTEGRATION.md.