From 60c340b07fc5c36e644e87dd067d080dc053168c Mon Sep 17 00:00:00 2001 From: Cam Proudlock Date: Thu, 27 Aug 2026 10:20:59 -0400 Subject: [PATCH] Plugin reports render; USB check-in/out audit report; kiosk drive lists --- CHANGELOG.md | 32 ++++ frontend/src/views/reports/ReportsIndex.vue | 163 ++++++++++++++++---- plugins/usb/api/routes.py | 126 ++++++++++++++- plugins/usb/api/selfhosted.py | 46 +++++- plugins/usb/frontend/views/USBKiosk.vue | 142 +++++++++++++---- plugins/usb/plugin.py | 20 +++ tests/test_plugins/test_usb_reports.py | 139 +++++++++++++++++ 7 files changed, 609 insertions(+), 59 deletions(-) create mode 100644 tests/test_plugins/test_usb_reports.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ff7f665..8798d461 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,38 @@ ADR-007 and ADR-002. ### Added +- **USB check-in / check-out audit report.** CMMC removable-media tracking is an + audit obligation and there was no report for it - answering "who had this + drive, and when" meant paging through the check-in/out list by hand. The + window is named rather than a day count: today, last month, six months, a + year, or all time. Events rather than totals, because that is the question an + audit actually asks, with a CSV export for the people who want it in a + spreadsheet. Works against the external `cmmc_usb` log or the self-hosted + tables; in self-hosted mode one stored loan expands into a take and a return, + so a returned drive is distinguishable from one still out. + +### Fixed + +- **Plugin reports rendered a raw JSON dump.** The reports page ran a hardcoded + switch over five core report ids and fell through to + `console.error('Unknown report')`, and its results panel was a matching chain + of per-id templates ending in a `JSON.stringify`. So a plugin could register a + report, watch it appear in the catalogue, and get a wall of JSON when it was + opened - which is where the three printedparts reports have been since they + were added. Any report that declares an endpoint is now fetched from it, and + any report returning `{columns, rows}` renders as a table. A plugin needs no + core code to have a working report. CSV export follows the same endpoint + rather than assuming `/api/reports/`, which does not exist for a plugin + report, and a report that fails now says so instead of showing an empty panel. + Export CSV is fetched through the api client rather than `window.open`: a new + window carries no Authorization header, so every report behind a real + `jwt_required` answered the export button with a 401. The core reports are + jwt-optional, which is the only reason the old way ever appeared to work. + +- **Reports had no way to choose a time period.** Endpoints accepted a window; + the page offered no control for it, so `printedparts-consumption` was + permanently stuck on its 30-day default. + - **USB kiosk: check a drive out and back in without a login.** The people who actually take USB drives are standing at the cabinet, and most of them have no ShopDB account - so check-out and check-in were gated behind a login that was diff --git a/frontend/src/views/reports/ReportsIndex.vue b/frontend/src/views/reports/ReportsIndex.vue index 34457c3d..89c62c32 100644 --- a/frontend/src/views/reports/ReportsIndex.vue +++ b/frontend/src/views/reports/ReportsIndex.vue @@ -47,8 +47,20 @@ +
{{ reportError }}
+
+