core.js still routed plugin-owned pages directly. Extracted all 11 into the
owning plugin's route file + moved their views into plugins/<name>/frontend/:
- computers: reports/pc-relationships, settings/pctypemapping
- printers: reports/toner, settings/printertypes, settings/zabbix (toner/supply
monitoring)
- machines: settings/machinetypes
- network: settings/networktypes
- warranty: settings/dellwarranty
- slides: settings/slides (its route file gains a default export; it was
toplevel-only)
- employees: NEW plugin frontend (employees/:sso + settings/employeedirectory) -
employees had no route file before; its pages lived only in core.js.
core.js now holds only core routes; all 14 bundled plugins are self-contained
under plugins/<name>/frontend/. Verified live: the extracted Machine Types
settings page renders in the settings rail from the machines plugin frontend.
Build + 58 vitest + naming green.
Relocate warranty, measuringtools, network, printers, usb, notifications,
computers, and slides into plugins/<name>/frontend/. Each plugin's views are
pulled from wherever they lived (own dir, plus the shared views/settings/,
views/reports/, views/print/ dirs, and top-level views) into the plugin's
frontend/views/, and its route file becomes the self-contained routes.js.
Handled the messy cases:
- computers: name mismatch (its views live in views/pcs/) - moved by following
the route file's own imports, so the dir name did not matter. Its OS/access-
protocol/PC-type settings views move with it (only computers.js routed them).
- network: NetworkHub's sibling sub-views (NetworkDevicesList, SubnetsBrowse,
not directly routed) moved too so its `./` imports resolve.
- printers: the qrLogo helper is SHARED with core AssetLabel, so it stays in
views/print/ and PrinterQR imports it via @/views/print/qrLogo.
- slides: route file is toplevel-only (TVDashboard); SlideManager stays core
(core.js routes /settings/slides).
frontend/src/views/ now holds only core views; frontend/src/router/routes/ holds
only core.js. All 13 plugins are self-contained under plugins/<name>/frontend/.
Verified live: Network (hub + moved sub-views), Computers (name mismatch),
GE-Enforce (helper), printedparts all render from their staged frontends. Build +
58 vitest + naming green.
DB review found four DateTime columns defaulting to db.func.now() (MySQL
session-timezone wall clock) while the rest of the schema stores naive UTC, so
one schema mixed two clocks and to_dict() labelled the local values UTC with a
'Z' suffix. Switch application.dateadded, computers.installeddate,
knowledgebase.lastupdated (default + onupdate), and slides.uploadeddate to the
module-level naive-UTC _utcnow callable already used elsewhere (apitoken.py).
ORM-side default only - no column-type change, no data migration; affects
new/updated rows.
Targeted tests pass (154); naming + pyflakes green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Framework:
- Per-plugin Alembic migration chains (ADR-008): every bundled plugin
carries its own chain with a stamp-only anchor at the ownership cutover;
new plugin schema lands in plugins/<name>/migrations/, never the core
chain. Deploys add flask plugin upgrade-all. Fixed a latent bug in the
shared alembic template (engine URL resolution) and taught the metadata
filter to include FK-referenced core tables.
- Frontend plugin route gating (ADR-009): plugin routes carry meta.plugin;
a disabled plugin's pages redirect to the dashboard via a cached,
fail-open check against the new public GET /api/plugins/enabled.
- get_reports() plugin hook (contract 0.5.0 -> 0.6.0): plugins contribute
report cards; warranty and toner cards moved off the hardcoded list.
Reports:
- Hub grouped by category with search; inline reports render at the top,
are URL-backed (?report=id, back-button and deep links work), expose
their server-side filter params as controls, and export CSV. Warranty
and Toner pages gained CSV export.
- Deleted the dead legacy Warranty Status report (always-zero buckets
from a retired column).
Theming and fonts:
- Inter (variable) bundled locally via @fontsource, replacing the Google
Fonts Roboto import - air-gapped installs now render correctly; tables
use tabular numerals.
- Optional brand_primary_dark_color, brand_accent_color,
brand_sidebar_color settings applied to CSS vars at bootstrap.
USB frontend repair (views were reading a dead legacy shape):
- List/detail/form and the employee profile USB panels remapped to the
real API shape (device_id/device_desc/checkinoutlog); employee panels
now use /usb/checkouts endpoints; external-mode /usb/checkouts/active
honors the badge filter; dead client methods pruned.
Also: warranties list page no longer requires login (matches app
convention); collector doc rewritten with a GE-Enforce integration guide
and paste-ready PowerShell reporter; ADR index and CHANGELOG updated.
Verified: 323 tests pass, naming/style green, frontend builds, plugin
migration dry-run green on scratch MySQL.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Make the app distributable to other GE Aerospace sites (one self-hosted
instance per site, ADR-004). GE values remain the shipped defaults; every
site-specific behavior is now a Setting an admin can change in the UI.
Settings-driven site config:
- Branding: site/QR/badge logos, favicon, primary color (upload endpoints
mirror the map-blueprint pattern; new Settings > Branding section).
- ServiceNow: search/incident/change URL templates ({ticket}), ticket
prefixes, enable toggle. Defaults point at the current
geaerospaceqa.service-now.com global search. Disabled = plain-text tickets.
- Employee-id regex (employeeid_pattern), printer hostname template,
QR label targets (qr_target_printer / qr_target_usb, blank = asset page,
else URL template with placeholders), usb_label_style (barcode|qr).
- West Jefferson floor-plan PNGs removed from the tree; generic placeholder
ships as the map default and sites upload their own blueprint.
Security closeout:
- dashboarddefaults writes now require admin.
- Collector: generic error messages (no str(exc) leak); API key accepted
via X-API-Key header only (BREAKING: querystring api_key removed).
- IP-based login rate limiting (AUTH_RATELIMIT_* knobs) atop account lockout.
- Setting.set() creation race fixed (IntegrityError retry).
Release engineering and docs:
- __version__ 0.5.0 (distinct from __contract_version__, ADR-007),
CHANGELOG.md, Gitea Actions CI config, frontend version aligned.
- One wizard-first install story across README/DEPLOY; new CONFIG.md,
UPGRADE.md, BACKUP-RESTORE.md; CLAUDE.md and ROADMAP de-staled.
- Dockerfile multi-stage build now bundles the frontend; compose binds
MySQL to 127.0.0.1; stale database/schema.sql and one-off SQL removed.
Debt and fixes:
- .query.get() -> db.session.get() sweep; datetime.utcnow() removed
(naive-UTC via timezone-aware now); users.py on authz decorators.
- Fixed 4 stale tests (slides feed shape, shopfloor splitperemployee,
plugin contract purity) and the USB label page field mapping (both usb
modes emit the cmmc shape: device_id/device_desc).
- Health endpoint reports the real version.
248 tests pass; naming/style check green; frontend builds; fresh-DB
flask db upgrade + seeds verified; QR targets verified by decoding
rendered codes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Feature work from the 2026-07 session:
Settings IA
- Replace the flat 27-card settings hub with a persistent two-pane shell
(SettingsLayout.vue): grouped, searchable left rail + content pane.
- Nest all settings/* routes under the shell via router post-processing;
shared nav catalog in settingsNav.js. Group by asset class (PCs, Printers,
Equipment, Network) so per-type settings stop scattering.
Custom fields (core)
- customfields + customfieldvalues tables (migration 7d14), CRUD API at
/api/customfields, per-asset value get/save.
- Settings management page + reusable CustomFieldsSection (detail) and
CustomFieldsInputs (form) wired into all four asset types.
Warranty (new plugin)
- plugins/warranty: warranties + warrantyassets (migration 7d15), derived
coverage status, provider abstraction (manual now; Dell/Lenovo/HP stubs).
- API CRUD + per-asset panel + report buckets; WarrantyPanel on all four
detail pages; Warranties management page; Warranty report + Reports card.
- Seed warranty.* permissions.
Printer drivers
- printerdrivers table (migration 7d13) linked to printer models; drivers now
surface on the matching printer's detail page.
Other
- PCDetail rebalanced (Network + Status + Warranty + custom fields on the right).
- Rename PCs list "Features" column to "Remote Access"; fix badge hover underline.
- Drop equipment islocationonly field.
- Centralize asset-type label/route maps into utils/assetTypes.js.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Second core feature pulled into a plugin. Slides is the minimal plugin shape:
a blueprint only - no model, no AssetType, no nav (it is purely an API surface
consumed by the TV dashboard).
- plugins/slides/: manifest (api_prefix /api/slides, no deps), api/ (moved
blueprint, contract-pure imports via shopdb.api, same /api/slides route so the
TV dashboard is unchanged), plugin.py (get_blueprint, get_models -> []).
- Fixed a latent bug in the move: error path used ErrorCodes.SERVER_ERROR which
does not exist -> ErrorCodes.INTERNAL_ERROR.
- De-cored: deleted shopdb/core/api/slides.py, removed from CORE_BLUEPRINT_NAMES
+ core/api/__init__ import/__all__. Registered in instance/plugins.json.
Pinned with a characterization test first; passes unchanged against the plugin
blueprint. 174 tests pass, naming green, app boots 8 bundled plugins, /api/slides
verified live.
Note: the shopfloor dashboard is NOT extractable - it is a frontend-only Vue view
aggregating the businessunits + notifications APIs, with no backend to move and no
frontend plugin system.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>