A site running external mode keeps its devices and check-in/out history in a separate cmmc_usb database. There was no way to bring that in, so the choice was to keep the second database forever or lose the history. This adds 'flask usb import-external' - dry run by default, --commit to write - which reads through the same connection settings the routes already use and never writes to the source. Two things it has to reconcile, and they are why it is a command rather than a few INSERTs. The schemas disagree about SHAPE. External is an event log, one row per action; self-hosted is a paired row carrying both times. Pairing walks each device in time order, and real logs after years of use are untidy. The rule is that nothing is invented and nothing vanishes: a drive still out becomes an open checkout and is NOT flagged (that is just a drive somebody has), a return with no matching issue is skipped and named, and two issues in a row leave the first open rather than inventing a return time nobody recorded. Identity is the other. usbdevices.serialnumber is unique and NOT NULL and the external devices table has no serial, so device_id becomes it. Blank or duplicated ids stop that device rather than inventing a key. external device_owner has no column here, so it is appended to the device notes rather than dropped. Also adds usbcheckouts.scannedviruses, the one field with nowhere to land. waswiped already means what external calls sanitized - selfhosted.py was mapping it - but scanned_viruses was HARDCODED None in the log output: the API promised the field and had nothing behind it, so an imported history would have rendered blank. It now reads and writes properly. Nullable, never defaulted to false. An unrecorded scan is not a failed scan, and for removable-media records those are very different claims. Full suite 2131 passed; seven new tests pin the pairing rules.
USB plugin - CMMC USB check-in/out database contract
The USB plugin tracks removable-media check-in/out for CMMC compliance. Device
and log state live in a separate, read-write MySQL database (cmmc_usb),
reached with parameterized pymysql via cmmc_usb_connection(). Names of people
are resolved from the HR employee directory (see the employees plugin).
The plugin's own reference tables (usbdevicetypes, usbdevices,
usbcheckouts) live in the main app database; only the live check-in/out data
is in cmmc_usb.
The schema is standardized across sites - the
cmmc_usbcheck-in/out solution is the same deployment everywhere, so the tables below match as-is and no schema adaptation is needed. The database name may differ per site, though - setcmmc_usb_db_name(defaultcmmc_usb) to match the local name. The view recipe at the end is only a fallback for a site that somehow differs. (Contrast the employee directory, which genuinely varies per site.)
Connection
Credentials resolve settings-first, then environment, except the password which is env-only (never stored in the app database).
| Field | Setting key (editable in the setup wizard) | Env var (fallback) |
|---|---|---|
| Host | cmmc_usb_db_host |
CMMC_USB_DB_HOST |
| Database | cmmc_usb_db_name |
CMMC_USB_DB_NAME |
| User | cmmc_usb_db_user |
CMMC_USB_DB_USER |
| Password | (not stored) | CMMC_USB_DB_PASSWORD |
Set the password in .env; the setup wizard's Features step shows the exact
line to paste. This database is read-write - the app account needs
SELECT, INSERT, UPDATE. Engine is MySQL/MariaDB (pymysql).
Required schema
The plugin runs raw SQL against these tables (or views - see below).
devices
| Column | Type | Notes |
|---|---|---|
device_id |
VARCHAR | Primary key; the device serial/tag |
device_desc |
VARCHAR | Description |
device_owner |
VARCHAR | Owner |
status |
VARCHAR | Check-in/out state |
locker_location |
VARCHAR | Where the device is stored |
Operations: SELECT (list + by id), INSERT (register device), UPDATE
(edit fields, change status).
checkinoutlog
| Column | Type | Notes |
|---|---|---|
log_id |
INT (PK) | Auto id |
badge_number |
VARCHAR | Person's badge |
device_id |
VARCHAR | FK to devices.device_id |
action |
VARCHAR | check-in / check-out |
timestamp |
DATETIME | When it happened |
scanned_viruses |
(text/int) | Scan result |
locker_location |
VARCHAR | Locker at time of event |
sanitized |
(bool/int) | Sanitization flag |
Operations: SELECT (history per device), INSERT (log an event).
users
| Column | Type | Notes |
|---|---|---|
badge_number |
VARCHAR | Primary key; the scanned badge |
first_name |
VARCHAR | Given name |
last_name |
VARCHAR | Surname |
Operations: SELECT by badge, INSERT (auto-add a badge on first scan).
Employee directory dependency
To turn a scanned badge into a name, the plugin also reads the HR employees
directory (via the employees plugin's connection). A badge shaped 0<digits>BZ
carries a PayNo (the digits); lookups try employees.SSO and employees.PayNo.
See plugins/employees/README.md for that schema and connection.
Adapting a different site schema (recommended: views)
Sites whose USB-tracking database uses different table/column names should
create read-only/updatable views named devices, checkinoutlog, and
users that map local columns to the names above. Example:
CREATE VIEW devices AS
SELECT
asset_tag AS device_id,
description AS device_desc,
owner AS device_owner,
state AS status,
storage_bay AS locker_location
FROM usb_assets;
Because the plugin writes to devices/checkinoutlog/users, either make the
views updatable (single-table views usually are) or expose real tables with
these column names. Grant the app account SELECT, INSERT, UPDATE.
Notes:
device_idandbadge_numberare the natural keys the plugin matches on.- If
cmmc_usbis unreachable, USB endpoints return an error and the rest of the app keeps working (the feature degrades, it does not crash the app).