Commit Graph

15 Commits

Author SHA1 Message Date
cproudlock
7a7c7f37d5 search: multi-word queries match by word, not the exact phrase
Global search did a single ilike('%CSF Roles%'), so any query with more than one
word required the exact contiguous phrase and usually returned nothing. Add
_word_match: split the query into words and AND them (OR across the searched
columns per word), so 'CSF Roles' matches a record with both words in any field,
any order. Applied across every domain (assets, applications, KB, employees
[selfhosted + external HR], notifications, hostnames, IP, custom fields,
vendor/model/type). External HR path uses a parameterized per-word LIKE.
2026-07-29 07:43:31 -04:00
cproudlock
8a2f984393 ADR-013 Phase 3: search routes via get_asset_presentation, not a hardcoded map
Global-search rows built the plugin detail URL from a hardcoded url_map of
plugin routes in core. Now core prefers a plugin's declared
get_asset_presentation route (ADR-010), substituting the core assetid via the
plugin's by-asset resolver; types that have not declared fall back to the legacy
id-keyed map, so nothing breaks. Measuring tools (which declare the route) link
through it now; machines/PCs/printers/network migrate off the hardcode as they
add a by-asset route + declaration. Presentation map is collected once per
search (cached on flask.g). 2 consumer tests; 26 search tests green.
2026-07-18 22:56:05 -04:00
cproudlock
b2dea82ac9 Global search: find employees in selfhosted mode, not just external HR DB
_search_employees always queried the external HR database (employee_connection),
so a site running the selfhosted employee directory (the app-owned
directoryemployees table) got zero employee results - searching an SSO or name
found nothing, and there was no way to reach the employee profile. Made it
mode-aware via the employee_directory_mode setting: selfhosted -> query the
DirectoryEmployee table (lazy, plugin-guarded); external -> the HR DB as before.

Verified: SSO 210009518 -> Jeff Pierce -> /employees/210009518; name "Pierce"
-> Pierce Cox, Andy Pierce, Jeff Pierce.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 13:39:18 -04:00
cproudlock
275224822e Add collector PC->printer links and searchable custom fields
All checks were successful
CI / backend (push) Successful in 1m24s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
Collector: the computers collector schema gains defaultprinter and
printers; apply_collector_payload resolves each reported identifier to
a printer asset (windowsname/hostname/sharename/assetnumber/IP,
first-hit case-insensitive) and idempotently syncs relationships -
defaultprinter (directional) for the default, connectedto for the
rest. Collector-created rows are tagged so a re-report archives dropped
links while manual relationships are never touched; unresolved
identifiers warn instead of failing. Both PC and printer detail pages
show the links via the shared relationships card (no frontend change).
GE-Enforce Win32_Printer collection snippet documented.

Searchable custom fields: a per-field searchable flag (migration 7d24);
global search matches custom-field values on flagged active fields and
routes each hit to the asset detail page, reusing the existing
(type,id) dedupe and search_<type>_enabled domain filter. Searchable
toggle on the Custom Fields settings page.

822 tests pass; both verified live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:01:20 -04:00
cproudlock
64a5abdb08 Wire measuring tools into identifiers and global search
All checks were successful
CI / backend (push) Successful in 1m14s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s
The measuringtools plugin was missing from two cross-cutting surfaces:
the asset-identifier matrix (no measuring_tool column or per-type
keys - gauge lab reference is their primary identifier) and global
search (results fell to a generic URL and gaugelabreference was never
searched). Measuring tools now have identifier toggles, gated
gauge-lab and maintenance-reference fields on their form and detail,
a search domain toggle, gage-tag search, and proper labels, routes,
and filter chips in search results.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 08:11:39 -04:00
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
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
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
a6f6c3f51f Extract Knowledge Base into a plugin (reference non-asset plugin)
First feature extracted from core into a plugin per "plugin is the product",
mirroring the notifications plugin. KB is a NON-asset plugin: it contributes a
model + blueprint + nav item but registers no AssetType.

- plugins/knowledgebase/: manifest.json (api_prefix /api/knowledgebase, no deps),
  models/ (KnowledgeBase, contract-pure imports via shopdb.api), api/ (the
  blueprint, same routes/prefix so the frontend is unchanged), plugin.py
  (get_blueprint + get_models + get_navigation_items).
- De-cored: removed shopdb/core/models/knowledgebase.py + api/knowledgebase.py,
  their __init__ exports, and 'knowledgebase' from CORE_BLUEPRINT_NAMES; dropped
  the hardcoded KB nav item from dashboard.py (now via the plugin nav hook).
- search.py and reports.py lazy-import KnowledgeBase from the plugin and degrade
  gracefully (search skips via _require_enabled when disabled; kb-popularity
  report returns 503 if the plugin is absent).
- Registered in instance/plugins.json (enabled).

The knowledgebase table stays in the core Alembic chain (bundled-plugin schema
folded into core, ADR-004); the model just maps it. KB was never in the
shopdb.api contract surface, so no __contract_version__ bump.

Pinned with characterization tests first (test_knowledgebase.py); they pass
unchanged against the plugin blueprint. 163 tests pass, naming green, app boots
7 bundled plugins, KB endpoint/nav/search verified live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:32:37 -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
a515c28e3b Decouple core from plugins; wire widgets hook; drop dead search hook
Architectural pass from the skill review ("plugin is the product" boundary).

Core no longer imports plugin models at module load (was a hard import-time
dependency that broke core if the computers plugin was absent/disabled):
- collector.py, applications.py, reports.py: lazy + guarded imports of the
  computers plugin models. Endpoints that need install-tracking now return 503
  when the plugin is absent instead of failing at import.

Search honors runtime enable/disable:
- search.py: _require_enabled(name) raises ImportError for a disabled plugin,
  so each plugin-scoped block skips it (a disabled plugin's rows leave search).
- Replace hardcoded root/rootpassword employee-DB connection in _search_employees
  with the shared env-backed employee_connection helper.

Plugin hooks (integrating-plugin-hooks: every hook needs a consumer):
- get_dashboard_widgets: add the consumer GET /api/dashboard/widgets (5 plugins
  already implemented the hook; it had none). Skips disabled, isolates in prod.
- get_searchable_fields: REMOVED. Zero plugins implemented it and there was no
  consumer; global search is a core concern over the asset model. Contract
  reduction, __contract_version__ 0.3.0 -> 0.4.0.

Docs/contract: PLUGIN-HOOKS.md (widgets consumer note, searchable-fields removal,
0.4.0), PLUGIN-QUICKSTART.md, ADR-001 hook list. Tests: widgets endpoint
aggregate + disabled-skip; contract tests for the removed/added hooks.

151 tests pass, naming/style green, app boots all 6 plugins.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:00:10 -04:00
cproudlock
fe7a00b260 Add full search parity and optimize map performance
Search: refactor into modular helpers, add IP/hostname/notification/
vendor/model/type search, smart auto-redirects for exact matches,
ServiceNOW prefix detection, filter buttons with counts, share links
with highlight support, and dark mode badge colors.

Map: fix N+1 queries with eager loading (248->28 queries), switch to
canvas-rendered circleMarkers for better performance with 500+ assets.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 23:07:09 -05:00
cproudlock
180e6a0915 Migrate frontend to plugin-based asset architecture
- Add equipmentApi and computersApi to replace legacy machinesApi
- Add controller vendor/model fields to Equipment model and forms
- Fix map marker navigation to use plugin-specific IDs (equipmentid,
  computerid, printerid, networkdeviceid) instead of assetid
- Fix search to use unified Asset table with correct plugin IDs
- Remove legacy printer search that used non-existent field names
- Enable optional JWT auth for detail endpoints (public read access)
- Clean up USB plugin models (remove unused checkout model)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 16:07:41 -05:00
cproudlock
c8b325d2e7 Add USB, Notifications, Network plugins and reusable EmployeeSearch component
New Plugins:
- USB plugin: Device checkout/checkin with employee lookup, checkout history
- Notifications plugin: Announcements with types, scheduling, shopfloor display
- Network plugin: Network device management with subnets and VLANs
- Equipment and Computers plugins: Asset type separation

Frontend:
- EmployeeSearch component: Reusable employee lookup with autocomplete
- USB views: List, detail, checkout/checkin modals
- Notifications views: List, form with recognition mode
- Network views: Device list, detail, form
- Calendar view with FullCalendar integration
- Shopfloor and TV dashboard views
- Reports index page
- Map editor for asset positioning
- Light/dark mode fixes for map tooltips

Backend:
- Employee search API with external lookup service
- Collector API for PowerShell data collection
- Reports API endpoints
- Slides API for TV dashboard
- Fixed AppVersion model (removed BaseModel inheritance)
- Added checkout_name column to usbcheckouts table

Styling:
- Unified detail page styles
- Improved pagination (page numbers instead of prev/next)
- Dark/light mode theme improvements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 16:37:49 -05:00
cproudlock
30dd65674d Initial commit: Shop Database Flask Application
Flask backend with Vue 3 frontend for shop floor machine management.
Includes database schema export for MySQL shopdb_flask database.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 16:07:34 -05:00