Skip to main content

Petstore: Audit

The Audit page, served at /console/ops/audit, is the smallest server-mode table in the petstore and a leaf of the navigation tree: one resource, one template, one query endpoint. It is a read-only view of the store's audit trail, with a search box on every column and a date range on the timestamp. Start here if you want the shortest path from a bean to a searchable table.

What this page shows​

  • A leaf page. AuditRest has a page operation, a POST /query operation and a convenience GET /rows. It has no child resources and no write operations.
  • Server mode over BeanQuery. query hands the DataTables request to DataTablesQuery.run against store().queryAudit(). A malformed search expression comes back as a 200 error envelope.
  • Typed column search. Each column declares a searchType: timestamp for at and text for the rest. The at column takes a range as its search value, in the BeanQuery form $between(2026-09-30T00:00:00Z,2026-10-01T00:00:00Z), and the query layer parses the ISO-8601 instants.
  • Newest first. The default order is at descending. Mutations made through the console (for example a pet saved, an order approved or a restock job) write an AuditEntry with the actor, entity, entity id, action and detail.
  • Export. Copy and CSV buttons in the ribbon.

Template​

The whole page is audit.ftlh, a single card:

<@card type="datatables" id="audit">{
dataMode: 'server',
dataUrl: '/console/ops/audit/query',
defaultOrder: [{ key: 'at', dir: 'desc' }],
columns: [
{ key: 'at', label: 'When', render: { id: 'timestamp' }, searchType: 'timestamp' },
{ key: 'actor', label: 'Actor', searchType: 'text' },
{ key: 'action', label: 'Action', searchType: 'text' }
],
ribbon: [ { type: 'export', buttons: ['copy', 'csv'] }, { type: 'refresh', id: 'refresh' } ]
}</@card>

The excerpt trims three columns (entity, entityId and detail); the real card has six.

Builder twin​

This is the shortest builder twin in the tour. OpsPageBuilders.audit is one expression of about ten lines, and ?builder renders it in place of the template.

PageSpec.create().tab("ops/audit").toolkit("views")
.table(TableSpec.create("audit").dataMode(DataMode.SERVER).dataUrl("/console/ops/audit/query")
.operators(SearchOperatorSet.standard())
.defaultOrder("at", Sort.DESC)
.columns(
Column.create("at").label("When").render("timestamp").searchType(SearchType.TIMESTAMP),
Column.create("actor").label("Actor").searchType(SearchType.TEXT))
.ribbon(RibbonItem.export("copy", "csv"), RibbonItem.refresh().id("refresh")));

Compare it with the template: the card body keys (dataMode, dataUrl, defaultOrder, columns, ribbon) each have a builder method of the same name. The builder also sets operators(SearchOperatorSet.standard()), which the template does not spell out.

Source and tests​

Source: juneau-petstore/juneau-petstore-core, classes AuditRest (in the ops package), OpsPageBuilders (in the pages package), AuditEntry and PetStore (in the service package), and the template audit.ftlh.

Tests: AuditRest_Test (the seeded trail is queryable, the timestamp $between range, a console write shows up, newest first), PetstorePages_ContractTest and PetstoreTwins_Test.

See also​