Computer and Printer payloads now surface the linked model imageurl the
way machines already did, and the machine/PC/printer detail heroes
render the photo when present (network devices and measuring tools
have no model link, so nothing to surface). Absent images render
nothing rather than a broken icon.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Most facilities consider a Dualpath pair one physical dual-bay machine.
New site setting dualpath_single_machine (default on): the machines
list, dashboard counts, machines-by-type report, and floor map collapse
each pair to its primary bay (lower assetnumber), with combined
2007 / 2008 labels; pagination totals stay honest. Detail pages remain
per-bay and always show a dual-bay sibling banner linking the partner.
Pair resolution lives in core services and joins the plugin contract
surface (0.8.0 -> 0.9.0).
On the WJ dataset: 31 pairs collapse, machine counts 262 -> 231, map
470 assets. Toggle verified live in both states, left on.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
activeTab read ?tab= only once at mount, so settings-rail links that
change just the query (Branding, Floor Map) updated the URL without
switching the panel. The query param is now the source of truth: a
watcher applies rail/back/forward navigation and tab clicks write the
query via router.replace.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Symmetric relationship types (isdirectional flag, migration 7d19) show
one entry per peer on the relationships card - a Dualpath pair no
longer lists its partner twice - and directional types read naturally
instead of Outgoing/Incoming. Deleting a collapsed entry removes every
underlying direction row.
Propagation is now real (migration 7d20): relationship types declare
propagation-through pairs in relationshiptypepropagations (M:N,
replacing the never-consumed single column); creating a controls link
on either bay of a Dualpath pair auto-creates it on the partner,
mirrored across both endpoints because live data stores controls as
bay -> PC. flask relationships propagate backfills existing data (29
rows fanned out on the WJ dataset, idempotent).
This also completes the tree that commit 1d21bf0 accidentally split
(core/models/__init__ imported RelationshipTypePropagation ahead of the
file that defines it), returning CI to green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Model photos: upload/replace/delete on /api/models/<id>/image (admin),
stored under instance/modelimages/ with a public serve route; thumbnail
plus Upload/Replace/Remove controls in the Models settings modal; the
URL field remains as a manual alternative.
Employee photos, mode-aware: self-hosted directory employees get
upload/replace/delete (photo-<sso> under instance/employeephotos/,
employees plugin migration 0002); external directory mode passes the
HR-supplied picture URL through read-only (writes 409). One resolver
feeds both consumers - the shopfloor recognition/recert kiosk cards and
the employee detail hero - in either mode.
Navigation fix: router-view is keyed on route path, so following a
relationship link between two assets of the same type (machine ->
dualpath machine) reloads the page instead of showing stale content;
query-only URL changes still avoid a remount.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the legacy supportteams/appowners pair: supportteams
(teamname unique, teamurl ServiceNow link) + supportteamcontacts
(multiple named contacts with SSO per team, the people you reach out
to), applications.supportteamid intact. Migration 7d18 migrates each
legacy team owner into a contact, drops appowners, and has a validated
downgrade. New /api/supportteams CRUD (admin writes, import-mode
timestamps, teamname lookup), Support card on application detail,
contacts column on the list, and a settings management page.
IMPORT-API.md mapping updated to the concrete endpoints.
658 tests pass; live dev migration applied (24 teams / 24 contacts);
fresh-install and downgrade round-trips verified on scratch DBs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four data-only hooks on BasePlugin (get_settings_cards,
get_asset_panels, get_map_overlays, get_asset_presentation) with a
GET-only /api/pluginui consumer surface copying the dashboard-widgets
semantics. Pilots: warranty declares its asset panel; measuringtools
supplies its settings card, presentation, and calibration overlay -
the last hardcoded settings-nav entry is now hook-sourced. Generic
renderers for panels/overlays/presentation deferred per the ADR's
incremental adoption plan (documented in CONTRACT-STABILITY.md).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The curated Controlled Machines (PC detail) and Connected PC (machine
detail) cards duplicated what the generic relationships card already
shows with type badges and inline add/remove - and hid every
non-controls relationship type. Removed the curated cards and their
plumbing (~105 lines of orphaned CSS included); the MachineForm
controlling-PC picker stays as the write path. Also theme-variable
fixes on the shared card styling.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The equipment plugin is now the machines plugin, ending the UI-vs-code
vocabulary split while the contract is pre-1.0 and nothing external
depends on the old names.
- plugins/equipment -> plugins/machines: manifest, class, /api/machines,
machines.* permissions, registry key (with an auto-migrating load shim
for existing installs).
- Tables: equipment -> machines (equipmentid -> machineid) and
equipmenttypes -> machinetypes, renamed in the plugin's own migration
chain (machines0002rename), idempotent for both upgrading and fresh
installs.
- The legacy core machinetypes lookup actually types the vendor MODELS
catalog, so it is renamed losslessly to modeltypes
(models.modeltypeid, /api/modeltypes, Model Types settings page)
rather than collapsed, freeing the machinetypes name. Core migration
7d17_machines_rename also flips data in place: assettypes row
equipment -> machine, auditlog entitytype, identifier_/search_
settings keys, permission rows, and renames alembic_version_equipment.
- Frontend: machinesApi/modeltypesApi, item.machine response shape,
assettype value compares 'equipment' -> 'machine' (map, search,
custom fields, relationships), routes machines.js with plugin gating
retagged, /print/machine-badge, Machine Types (subtypes) and Model
Types (catalog) settings pages, machines-by-type report id.
- Docs swept; ADRs left as history per the authoring rule.
Upgrade: flask db upgrade then flask plugin upgrade-all.
Verified: dev DB flipped live (262 machines, 35 modeltypes, 95 models
retyped, zero equipment tables remain); fresh scratch-MySQL install
produces the new names; 341 tests green; naming/style green; frontend
builds; live E2E on machines list/detail, PC relationships, map,
reports, and both settings pages.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Relationship type names are data and the seeded rows mix casings
(controls, Controlled By, connectedto); the PC and machine detail/form
pages compared === Controls and silently showed no relationships.
Compares are now case-folded in all 7 sites.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Display label only, matching the Machines relabel; the endpoint id and
equipmenttype entity are unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ADR-010 (PROPOSED): four data-only frontend hooks following the
get_reports precedent - get_settings_cards, get_asset_panels,
get_map_overlays, get_asset_presentation - batched as a future contract
0.7.0. Warranty proves asset panels first, measuringtools the rest;
accept only after both run on the hooks.
Display-label swap Equipment -> Machines across nav, list, form,
detail, search, map editor, and settings copy (incl the API-failure
fallback nav). Identifiers unchanged: plugin name, tables,
/api/equipment, asset type, and routes all stay.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gage-lab instruments as Asset extensions: measuringtooltypes (color-coded
lookup) + measuringtools (calibration interval/dates, provider, notes) with
calibration status derived at read time (overdue / due soon / current /
unknown), never stored. Full CRUD API with permission-gated writes, types
management with in-use guard, calibration report, nav/reports/config-schema
hooks, and a complete frontend (list/detail/form, types settings page,
calibration report page, gated routes per ADR-009).
First plugin whose migration chain really creates tables post-cutover
(ADR-008), and the working example for docs/PLUGIN-GUIDE.md - a 12-section
walkthrough of building a plugin on this framework, linked from
PLUGIN-QUICKSTART and PLUGINS.
Verified: full suite 323 passing, live E2E on all four pages, fresh
scratch-MySQL migration dry-run green.
Co-Authored-By: Claude Fable 5 <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>
No CLI needed for a UI-driven install.
- Login page: when the instance has no users yet (/setup/needs-admin), it shows
a "create the first admin" form instead of login. Submitting creates the admin
(admin role = full access / superadmin) via /setup/create-admin, logs in, and
goes to the setup wizard.
- Wizard Starter Data step: "Seed core reference data" button (statuses, types,
permissions, default settings via /setup/seed-reference) alongside the common
vendors, so a UI-first install gets the data the app needs.
- Add waitress to requirements.txt (the IIS web.config launches it but it was
missing - found while validating the Windows install).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When the employees directory is enabled, look up the current user by their SSO
(= username) and show their full name + photo in the sidebar user menu.
- auth store: enrichFromDirectory() runs on login + fetchUser; sets
directoryname/directorypicture from employeesApi.lookup(sso). Best-effort -
falls back to the raw SSO when employees is off, the SSO is not in the
directory, or the lookup fails.
- displayName / avatarUrl getters; sidebar shows the photo (/static/employees/
<Picture>) + name over the SSO. Broken photos hide gracefully.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Validated a clean install on MySQL 5.6 end to end and fixed the blockers.
- migrations/env.py: force alembic_version.version_num to VARCHAR(128) in its
own committed connection before running migrations. It was VARCHAR(32); the
revision id 7d02_widen_notification_employee_cols (37 chars) truncated, so the
next migration's version bump matched 0 rows and `flask db upgrade` died at
7d03 on a fresh DB. Now upgrades run clean to head.
- flask db-utils preflight: checks Python, required env, DB connectivity, and
the MySQL 5.6 utf8mb4 index flags (innodb_large_prefix/Barracuda) - the 767
prerequisite - and prints exact fixes. Exits non-zero on blockers.
- flask seed admin --username --email [--password]: real first-admin command
(generates + prints a password once). The docs referenced it but only the
dev-only test-user existed.
- setup endpoints for a UI-driven first run: /setup/needs-admin,
/setup/create-admin (guarded to zero-users), /setup/seed-reference.
- Wizard: drop the PC-access-domain question (always .device.geaerospace.net);
the setting keeps its default.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Floor Map settings only had text fields for the blueprint image path with
no way to upload one. Add a real upload.
- POST /settings/map-blueprint (multipart: file + theme) saves the image to the
instance maps dir and points map_blueprint_<theme> at the served URL;
GET /settings/map-blueprint/<file> serves it (public - kiosks read the map).
- Settings > Floor Map: a file picker + thumbnail next to each theme's field
(still accepts a manual path/URL too).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Answers the confusion of asking for a DB connection while also offering to
create the tables. Each self-host-capable plugin (employees, usb) now shows a
mode choice; the external connection fields appear only for "connect your own
database". Default is self-hosted (create tables here) - the external path is
the niche/our-site option.
- provisioning_note gains mode_setting; employee_directory_mode + usb_directory_
mode settings (both default 'selfhosted').
- Wizard renders the radio, shows the note for self-hosted and the config fields
for external, and saves the chosen mode.
Employees works fully in both modes. USB self-hosted ROUTING is still TODO - the
USB routes read the external cmmc_usb schema; wiring them to the app-owned
tables is the remaining work (tracked).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both plugins provision extra tables, so they now install disabled and explain
themselves before a site opts in.
- Plugin contract gains get_provisioning_note() -> {tables, note, docs}.
Employees and USB implement it (what tables get created in shopdb, how they
are referenced, link to the schema README; USB references the captured
DLP/reminder plans).
- Manifest default_enabled=false for employees + usb; the plugins list API
returns provisioning_note + default_enabled; install now registers a plugin
disabled when default_enabled is false.
- Setup wizard Features step renders the provisioning note the moment a plugin
with one is enabled.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Most sites have no external HR database, so add a self-hosted directory mode.
- New employee_directory_mode setting: 'external' (default; read a separate HR
DB, unchanged) or 'selfhosted' (app-owned table).
- DirectoryEmployee model + directoryemployees table (migration 7d16). to_dict
emits the same keys the external contract uses (SSO/First_Name/...), so both
modes share one response shape and the frontend is unchanged.
- Employee search / single / batch lookup branch on the mode.
- Self-hosted-only management endpoints: list, create, update, delete, and CSV
import (upsert by SSO). Guarded so they only work in self-hosted mode.
- EmployeeDirectory.vue management page (Settings > Locations & Organization):
table + search + pagination, add/edit/delete, CSV import (file or paste).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Plugin contract gains get_config_schema(); the plugins list API returns it.
Employees plugin declares its directory-DB fields (host/name/user + password).
- employee_connection reads host/name/user settings-first (env fallback); the
password stays env-only.
- Setup wizard Features step renders each enabled plugin's config: non-secret
fields save to settings; secrets are never stored - the wizard emits .env
lines to paste. Fixed the plugins-list data path (data.plugins).
- Settings PUT now upserts (creates the row on first write) so plugin-config
keys can be saved without pre-seeding.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- setup_complete site setting; a fresh admin is steered to /setup until it is
finished (skippable for the session).
- SetupWizard.vue: Site (facility/base-url/access-domain), Features (plugin
enable/disable), Floor Map dimensions, Starter Data (seed common vendors),
Finish. Reuses the settings + plugins APIs.
- setup blueprint: POST /setup/seed-starter (idempotent common vendors) and
POST /setup/complete. setupState composable drives the router redirect.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Shared utils/apiError.js reads the correct nested error message (with
fallbacks); swept 37 views/components off the shallow path so real backend
messages (e.g. in-use 409s) surface instead of generic "Failed".
- useWarrantyBadge composable: warranty hero status/end-date badge now on PC,
equipment, printer, and network detail heroes (one shared fetch feeds the
badge + the WarrantyPanel).
- Warranties list: client-side search (vendor/level/tag/asset) + pagination;
truncate long service levels so they stop blowing out the table width.
- "Re-check all" button + POST /warranty/sync/dell?all=true to re-pull dated
Dell warranties, not just missing ones.
- Deprecate the standalone pxe-images/warranty_sync.py in favor of the plugin.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- POST /warranty/sync/dell: auto-detect Dell hardware by service tag (asset
serial) and create warranties for the ones Dell recognizes, in batches of 100.
Only looks up assets that lack a dated warranty, so re-runs are cheap and
non-Dell serials are filtered out by Dell. DellProvider.bulk_lookup batches
tags over the cached token.
- "Sync Dell" button on the Warranties page with a toast summary.
- WarrantyPanel "Add one / Add-manage" links deep-link to the warranties add
modal pre-linked to that asset (?addfor=); WarrantiesList opens it prefilled.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a useToast() composable + a single ToastHost mounted in AppLayout. Convert
every alert() across views/components (29 call sites, all error paths) to
toast.error, and use toast.success to confirm a warranty refresh. Kills the
native "localhost says" dialog and gives consistent, dismissable feedback.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Error messages nest at data.data.error.message; the warranty handlers read one
level too shallow so every failure showed a generic "failed". Read the correct
path (with fallbacks) so the actual reason shows - e.g. Dell's rate-limit
cooldown - and confirm the pulled coverage on a successful refresh.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The API URL hint still showed the old device.warranty path; align it with the
corrected sbil/eapi/v5 endpoint the provider now defaults to.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- DellProvider: real Dell TechDirect lookup. OAuth2 via HTTP Basic auth,
asset-entitlements under /PROD/sbil/eapi/v5 (the device.warranty path 404s
for this account), map latest dated entitlement to service level + dates.
Cache the token process-wide; Dell rate-limits the token endpoint and a
fresh request per refresh trips a 401 cooldown. Verified against live Dell.
- Lenovo/HP stay config-shaped stubs.
- Settings: warranty_dell_* keys (category integrations); Dell Warranty Lookup
block in System Settings > Integrations (enable + client id/secret masked +
optional token/API URL overrides).
- WarrantyPanel takes optional pre-fetched items; PCDetail fetches once and
feeds both the panel and a new hero warranty-status/end-date badge.
- tools/mock_dell.py for offline testing of the provider flow.
Co-Authored-By: Claude Opus 4.8 (1M context) <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>
The settings index showed all 6 groups stacked (still a long scroll). Switch it
to the same pattern as System Settings: a left group-tab nav showing one group's
cards at a time, plus a search box that flattens to matching cards across all
groups. Same group data, no routes changed.
Frontend-only. Build + naming green, verified.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
System Settings had grown to one long scroll (Integrations, Email, Audit,
Authentication, Asset Identifiers, Global Search, PC Type Mapping). Now a
vertical tab nav shows one section at a time, plus a search box that filters the
tabs by label/keywords (and shows every matching section while searching). Type
"toner" -> Integrations, "fqdn" -> Asset Identifiers, etc.
Frontend-only, behavior of each section unchanged. Build + naming green, verified.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The settings index had grown to 16 flat cards with no organization. Group them
into purpose-based sections (Asset Reference Data, Locations & Organization,
Network, Displays & Kiosks, System, Access & Audit) so it stays scannable as it
grows. Cards are now data-driven (one group list) instead of repeated markup;
all existing routes/cards preserved.
Frontend-only. Build + naming green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Classic feature gap: a shopfloor/lobby kiosk auto-selects which business unit to
show based on the display PC's IP (classic dashboarddefaults table +
apivisitorlocation.asp). For the main admin dashboard this does nothing - it is
kiosk/visitor-display infra.
- Model: DashboardDefault (dashboarddefaults: ipaddress unique, businessunitid
FK, description). Migration 7d01_dashboarddefaults (head).
- API (core, /api/dashboarddefaults): CRUD + GET /visitor-location that resolves
the calling display's business unit from its IP (X-Forwarded-For/remote_addr,
or explicit ?ipaddress=); unmapped IP returns a null businessunitid, not an
error. Unauthenticated resolve (kiosks); writes are admin.
- Frontend: ShopfloorDashboard auto-selects its business unit via visitor-location
on load when none is chosen; Settings > Dashboard Defaults CRUD page +
dashboardDefaultsApi client.
Tests: create + resolve by IP -> BU; unmapped IP -> null; duplicate IP 409.
191 tests pass, naming green, app boots, endpoint + admin page verified live.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends the computers collector so it can replace the classic api.asp
updateCompleteAsset path that the shopfloor PC fleet uses to auto-update data.
Collector schema (project naming) now accepts the GE-Enforce/enrollment shape:
machinenumber, pctype, pcsubtype, serialnumber, loggedinuser, lastboottime,
lastcheckin, ipaddress, vendorname, modelnumber, osname, installedsoftware.
- machinenumber -> Asset.assetnumber (skips the 9999 imaging placeholder, falls
back to hostname), on create and update.
- pctype -> ComputerType via a configurable mapping (see below).
- vendor/model created if missing (free vocab); OS looked up (controlled, warns
if unknown); pcsubtype accepted but not yet stored (warning).
- Dropped per scope: VNC/WinRM flags, warranty, DNC config, multi-NIC.
Configurable pc-type mapping (the gea-shopfloor-* imaging taxonomy ->
ComputerType): defaults + resolution live in plugins/computers/pctypemap.py
(plugin domain, contract-pure - reads Setting via shopdb.api); overrides stored
as pctypemap_<pxetype> settings, seeded on plugin install, edited in Settings >
System > "Collector PC Type Mapping" (new UI section).
Migration doc: docs/COLLECTOR-INTEGRATION.md maps classic api.asp fields +
GE-Enforce status fields to the collector schema, documents machine-number
sourcing (registry MachineNo first, then C:\Enrollment\machine-number.txt) and
that the transport is interim.
Tests: complete-asset payload maps machinenumber/pctype/vendor/model/os; 9999
placeholder falls back to hostname. 186 tests pass, naming green, app boots,
mapping UI verified.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lets an admin choose which content types appear in global search results,
independent of whether the owning plugin is enabled (the existing
_require_enabled gating was all-or-nothing per plugin).
- settings.py: SEARCH_DOMAINS const (9 result types: application, knowledgebase,
employee, equipment, computer, printer, network_device, notification, subnet)
+ seed keys search_<type>_enabled (boolean, default true) in
build_default_settings (covers API seed + CLI).
- search.py: global_search loads disabled types in one query (category 'search')
and filters the deduped results by type before counts/truncation. Missing key
= enabled.
- SystemSettings.vue: "Global Search" section, one toggle per domain (mirrors the
identifier pattern; create-on-404 fallback so an un-reseeded deploy still works).
- Tests: domain included by default, disabled domain excluded, seed creates the
9 keys.
166 tests pass, naming green, build green. Verified live: toggling
search_knowledgebase_enabled off drops knowledgebase from search counts and back
on restores it. Dev DB seeded (9 keys).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verification audit (re-run of the 6 skill lenses) confirmed the prior fixes hold
and surfaced a few misses:
Security (HIGH):
- search.py _check_smart_redirect still opened a raw pymysql connection with
root/rootpassword (reachable on any 9-digit SSO query). Now uses the shared
env-backed employee_connection helper.
- Deleted dead shopdb/core/services/employee_service.py (zero importers; carried
another root/rootpassword literal). No hardcoded credentials remain in app
logic; config.py dev defaults stay gated by ProductionConfig.validate.
Dead hook:
- get_services was implemented by the printers plugin but had no consumer (docs
claimed otherwise). Added PluginManager.get_service(name) that resolves a
service from enabled plugins; updated PLUGIN-HOOKS.md.
Tests:
- search disabled-plugin exclusion (the high-value gap): enabled plugin's
hostname appears, disabled plugin's hostname drops out (searched by a hostname
distinct from assetnumber so only the gated domain can match).
- get_service consumer test (unknown name -> None).
Simplify:
- Extract the triplicated GE_LOGO_SVG + loadLogo + drawLogoOverlay into shared
frontend/src/views/print/qrLogo.js (renderQrDataUrl); both QR views use it.
- applications.py: lift the misplaced pagination import to the top; drop unused
Computer unpacking in the 3 endpoints that only touch ComputerInstalledApp.
154 tests pass, naming/style green, app boots, QR render verified.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- USBLabelBatch.vue: recolor controls/selection/cell borders to the app CSS
variables (was hardcoded #667eea/#28a745/#dc3545) and add
`print-color-adjust: exact` so barcodes print even with "Background graphics"
off. (Barcodes render as vector SVG, so no canvas->img change needed.)
- ADR-004: document the resolved migration strategy - single core Alembic chain
(`flask db upgrade`), bundled plugin schema folded into core (7c04), resolving
the Phase 7B per-plugin-chain conflict footgun. External plugins may still
ship their own chains.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
QR codes were drawn to live <canvas> elements, which print unreliably (often
blank in the printout). The print pages also used hardcoded off-brand colors
(#667eea indigo) instead of the app's palette.
- PrinterQRBatch.vue + PrinterQRSingle.vue: render each QR (with the GE
monogram composited in) to a data-URL <img> instead of a live <canvas>.
Images print every time.
- Add `print-color-adjust: exact` so the browser prints the QR images and
borders even when the user's "Background graphics" option is off.
- Cache the GE logo image once instead of reloading it per code.
- Recolor controls/buttons/selection/label borders to the app CSS variables
(--primary, --success, --danger, --bg-card, --text...), so the pages match
the scheme and adapt to dark mode. Printed labels stay black-on-white.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The identifier-flags composable fetched settings once and cached them in a
module singleton, so toggling an identifier in Settings (gauge/maintenance/FQDN
per asset type) did not take effect on already-open asset views until a full
page reload. Disabled identifiers kept showing.
- identifierSettings.js: extract applySetting/fetchFlags; export
reloadIdentifierFlags() and setIdentifierFlag(name, assettype, enabled) to
mutate the shared reactive state.
- SystemSettings.vue: push each successful matrix toggle into the shared state
via setIdentifierFlag so dependent views react immediately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Optional asset identifiers (gauge lab reference, maintenance reference, FQDN)
were global per-identifier and only surfaced on equipment. Now they are
toggleable per asset type and rendered on every asset type.
- settings: replace 3 global identifier toggles with a per-type matrix. New
keys identifier_<name>_<assettype>_enabled (3 identifiers x 4 types).
IDENTIFIER_LABELS / IDENTIFIER_ASSETTYPES constants drive the seed (API seed
and CLI seed settings).
- composable: identifierSettings now exposes isEnabled(name, assettype),
per-type flag winning over the legacy global key, defaulting on.
- backend writes: computers, network, printers asset create + update now
accept gaugelabreference and maintenancereference (equipment already did).
Reads already flowed through Asset.to_dict.
- frontend: Settings page renders an identifier x asset-type toggle matrix.
Equipment, PC, printer, network forms and detail pages show gauge/maintenance
(and FQDN where applicable) gated by isEnabled(name, type).
Legacy global identifier_<name>_enabled keys are still honored as a fallback
for older installs. SystemSettings toggles upsert (create on 404) so a deploy
that has not re-seeded still works on first toggle.
144 tests pass, naming/style check green, frontend builds. Verified live:
matrix renders, PC form shows the fields, PUT persists gauge/maintenance on a
PC and reads back.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET /api/plugins now lists all discovered plugins (enabled or not) with status;
PUT /api/plugins/<name> toggles enabled (persists to plugins.json). New
Settings > Plugins admin page with per-plugin toggles. Route changes apply on
next restart. Replaces CLI-only enable/disable for self-serve admin.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On a new PC, the PC Number (assetnumber) auto-fills from the serial number as
it is typed, but stops auto-filling once the user edits it manually. Edit mode
never auto-overwrites.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PCs are identified by hostname (the primary identifier) with serial as the
fallback; the assetnumber is seeded from hostname rather than a separate
"PC Number" field. PC list shows Hostname/Serial (Asset Tag column removed),
PC detail title leads with hostname.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- New locationtypes lookup (LocationType model) seeded with section, cell,
subcell, operation, meetingroom, lab, office, storage, hallway,
networkcloset, building.
- locations gains locationtypeid + parentlocationid (self-FK) for the site
location tree. Migration 7c03; cli reference-data seeds the types.
- GET /api/locations/types; locations CRUD accepts type + parent; list/detail
return locationtypename + parentlocationname.
- Locations settings page: Type column + Type/Parent selectors in the modal.
Realizes the operation-as-Location model (operations are locations with
locationtypeid='operation').
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add applications.isrequired (model + migration 7c02 + create/update + form
checkbox). The software-compliance report compared required apps against
installs but no such flag existed, so it 500'd; now it works.
- Repair relationshiptypes rows with 0000-00-00 createddate/modifieddate
(partof/connectedto) to real timestamps.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The asset/computer model is now the single source of truth. Remove the Machine
instance layer end to end:
- Delete models Machine, MachineStatus, PCType, MachineRelationship,
InstalledApp, PrinterData; keep MachineType (models.machinetypeid still
references it).
- Delete the /api/machines, /api/statuses, /api/pctypes blueprints and the
legacy /api/printers/legacy (PrinterData) blueprint.
- Drop the deprecated communications.machineid column and its FK.
- Migration 7c01 drops tables machines, machinestatuses, pctypes,
machinerelationships, installedapps, printerdata (idempotent).
- Fix remaining readers (applications install counts) to ComputerInstalledApp.
- Frontend: remove dead machinesApi/statusesApi/pctypesApi wrappers; repoint
the PC Types settings page at computer types.
143 tests pass; all asset/computer/dashboard/report/collector endpoints 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>