Features Apograph CMS on GitHub

Insights and the activity log

What the dashboard reports, and what the audit log does and does not record.

Documents 0.5.2 Updated Edit this page Report a problem

On this page

Insights

A dashboard per workspace, at Insights in the sidebar, with a range picker.

The page itself ships no widgets. It provides the frame — the route, the range picker, the card shell, a per-widget error boundary and the chart primitives — and every card arrives from the plugin that owns the data behind it. A dashboard is the module most likely to slowly become the one that knows about every other one, and this is what prevents that.

The content widgets

WidgetAnswers
TotalsEntry, published and draft counts, the change over the range, and a sparkline
PipelineDraft against published, per content type
VelocityEntries published per time bucket across the range
StalePublished entries bucketed by time since last edit — 30, 90, 180, 365 days and older
UnshippedLive entries carrying unpublished edits — the Modified state
PunchcardEdits by weekday and hour

Media and translation widgets

Media contributes storage, uploads over time, and alt coverage — how many images have no text alternative. Localization contributes translation coverage per language and content type.

Each widget owns its own request

Six content endpoints rather than one combined payload, so a slow or failing aggregate degrades one card instead of blanking the dashboard. A widget that throws shows its own error inside its own card, named after the widget so the failure is identifiable.

The activity log

An append-only audit trail, at Activity. There is also a per-user view on each member’s page, and a recent-activity panel on the home page.

What it records

Each row is an event with a time, an actor, a kind, and a subject.

SourceKinds it records
IdentitySign-in, API token lifecycle, workspace lifecycle
UsersMember invitation, edit, disable, enable
ContentPublish lifecycle
MediaAsset and folder lifecycle

It is written in the same transaction

An audit row is not written by the code that performs the action. The action appends a domain event to the transactional outbox using its own transaction, so the event commits if and only if the change does — and never without it. After commit, the event is delivered to the activity plugin’s subscriber, which writes the row.

Delivery is at-least-once and the insert is keyed on the event id, so a redelivery never double-records.

The practical consequence: there is no window in which a change is applied but unrecorded, and no way for an audit row to describe something that was rolled back.

Filtering

The log filters by subject type, subject id, actor, actor email, event kind and a date range; it pages and sorts by time or kind.

What it does not record

An unmapped event kind is audited by nobody, silently

The audit is derived from the outbox downstream of the write. An event kind that has no mapper in the activity plugin is not an error anywhere: the dispatcher finds no subscriber, marks the event delivered, and the action is simply never audited.

That failure is completely silent, and it has happened three times in this codebase — API tokens, entry publishes, and the whole media library each shipped unaudited for a period. All three are mapped as of 0.5.0, which also pinned the comparison as a test, so the next gap fails a suite rather than waiting for somebody to notice. If you add a plugin that emits events you expect to see here, you must also add its mapper, and the check is one query: compare the distinct kinds in outbox_events against the audited set.

Reads are not audited — only changes. Nothing records that somebody looked at a record, and there is no access log beyond whatever your reverse proxy keeps.