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.
AuditResthas a page operation, aPOST /queryoperation and a convenienceGET /rows. It has no child resources and no write operations. - Server mode over BeanQuery.
queryhands the DataTables request toDataTablesQuery.runagainststore().queryAudit(). A malformed search expression comes back as a 200errorenvelope. - Typed column search. Each column declares a
searchType:timestampforatandtextfor the rest. Theatcolumn 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
atdescending. Mutations made through the console (for example a pet saved, an order approved or a restock job) write anAuditEntrywith 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
- Twin pattern: see 19.17. Template and Builder Twins.
- Petstore: Jobs: the restock job writes audit entries.
- Petstore: Pending Changes: applying a change writes one too.
- Data Tables: the request and response envelopes, and
DataTablesQuery. - Rich Table Views: what a table card can do.
- Rich Table Views: Catalog: column types and search types.