From 4b0f92b147fb6b4c48f4303b15c3cc1da23dfc23 Mon Sep 17 00:00:00 2001 From: cliu Date: Thu, 1 Oct 2026 17:15:27 +1000 Subject: [PATCH] Add audit selection group, audit report and event logger setup to CI-Eng-Sample Co-Authored-By: Claude Opus 5.5 --- examples/CI-Eng-Sample/19_audit_df.qli | 42 +++++++++++++++++++ examples/CI-Eng-Sample/79_remove_audit_df.qli | 19 +++++++++ examples/CI-Eng-Sample/README.md | 25 ++++++++++- examples/CI-Eng-Sample/audit/adtlogci.sup | 11 +++++ examples/CI-Eng-Sample/load.cmd | 2 + examples/CI-Eng-Sample/remove.cmd | 3 +- 6 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 examples/CI-Eng-Sample/19_audit_df.qli create mode 100644 examples/CI-Eng-Sample/79_remove_audit_df.qli create mode 100644 examples/CI-Eng-Sample/audit/adtlogci.sup diff --git a/examples/CI-Eng-Sample/19_audit_df.qli b/examples/CI-Eng-Sample/19_audit_df.qli new file mode 100644 index 0000000..233c0be --- /dev/null +++ b/examples/CI-Eng-Sample/19_audit_df.qli @@ -0,0 +1,42 @@ +@LANGUAGE +ENGLISH + +@VERSION +1.03.00 + +! CI-Eng-Sample audit trail examples, CI Server R1.03.00. Load after 18_report_df. +! CIENG_ITEMS : AUDIT Service selection group = events on CI-Eng-Sample objects. Expressions +! have no LIKE: a name prefix is matched with >= "CI-Eng-Sample." and +! < "CI-Eng-Sample/" ('/' sorts right after '.'). COMPILE 1 compiles on load; +! the result is in LISTING and VALID_EXPRESSION. +! CIENG_AUDIT : ad-hoc report of the audit events on CI-Eng-Sample of the last hour. +@FIELDS +NAME,DESCRIPTION,EXPRESSION,COMPILE + +@AUDIT_SELGRP_DF +"CIENG_ITEMS","Audit events on CI-Eng-Sample",@" +sbid >= "CI-Eng-Sample." and sbid < "CI-Eng-Sample/" +"@,1 + +@FIELDS +NAME,DESCRIPTION,PRIORITY,ADHOC_ENABLED,LIFE_TIME,ADHOC_DEST,QUERY + +@REPORT_DF +"CIENG_AUDIT","CI-Eng-Sample audit trail, last hour",0,1,"1 Weeks","CIENG_FILE",@" +/* Who changed what on CI-Eng-Sample in the last hour */ +set @width 160; +column t as 'DD-MM-YYYY hh:mm:ss' heading 'Time'; +column grp as 'A12' heading 'Subject'; +column attr as 'A16' heading 'Attribute'; +column act as 'A8' heading 'Action'; +column src as 'A12' heading 'Source'; +column id as 'A32' heading 'Name'; +column usr as 'A18' heading 'User'; +column ov as 'A12' heading 'Old'; +column nv as 'A12' heading 'New'; +select action_time t, subject_group grp, subject_attribute attr, subject_action act, + subject_source src, subject_ident id, user_name usr, old_value ov, new_value nv + from audit_trail + where action_time > @date - 3600 and subject_ident like 'CI-Eng-Sample.%' + order by action_time; +"@ diff --git a/examples/CI-Eng-Sample/79_remove_audit_df.qli b/examples/CI-Eng-Sample/79_remove_audit_df.qli new file mode 100644 index 0000000..30b7119 --- /dev/null +++ b/examples/CI-Eng-Sample/79_remove_audit_df.qli @@ -0,0 +1,19 @@ +@LANGUAGE +ENGLISH + +@VERSION +1.03.00 + +! Remove the CI-Eng-Sample audit report and selection group. Audit trail records themselves +! cannot be deleted (only whole units of storage group ADT, HISTORY Service). +@FIELDS +NAME + +@REPORT_DF +"CIENG_AUDIT" + +@FIELDS +NAME + +@AUDIT_SELGRP_DF +"CIENG_ITEMS" diff --git a/examples/CI-Eng-Sample/README.md b/examples/CI-Eng-Sample/README.md index 6f822ed..ea5b7a3 100644 --- a/examples/CI-Eng-Sample/README.md +++ b/examples/CI-Eng-Sample/README.md @@ -77,6 +77,8 @@ cmd /c "\examples\CI-Eng-Sample\remove.cmd" | `CIENG_TANK1` | Report | Values of the `TANK1` items + last-hour statistics of one item (ad-hoc parameter chosen in an item browser, default `LEVEL`); daily 06:00 local time and ad hoc; output `CIENG_FILE`; lifetime 1 week | | `CIENG_INFLOW_HI` | Report | Generated 5 s after `TANK1.INFLOW` rises above 400; output `CIENG_FILE` | | `CIENG_HTML` | Report | Ad hoc; `TANK1` values as an HTML table (cursor loop) plus the logbook text | +| `CIENG_AUDIT` | Report | Ad hoc; audit trail events on `CI-Eng-Sample.*` of the last hour | +| `CIENG_ITEMS` | Audit selection group | Expression `sbid >= "CI-Eng-Sample." and sbid < "CI-Eng-Sample/"`, compiled on load | Items created by objects (`TANK1.CALC.VOLUME`, `TANK1.JFLOW.sPerMin`) and their sections are deleted with the objects; they are not in `02_item_df.qli`. @@ -102,9 +104,11 @@ New items start with status 13 (`NOT INIT`) until a value is written. | `16_item_alarm_df.qli` | Switch on alarming for `TANK1.LEVEL` (loaded with `dssqld -m`) | | `17_printer_df.tpl`, `17_printer_df.ps1` | Report file device and destination; the script fills in this system's node name and `%TLS_LST_PATH%` and writes `17_printer_df.qli` (not in the repo) | | `18_report_df.qli` | Reports (`REPORT_DF`, RQL in the `QUERY` memo) | +| `19_audit_df.qli` | Audit selection group (`AUDIT_SELGRP_DF`) and audit report | +| `audit/adtlogci.sup` | Setup file for an extra audit event logger `ADTLOGCI` filtered on `CIENG_ITEMS` (copied to `tls\sup` by an administrator, not by `load.cmd`) | | `83a_stop_report_df.qli` | Clears the schedule of `CIENG_TANK1` before removal (a scheduled report cannot be deleted) | | `equipment/eqpciengslv_start.cmd`, `_stop.cmd` | Start/stop the Modbus slave mirror (copy to `tls\com`) | -| `76_`…`93_remove_*.qli` | Names to delete, in reverse order (reports and printer `83`/`84` first, alarm definitions last, `93`) | +| `76_`…`93_remove_*.qli` | Names to delete, in reverse order (audit `79`, reports and printer `83`/`84` first, alarm definitions last, `93`) | | `load.cmd`, `remove.cmd` | Run the steps in the right order | | `displays/CIENG_Tank.xml` | Display (HTML mode): title, number field on `TANK1.LEVEL`, alarm overview, trend of `TANK1.LEVEL`, button *Operator layout* | | `displays/CIENG_Menu.xml` | Navigation display (200 x 700): *Tank 1*, *Current Alarm* (main frame), *Tank 1 popup* (layout `popup`) | @@ -185,7 +189,8 @@ gets its own storage group of type *Report*. Try them (skill `reporting`, task ` - Ad hoc: `REPORT_DF` `NAME,PARAM_VALUE_1,GENERATE` = `"CIENG_TANK1","CI-Eng-Sample.TANK1.INFLOW",1` with `dssqld -m`, or in the Engineering Module *Configuration > Reporting > Reports* › right-click - › *Generate*. The output lands in `%TLS_LST_PATH%\cieng_report.txt` and in `REPORT_VAL`. + › *Generate*. The output lands in `REPORT_VAL`; runs from the Engineering Module also print to + `%TLS_LST_PATH%\cieng_report.txt` (runs from `dssqld` do not print, ISS-045). - Trigger: `itmval -p -n CI-Eng-Sample.TANK1.INFLOW -v 450` → `CIENG_INFLOW_HI` 5 s later; write a value below 400 before the next test. - `CIENG_TANK1` also runs every day at 06:00 (local time). @@ -193,6 +198,22 @@ gets its own storage group of type *Report*. Try them (skill `reporting`, task ` `remove.cmd` clears the schedule, removes the reports (with their generated reports and logbook files), the destination and device, and deletes `cieng_report.txt`. +## Audit trail + +No admin rights needed for `19_audit_df.qli`. Every change made by `load.cmd`, `dssqld` or the +Engineering Module is in the audit trail (skill `audit-trail`): + +- Generate `CIENG_AUDIT` (ad hoc, like the other reports) to list the last hour of changes on + `CI-Eng-Sample.*`: time, dataset, field, action, source, user, old and new value. +- Export: `dssqld -e audit.qlo -d AUDIT_TRAIL` (whole trail) or a qli with `@SELECT` (keep `NAME` + in `@FIELDS`). +- Event logger (optional, admin): copy `audit\adtlogci.sup` to `%TLS_ROOT_PATH%\tls\sup\`, then + run `adtlog -n adtlogci` in a console; it prints each new CI-Eng-Sample event on one line + (Ctrl+C to stop). Delete the file from `tls\sup` when the sample is removed. + +Audit records themselves cannot be deleted; `remove.cmd` removes only the report and the +selection group. + ## Planned additions | Phase | Adds | diff --git a/examples/CI-Eng-Sample/audit/adtlogci.sup b/examples/CI-Eng-Sample/audit/adtlogci.sup new file mode 100644 index 0000000..ef1a703 --- /dev/null +++ b/examples/CI-Eng-Sample/audit/adtlogci.sup @@ -0,0 +1,11 @@ +! CI-Eng-Sample: setup file for an extra AUDIT Service event logger (ADTLOG) named ADTLOGCI. +! A setup file is named after the process; copy this file to %TLS_ROOT_PATH%\tls\sup (admin) +! and start the logger with: adtlog -n adtlogci +! It prints only events of selection group CIENG_ITEMS (load 19_audit_df.qli first; the +! group must have compiled, else the logger exits). Output goes to stdout (no PRT_DEV). +! Keywords: see adtsys.sup (template) and knowledge/manuals/IM36K10L25-01EN.md. +SEL_GROUP = "CIENG_ITEMS" +TIMEFMT = "DD-MM-YYYY hh:mm:ss" +E_LAYOUT = ACTM,19,SBGP,12,SATR,14,SACT,8,SACS,12,SBID,32,USER,18,OVAL,12,NVAL,12 +HEADER = "Time Subject Attribute Action Source Name User Old New" +HEADER_AFTER = 20 diff --git a/examples/CI-Eng-Sample/load.cmd b/examples/CI-Eng-Sample/load.cmd index c8f2dac..a8012d9 100644 --- a/examples/CI-Eng-Sample/load.cmd +++ b/examples/CI-Eng-Sample/load.cmd @@ -42,3 +42,5 @@ rem from 17_printer_df.tpl), then the report definitions. powershell -NoProfile -ExecutionPolicy Bypass -File 17_printer_df.ps1 dssqld -i 17_printer_df.qli -l 17_printer_df.qlo dssqld -i 18_report_df.qli -l 18_report_df.qlo +rem Audit trail: selection group (compiled on load) and an ad-hoc audit report. +dssqld -i 19_audit_df.qli -l 19_audit_df.qlo diff --git a/examples/CI-Eng-Sample/remove.cmd b/examples/CI-Eng-Sample/remove.cmd index 9ccce96..3fd02aa 100644 --- a/examples/CI-Eng-Sample/remove.cmd +++ b/examples/CI-Eng-Sample/remove.cmd @@ -1,10 +1,11 @@ @echo off rem Remove CI-Eng-Sample from a CI Server R1.03 TEST system (also deletes its history). rem Run in an elevated prompt while CI Server is running; call this script by full path. -rem Order: reports, printer, OPC UA, Modbus, auth group, object, class, trigger group, storage links, stop + delete storage groups, items, sections (children first). +rem Order: audit report + selection group, reports, printer, OPC UA, Modbus, auth group, object, class, trigger group, storage links, stop + delete storage groups, items, sections (children first). rem A storage group with an active unit cannot be deleted (HIS-E-GROUPHASUNT), so the rem group first gets a stop time one minute ahead; the script waits until it has passed. cd /d "%~dp0" +dssqld -r 79_remove_audit_df.qli -l 79_remove_audit_df.qlo rem Reports first (they refer to TANK1.INFLOW and the destination), then destination and device. rem A scheduled report cannot be deleted, so its schedule is cleared first. dssqld -m 83a_stop_report_df.qli -l 83a_stop_report_df.qlo