Appearance
Audit Log
Last updated
Audit Log (admin section of the sidebar) has four tabs: who did what (Activity), what went wrong in the background (Errors), every electronic signature (Signatures), and the tamper check (Integrity). Admin only.
Activity
A row per recorded action, newest first, 25 per page: Timestamp, User, Action, Resource, Details.
What is recorded includes:
- Sign-in (
login, with the method), failed sign-in (login_failed, with the email tried),logout, second-factor events (mfa_enroll,mfa_enabled,mfa_disabled,mfa_admin_reset,mfa_console_reset). - User administration:
create,update,disable,enable,deleteonuser, feature and resource permission changes, security level changes. - Configuration changes across the product (devices, tags, dashboards, scripts, settings, secrets — never secret values), backup export/import, subsystem and factory resets.
- Tag write denials (
write_denied), OPC-UA client writes (opcua_write), appliance network, time, hostname, restart, reboot and certificate actions.
Rows written by the system itself (failed sign-ins, OPC-UA client writes) have no user name. Each row also records the person's printed name and email when it is written; after the account is deleted, User shows that recorded name.
Field changes. While the Enhanced Audit Trail feature (enhanced_audit, under Feature Toggles in System → Settings) is on, edits in these areas also record each changed field's old and new value: shifts, work instructions, downtime reasons, quality reasons, recipes and work orders. Those rows carry a N changes badge; click the row to expand old → new values.
- Other areas record only a summary in Details, whether the feature is on or off.
- With the feature off, the covered areas also record only the summary. Turning it on applies from the next edit; it does not fill in earlier ones, and turning it off does not delete field changes already recorded.
- The feature is off on a new appliance. An appliance that had already recorded field changes before this toggle took effect was switched on during the update, so its recording carries on.
Filter and export
- Filter by user ID... takes a numeric user id (not a name or email). User ids are in the order users were created; the seeded admin is 1.
- Export CSV downloads
audit-log-YYYY-MM-DD.csvwith ID, Timestamp, User, Email, Recorded as, Action, Resource, Details and Record hash, respecting the user filter. It contains every matching row, newest first; a very long log takes a few seconds while the page fetches it 100 rows at a time. Timestamps in the file are UTC, in ISO 8601 form (for example2026-09-15T20:51:14Z).
Errors
Warnings and errors logged by background services: Timestamp, Level, Module, Message, Device. Filter with Filter by module... (exact module name, e.g. appliance, driverBridge, alarmEngine, scanner, workflowEngine, auth) and the level list (error, warn, info). Messages longer than the column are truncated on screen; use the API for the full text and details.
This is a small, curated log — most diagnostics go to the service logs instead. For driver problems see Driver Health.
Signatures
Every electronic signature, newest first, 25 per page: Date and time, Printed name, Meaning (Approved, Rejected, Responsible for this action or Performed and verified), What was signed (with the action under it), Reason and Record (for example work_order #12; hover for the hash of the record's content at signing). Export CSV downloads esignatures-YYYY-MM-DD.csv with the meaning written out in words. Signatures made before meanings were recorded show —. See Electronic signatures.
Integrity
The activity log, field changes and electronic signatures are each a hash chain: every record stores a SHA-256 hash of its own content plus the hash of the record before it. The database itself refuses to change or delete these records.
- Verify now walks every chain and shows, per chain, the number of records, the ID range, the Newest hash and OK or Broken. A broken chain names the first broken link (First broken link: Activity log, record #500): a record changed after it was written, or one that does not follow the record before it (a record before it was removed, inserted or changed).
- Export report saves the result as JSON. Keep it, or write down the newest hashes: a later report that still verifies, with the same records, proves nothing before them changed.
- Each verification is itself recorded in the activity log (
verifyonaudit_chain).
Removing old records: checkpoints
Old audit records can only be removed by sealing a checkpoint.
- Pick a date in Remove records older than and click Seal checkpoint and remove.
- Confirm, then sign: your password and a reason are always required here, whatever the signature settings say.
- The appliance first verifies every chain and refuses if one is broken (The audit trail does not verify … Old records cannot be removed until this is investigated.).
- It writes the records it removes, with their hashes, to an archive file
audit-checkpoint-<id>-<date>.jsonin theaudit-archivefolder next to the database, then records the checkpoint: the date, you, the reason, how many records were removed and the hash of the last one removed from each chain. The remaining chain starts from that hash. The checkpoint is signed with this appliance's own Ed25519 key (the same key paired appliances use) and chained to the previous checkpoint. - Download the archive from the checkpoint's row (Archive) and keep it with your records. Archive files are not part of backups.
Removal always takes everything up to the newest record older than the date, so the remaining chain has no gaps. Field changes go with their activity row. A signature linked to a work order or sign-off can be removed too; the release or sign-off keeps the signer's printed name and time, and its link to the signature is cleared. Checkpoints can never be changed or deleted. A checkpoint signed on a different appliance (restored from its backup) shows Another appliance with that key's fingerprint.
Timestamps and retention
- Times are stored in UTC and shown in your browser's time zone. The API returns them as ISO 8601 UTC (ending in
Z). - Nothing is pruned automatically. Activity and signature records are removed only by a signed checkpoint (above) or a factory reset. Error rows are removed by Reset a Subsystem → Operational history, a factory reset, or deleting the device they belong to.
- Records written before this audit chain existed are given their hashes once, at the first start after the update.
What the chain does and does not prove
- It proves that, since each record was written, nothing in it was changed and no record was removed or inserted, unless the chain was rebuilt from that point on.
- Someone with shell access to the appliance can drop the database's protections and recompute every hash after a change. The chain cannot stop that; what shows it is a newest hash, or an exported report, kept somewhere else and compared later. Checkpoints are also signed with the appliance key, which is stored outside the database.
- Restoring an older backup brings back that backup's chain; what happened after the backup is not in it. The restore itself is recorded after the restart (see Backup).
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /api/admin/audit/activity | ?limit=25&offset=0&userId=3; limit max 100; includes fieldChanges, actor (recorded name) and rowHash |
| GET | /api/admin/audit/errors | ?limit=25&offset=0&module=appliance&level=error |
| GET | /api/audit-chain/verify | Verify every chain; returns per-chain results, intact and firstBreak |
| GET | /api/audit-chain/checkpoints | Sealed checkpoints, newest first |
| POST | /api/audit-chain/checkpoints | { "cutoff": "2024-01-01T00:00:00Z", "signature": { "password": "…", "reason": "…" } }; 409 when the chain does not verify |
| GET | /api/audit-chain/checkpoints/:id/archive | Download the archive of the records a checkpoint removed |
Script: export the full activity log
python
import csv, requests
HOST = "https://scada-line3.local"
s = requests.Session()
s.verify = False # appliance CA not installed on this machine
tok = s.post(f"{HOST}/api/auth/login",
json={"email": "admin@scada.ink", "password": "your-password"}).json()
s.headers["Authorization"] = "Bearer " + tok["data"]["tokens"]["token"]
rows, offset = [], 0
while True:
page = s.get(f"{HOST}/api/admin/audit/activity",
params={"limit": 100, "offset": offset}).json()
rows += page["data"]
offset += 100
if offset >= page["pagination"]["total"]:
break
with open("audit.csv", "w", newline="") as f:
w = csv.writer(f)
w.writerow(["id", "timestamp_utc", "user", "action", "resource", "details"])
for r in rows:
w.writerow([r["id"], r["timestamp"], r["userEmail"], r["action"], r["resource"], r["details"]])If the appliance's second factor is on for this account, use an account without it for scripts, or an API token where the endpoint supports one.