Commit Graph

155 Commits

Author SHA1 Message Date
cproudlock
48d3160bc5 Rename the equipment domain to machines; retype the models catalog (ADR-011)
All checks were successful
CI / backend (push) Successful in 23s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
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>
2026-07-11 15:17:42 -04:00
cproudlock
e6edbd23f8 Fix case-sensitive relationship type compares
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>
2026-07-11 14:30:02 -04:00
cproudlock
26c280df25 Rename the Equipment by Type report card to Machines by Type
All checks were successful
CI / backend (push) Successful in 23s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 8s
Display label only, matching the Machines relabel; the endpoint id and
equipmenttype entity are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 14:20:41 -04:00
cproudlock
dc9fe5834e Draft ADR-010 frontend hook contract; relabel Equipment to Machines
All checks were successful
CI / backend (push) Successful in 23s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 7s
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>
2026-07-11 14:16:59 -04:00
cproudlock
2efe17b743 Add the measuringtools plugin (ADR-005) and the plugin-system tutorial
Some checks failed
CI / backend (push) Failing after 28s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 43s
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>
2026-07-11 10:02:01 -04:00
cproudlock
22e623c1f6 Plugin framework maturation, reports overhaul, theming, and USB frontend repair
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>
2026-07-11 10:01:47 -04:00
cproudlock
b8c22244a1 Multi-site distribution readiness: settings-driven site config, security closeout, release engineering, v0.5.0
Some checks failed
CI / backend (push) Failing after 2s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
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>
2026-07-10 15:02:07 -04:00
cproudlock
57efb5f965 First-run creates the superadmin in-app; wizard seeds reference data
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>
2026-07-10 11:53:38 -04:00
cproudlock
301eaa6375 Enrich the logged-in user with directory name + photo (by SSO)
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>
2026-07-10 11:36:00 -04:00
cproudlock
24d5b00dc2 Fix fresh-install migration blocker + add preflight, seed admin, first-run endpoints
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>
2026-07-10 11:28:22 -04:00
cproudlock
596539887a Wizard Floor Map step: upload light/dark blueprints (not just dimensions)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 10:51:56 -04:00
cproudlock
b26d0d1b87 Upload floor-map blueprints (light/dark) instead of typing a path
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>
2026-07-10 10:50:51 -04:00
cproudlock
d08f341403 Wizard: choose "create tables here" vs "connect your own database" per plugin
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>
2026-07-10 10:38:39 -04:00
cproudlock
0e0bbc0604 Employees + USB default-disabled with an enable-time provisioning note
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>
2026-07-10 09:53:43 -04:00
cproudlock
56b7874f8d Self-hosted employee directory (in-app management + CSV import)
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>
2026-07-10 08:56:12 -04:00
cproudlock
087ece0f8c Setup wizard P2: per-plugin config schema + settings-first creds
- 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>
2026-07-10 08:10:48 -04:00
cproudlock
843b225a47 First-run setup wizard (/setup)
- 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>
2026-07-10 07:47:01 -04:00
cproudlock
ec2de635ed Warranty polish: hero badges everywhere, search/pagination, re-check, shared apiError
- 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>
2026-07-09 17:23:21 -04:00
cproudlock
640b8de1b2 Bulk Dell warranty sync + add-warranty from asset pages
- 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>
2026-07-09 17:08:42 -04:00
cproudlock
d6142b10b4 Global toast notifications; replace all alert() calls
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>
2026-07-09 16:56:25 -04:00
cproudlock
9cc7cc7529 Surface real API error on warranty refresh
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>
2026-07-09 16:22:46 -04:00
cproudlock
08a9290071 Fix stale Dell API URL placeholder in settings
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>
2026-07-09 16:12:57 -04:00
cproudlock
7e9650a62b Warranty phase 2: real Dell provider + settings UI + PC hero badge
- 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>
2026-07-09 16:05:26 -04:00
cproudlock
78a0ee8d83 Add custom fields + warranty plugin, rework settings into two-pane shell
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>
2026-07-09 15:37:21 -04:00
cproudlock
419f26107d Tab + search the Settings index (match System Settings)
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>
2026-06-26 22:25:12 -04:00
cproudlock
6f4c161c0f Tab + search the System Settings page to cut scrolling
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>
2026-06-26 22:16:39 -04:00
cproudlock
398353ff79 Group the settings index into labeled sections
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>
2026-06-26 22:12:10 -04:00
cproudlock
a4b98e10db Add dashboard defaults (visitor-IP -> business-unit) for kiosk displays
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>
2026-06-26 22:09:38 -04:00
cproudlock
10ed83e14c Collector: ingest GE-Enforce/enrollment data with configurable pc-type mapping
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>
2026-06-26 21:37:06 -04:00
cproudlock
8ddf771b72 Add per-domain global-search toggles
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>
2026-06-26 20:39:34 -04:00
cproudlock
60641161d5 Second-pass review fixes: kill last hardcoded creds, wire get_services, dedup
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>
2026-06-26 20:18:01 -04:00
cproudlock
0ee4f5a6ba USB label batch: brand colors + print-color-adjust; document migration strategy
- 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>
2026-06-26 20:01:51 -04:00
cproudlock
6979edfc0c Fix QR codes missing from printed labels + match app color scheme
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>
2026-06-26 19:43:32 -04:00
cproudlock
ccead771e6 Fix stale identifier flags: refresh composable on Settings toggle
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>
2026-06-26 19:43:22 -04:00
cproudlock
37ffb4add5 Make gauge/maintenance identifiers per-asset-type and extend to all types
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>
2026-06-26 16:34:50 -04:00
cproudlock
59ef0220b5 Add web UI to enable/disable plugins
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>
2026-06-26 15:01:14 -04:00
cproudlock
820d3650ff PC Number defaults to serial number (editable)
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>
2026-06-26 12:29:24 -04:00
cproudlock
6f70575142 Revert "PCs: identify by hostname/serial, drop the PC Number field"
This reverts commit 76ca95cc57.
2026-06-26 12:25:32 -04:00
cproudlock
76ca95cc57 PCs: identify by hostname/serial, drop the PC Number field
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>
2026-06-26 12:23:37 -04:00
cproudlock
6fb8adc256 Add locationtypes + location tree (ADR-001)
- 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>
2026-06-26 12:09:05 -04:00
cproudlock
a58feebe7a Fix software-compliance report + relationshiptypes zero-dates
- 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>
2026-06-26 10:11:24 -04:00
cproudlock
b0a0670fcd Retire the legacy Machine model (ADR-001 cutover)
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>
2026-06-26 09:59:17 -04:00
cproudlock
cb47b9087e Move application install tracking off Machine onto Computer
- applications.py installed-on + per-computer install/uninstall/update endpoints
  now use Computer / ComputerInstalledApp instead of Machine / InstalledApp.
- ApplicationDetail "Installed On" list reads the computer shape.
- Drop the unused Machine/MachineType import from the assets map endpoint.

No active core endpoint uses the Machine model anymore (only the legacy
/api/machines blueprint and reference-data seeder remain).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 09:35:51 -04:00
cproudlock
167a9514cf Move relationship types + dashboard recent off the machines blueprint
- Add /api/assets/relationshiptypes (GET/POST) serving RelationshipType; point
  relationshipTypesApi at it instead of the legacy /api/machines blueprint.
- Dashboard recent-devices widget reads assetsApi.list (asset shape) instead
  of machinesApi.

No frontend code references machinesApi anymore.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 09:29:30 -04:00
cproudlock
7db011cb78 Network device form: use asset statuses instead of legacy machine statuses
Last form still reading /api/statuses (MachineStatus); repoint to
assetsApi.statuses so its status dropdown matches the rest of the app.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 09:17:15 -04:00
cproudlock
e631564377 Frontend: asset identifiers, statuses, printer types, UI fixes
- Wire gauge/maintenance refs + identifier toggles into equipment pages.
- Repoint status dropdowns and Settings>Statuses to asset statuses.
- Printer type column/filter; require vendor before model; printer-model picker.
- Per-type identifier labels; printer detail title fallback.
- Fixes: truncate long description cells, opaque supply modal, readable
  dark-mode autofill, drum/waste default colorless.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 08:35:32 -04:00
cproudlock
4626280dc4 Add gauge lab + maintenance asset references with enable/disable toggles
Dedicated assets.gaugelabreference and assets.maintenancereference columns
(distinct from assetnumber), surfaced on equipment. Add global per-identifier
enable/disable settings (gauge/maintenance/FQDN) read via a shared composable
and toggled in System Settings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 08:35:03 -04:00
cproudlock
40686ab0d2 Phase 7A: wire ADR-001 asset position contract surface
Lock the position-resolution columns from ADR-001 in code so
resolve_asset_position's relationship walk activates.

Schema
- Asset.mapleft -> Asset.mapx, Asset.maptop -> Asset.mapy
- Location.mapx / Location.mapy added (fallback for priority 3 of the
  ADR-001 resolution chain)
- AssetRelationship.label (free-text nuance per ADR-001)
- AssetRelationship.inheritsposition (bool, server_default true, controls
  whether the resolved-position walk follows the edge)
- RelationshipType.propagatesthroughid (self-FK; sibling-propagation rail)

Seeds
- Three canonical ADR-001 relationship types created idempotently:
  partof, controls, connectedto
- controls.propagatesthroughid wired to partof (partof + connectedto stay
  null per ADR-001 table). Both via Alembic migration AND CLI seed command
  so a fresh test fixture and a sister-site deploy both end up correct.
- Legacy connection types (Serial Cable, Direct Ethernet, USB, WiFi,
  Dualpath) retained for backward compat with pre-1.0 relationship rows.

Resolver
- shopdb.api.resolve_asset_position now walks inheritsposition=true edges
  of type partof (then controls), recursively, depth-capped at 3 with
  visited-set cycle protection. Inactive edges + non-inheritable types
  are skipped. Falls through to the existing location fallback when the
  walk yields nothing.

Tests
- 11 new test_api_namespace cases cover: partof walk, controls-after-
  partof ordering, connectedto skipped, inheritsposition=false skipped,
  recursion, cycle break, depth-3 cap, self-beats-related, related-beats-
  location, inactive-edge skip.
- 111 tests pass. Naming/style check green.

Migration
- migrations/versions/7a01_adr001_position_contract.py:
  - alter_column renames on assets (no data loss)
  - add_column on locations + relationshiptypes + assetrelationships
  - idempotent seed of three ADR types + propagation FK wire-up
  - downgrade reverses + best-effort deletion of seeded types that have
    no FK refs

Backend rename (mapleft/maptop -> mapx/mapy)
- shopdb/core/api/assets.py
- plugins/{computers,equipment,network,printers}/api/...
- scripts/migration/migrate_assets.py
- Legacy Machine model + machines API + import_from_mysql.py UNCHANGED
  (per ADR-001 Machine retires; not part of the asset contract)

Frontend rename
- frontend/src/components/ShopFloorMap.vue
- frontend/src/views/{MapEditor.vue, pcs/{PCDetail,PCForm}.vue,
  printers/{PrinterDetail,PrinterForm}.vue,
  machines/{MachineDetail,MachineForm}.vue,
  network/NetworkDeviceForm.vue}
- Form field labels + v-model bindings + computed flags switched in
  lockstep with the backend.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 14:14:22 -04:00
cproudlock
ce7259c17a Phase 0: lock platform contract, naming convention, and style enforcement
Establishes the framework's foundation as a multi-site adoptable platform.

ADRs (migrations/adr/):
- ADR-001 (ACCEPTED): Asset is the platform contract; Machine retires.
  Three relationship types (partof, controls, connectedto) with free-text
  label, position-resolution chain (asset > related > location),
  hierarchical locations, sibling-bay propagation.
- ADR-002 (ACCEPTED): Plugin contract semver via __contract_version__.
- ADR-003 (ACCEPTED): Hybrid plugin distribution (in-tree bundled +
  filesystem-based external).
- ADR-004 (ACCEPTED): Per-site instances, not multi-tenant.
- ADR-005 (ACCEPTED): Equipment plugin (manufacturing) split from
  measuringtools plugin (metrology). Subtype-table pattern for protocol
  data (FOCAS, CLM, MTConnect).
- ADR-006 (ACCEPTED): Plugin collector contract via get_collector_schema
  hook with API-key auth and identity-based upsert.

Naming convention v1 (CONTRIBUTING.md):
- DB tables/columns: lowercase concatenated, no underscores or dashes
- DB-mirrored Python/JS variables match column names exactly; pure code
  follows host-language convention (PEP 8 / camelCase)
- Closed acronym allowlist (universal + shop-floor domain), banned
  shorthand list with suffix exception (printers_bp etc allowed)
- Plain ASCII everywhere: chat, docs, comments, string literals

Style enforcement (scripts/check-naming-and-style.sh):
- Pre-commit-runnable check script: non-ASCII, banned shorthand,
  snake_case DB names, snake_case API params in frontend
- Fixes 14 violations across 11 files (Unicode arrows, snake_case
  params, ctx -> canvasContext, res -> response, req -> request_obj)

Project state (CLAUDE.md, README.md, frontend/CLAUDE.md):
- De-staled CLAUDE.md to reflect actual current state
- README unifies DB story (MySQL canonical, SQLite test-only)
- frontend/CLAUDE.md points at root convention

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 14:47:30 -04:00
cproudlock
2c36c0c873 Add system settings, audit logging, user management, and dark mode fixes
System Settings:
- Add SystemSettings.vue with Zabbix integration, SMTP/email config, SAML SSO settings
- Add Setting model with key-value storage and typed values
- Add settings API with caching

Audit Logging:
- Add AuditLog model tracking user, IP, action, entity changes
- Add comprehensive audit logging to all CRUD operations:
  - Machines, Computers, Equipment, Network devices, VLANs, Subnets
  - Printers, USB devices (including checkout/checkin)
  - Applications, Settings, Users/Roles
- Track old/new values for all field changes
- Mask sensitive values (passwords, tokens) in logs

User Management:
- Add UsersList.vue with full user CRUD
- Add Role management with granular permissions
- Add 41 predefined permissions across 10 categories
- Add users API with roles and permissions endpoints

Reports:
- Add TonerReport.vue for printer supply monitoring

Dark Mode Fixes:
- Fix map position section in PCForm, PrinterForm
- Fix alert-warning in KnowledgeBaseDetail
- All components now use CSS variables for theming

CLI Commands:
- Add flask seed permissions
- Add flask seed settings

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 22:16:56 -05:00