USB check-in / check-out audit, as a report rather than a paged list
CMMC removable-media tracking is an audit obligation, and answering "who had this drive, and when" meant paging through the check-in/out list by hand. GET /api/usb/reports/audit returns the events - a take and a return are separate rows, because a count cannot answer the question an audit actually asks - with summary figures riding along for the people who only want the shape of it, and a CSV for the people who want it in a spreadsheet. The window is named rather than a day count: today, last month, six months, a year, all time. Nobody should have to work out that a year is 365. Both directory modes answer in one voice. The external path reads the cmmc_usb checkinoutlog; self-hosted expands one usbcheckouts row into its two ends, so a returned drive is distinguishable from one still out, and the window filter runs in SQL against either end of the loan - a drive taken before the window and returned inside it is a check-in the window has to show. Rows come back from the mode-specific half; the CSV, the summary and the response shape are built once in the route. Ordering is total in both modes. Timestamp alone ties for two events in the same second, and the same query then returns them in a different order each run, which reads as the log being unstable.
This commit is contained in:
@@ -88,6 +88,6 @@ Manifest-less directories under `plugins/` are core frontend surface and always
|
||||
|
||||
## Size
|
||||
|
||||
- test functions defined: **1355** (parametrised cases collect higher)
|
||||
- documented API paths: **304** (`docs/openapi.json`, regenerate with `scripts/gen_openapi.py`)
|
||||
- test functions defined: **1366** (parametrised cases collect higher)
|
||||
- documented API paths: **305** (`docs/openapi.json`, regenerate with `scripts/gen_openapi.py`)
|
||||
|
||||
|
||||
@@ -2842,6 +2842,14 @@
|
||||
"params": "body: badge",
|
||||
"purpose": "Drives checked out to OTHER people, with holder names, so somebody can return one left behind. Behind its own step rather than on the main kiosk screen, and 404 when usb_kiosk_allow_return_for_others is off - there is no point naming holders of drives the kiosk will not let anyone return",
|
||||
"example": "curl -X POST http://localhost:5001/api/usb/kiosk/others -H 'Content-Type: application/json' -d '{\"badge\":\"270015376\"}'"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/usb/reports/audit",
|
||||
"auth": "jwt + usb.view",
|
||||
"params": "query: period (day|month|sixmonths|year|all, default month) or days=<n> (0 = all time); format=csv for a download",
|
||||
"purpose": "Removable-media audit: every check-out and check-in in the window, newest first, with who, which drive, when and which locker - plus summary counts. Rows rather than totals, because an audit asks who had a specific drive on a specific day. 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",
|
||||
"example": "curl 'http://localhost:5001/api/usb/reports/audit?period=year' -H \"Authorization: Bearer $TOKEN\""
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -16473,6 +16473,44 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/usb/reports/audit": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"plugin-usb"
|
||||
],
|
||||
"summary": "Removable-media audit: every check-out and check-in in the window, newest first, with who, which drive, when and which...",
|
||||
"description": "Removable-media audit: every check-out and check-in in the window, newest first, with who, which drive, when and which locker - plus summary counts. Rows rather than totals, because an audit asks who had a specific drive on a specific day. 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\n\n**Auth:** jwt + usb.view\n\n**Params:** query: period (day|month|sixmonths|year|all, default month) or days=<n> (0 = all time); format=csv for a download\n\n**Example:**\n```\ncurl 'http://localhost:5001/api/usb/reports/audit?period=year' -H \"Authorization: Bearer $TOKEN\"\n```",
|
||||
"security": [
|
||||
{
|
||||
"bearerAuth": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success. Body is the success_response envelope: {status, data, meta}.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"$ref": "#/components/schemas/SuccessEnvelope"
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Error. Body is the error envelope; the code and message are nested under data.error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"$ref": "#/components/schemas/ErrorEnvelope"
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Missing or invalid credentials."
|
||||
},
|
||||
"403": {
|
||||
"description": "Authenticated, but not permitted."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/printers/types": {
|
||||
"get": {
|
||||
"tags": [
|
||||
|
||||
Reference in New Issue
Block a user