Commit Graph

98 Commits

Author SHA1 Message Date
Proudlock
fe09d2745e Implement plugin dynamic settings routes architecture
Plugins can now register their settings pages dynamically via get_settings_routes()
hook, eliminating hardcoded imports that break lean builds.

Backend:
- Add get_settings_routes() hook to BasePlugin (shopdb/plugins/base.py)
- Implement in ComputersPlugin for Operating Systems management
- Add /api/plugins/settings-routes endpoint to expose plugin routes

Frontend:
- ModelCatalogHub fetches plugin routes and dynamically adds them via router.addRoute()
- Tabs render dynamically from pluginTabs ref
- Components lazy-load when clicked

Benefits:
- Lean builds work without manual route exclusions
- Plugins self-contain their UI extensions
- No core code changes needed when adding plugin settings pages
- Follows plugin architecture best practices

Fixes TODO from commit a03fd3de4f

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-09-11 16:49:10 -04:00
Proudlock
a03fd3de4f Fix CI failures: remove unicode and fix lean build
- Replace all unicode (emojis, checkmarks, arrows, em-dashes) with ASCII in .claude/ docs
- Comment out OperatingSystemsList route in core.js - breaks lean builds without computers plugin
- Comment out Operating Systems tab in ModelCatalogHub
- TODO: Plugins should dynamically register their settings routes

Fixes #2 CI check failures on wj branch

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-09-11 16:42:19 -04:00
Proudlock
f9f1d39f08 Consolidate settings into 8 hubs and fix security vulnerabilities
Settings Consolidation (37+ pages to 8 hubs):
- Asset Type Hub: Unified type management with plugin descriptors
- Model Catalog Hub: Vendors, types, models, operating systems
- Asset Configuration Hub: Statuses, relationships, fields, identifiers
- Security Hub: Authentication, tokens, users, audit
- Site Configuration Hub: Site settings, floor map, branding, imports
- Integrations Hub: ServiceNow, Zabbix, Dell warranty
- Locations Hub: Locations, business units, employees
- Network Infrastructure Hub: VLANs, subnets

Plugin Descriptor Pattern:
- Add get_asset_type_descriptor hook to BasePlugin
- Implement descriptors in 6 plugins (printers, computers, machines, network, measuringtools, locations)
- Add /api/pluginui/asset-type-descriptors and core-type-descriptors endpoints
- Generic SubtypesTab component renders CRUD UI from descriptor schema

Security Fixes:
- Fix stored XSS via javascript: URIs in Models and Support Teams
- Add URL scheme validation (allow only http/https) in create/update endpoints
- Fix plugin contract violations in endpoints plugin (use shopdb.api imports)
- Add missing requiresAdmin guard to printer model supplies route
- Add user-facing error messages to API token load failures

Integration Improvements:
- Add persistent tab navigation to ServiceNow, Zabbix, and Dell Warranty settings
- Standardize integration settings UI across all three pages
- Fix tab visibility when navigating between integrations

Tested with 94-agent orchestrated review for ADR compliance and security.
2026-09-11 10:45:12 -04:00
benjamin.baker
68ddf87d3b Gate operator reports behind their own grant
Some checks failed
CI / backend (push) Has been cancelled
CI / naming (push) Has been cancelled
CI / gates (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / lean-build (push) Has been cancelled
Operator report screenshots are an uncontrolled capture of whatever was
on the operator's screen when they filed the report, so they can carry
part numbers, customer names, or other shopfloor content. The rest of
endpoints.view is uniform machine-collected telemetry. Reading a PC's
CPU load does not imply a right to read someone's screen, so the reports
and their images move to endpoints.operatorreports.

The image route is the real control point, not the panel: the panel only
decides whether a tab is drawn, while the bytes stay reachable by URL to
anyone who can guess an id. Both GET routes now require the new grant.

Migration 7d47 backfills the grant to everyone holding endpoints.view so
nobody loses a panel they were already using; tightening from there is a
per-account decision in the user editor. The grant is in the scope
denylist, so it is a per-user direct grant and Reporter cannot hold it.

Also adds test_user_editor_checkboxes_match_the_server_action_list. The
SCOPE_ENDPOINT_ACTIONS comment claimed a sync guard kept the Vue
checkbox list aligned with the Python tuple, but no such test existed.
The lists had already drifted: endpoints.operatorreports reached a
running container with no way to grant it through the UI. The guard
catches drift in both directions - a name only in Python ships the
feature dark, and a name only in Vue is worse, since the admin ticks a
box and believes access was granted while the server refuses to store it.
2026-09-08 07:36:19 -04:00
benjamin.baker
3626d7dd2e Add Report a Bug link to the sidebar
Sits in the sidebar footer below Settings and above the Light/Dark toggle, visible to everyone including guests, and opens the team's SharePoint bug list in a new tab with rel=noopener.

The URL is a development placeholder for the wilm pre-production window, marked as such in a comment next to the constant so it is not mistaken for a shipped integration. Styling matches .theme-toggle exactly and uses fixed white alphas rather than theme tokens, because the sidebar is dark in both themes - a var(--text-*) here would resolve to a dark colour on a dark sidebar in light mode and vanish, which is the same trap the lock modal hit earlier.
2026-09-07 12:48:15 -04:00
benjamin.baker
0e43ac6b8d Add prebuilt user scopes and guard invalid Reporter grants
A newly created user held no roles and no permissions, so the sidebar filtered every nav item away and the account appeared broken. Adds three prebuilt scopes (Reporter, Operator, Technician) derived from the live permission catalog by verb rather than hand-listed, so a new plugin permission lands in the right scope automatically instead of silently going ungranted. users.view is granted to all three since a user who cannot see the directory cannot collaborate; users.create/edit/delete stay admin-only. Role.seedscopes() is additive and idempotent, seeded via flask seed permissions, and preserves custom grants on rerun.

Reporter guard: the auth model is additive across admin, direct grants and roles, so Reporter cannot be expressed as merely fewer grants - a direct endpoint-action grant would silently re-arm a read-only account. Rather than change the global permission predicate and alter every route's semantics, _check_scope_grants() rejects the invalid combination at write time on both create and update. The update path validates the effective post-update state, so demoting a Technician to Reporter is refused while a stale endpoint grant remains rather than quietly leaving a read-only user with write authority.

Validation runs before the User object is constructed. Assigning user.roles first put the relationship into the session autoflush path while the User was still unattached; nothing broke because the refusal returns before db.session.add(), but it left a trap where any query added below that point would autoflush a half-built user and surface later as an orphan row. Verified with -W error::sqlalchemy.exc.SAWarning: 25 passed.
2026-09-07 12:39:24 -04:00
Proudlock
6d8c03bf6a Fix: Hide kiosk links for plugins that aren't installed
The sidebar was showing USB Kiosk and Parts Kiosk links even when those
plugins were not installed, because isPluginEnabled() fails open (returns
true before the plugin list loads).

Changes:
- Load enabled plugins list during AppLayout mount
- Make kiosk visibility reactive with computed properties
- Check pluginsLoaded flag before showing kiosk links
- showUsbKiosk and showPartsKiosk now wait for real plugin state

Before: isPluginEnabled('usb') returns true immediately (enabledSet null)
After: Waits for plugin list, correctly returns false when not installed

Behavior:
- USB Kiosk: Hidden if usb plugin not installed/enabled
- Parts Kiosk: Hidden if printedparts plugin not installed/enabled
- Shopfloor Dashboard: Still shows (core feature)
- TV Slideshow: Still shows if slides plugin exists

User reported: USB Kiosk showing when USB plugin not installed
Fixed: Kiosk links now correctly gate on plugin installation status

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-09-07 03:06:07 -04:00
Proudlock
a6af09858c Merge wilm: gevent migration, endpoint telemetry phase 2, backup/restore
Brings in 98 commits from wilm (origin/wilm @ 811e6ca61a):

Major features:
- Move API to gevent workers for better concurrency
- Endpoint agent startup jitter (prevents thundering herd)
- Complete backup/restore with restic
- Endpoint telemetry phase 2 (disk health, mapped drives, user context)
- PC Detail UI rework with new cards and tabs
- Dell Command actions and Corporate Tools tab
- Agent version column in Fleet view

Conflict resolution:
- Backup/restore: Accepted wilm implementation (10 files)
- Agent startup jitter: Accepted wilm v1.12.0
- PC Types list fix: Accepted wilm version
- Endpoint plugin domain clamping: Kept ours (defensive)
- Test files: Accepted wilm versions for consistency

Testing required:
- Verify gevent handles concurrent requests
- Test backup/restore functionality
- Verify agent startup jitter timing
- Test endpoint telemetry collection
- Validate PC Detail UI changes

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-09-06 21:23:42 -04:00
95abe72729 Endpoint telemetry, PC Detail rework, a checked backup/restore path, and setup-wizard fixes 2026-09-06 19:25:02 -04:00
993353dfc0 Endpoint telemetry, PC Detail rework and a checked backup/restore path 2026-09-06 18:53:11 -04:00
benjamin.baker
b973561cb7 Gate restore on the dump's data revision, not the code head
A restore could load a database dump whose schema was older than the running
code. The manifest stamped schemarev from the code image on disk, so the
compatibility check compared the code against itself and always passed. A set
captured from a database at 7d37_import_batches_and_journal restored under
code at 7d41_drop_guest_tools_secrets, leaving the data behind the code: every
query on a table the newer migrations touched failed with 1054 Unknown column,
which surfaced as empty configuration-policy dropdowns and a failure to load
data on edit PC.

The authoritative signal is the alembic_version of the database that was
dumped, so manifests now carry datarev alongside schemarev, backup.sh reads it
from the database and warns when it has drifted from the image, and the gate
asks whether the running code knows that revision. Data behind the code is
allowed because it can be rolled forward; data ahead of the code is refused
because it cannot.

Rolling it forward has to happen somewhere with an app context. The backup
sidecar is mysql plus restic plus alembic only and has no Flask, so restore.sh
loads the dump and parks the run in an upgrading phase behind an
.upgrade-requested sentinel rather than declaring a success it cannot verify.
The api container owns the Flask CLI and already runs these exact idempotent
commands on every deploy, so a finalize endpoint there runs db upgrade plus
plugin upgrade-all and stamps the terminal phase. The UI drives it on seeing
the upgrading phase, so recovery needs no operator intervention.

The live simulation runs one codebase against itself, so its dump is always at
the same revision as the code restoring it and it can never reproduce this
drift. The gate therefore gets a standalone tester that builds throwaway
migration chains and dumps offline, invoked as a simulation preflight. It
caught a real bug in the legacy dump-parse fallback, where the revision was
matched loosely enough to return the alembic_version table name instead of the
value it holds.
2026-09-04 15:53:58 -04:00
benjamin.baker
debe1fce69 it is risen - add restore progress and backup/restore history
The watcher scripts now write live status and append-only run history onto the shared volume; the api exposes them via /shopdbbackups/status and /history. The restore modal shows an indeterminate spinner with phase labels, and two cards list backup and restore run records. Timestamps render in UTC to match the Zulu-stamped set ids, the history directory is excluded from the set list, and a stale-status guard stops a previous run's done phase from collapsing the spinner on the next restore.
2026-09-03 20:43:42 -04:00
benjamin.baker
7e4469c44e Wire restore into the backup watcher and fix backup timestamps
The api queued .restore-requested but nothing consumed it, so restores silently did nothing. The watcher now services the restore sentinel by running restore.sh, and its instance_data mount is read-write so restore can lay files back down (the manual one-shot stays read-only). Backup stamps use a colon-free UTC form that Date could not parse, so the UI showed raw strings; normalize them before formatting.
2026-09-03 20:26:01 -04:00
benjamin.baker
19071ec6c6 Add opt-in backup overlay with third-container restic store
Backups are per-site and not part of the base stack, so they ship as a separate compose overlay with an isolated restic REST server the app can only reach through the backup utility (no shell or db creds). The sidecar image trusts the corporate proxy root CA at build time so its Oracle yum, github, and PyPI fetches succeed behind TLS interception; shell scripts are pinned to LF via .gitattributes so the container entrypoints do not die on a CRLF shebang.
2026-09-03 20:06:45 -04:00
ff9ae693a6 Remediate the 2026-09-03 feature-test findings 2026-09-03 17:14:53 -04:00
ddf41c3e65 Make a partly-undone import retryable, and refuse a plan it cannot deliver 2026-09-03 09:16:01 -04:00
b0bd73f62d Import the wilm agent store, WINC adoption work and batches CLI, with four fixes 2026-09-03 08:16:38 -04:00
bb733f1332 Merge the wilm work, act on the five-agent review, verify the launch-path policy on Windows 2026-09-02 16:15:53 -04:00
64c51ce805 Let a guest actually use search: declare the shipped guest grants, and draw the box for whoever can search 2026-08-31 09:56:49 -04:00
6680c3c5de Merge the wilm delivery work into main
Ben Baker's installed-software collection, software delivery (run_script and
update_app), agentless adoption tool and command-queue hardening, with his
authorship preserved.

Reviewed before promotion. Fixed: the endpoints plugin tests were never collected
by pytest, the SBOM snapshot table's key would exhaust on MySQL, ingest did not
truncate two columns strict MySQL would reject, and update_app's version branch
pinned a digest for a file that route never serves. Agent packaging moved off
require_role('admin') onto a new endpoints.agentpackage permission.

Contract 0.27.0. Full suite 2231, frontend 314.
2026-08-30 19:41:15 -04:00
8f4fe74dcb Call the inventory panel Assets, and refuse dist builds off a site branch
The dashboard panel now matches the sidebar's own vocabulary. The export refuses
--dist anywhere but main, because the dist directories are what the prod update
script mirrors onto the server and they are not branch-qualified.
2026-08-30 12:58:00 -04:00
43fed63277 Site branch tooling, branch-aware publication, dashboard fixes
Publication now mirrors the branch and writes one bundle per branch. Dashboard:
guests see what they can reach rather than a blank page, and the estate total
lists every type it counts.
2026-08-30 11:19:00 -04:00
870e068c44 Rebuild the dashboard around what needs a person
The board was one card per source in a fixed grid: every source got the same
small box whatever it had to say (warranties showed 5 of 50), and a box per
source flattened priority, while the largest type on the page belonged to counts
that need nobody.

Now a severity rollup, one worst-first queue of everything needing a person, and
a row of context beneath. The queue is full width, capped per source so a noisy
check cannot bury a rare one, and names what did not fit with a link to it. Chips
filter the queue by check as well as by severity; both narrow what is already
loaded, so the counts and rows cannot disagree.

Dropped: an 'All assets' tile shown beside its own four addends, a printer strip
carrying the same count a third time, three cards whose job was to display 0, and
a Recent Devices table listing five machines all 'In Use'.

Contract 0.26.0 adds optional 'shortname' on dashboard cards - a title is written
as a heading and ran wider than the hostname it qualified as a row badge.

Also: the supplies card reported 'Low Supplies 0' when Zabbix was unreachable,
which reads as reassurance rather than the truth that nothing could see the
cartridges. It now distinguishes unreachable (says so) from unconfigured (stays
quiet - the site declined the feature) from a real zero.

Full suite 2182 passed, frontend 314 passed.
2026-08-29 21:35:03 -04:00
765496a4cf File the Fleet nav item under Assets and store policy JSON as TEXT 2026-08-28 16:23:21 -04:00
cproudlock
75627d4398 Close the review findings on the policy routes, installer and import
Applied on top of af13337 so nothing already on wilm is rewritten.

AUTHORIZATION. The six new policy GETs were @jwt_required(optional=True) with
no permission at all, so an anonymous caller could list configuration and
business policies - gated only by a feature flag that 404s while off. They now
require computers.view. The six policy write routes stacked @jwt_required()
above @require_permission, the same pattern removed from nine endpoints routes
in this very push: the outer decorator answers an anonymous request 401 before
require_permission can consult the guest role, so a grant to guests silently
does nothing. Removed, and the now-unused import with them.

tests/test_core/test_public_read_surface.py failed at af13337 with seven ungated
GETs. It passes now. The seventh, endpoints.claim_next_command, is genuinely
agent-facing - authenticated in the handler by the endpoints collector key,
because a bay has no user session - so it joins PUBLIC_ENDPOINTS with its reason
written down, exactly as report_command_result already does in test_authz.py.
Note which half is public: claiming a queued command is the agent's business,
while ENQUEUEING one is the real authority and stays gated on endpoints.command.

INSTALLER. -BaseUrl and -CollectorKey are now mandatory with no defaults, and
https is enforced unless -AllowInsecureHttp is passed for a local test box. The
defaults were a real hostname (ADR-015) and a live collector key: a key in
source is a key in git history, and that key is also the command channel's kill
switch. Removing it here does not undo the disclosure - it must be rotated on
the server, and purged from af13337 - but it stops the next clone carrying it.

IMPORT. A committed import wrote thousands of rows and left no audit trail; it
now writes one AuditLog row inside the same transaction, so it cannot survive a
rollback or go missing from a commit. The computer-type dropdown asked for
per_page: 200, which the API ignores in favour of perpage and clamps to 100
anyway - it uses a new computersApi.types.listAll(), the pattern already applied
to the other selectors. And the comment claiming the template example rows are
"emitted as commented rows the engine skips" was the opposite of the truth: they
import as live data, the emitted instructions say so, and a test depends on it.

Not fixed here, deliberately: rotating the leaked key, purging it from history,
and the endpoint agent's own hardening (per-host credentials, a protected
process denylist so stop cannot kill security tooling, signing before the
PowerShell runner is enabled, registry ACLs, and the 25-second long poll that
will pin a worker thread per bay).
2026-08-28 15:52:15 -04:00
benjamin.baker
af133372b4 Gate endpoint UI refresh on fresh telemetry and land endpoint agent
A command reaching 'succeeded' only proves the agent ran the action, not that it posted a new telemetry snapshot. Reloading panels on command success alone re-rendered stale service/process rows, so a stopped process could still show running right after Refresh Status.

Add a lightweight telemetrymeta probe exposing lastreportedat and gate both the header Refresh Status action and inline row Start/Stop on that stamp advancing past a pre-action baseline. Polling is bounded so an offline or idle agent never hangs the button, and messaging distinguishes a confirmed refresh from a queued one.

Bring the .NET endpoint agent source (service, tray, installer) into the repo so Cameron can merge and test, with an agent/.gitignore keeping bin/obj/publish out. Includes the DncMain launch-path fix, tray single-file host env scrub, and best-effort file logger.
2026-08-28 15:07:20 -04:00
benjamin.baker
c001657582 Merge remote-tracking branch 'origin/main' into wilm
# Conflicts:
#	shopdb/__init__.py
2026-08-28 09:30:35 -04:00
e8f1de811f nav items declare their permission 2026-08-27 14:51:04 -04:00
4da6c6aa36 guest role; guide section ticking 2026-08-27 13:02:35 -04:00
5f069b1a5f Email report on every report page; kiosk long-name fix 2026-08-27 11:01:28 -04:00
60c340b07f Plugin reports render; USB check-in/out audit report; kiosk drive lists 2026-08-27 10:20:59 -04:00
376298dc02 Navigation sections declared, guide editor drag + references, USB settings, permission refresh 2026-08-27 09:30:08 -04:00
0b8c455b99 USB kiosk sidebar link 2026-08-27 08:56:27 -04:00
benjamin.baker
3deff4f716 Merge remote-tracking branch 'origin/main' into wilm
# Conflicts:
#	frontend/src/views/settings/SiteSettings.vue
2026-08-27 05:45:52 -04:00
benjamin.baker
8b1ba3c310 add endpoints plugin with fleet list view and monitoring drafts
Server-side RMM telemetry plugin: current-snapshot endpointstates plus append-only curated endpointevents, resolved to existing Computer assets by hostname. Adds ADR-010 asset panels (status, services, recent events), dashboard quiet and stability cards, and a minimal Fleet list view that links into existing PC detail pages. Registers the activity sidebar icon and a dev-only sample seeder. Ingest arrives on the generic collector route; the future C# agent design record lives in docs/DESIGN-DECISIONS-agent.md.
2026-08-27 05:41:06 -04:00
0738d71525 Guides image + draft gating, audit retention, ADR-015 gate, backup credential redaction 2026-08-26 15:08:53 -04:00
benjamin.baker
3b3a7cf910 Fix location dropdowns silently truncating at the 100-row backend cap
The backend clamps perpage to MAX_PAGE_SIZE (100), so location selectors that requested a large page received only the first page and still got a 200. Late-alphabet locations (past ~C5W) simply went missing from every module's dropdown, and no error surfaced.

Add locationsApi.listAll, which pages through the full set via fetchAllPages, matching the established machines/printers pattern, and switch every location selector consumer to it.
2026-08-26 13:25:07 -04:00
f221fba540 Guides: procedures written and read inside ShopDB 2026-08-26 12:42:24 -04:00
benjamin.baker
0577e32240 add CSV import guide and clarify Azure export location
Adds a concise native-CSV import guide covering exact headers, required columns, foreign-key-by-name resolution, import order, and the do-not-edit rule for Azure/Thor exports. Also clarifies the Import Site Assets Azure card that Bulk operations lives in the top command bar of the group Members page, matching the Entra UI.
2026-08-25 17:12:01 -04:00
benjamin.baker
bdef21da50 add Entra device identity to computers and Azure/Thor import sources
PC records now carry Entra/Azure device identity so imported machines keep a durable link back to their directory object instead of matching on hostname alone. Adds azuredeviceid, azureobjectid, and azuredevicecategory to the Computer model with matching migrations, surfaces them read-only in PCDetail as an Entra Device Info card, and lets PCForm edit the device id directly. The computers plugin registers a machineassetnumber import hook that reconciles PC-to-machine links during bulk import and warns on unresolved or conflicting relationships.

Adds importsources adapters that normalize Azure device-group and Thor PC inventory exports into canonical ShopDB CSVs via hostname alignment, exposes register_import_hook and RowWarning on the shopdb.api contract surface (contract 0.21.0), and refreshes the Import Site Assets page with clearer source instructions. Includes reconcile_locations maintenance script and coverage for the new import paths.
2026-08-25 16:46:00 -04:00
benjamin.baker
f4d8868463 Merge remote-tracking branch 'origin/main' into wilm 2026-08-24 14:52:59 -04:00
benjamin.baker
4a1b97362b Add site asset CSV import with fuzzy reconciliation
Sites seed inventory from the Asset Collector script or spreadsheet exports, but lookup names drift between sources (Dell vs Dell Inc.), which would otherwise create duplicate vendors/models/types on every import. The importer now normalizes and fuzzy-matches near-miss references onto existing lookup rows within an operator-set similarity threshold, auto-applying the match while reporting it for review so nothing merges silently. Operating systems and asset rows are never reconciled. Adds the dedicated Import Site Assets page with script/template download and a multi-file dry-run/commit workflow.
2026-08-24 14:29:02 -04:00
4242028270 Secure send: hand somebody a password without leaving it in a chat window 2026-08-21 13:44:34 -04:00
04107ff8ab Search spelling suggestions across every list 2026-08-21 11:15:06 -04:00
09e9d5f047 Carry the level everywhere a position is drawn, and gate it per occurrence 2026-08-18 09:37:29 -04:00
e67add5fbe Give the map editor's search box room for its own placeholder 2026-08-17 15:26:54 -04:00
3af77bdb5b Make the map editor accept a click, and unclutter its panel 2026-08-17 15:21:34 -04:00
4c3d5746e8 Fix the levels viewer: markers were drawn on whichever plan was showing 2026-08-17 15:01:09 -04:00
ece0e15f03 Page past the 100-row cap in pickers and batch label sheets 2026-08-17 14:17:28 -04:00
fbd99041b0 Buildings and levels for the floor map, and make every identifier searchable 2026-08-17 13:03:54 -04:00