Initial import: shopdb-flask multi-site asset platform
Flask + Vue rewrite of the classic-ASP shopdb, built as a plugin framework so sister GE Aerospace sites can adopt it. Product version 0.7.0-pre-0.8.0, plugin contract 0.11.0, 966 backend tests. Key entry points: - docs/INSTALL-WINDOWS-IIS.md - tested Windows + IIS install runbook (own-site and subpath deployment methods) - docs/PILOT-DEPLOY.md - production pilot: stand up + import + verify - docs/IMPORT-API.md - schema-agnostic legacy data import contract - scripts/site_imports/wjf/ - West Jefferson reference import loader - docs/adr/ - twelve accepted architecture decision records
This commit is contained in:
476
CHANGELOG.md
Normal file
476
CHANGELOG.md
Normal file
@@ -0,0 +1,476 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to shopdb-flask are recorded here.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
The product version (`__version__`) and the plugin-contract version
|
||||
(`__contract_version__`) are distinct series with independent bump rules; see
|
||||
ADR-007 and ADR-002.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- Asset detail pages (machines, PCs, printers, network devices, measuring
|
||||
tools) now share one canonical card skeleton: Identity -> type-specific ->
|
||||
status -> Location & Organization -> domain -> Custom Fields -> Warranty ->
|
||||
Relationships -> Notes -> audit footer. The location card reads "Location &
|
||||
Organization" on every page. The network device page was rebuilt into the
|
||||
family (its "Asset Information" folded into Identity, "Record Info" converted
|
||||
to the standard audit footer). Printer Notes moved out of mid-page to
|
||||
second-to-last and the printer gained an audit footer. Template reordering
|
||||
only; no data or API changes.
|
||||
|
||||
### Fixed
|
||||
|
||||
- List pages keep the current page (and search term) in the URL query, so
|
||||
paging to page 9, opening an item, and hitting browser Back returns to page 9
|
||||
instead of resetting to page 1. Applies to all 18 list views via a shared
|
||||
useListQuery composable; page 1 with no search stays a bare path.
|
||||
- PC detail Installed Applications no longer 500s and silently disappears on
|
||||
real PCs (ComputerInstalledApp had no to_dict); the section renders app
|
||||
name, version, and description again.
|
||||
- Employee detail skips its USB panels when the usb plugin is disabled (no
|
||||
more 404 console noise).
|
||||
- Shopfloor kiosk header text is readable (light on the dark navy header).
|
||||
|
||||
|
||||
### Added
|
||||
|
||||
- Collector-driven PC -> printer relationships. The computers collector schema
|
||||
gained optional `defaultprinter` (string) and `printers` (array of strings)
|
||||
fields carrying Win32_Printer identifiers. On ingest each identifier resolves
|
||||
to a printer asset (by windows name / share / hostname / asset number-name or
|
||||
a communications IP) and the PC is linked to it: the default via a
|
||||
`defaultprinter` relationship, the rest via `connectedto`. The links render in
|
||||
the shared Relationships card on both the PC and printer detail pages. The
|
||||
sync is idempotent and archives collector-created links to printers no longer
|
||||
reported (tagged `assetrelationships.label = 'collector:printers'`, so
|
||||
manually-created links are never touched); unresolved identifiers become
|
||||
response warnings, never failures. The collector response carries
|
||||
`printerlinkcount` and a `printerlinks` list. See docs/COLLECTOR-INTEGRATION.md.
|
||||
- Searchable custom fields. Each custom-field definition gained a `searchable`
|
||||
flag (Settings > Custom Fields). When on, that field's stored values are
|
||||
matched by global search and a hit routes to the owning asset's detail page.
|
||||
The asset's `search_<type>_enabled` domain toggle still applies, and matches
|
||||
dedupe against built-in-field asset hits so an asset appears once. Inactive or
|
||||
non-searchable fields are never matched.
|
||||
- Single-label sheet-position printing. The single asset-label page
|
||||
(`/print/asset-label/:assettype/:id`) gained an Output control that toggles
|
||||
between the standalone label (unchanged default) and placing that one label at
|
||||
a chosen cell (1-6, via a 2x3 grid picker) of a ULINE 6-up sheet, leaving the
|
||||
other five cells blank. This prints a single label onto the correct physical
|
||||
spot of a partially-used sheet instead of wasting a fresh sheet, mirroring the
|
||||
legacy shopdb behavior and complementing the batch page's start-cell offset.
|
||||
The ULINE 6-up cell layout and dimensions are replicated from
|
||||
`AssetLabelBatch.vue` (left untouched); encode resolution stays shared via
|
||||
`assetLabel.js`.
|
||||
|
||||
## [0.7.0] - 2026-07-12
|
||||
|
||||
### Added
|
||||
|
||||
- Email sending. A stdlib-only mail service (`shopdb/utils/mailer.py`;
|
||||
`smtplib`/`ssl`/`email`) reads the existing `email` SMTP settings
|
||||
settings-first with an `SMTP_*` env fallback, sends multipart HTML+text, and
|
||||
is a graceful no-op (logs a warning, returns False) when email is disabled or
|
||||
the host is unset. The SMTP password is never logged. Three flows use it:
|
||||
(1) New-user welcome + forced first-login password change. Admin-created users
|
||||
(POST `/api/users`) are flagged `mustchangepassword` (new `users` column,
|
||||
migration `7d23_user_mustchangepassword`, default false) and sent a
|
||||
best-effort welcome email with the facility name, username, temporary
|
||||
password, and sign-in link; the account is created even if mail fails
|
||||
(response carries a `warning`). Login returns `mustchangepassword`; the
|
||||
frontend forces the user through a new `/change-password` view (POST
|
||||
`/api/auth/change-password`, jwt-guarded) before the app, and changing the
|
||||
password clears the flag and resets lockout counters. A self-service "Change
|
||||
password" entry is also available from the user menu.
|
||||
(2) Test email. POST `/api/settings/test-email` (settings.edit) sends a probe
|
||||
and surfaces any SMTP error with the password scrubbed; wires up the Email /
|
||||
SMTP settings page "Send Test Email" button.
|
||||
(3) On-demand alert/report delivery. POST `/api/reports/email`
|
||||
(reports.export) mails `{subject, columns, rows}` as an HTML table to a
|
||||
supplied recipient or the site `alert_recipients`; an "Email report" button on
|
||||
the Warranty and Toner report pages posts the loaded rows. There is no
|
||||
scheduler: automation is an external cron hitting the endpoint with a scoped
|
||||
API token (PAT). Documented in `docs/CONFIG.md`.
|
||||
- Shared asset label/code generator: a single `/print/asset-label/<assettype>/<id>`
|
||||
page (public, like the other `/print/*` routes) that any asset detail page
|
||||
opens via a "Print Label" button (machines, computers, printers, network
|
||||
devices, measuring tools). A no-print controls panel toggles the layout
|
||||
(`card` badge vs `plain` code-only), the code type (QR vs CODE128 barcode),
|
||||
and what the code encodes: the asset page link, asset number, serial number,
|
||||
a per-type custom target template, or - for measuring tools by default - the
|
||||
tool's inspection location code so every tool at one operation shares one
|
||||
code (e.g. `0615`). QR codes reuse the shared logo-overlay renderer. New
|
||||
`printing` settings seed and surface on the Printing & Labels settings page:
|
||||
`qr_target_machine`, `qr_target_computer`, `qr_target_network_device`,
|
||||
`qr_target_measuring_tool`; `label_default_style` (default `card`) and
|
||||
`label_default_codetype` (default `qr`); and a per-asset-type default for what
|
||||
the code encodes, `label_default_encodes_<type>` (machines default to their
|
||||
machine number, measuring tools to their inspection location code, the rest to
|
||||
a page link), all overridable on the label page itself. When the chosen field
|
||||
has no value (e.g. serial number on an asset with none), the label states so
|
||||
instead of rendering an empty code. Asset payloads now carry a derived
|
||||
`locationcode` (leading token of the resolved own/inherited location name).
|
||||
- Batch label sheets: a "Print Labels" button on each asset list page opens
|
||||
`/print/asset-label-batch/<assettype>`, a multi-select sheet that lays the
|
||||
chosen assets onto ULINE label pages (6-up 3 in x 3 in, or a dense 72-up
|
||||
mini-label format), with a start-cell offset to reuse partial sheets. Shares
|
||||
the same code-type and encode settings/defaults as the single label, so a
|
||||
batch of measuring tools encodes each tool's inspection location code just
|
||||
like the single label does. Restores the ULINE batch printing the legacy
|
||||
shopdb had, generalized across all asset types.
|
||||
- Support-team contact UX: the settings Support Teams page now manages each
|
||||
team's contacts in a per-team "Contacts (N)" modal (name, SSO, active, plus
|
||||
Add/Edit/Delete) instead of an inline row expander, and the application
|
||||
detail Support card renders Email (`mailto:`) and Microsoft Teams chat
|
||||
(`teams.microsoft.com/l/chat`) action buttons for every contact that has an
|
||||
SSO. Both link targets derive as `sso@<domain>` from a new `site` setting
|
||||
`contact_email_domain` (default `geaerospace.com`; blank hides the buttons),
|
||||
surfaced in Site & Facility settings under Naming & Patterns and read on the
|
||||
frontend via `getContactEmailDomain()`.
|
||||
- Plugin `get_permissions` hook (contract 0.10.0) so a plugin declares the RBAC
|
||||
permissions its own routes enforce, instead of core accumulating every
|
||||
plugin's permissions in `Permission.PERMISSIONS` (plugin-is-the-product). The
|
||||
core catalog (`Permission.CORE_PERMISSIONS`) now holds only genuinely core
|
||||
sets (assets, applications, reports, settings, users, audit, apitokens,
|
||||
collector); the 36 permissions for machines, computers, printers, network,
|
||||
knowledgebase, notifications, usb, warranty, and measuringtools moved into
|
||||
each owning plugin's hook. New core helper `full_permission_catalog()` merges
|
||||
core plus every ENABLED plugin's permissions and backs all three consumers:
|
||||
`flask seed permissions`, the role grid (`GET /api/users/permissions`), and
|
||||
API-token scope validation (`ApiToken.unknown_scope_names`). Plugin install
|
||||
and enable seed the plugin's own permissions idempotently. A disabled plugin
|
||||
drops out of the catalog (no new scope grants or role assignments), but its
|
||||
existing `Permission` rows and role links persist so current roles keep
|
||||
working. Docs: `docs/PLUGIN-HOOKS.md` new section, `docs/PLUGIN-GUIDE.md`
|
||||
permissions walkthrough rewritten to the hook, `docs/PLUGIN-QUICKSTART.md`
|
||||
hooks table row.
|
||||
- Personal API tokens (PATs) so scripts and integrations authenticate without
|
||||
the hourly-expiring login JWT (immediate consumer: long legacy-import runs
|
||||
that die when the JWT expires mid-run). New core `apitokens` table + migration
|
||||
`7d21_apitokens` (stores only the sha256 hash of each secret; the full secret
|
||||
`shopdb_pat_<40 hex>` is shown ONCE at creation). New core blueprint
|
||||
`/api/apitokens` (list own / admin `?all=true`; create; rename or deactivate;
|
||||
revoke). A `Bearer shopdb_pat_...` header is recognized before any JWT decode
|
||||
by a before_request shim that mints a request-scoped JWT for the token's
|
||||
owner, so the entire existing auth+authz stack (jwt_required,
|
||||
require_permission, require_role, import mode, current_user) authenticates the
|
||||
PAT as its owner with zero decorator changes; an invalid, revoked, or expired
|
||||
PAT gets a clean 401. `lastusedat` is stamped on use (throttled to at most one
|
||||
write per 60s). Any authenticated user manages their own tokens; admins may
|
||||
list or revoke anyone's. New Settings > API Tokens page (`ApiTokensList.vue`)
|
||||
with a create modal that reveals the secret once (copy button) and an admin
|
||||
All Tokens section. Docs: `docs/IMPORT-API.md` and `docs/CONFIG.md` updated to
|
||||
recommend a PAT for imports. Core feature; no plugin-contract change.
|
||||
- Optional permission scopes on personal API tokens. A token MAY carry a scopes
|
||||
list (permission names, migration `7d22_apitokens_scopes` adds the nullable
|
||||
`apitokens.scopes` JSON column); NULL keeps the original behavior (acts as its
|
||||
owner). A scoped token grants ONLY the listed permissions, intersected with
|
||||
what the owner actually holds at use time, and SUSPENDS the admin-role bypass,
|
||||
so a scoped token minted by an admin is genuinely limited: it is denied on
|
||||
role-gated (`require_role`) endpoints and gets no import mode. The shim mints
|
||||
the request JWT with a `patscopes` claim that `require_permission`,
|
||||
`require_role`, and `import_mode_active` read; normal login JWTs carry no such
|
||||
claim and are unaffected (zero regression). Scopes are validated at write time
|
||||
against the token OWNER's permissions (the scope ceiling - a token can never
|
||||
grant more than its owner holds; when an admin edits another user's token the
|
||||
ceiling is that owner's permissions), rejecting unknown or unheld names 400.
|
||||
Minting/managing tokens now requires the new `apitokens.create` permission
|
||||
(category `apitokens`; admins hold it by default, grantable via the roles UI)
|
||||
rather than being open to any authenticated user. The Settings > API Tokens
|
||||
create/edit modals gain a "Restrict permissions" section (a category-grouped
|
||||
checkbox grid limited to the permissions the creator holds) and the token
|
||||
lists show a full-access / N-permissions access chip.
|
||||
- Managed collector service tokens: the collector ingest API
|
||||
(`/api/collector/<plugin>` + the legacy `/pc` `/apps` `/heartbeat` `/bulk`
|
||||
`/status` endpoints) now ALSO accepts a managed API token scoped to the new
|
||||
`collector.ingest` permission (category `collector`), on top of the existing
|
||||
`COLLECTOR_API_KEY[_<PLUGIN>]` env keys (which stay supported as a
|
||||
bootstrap/legacy fallback - nothing breaks). The token may be presented in
|
||||
`X-API-Key` (as GE-Enforce sends today) OR as an `Authorization: Bearer`
|
||||
token; both transports validate the PAT the same way the login shim does
|
||||
(hash lookup, active, unexpired, active owner) via a shared
|
||||
`resolve_api_token` helper refactored out of `apitoken_auth.py`, require
|
||||
`collector.ingest` in the token's scope list AND that the owner holds it, and
|
||||
stamp `lastusedat` (same 60s throttle). A token scoped to ONLY
|
||||
`collector.ingest` is a collector service token: it authorizes the collector
|
||||
API and NOTHING else - the existing scoped-token machinery denies it on every
|
||||
permission- and role-gated route and on import mode, so a leaked collector
|
||||
token cannot touch the regular API. Recommended flow (documented): an admin
|
||||
mints the scoped token (the scope suspends the admin bypass, containing it);
|
||||
rotate by minting a new one, deploying via `site-config.json`, watching
|
||||
`lastusedat`, then revoking the old. The Settings > API Tokens create modal
|
||||
gains a "Collector service token" quick-preset (pre-selects only
|
||||
`collector.ingest`). Docs: `docs/COLLECTOR-INTEGRATION.md` (new "Managed
|
||||
collector tokens" section) and `docs/CONFIG.md`. Core feature; no
|
||||
plugin-contract change.
|
||||
- Vendor-model photos on asset detail heroes: computers and printers now
|
||||
surface the linked model's `imageurl` in their extension payloads (the
|
||||
field machines already exposed), and the machine, PC, printer, network
|
||||
device, and measuring tool detail pages render the photo in the hero card
|
||||
when present (hidden cleanly when absent). Network devices and measuring
|
||||
tools have no model link yet, so their heroes stay photo-less until one
|
||||
is added.
|
||||
- Dualpath "single machine" site toggle (`dualpath_single_machine`, default
|
||||
on). A Dualpath relationship pair is one physical dual-bay machine (single
|
||||
controller, bay-selector switch); when on, the machines list, dashboard and
|
||||
machines-by-type counts, and the floor map collapse each pair to one entry
|
||||
(the lower natural-sort assetnumber is PRIMARY; the SECONDARY bay is hidden)
|
||||
and show a combined `2007 / 2008` label. The data model is unchanged (both
|
||||
bay records always exist); detail pages stay per-bay and always show a
|
||||
sibling-bay banner regardless of the toggle. Contract surface (plugin
|
||||
contract bumped 0.8.0 -> 0.9.0, additive): new `shopdb.api` helpers
|
||||
`resolve_dualpath_pairs` and `dualpath_single_machine_enabled`, consumed by
|
||||
the machines plugin to collapse pairs contract-purely.
|
||||
- Relationship propagation, wired and data-driven: relationship types
|
||||
declare propagation-through pairs (relationshiptypepropagations M:N,
|
||||
replacing the never-consumed single column); creating a controls link on
|
||||
one Dualpath bay auto-creates it on the partner bay, and
|
||||
`flask relationships propagate` backfills existing data.
|
||||
- Employee photos, mode-aware: self-hosted directory employees support
|
||||
upload/replace/delete (admin), served publicly for kiosk cards; external
|
||||
directory mode passes the HR-supplied picture URL through read-only. One
|
||||
resolver feeds the shopfloor recognition/recert cards and the employee
|
||||
detail hero in either mode.
|
||||
- Vendor-model photo management. New admin-gated core endpoints
|
||||
`POST /api/models/<modelid>/image` (multipart `file`, png/jpg/jpeg/gif/webp/svg,
|
||||
one image per model, replace semantics) and
|
||||
`DELETE /api/models/<modelid>/image`, plus the public
|
||||
`GET /api/models/image/<filename>` serve route. Uploads land in
|
||||
`instance/modelimages/` (survives upgrades, backed up with the rest of
|
||||
`instance/`) and set `models.imageurl` to the served URL; the manual Image URL
|
||||
field still accepts external URLs and the shipped `/images/models/*` assets
|
||||
(upload is additive). Delete only removes files we own under the instance dir.
|
||||
The Models settings page grows a thumbnail, Upload/Replace, and Remove
|
||||
controls in the edit modal. Asset hero images (e.g. the machine badge) read
|
||||
`imageurl` unchanged, so uploaded photos render with no consumer changes.
|
||||
|
||||
- Application support teams with contacts, replacing the legacy
|
||||
supportteams/appowners pair. New core `supportteamcontacts` table (multiple
|
||||
named contacts per team, ordered by `sortorder`); `supportteams` keeps
|
||||
`teamname` (now unique) and `teamurl` (a ServiceNow group deep link) and
|
||||
sheds the single-owner `appownerid` FK. New core blueprint at
|
||||
`/api/supportteams` (team + nested contact CRUD, admin-gated; `?teamname`
|
||||
exact-match lookup for import; delete a team 409s while any application still
|
||||
references it). Migration `7d18_supportteamcontacts` migrates each legacy
|
||||
team's app owner into one contact. Application payloads now flatten
|
||||
`supportteamname`, `teamurl`, and the team's active `contacts`; a Support
|
||||
card on the application detail page and a new `settings/supportteams`
|
||||
management page render them.
|
||||
|
||||
- Import mode: a complete, idempotent HTTP migration surface so a migration script
|
||||
can import the classic ASP shopdb through the API alone (no direct DB writes).
|
||||
- Contract surface (plugin contract bumped 0.7.0 -> 0.8.0, additive): new
|
||||
`shopdb.api` helpers `apply_import_timestamps`, `import_mode_active`,
|
||||
`parse_import_datetime` in `shopdb/utils/import_mode.py`. When the caller is
|
||||
an admin AND sends header `X-Import-Mode: true`, create/update endpoints
|
||||
accept optional `createddate` / `modifieddate` (ISO or legacy
|
||||
`YYYY-MM-DD HH:MM:SS`, naive-UTC) and preserve them instead of stamping now.
|
||||
Non-admin or missing header: the fields are ignored exactly as before.
|
||||
Wired into every timestamped import target: assets (all five type plugins),
|
||||
vendors, models, modeltypes, businessunits, locations, operating systems,
|
||||
applications, knowledge base, USB devices, and asset relationships.
|
||||
- Natural-key exact-match lookup filters for the documented
|
||||
lookup-then-upsert idempotency recipe: `assetnumber` on all five asset
|
||||
plugin list endpoints; `vendor`, `modelnumber`, `modeltype`,
|
||||
`businessunit`, `locationname`, `osname`/`osversion`, `appname`,
|
||||
knowledge base `linkurl`/`shortdescription`, warranty `servicetag`/`vendor`,
|
||||
and notification `ticketnumber`.
|
||||
- Backdated event history: in import mode the selfhosted USB checkout/checkin
|
||||
endpoints accept optional `checkouttime` / `checkintime` overrides so
|
||||
migrated `usbcheckouts` rows keep their real event times.
|
||||
- New operator manual `docs/IMPORT-API.md` grounded in the real `prodscratch`
|
||||
legacy schema: order of operations, a full table-by-table mapping, honest
|
||||
no-target list with dispositions, a worked idempotent Python importer, and
|
||||
row-count parity checks.
|
||||
|
||||
### Changed
|
||||
|
||||
- System Settings is no longer one tabbed page. The inner tab bar is gone and
|
||||
each section is its own routed settings page reached through the settings
|
||||
rail: ServiceNow, Zabbix Supplies, Dell Warranty, and Collector PC Types
|
||||
(the former Integrations dumping ground, now split three-plus ways), plus
|
||||
Branding, Floor Map, Printing & Labels, Email / SMTP, Authentication,
|
||||
Audit & Logging, Asset Identifiers, and Global Search. The rail regroups
|
||||
these under Site & Facility, Integrations, Communication, Search & Identity,
|
||||
and Access & Security. Shared load/save/upload plumbing moved into a
|
||||
`useSystemSettings` composable so the pages stay thin. Old bookmarks keep
|
||||
working: `/settings/system` and every `/settings/system?tab=<key>` redirect
|
||||
to the matching new page.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Audit log: hovering a user's SSO now shows their full name (best-effort,
|
||||
resolved from the employee directory in either mode).
|
||||
- Refreshed the internal status docs to match the code (project active
|
||||
state, CONTRACT-STABILITY.md and README plugin list at contract 0.10.0),
|
||||
corrected the get_asset_panels endpoint path in the hook docstring, and
|
||||
removed leftover debug console.log lines.
|
||||
- Measuring tools wired into the remaining cross-cutting surfaces an audit
|
||||
found them missing from: generic asset serialization (typedata + pluginid,
|
||||
which also fixes relationship-card links to tools), map subtype
|
||||
filtering/coloring and the MapEditor filter, dashboard totals, warranty
|
||||
asset links (via a new by-asset detail route), and the two ADR-010 hook
|
||||
declarations (presentation route token corrected; the calibration
|
||||
map-overlay endpoint now actually exists). The login avatar also resolves
|
||||
through the employee-photo helper, so self-hosted uploads show.
|
||||
- Measuring tools are now wired into the asset-identifier matrix and global
|
||||
search. The Settings identifier matrix gains a Measuring Tool column and the
|
||||
gauge-lab and maintenance reference inputs/rows on the measuring-tool form
|
||||
and detail pages honor those per-type toggles (a maintenance-reference field
|
||||
was added, matching the other asset types). Global search gains a Measuring
|
||||
Tools domain toggle and filter chip, routes measuring-tool hits to
|
||||
`/measuringtools/<id>` (previously the generic `/assets/<id>` fallback), and
|
||||
matches on `gaugelabreference` so a gage-tag lookup resolves the tool.
|
||||
- Site & Facility settings page renders booleans as toggles and the
|
||||
directory-mode settings as dropdowns, with labels and help text for every
|
||||
field (no more raw keys or type-true/false boxes).
|
||||
- System Settings tabs follow the URL: clicking a settings-rail link that
|
||||
only changes the ?tab= query (Branding, Floor Map) now switches the right
|
||||
panel, tab clicks update the URL, and browser back/forward restore tabs.
|
||||
- Following a relationship link between two assets of the same type now loads
|
||||
the destination page instead of stale content (router-view keyed on path;
|
||||
query-only URL changes still avoid a remount).
|
||||
- Asset relationships card no longer lists a symmetric peer twice. Relationship
|
||||
types gain `relationshiptypes.isdirectional` (migration
|
||||
`7d19_relationshiptype_directional`; seeded false for the connection-like
|
||||
types Dualpath, connectedto, Cluster Member, Serial Cable, Direct Ethernet,
|
||||
USB, WiFi, true for controls/Controlled By/Backup For/Master-Slave/partof/
|
||||
defaultprinter). The card now collapses every stored direction row of a
|
||||
symmetric type into one direction-blind "Connected" entry per peer (deleting
|
||||
it removes all collapsed rows), while directional types drop the
|
||||
Outgoing/Incoming headers for inline `Type -> peer` / `<- Type from peer`
|
||||
phrasing. The type CRUD and the per-asset relationships endpoint carry
|
||||
`isdirectional`; the Relationship Types settings page gains a Directional
|
||||
toggle.
|
||||
|
||||
## [0.6.0] - 2026-07-11
|
||||
|
||||
### Added
|
||||
|
||||
- measuringtools plugin (ADR-005): gage-lab instruments as Asset extensions
|
||||
with type lookup (color-coded), calibration tracking (derived
|
||||
overdue/due-soon/current status), calibration report, and full frontend.
|
||||
Built as the framework exemplar; docs/PLUGIN-GUIDE.md walks through its
|
||||
construction step by step as the plugin-system tutorial.
|
||||
- CSV export on the Warranty and Toner report pages; per-report filter
|
||||
controls (business unit, asset type, location, application, limit) on the
|
||||
inline core reports; report open-state is URL-backed and deep-linkable.
|
||||
- Per-plugin Alembic migration chains (ADR-008): every bundled plugin now
|
||||
carries its own chain with a stamp-only anchor; new plugin schema changes
|
||||
land in `plugins/<name>/migrations/`, never the core chain. Deploys run
|
||||
`flask plugin upgrade-all` after `flask db upgrade`.
|
||||
- Frontend plugin route gating (ADR-009): a disabled backend plugin's pages
|
||||
redirect to the dashboard; new public `GET /api/plugins/enabled`.
|
||||
- `get_reports()` plugin hook (plugin contract 0.5.0 -> 0.6.0): plugins
|
||||
contribute their own report cards; warranty and toner cards moved off the
|
||||
hardcoded frontend list.
|
||||
- Reports hub grouped by category with a search filter.
|
||||
- Configurable QR label targets: `qr_target_printer` / `qr_target_usb`
|
||||
settings (blank = the asset's own page, else a URL template with
|
||||
placeholders) and a `usb_label_style` barcode/QR toggle for USB
|
||||
mini-labels. New Settings > Printing & Labels section.
|
||||
- Site palette theming: optional `brand_primary_dark_color`,
|
||||
`brand_accent_color`, `brand_sidebar_color` settings applied at bootstrap.
|
||||
- Collector integration guide rewrite: header-only auth reference and a
|
||||
paste-ready GE-Enforce PowerShell reporting function.
|
||||
|
||||
### Changed
|
||||
|
||||
- Equipment -> machines rename (backend). The equipment plugin is now the
|
||||
machines plugin: `/api/equipment` -> `/api/machines`, tables
|
||||
`equipment`/`equipmenttypes` -> `machines`/`machinetypes` (columns
|
||||
`equipmentid` -> `machineid`, `equipmenttypeid` -> `machinetypeid`,
|
||||
`equipmenttype` -> `machinetype`), permissions `equipment.*` ->
|
||||
`machines.*`, assettype value `equipment` -> `machine`. The legacy core
|
||||
`machinetypes` lookup (it types the vendor MODELS catalog, not machine
|
||||
instances) is renamed to `modeltypes` (`/api/machinetypes` ->
|
||||
`/api/modeltypes`, `models.machinetypeid` -> `models.modeltypeid`) to
|
||||
free the name. Data flips migrate assettypes, auditlog entitytype,
|
||||
settings keys (`identifier_*_equipment_enabled` ->
|
||||
`identifier_*_machine_enabled`, `search_equipment_enabled` ->
|
||||
`search_machine_enabled`), and permission rows in place; plugins.json
|
||||
registry entries carry over automatically. Upgrade: run
|
||||
`flask db upgrade` then `flask plugin upgrade-all`.
|
||||
|
||||
- Inter (variable) replaces Roboto, bundled locally - no Google Fonts
|
||||
fetch, so air-gapped installs render correctly. Tables use tabular
|
||||
numerals.
|
||||
- ServiceNow defaults point at the current geaerospaceqa.service-now.com
|
||||
global search (search, incident, and change links).
|
||||
|
||||
### Fixed
|
||||
|
||||
- USB frontend remapped to the actual API shape (`device_id` /
|
||||
`device_desc`): device list, detail, form, label batch, and the employee
|
||||
profile's checked-out/history panels were all reading dead legacy fields.
|
||||
- External-mode `GET /api/usb/checkouts/active` now honors the `badge`
|
||||
filter.
|
||||
- Warranties list page no longer demands login (matches every other list
|
||||
page; reads were already public).
|
||||
- Removed the dead legacy Warranty Status report (always-zero buckets from
|
||||
a retired column); the warranty plugin's report is the real one.
|
||||
- Pruned dead usbApi client methods that had no backend routes.
|
||||
|
||||
## [0.5.0] - 2026-07-10
|
||||
|
||||
First release cut with a version, tag, changelog, and CI. Focused on
|
||||
letting other GE Aerospace sites stand up their own self-hosted instance
|
||||
(single-tenant per ADR-004).
|
||||
|
||||
### Added
|
||||
|
||||
- First-run setup wizard (`/setup`): creates the initial superadmin
|
||||
in-app, configures each plugin (create tables here vs connect your own
|
||||
database), uploads light/dark floor-map blueprints, and seeds starter
|
||||
reference data.
|
||||
- Self-hosted employee directory and USB plugins: in-app management plus
|
||||
CSV import, no external database required. Both ship default-disabled
|
||||
with an enable-time provisioning note.
|
||||
- Dell warranty plugin: real Dell provider, bulk warranty sync,
|
||||
add-warranty from asset pages, PC hero warranty badge, disk-cached Dell
|
||||
API token.
|
||||
- Custom fields, and a two-pane settings shell with tabbed, searchable
|
||||
System Settings and Settings index pages.
|
||||
- Dashboard defaults (visitor-IP to business-unit mapping) for kiosk
|
||||
displays; printer installer endpoint (data plus floor-map positions).
|
||||
- Global toast notifications replacing `alert()` calls.
|
||||
- Multi-stage Docker build that compiles the Vue frontend and ships
|
||||
`frontend/dist`, which Flask serves.
|
||||
- Documentation overhaul: new CONFIG, UPGRADE, and BACKUP-RESTORE guides;
|
||||
reconciled README, DEPLOY, status docs, and ROADMAP.
|
||||
- ADR-007 (product versioning and releases), CHANGELOG, and best-effort
|
||||
CI (backend tests, naming/style gate, frontend build).
|
||||
|
||||
### Changed
|
||||
|
||||
- Plugin contract (`__contract_version__`) settled at 0.5.0: full plugin
|
||||
import surface exposed via `shopdb.api`, dead search hook removed, and
|
||||
the dashboard-widgets hook wired to a real consumer.
|
||||
- Role-based access control now enforced on write routes, including
|
||||
admin-only guards on dashboard-defaults writes.
|
||||
- Branding, ServiceNow integration, employee-ID pattern, printer
|
||||
hostname template, and floor-plan blueprints are settings-driven and
|
||||
per-site configurable, with GE defaults preserved as shipped fallbacks
|
||||
(branding and floor-plan configurability landed in this release; some
|
||||
consumer wiring continues under Unreleased).
|
||||
|
||||
### Security
|
||||
|
||||
- Dashboard-defaults writes now require admin authorization instead of
|
||||
any authenticated user.
|
||||
- Collector error responses no longer leak exception detail; failures are
|
||||
logged server-side with generic client-facing messages.
|
||||
- Login rate limiting added (IP-based fixed window) on top of the
|
||||
existing account lockout.
|
||||
|
||||
### BREAKING
|
||||
|
||||
- Collector API key must now be sent in the `X-API-Key` header. The
|
||||
api-key-in-querystring fallback has been removed. Update any collector
|
||||
integration that passed the key as a query parameter. See
|
||||
`docs/COLLECTOR-INTEGRATION.md`.
|
||||
|
||||
Reference in New Issue
Block a user