Step-by-Step Guide to building a custom real-time audit dashboard block for Gutenberg using REST API custom routes
Setting Up Custom REST API Routes for Audit Data
To build a real-time audit dashboard, we first need a way to expose our audit data via the WordPress REST API. This involves creating custom routes that will serve the data our Gutenberg block will consume. We’ll define these routes within a custom plugin to keep our codebase organized and maintainable.
Let’s start by creating a basic plugin structure. Create a new folder named real-time-audit-dashboard in your wp-content/plugins/ directory. Inside this folder, create a main plugin file, for example, real-time-audit-dashboard.php.
Plugin File: real-time-audit-dashboard.php
<?php
/**
* Plugin Name: Real-time Audit Dashboard
* Description: Provides a custom REST API endpoint and Gutenberg block for real-time audit data.
* Version: 1.0
* Author: Your Name
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Register custom REST API routes.
*/
function rtad_register_api_routes() {
// Route for fetching audit logs
register_rest_route( 'rtad/v1', '/logs', array(
'methods' => 'GET',
'callback' => 'rtad_get_audit_logs',
'permission_callback' => function() {
// Ensure only authenticated users with 'manage_options' capability can access
return current_user_can( 'manage_options' );
}
) );
}
add_action( 'rest_api_init', 'rtad_register_api_routes' );
/**
* Callback function to retrieve audit logs.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
function rtad_get_audit_logs( WP_REST_Request $request ) {
// In a real-world scenario, you would query your audit log database or system here.
// For demonstration, we'll return dummy data.
$dummy_logs = array(
array(
'id' => 1,
'timestamp' => current_time( 'mysql' ),
'user_id' => 1,
'username' => 'admin',
'action' => 'User logged in',
'details' => 'Successful login from IP 192.168.1.100',
),
array(
'id' => 2,
'timestamp' => date( 'Y-m-d H:i:s', strtotime( '-5 minutes' ) ),
'user_id' => 2,
'username' => 'editor',
'action' => 'Post published',
'details' => 'Published post "My Awesome Article" (ID: 123)',
),
array(
'id' => 3,
'timestamp' => date( 'Y-m-d H:i:s', strtotime( '-10 minutes' ) ),
'user_id' => 1,
'username' => 'admin',
'action' => 'Settings updated',
'details' => 'Updated site title to "New Site Title"',
),
);
// You might want to add pagination, filtering, etc. here based on $request parameters.
// For simplicity, we return all dummy logs.
$response = new WP_REST_Response( $dummy_logs, 200 );
$response->header( 'X-WP-Total', count( $dummy_logs ) );
$response->header( 'X-WP-TotalPages', 1 ); // Assuming no pagination for this example
return $response;
}
// Placeholder for Gutenberg block registration and rendering
// This will be covered in subsequent sections.
?>
In this file:
- We define the basic plugin header.
- The
rtad_register_api_routesfunction hooks intorest_api_initto register our custom endpoint. - We use
register_rest_routeto define the endpoint/rtad/v1/logs. - The
methodsare set toGET, indicating we’ll be fetching data. - The
callbackis set tortad_get_audit_logs, which will handle the request. - A
permission_callbackis crucial for security, ensuring only users with themanage_optionscapability can access this sensitive data. - The
rtad_get_audit_logsfunction currently returns dummy data. In a production environment, this is where you’d integrate with your actual audit logging mechanism (e.g., a custom table, a third-party service).
After activating this plugin, you can test the endpoint by visiting your-wordpress-site.com/wp-json/rtad/v1/logs in your browser (while logged in as an administrator). You should see a JSON response with the dummy audit logs.
Developing the Gutenberg Block: JavaScript and JSX
Now, let’s build the Gutenberg block that will display this audit data. Gutenberg blocks are developed using JavaScript (and often React/JSX). We’ll need to enqueue a JavaScript file for our block.
Plugin File: real-time-audit-dashboard.php (Continued)
<?php
// ... (previous code for API routes) ...
/**
* Enqueue block editor assets.
*/
function rtad_enqueue_block_assets() {
// Enqueue the JavaScript file for the block editor.
wp_enqueue_script(
'rtad-audit-dashboard-block-editor', // Unique handle.
plugin_dir_url( __FILE__ ) . 'build/index.js', // Path to the JS file.
array( 'wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-api-fetch' ), // Dependencies.
filemtime( plugin_dir_path( __FILE__ ) . 'build/index.js' ) // Version based on file modification time.
);
// Enqueue the CSS file for the block editor.
wp_enqueue_style(
'rtad-audit-dashboard-block-editor-css', // Unique handle.
plugin_dir_url( __FILE__ ) . 'build/index.css', // Path to the CSS file.
array( 'wp-edit-blocks' ), // Dependency.
filemtime( plugin_dir_path( __FILE__ ) . 'build/index.css' ) // Version.
);
}
add_action( 'enqueue_block_editor_assets', 'rtad_enqueue_block_assets' );
/**
* Register the block.
*/
function rtad_register_audit_dashboard_block() {
register_block_type( 'rtad/audit-dashboard', array(
'editor_script' => 'rtad-audit-dashboard-block-editor',
'editor_style' => 'rtad-audit-dashboard-block-editor-css',
'render_callback' => 'rtad_render_audit_dashboard_block', // For front-end rendering if needed
) );
}
add_action( 'init', 'rtad_register_audit_dashboard_block' );
/**
* Server-side rendering callback for the block (optional, for front-end).
*
* @param array $attributes Block attributes.
* @return string HTML output.
*/
function rtad_render_audit_dashboard_block( $attributes ) {
// This callback is for rendering the block on the front-end.
// For a real-time dashboard, you'd likely rely on JavaScript to fetch and update data.
// However, you could pre-render a loading state or basic structure here.
return '<div class="rtad-audit-dashboard-frontend">Loading audit data...</div>';
}
?>
Here’s what’s new:
rtad_enqueue_block_assets: This function hooks intoenqueue_block_editor_assetsto load our JavaScript and CSS files specifically for the block editor.- We’re using
wp_enqueue_scriptandwp_enqueue_style. - Dependencies include
wp-blocks,wp-element,wp-editor,wp-components(for UI elements), and crucially,wp-api-fetch(WordPress’s built-in utility for making REST API requests). - The script path is
build/index.jsand the style path isbuild/index.css. This implies a build process (like Webpack or Rollup) will compile our source JavaScript/JSX into these files. rtad_register_audit_dashboard_block: This function hooks intoinitand usesregister_block_typeto register our block with WordPress. We specify the block name (rtad/audit-dashboard) and link it to the enqueued editor script and style.rtad_render_audit_dashboard_block: This is an optional server-side rendering callback. For a truly real-time dashboard, client-side JavaScript will handle data fetching and updates. This callback might be used to render a placeholder or initial state on the front-end.
Build Process Setup (Node.js, npm/yarn)
To compile our JavaScript and JSX into the build/index.js and build/index.css files, we need a build tool. A common setup for WordPress plugins involves Node.js, npm (or yarn), and a bundler like Webpack.
First, create a package.json file in the root of your plugin directory (real-time-audit-dashboard/).
package.json
{
"name": "real-time-audit-dashboard",
"version": "1.0.0",
"description": "Gutenberg block for real-time audit dashboard.",
"main": "build/index.js",
"scripts": {
"build": "wp-scripts build",
"start": "wp-scripts start"
},
"keywords": [
"wordpress",
"gutenberg",
"block"
],
"author": "Your Name",
"license": "GPL-2.0-or-later",
"devDependencies": {
"@wordpress/scripts": "^26.0.0"
}
}
In your plugin’s root directory, run:
npm install
This will install the necessary development dependencies, including @wordpress/scripts, which provides pre-configured scripts for building WordPress packages. The scripts section defines commands:
npm run build: Compiles your source files into production-readybuild/index.jsandbuild/index.css.npm run start: Watches your source files for changes and automatically recompiles them, useful during development.
You’ll need to create a src/ directory within your plugin folder. Inside src/, create index.js and index.scss (or index.css).
Source File: src/index.js
/**
* WordPress dependencies.
*/
const { registerBlockType } = wp.blocks;
const { Component } = wp.element;
const { apiFetch } = wp;
const { InspectorControls, PanelColorSettings } = wp.editor;
const { PanelBody, Spinner, Table, TableRow, TableCell, TableHead, TableBody, Placeholder, Button } = wp.components;
/**
* Internal dependencies.
*/
import './style.scss'; // Import styles for both editor and front-end.
import './editor.scss'; // Import styles specific to the editor.
/**
* Audit Log Data Fetcher Component.
*/
class AuditLogFetcher extends Component {
constructor(props) {
super(props);
this.state = {
logs: [],
isLoading: true,
error: null,
};
}
componentDidMount() {
this.fetchLogs();
}
fetchLogs() {
this.setState({ isLoading: true, error: null });
apiFetch({ path: '/rtad/v1/logs' })
.then(logs => {
this.setState({ logs, isLoading: false });
})
.catch(error => {
this.setState({ error, isLoading: false });
console.error('Error fetching audit logs:', error);
});
}
render() {
const { logs, isLoading, error } = this.state;
const { backgroundColor } = this.props; // Get color from attributes
if (isLoading) {
return (
<Placeholder icon="admin-generic" label="Audit Dashboard">
<Spinner />
<p>Loading audit data...</p>
</Placeholder>
);
}
if (error) {
return (
<Placeholder icon="warning" label="Audit Dashboard">
<p>Error loading audit data. Please try again.</p>
<Button isDefault onClick={ () => this.fetchLogs() }>
Retry
</Button>
</Placeholder>
);
}
if (logs.length === 0) {
return (
<Placeholder icon="info" label="Audit Dashboard">
<p>No audit logs found.</p>
</Placeholder>
);
}
// Apply background color if set
const blockStyle = {
backgroundColor: backgroundColor ? backgroundColor : '#f0f0f0', // Default background
padding: '20px',
borderRadius: '5px',
};
return (
<div style={ blockStyle }>
<h3>Recent Audit Logs</h3>
<Table>
<TableHead>
<TableRow>
<TableCell>Timestamp</TableCell>
<TableCell>User</TableCell>
<TableCell>Action</TableCell>
<TableCell>Details</TableCell>
</TableRow>
</TableHead>
<TableBody>
{ logs.map( log => (
<TableRow key={ log.id }>
<TableCell>{ log.timestamp }</TableCell>
<TableCell>{ log.username }</TableCell>
<TableCell>{ log.action }</TableCell>
<TableCell>{ log.details }</TableCell>
</TableRow>
)) }
</TableBody>
</Table>
</div>
);
}
}
/**
* Block Settings.
*/
const settings = {
title: 'Audit Dashboard',
icon: 'chart-bar', // Or any other suitable icon
category: 'widgets', // Or 'design', 'plugins', etc.
attributes: {
backgroundColor: {
type: 'string',
default: '#f0f0f0',
},
},
edit: function(props) {
const { attributes, setAttributes } = props;
const { backgroundColor } = attributes;
const onChangeBackgroundColor = ( newColor ) => {
setAttributes( { backgroundColor: newColor } );
};
return (
<div className="rtad-audit-dashboard-editor">
<InspectorControls>
<PanelBody title="Display Settings" initialOpen={ true }>
<PanelColorSettings
title="Background Color"
initialOpen={ true }
colorSettings={ [
{
value: backgroundColor,
onChange: onChangeBackgroundColor,
label: 'Background Color',
},
] }
/>
</PanelBody>
</InspectorControls>
<AuditLogFetcher backgroundColor={ backgroundColor } />
</div>
);
},
save: function(props) {
const { attributes } = props;
const { backgroundColor } = attributes;
// The save function should return static HTML or null if rendering is handled by PHP.
// For a dynamic block like this, we often return null and rely on server-side rendering
// or client-side rendering on the front-end.
// If we want to render something static on save, it would be:
// return (
// <div style={ { backgroundColor: backgroundColor, padding: '20px', borderRadius: '5px' } }>
// <p>Audit Dashboard (will load dynamically)</p>
// </div>
// );
// Returning null means the block will be rendered by the server_render_callback or client-side JS.
// Since we have a server_render_callback, we can use that.
// If not, the block will be empty on the front-end until JS loads.
return null;
},
};
registerBlockType( 'rtad/audit-dashboard', settings );
And a basic SCSS file for styling:
Source File: src/index.scss
.rtad-audit-dashboard-editor,
.rtad-audit-dashboard-frontend {
border: 1px solid #ccc;
padding: 15px;
margin-bottom: 15px;
background-color: #f9f9f9;
border-radius: 4px;
h3 {
margin-top: 0;
color: #333;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
}
}
In src/index.js:
- We import necessary components from
@wordpress/blocks,@wordpress/element,@wordpress/editor, and@wordpress/components. wp.apiFetchis imported for making REST API requests.AuditLogFetcheris a React component that extendsComponent.componentDidMount: This lifecycle method is called after the component is mounted to the DOM. It’s the perfect place to callfetchLogs.fetchLogs: This method usesapiFetchto make a GET request to our custom REST API endpoint (/rtad/v1/logs). It updates the component’s state with the fetched logs, or an error if one occurs.- The
rendermethod handles different states: loading (shows a spinner), error (shows an error message and a retry button), no logs found, and the actual display of logs in a<Table>component from@wordpress/components. InspectorControls: This allows us to add settings to the block’s sidebar (Inspector). Here, we’ve added aPanelColorSettingsto allow users to change the background color of the block.attributes: We definebackgroundColoras an attribute, which means its value will be saved with the post and can be controlled by the user.edit(props): This function defines how the block appears in the editor. It renders theInspectorControlsand theAuditLogFetchercomponent, passing thebackgroundColorattribute down.save(props): This function defines the static HTML saved to the database. For dynamic blocks that fetch data client-side, it’s common to returnnull, indicating that the rendering will be handled by JavaScript on the front-end or by a server-side callback. We’ve also included a commented-out example of static HTML output.
After setting up the source files, run:
npm run build
This command will generate the build/index.js and build/index.css files required by our plugin.
Real-time Updates and Considerations
The current implementation fetches logs when the block is loaded in the editor or on the front-end. For true “real-time” updates, you would typically implement one of the following:
- Polling: Use
setIntervalwithin theAuditLogFetchercomponent to periodically re-fetch logs (e.g., every 30 seconds). This is the simplest approach but can be inefficient. - WebSockets: For more immediate updates, integrate a WebSocket solution. WordPress doesn’t have native WebSocket support, so you’d need a plugin or a separate service to handle WebSocket connections and push updates to the client.
- Server-Sent Events (SSE): A simpler alternative to WebSockets for one-way communication from server to client. You could create another REST API endpoint that keeps the connection open and streams data.
Polling Example (within AuditLogFetcher):
// Inside AuditLogFetcher class
componentDidMount() {
this.fetchLogs(); // Initial fetch
this.intervalId = setInterval(() => this.fetchLogs(), 30000); // Fetch every 30 seconds
}
componentWillUnmount() {
clearInterval(this.intervalId); // Clean up the interval when the component unmounts
}
// ... rest of the class
Security Considerations:
- The
permission_callbackon the REST API route is critical. Ensure it’s robust and only allows authorized users. - Sanitize and validate any data you receive from the API (though in this case, we’re only sending data).
- Be mindful of the performance impact of frequent API calls, especially on busy sites.
By following these steps, you’ve created a custom Gutenberg block that leverages custom REST API routes to display real-time audit data, complete with basic styling and user-configurable options.