Modules
The Audit Module
A screen for the audit trail wire-core already records — the event, the person, the record, and the before and after of every field that moved.
On this page
The engine has shipped for versions: HasAuditable fires the events,
AuditLogger writes them, and a pruning command keeps the table honest. What no
package shipped was a way to read any of it without SQL.
composer require nyoncode/wire-module-auditphp artisan wire-module-audit:install
How It Works
Read-only, and that is the design. An audit entry that can be edited is not
an audit entry, so the resource declares an index and a view and nothing else —
no form, no create page, and ProvidesResourceForm deliberately absent. There is
no delete button either: retention is wire-core.audit.retention_days and the
wire-core:audit-prune command, which is a scheduled decision rather than a
button somebody can be talked into pressing.
Over wire-core's own model. The module owns no table: AuditEntry, its
migration and its change diff belong to core, and this points at them. Core
publishes that migration on demand, so a fresh installation may have the screen
before it has the table — the installer says so rather than leaving you an empty
log to explain.
php artisan vendor:publish --tag=wire-core::migrationsphp artisan migrate
Every column has a named owner. The event's label and colour, the actor's name, the record's name, the fields that moved — each answer is needed in a column, a filter and the detail page, and a screen that worked each of them out three times would eventually work them out differently.
Recording is still core's switch. With wire-core.audit.enabled off there is
nothing to show, and the installer says that too.
Who Did It
An entry stores the key of whoever was signed in; the screen shows a name, read through the relation core defines and eager-loaded once for the page rather than once per row.
Three answers, because they are three different facts:
| Stored | Shown | Why |
|---|---|---|
no user_id |
System | Seeders, queued jobs and console commands audit changes with no auth context — AuditLogger records those on purpose |
| a key with no user | Unknown user #12 | The account is gone, so the key is all that is left of it — and it stays visible |
| a key with a user | their name | The first of the configured attributes they actually have |
name is a convention rather than a contract, so which attribute reads as a
person's name is yours to say:
// config/wire-module-audit.php'actor' => [ 'attributes' => ['full_name', 'email'],],
The user model itself is core's setting, because core defines the relation:
// config/wire-core.php'audit' => [ 'user_model' => App\Models\Account::class,],
What Changed
The list names the fields that moved; the entry page puts the before beside the
after as one table, a row per field, over the diff core already computes
(AuditEntry::getChangeDiff()).
One table rather than a card per field, which is what it drew first: a card
carries the Field / Old / New headings once per row, so an update touching
eight columns repeated them twenty-four times — the work the diff exists to have
already done. It is core's ChangesEntry, the same
component the trail slide-over draws inside a record, so a change reads the same
in both places.
How a value reads has one owner too,
NyonCode\WireCore\Foundation\ValueObjects\ChangeSet: a stored array — a JSON
column, a bulk action's list of ids — is rendered as text rather than as the word
Array; a boolean is true or false rather than 1 and nothing; and a value
that was not there reads as (empty) rather than as a blank that looks
unchanged.
The page itself is three sections, in the order the questions arrive: what
happened (the event, the moment, the person, the record), changes, and
the request it came in on — the IP and the user agent AuditLogger records,
plus anything the event carried. The last one starts collapsed: it is the half
nobody opens the page for, and the half that can be longest.
From The Log To The Record
A row says something happened to invoice seven, and the next thing anybody wants is invoice seven. Where the audited model has a resource and that resource is routed, the row offers a link to it — resolved through the registry, in the zone the list was opened in.
Where it cannot be resolved there is no link, rather than one that leads nowhere. That covers more cases than it sounds: a model with no resource, an application that routes the log and nothing else, and a class the log outlived.
Applications that keep class names out of their database are read back through
the same map that wrote them, so a morphMap alias is labelled and linked like
any other type.
Guarding The Screen
Unset, the log is as open as the rest of the panel. Name an ability and it guards the routes and hides the links that lead to them, from the one line:
// config/wire-module-audit.php'permission' => 'audit.view',
It is checked through Gate::allows(), which both permission packages register
themselves into — so a wildcard (audit.*) and a super-admin bypass work without
this module knowing they exist. An audit log is the screen most worth naming one
for.
What You Get
| Screen | Notes |
|---|---|
| Audit log | Event, record, actor, the fields that changed, when — filter by event, record type, actor and date range; newest first |
| One entry | The before and after of every field that moved, and the request it arrived in |
Configuration
| Key | Default | Description |
|---|---|---|
model |
AuditEntry::class |
The entry model; it must extend core's, and one that does not is refused rather than shown as an empty log |
actor.attributes |
['name', 'email'] |
Which attribute of a user reads as their name; the first they have wins |
permission |
null |
Ability required to read the log — null leaves it open |
navigation.group |
system |
Menu group |
navigation.label |
null |
Menu group heading; null uses the module's own |
navigation.icon |
outline:clipboard-document-list |
Menu icon |
navigation.sort |
95 |
Menu group order |
This module ships no views of its own — its screens are the framework's table and
infolist over the trail wire-core records, so they are restyled where every
other screen is (Theming). What it does ship is wording:
php artisan vendor:publish --tag=wire-module-audit::translations, merged over
the package's file key by key, so an override holds only the lines you changed.