⏱ 7 min read | ~1344 words
AI-Enhanced Log Analysis and Anomaly Alert System — Part 6: Creating a PHP Dashboard for Alerts and Visualizations
Based on my technical understanding as a Lead Programmer Analyst (PHP, Perl, Python, Shell), I’ll walk you through building a production‑ready, single‑page PHP dashboard that surfaces AI‑generated anomalies, lets ops engineers triage them, and visualizes trends with modern JavaScript charts. This article is the sixth installment of the “AI‑Enhanced Log Analysis and Anomaly Alert System” series.
Quick Recap of Parts 1‑5
In Part 1 we defined the log ingestion pipeline (Fluent Bit → OpenObserve). Part 2 showed how to train a Claude 4.1‑based “reasoning agent” that turns raw logs into structured anomaly events. Part 3 covered the REST API that exposes those events, Part 4 demonstrated how to store alerts in PostgreSQL, and Part 5 introduced a lightweight Python service that periodically re‑scores logs using GPT‑5 parallel agents for early‑failure prediction.
Why a PHP Dashboard?
PHP remains the lingua franca for many internal tools, especially when you need tight integration with existing authentication layers (LDAP, SSO) and a quick time‑to‑market. In 2026, the market still favors purpose‑built log visualizers such as OpenObserve, Grafana, and Kibana, but they fall short on conversational investigation and real‑time AI feedback. As noted in OpenObserve’s “Best Log Visualization Tools in 2026”, Kibana’s AI features require the Elastic ML add‑on and still lack a natural‑language interface. Our PHP dashboard fills that gap by embedding the AI agent directly into the UI, letting users ask “Why did this spike happen?” and get a synthesized answer on the spot.
Architecture Overview
| Component | Technology | Responsibility |
|---|---|---|
| Log Ingestion | Fluent Bit → OpenObserve | Collect raw logs, forward to storage. |
| AI Reasoning Service | Claude 4.1 + GPT‑5 parallel agents (Python) | Detect anomalies, generate explanations. |
| Alert API | FastAPI (Python) – /api/alerts |
Expose JSON payloads for the dashboard. |
| Persistence | PostgreSQL (alerts, profiles) | Store anomaly metadata, user acknowledgments. |
| Dashboard | PHP 8.2 (Laravel‑lite) + Vue 3 + Chart.js | Render tables, charts, and conversational UI. |
Prerequisites
- PHP 8.2+ with
pdo_pgsqlextension enabled. - Composer (for autoloading only – we’ll keep the stack minimal).
- Node.js (optional, only if you want to compile Vue components; we’ll use CDN‑based builds).
- Access to the
/api/alertsendpoint from Part 3 (CORS enabled forhttps://your‑dashboard.domain). - A PostgreSQL database with the
alertstable created in Part 4.
Step 1 – Bootstrap the PHP Project
Start by creating a folder called dashboard and initialise Composer. We’ll use PSR‑4 autoloading for a tiny “service” layer that talks to the Alert API.
mkdir dashboard && cd dashboard
composer init --name="yourorg/ai‑log‑dashboard" --require="guzzlehttp/guzzle:^7.8" --no-interaction
composer install
Now create the directory structure:
dashboard/
├─ public/
│ └─ index.php
├─ src/
│ ├─ ApiClient.php
│ └─ AlertRepository.php
├─ vendor/
└─ composer.json
Step 2 – API Client (src/ApiClient.php)
The client uses Guzzle to fetch alerts. It also adds a tiny retry logic that’s useful when the AI service is temporarily throttling.
<?php
namespace Dashboard;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\TransferException;
class ApiClient
{
private Client $client;
private string $baseUri;
public function __construct(string $baseUri, int $timeout = 5)
{
$this->baseUri = rtrim($baseUri, '/');
$this->client = new Client([
'base_uri' => $this->baseUri,
'timeout' => $timeout,
'http_errors' => false,
'verify' => false // In prod use proper certs
]);
}
/**
* Fetch alerts with optional query parameters.
*
* @param array $query ['severity' => 'high', 'since' => '2026-09-01']
* @return array
*/
public function fetchAlerts(array $query = []): array
{
$uri = '/api/alerts';
$attempts = 3;
while ($attempts--) {
try {
$response = $this->client->get($uri, ['query' => $query]);
$code = $response->getStatusCode();
if ($code === 200) {
return json_decode($response->getBody(), true);
}
// Non‑200 – log and retry after a short sleep
error_log("Alert API returned $code, retrying…");
sleep(1);
} catch (TransferException $e) {
error_log('Alert API network error: '.$e->getMessage());
sleep(1);
}
}
// If we reach here, return empty list – dashboard will show a friendly message.
return [];
}
}
?>
Step 3 – Repository Layer (src/AlertRepository.php)
The repository abstracts pagination, filtering, and optional enrichment (e.g., fetching a related “root cause” from the AI agent on demand).
<?php
namespace Dashboard;
class AlertRepository
{
private ApiClient $api;
public function __construct(ApiClient $api)
{
$this->api = $api;
}
/**
* Returns a paginated slice of alerts.
*
* @param int $page 1‑based page number
* @param int $limit items per page
* @param array $filters ['severity'=>'high','service'=>'auth']
* @return array ['data'=>[], 'total'=>int]
*/
public function getPaginated(int $page = 1, int $limit = 20, array $filters = []): array
{
$offset = ($page - 1) * $limit;
$query = array_merge($filters, [
'limit' => $limit,
'offset' => $offset,
]);
$payload = $this->api->fetchAlerts($query);
// The AI service already returns total count; if not, we estimate.
$total = $payload['total'] ?? ($offset + count($payload['alerts'] ?? []));
return [
'data' => $payload['alerts'] ?? [],
'total' => $total,
];
}
}
?>
Step 4 – Front‑End Skeleton (public/index.php)
We’ll use Vue 3 via CDN for reactivity, Chart.js for visualizations, and DataTables (also CDN) for the tabular view. The page loads the first page of alerts via an AJAX call to backend.php (a thin PHP wrapper around the repository).
<?php
require __DIR__ . '/../vendor/autoload.php';
use Dashboard\ApiClient;
use Dashboard\AlertRepository;
// Configuration – in real life pull from env vars
$apiBase = 'https://ai‑alert‑service.example.com';
$apiClient = new ApiClient($apiBase);
$repo = new AlertRepository($apiClient);
// Simple router – only /backend.php is used by the front‑end
if ($_SERVER['REQUEST_METHOD'] === 'GET' && strpos($_SERVER['REQUEST_URI'], '/backend.php') === 0) {
$page = (int)($_GET['page'] ?? 1);
$limit = (int)($_GET['limit'] ?? 20);
$sev = $_GET['severity'] ?? null;
$service = $_GET['service'] ?? null;
$filters = array_filter([
'severity' => $sev,
'service' => $service,
]);
$result = $repo->getPaginated($page, $limit, $filters);
header('Content-Type: application/json');
echo json_encode($result);
exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>AI‑Log Dashboard</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
<script src="https://cdn.jsdelivr.net/npm/vue@3.4.0/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@1.7.2/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<script src="https://cdn.datatables.net/2.0.8/js/dataTables.min.js"></script>
<link rel="stylesheet" href="https://cdn.datatables.net/2.0.8/css/dataTables.bootstrap5.min.css">
<style>
.severity-high {color:#c0392b; font-weight:bold;}
.severity-medium {color:#e67e22;}
.severity-low {color:#27ae60;}
.chart-container {position:relative; height:300px;}
</style>
</head>
<body class="bg-light">
<div id="app" class="container py-4">
<h2 class="mb-4">AI‑Enhanced Log Anomaly Dashboard</h2>
<!-- Filter Bar -->
<div class="row g-3 mb-3">
<div class="col-md-3">
<select v-model="filters.severity" class="form-select">
<option value="">All Severities</option>
<option value="high">High</option>
<option value="medium">Medium</option>
<option value="low">Low</option>
</select>
</div>
<div class="col-md-3">
<input v-model="filters.service" type="text" class="form-control" placeholder="Service name">
</div>
<div class="col-md-2">
<button @click="loadAlerts" class="btn btn-primary w-100">Apply</button>
</div>
</div>
<!-- Charts Row -->
<div class="row g-4">
<div class="col-lg-6">
<div class="card">
<div class="card-header">Alerts Over Time</div>
<div class="card-body">
<canvas id="timeChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card">
<div class="card-header">Severity Distribution</div>
<div class="card-body">
<canvas id="severityChart"></canvas>
</div>
</div>
</div>
</div>
<!-- Alerts Table -->
<div class="mt-4">
<table id="alertsTable" class="table table-hover table-striped">
<thead class="table-dark">
<tr>
<th>Time</th>
<th>Service</th>
<th>Severity</th>
<th>Message</th>
<th>Root‑Cause (AI)</th>
</tr>
</thead>
<tbody>
<tr v-for="alert in alerts" :key="alert.id">
<td>{{ alert.timestamp }}</td>
<td>{{ alert.service }}</td>
<td :class="'severity-'+alert.severity">{{ alert.severity | capitalize }}</td>
<td>{{ alert.message }}</td>
<td>
<button @click="fetchRootCause(alert)" class="btn btn-sm btn-outline-info">
{{ alert.root_cause ? 'View' : 'Explain' }}
</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Pagination Controls -->
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center mt-3">
<li class="page-item" :class="{disabled: page===1}">
<a class="page-link" href="#" @click.prevent="changePage(page-1)">Previous</a>
</li>
<li class="page-item" :class="{active: p===page}" v-for="p in totalPages" :key="p">
<a class="page-link" href="#" @click.prevent="changePage(p)">{{ p }}</a>
</li>
<li class="page-item" :class="{disabled: page===totalPages}">
<a class="page-link" href="#" @click.prevent="changePage(page+1)">Next</a>
</li>
</ul>
</nav>
</div>
<script>
const { createApp } = Vue;
const app = createApp({
data() {
return {
alerts: [],
page: 1,
limit: 20,
total: 0,
filters: {
severity: '',
service: ''
},
timeChart: null,
severityChart: null,
};
},
computed: {
totalPages() {
return Math.ceil(this.total / this.limit) || 1;
}
},
methods: {
loadAlerts() {
const params = {
page: this.page,
limit: this.limit,
severity: this.filters.severity,
service: this.filters.service
};
axios.get('backend.php', { params })
.then(res => {
this.alerts = res.data.data;
this.total = res.data.total;
this.renderCharts();
// Re‑initialize DataTables after Vue updates the DOM
this.$nextTick(() => {
if ( $.fn.DataTable.isDataTable('#alertsTable') ) {
$('#alertsTable').DataTable().destroy();
}
$('#alertsTable').DataTable({
paging: false,
info: false,
searching: false
});
});
})
.catch(err => console.error(err));
},
change
❓ Frequently Asked Questions
What technologies are required to build the PHP dashboard for the AI‑enhanced log analysis system?
You need PHP 8+, a web server (Apache or Nginx), MySQL/MariaDB for storing alerts, Composer for dependencies, Chart.js (or similar) for visualizations, and the AI model API (e.g., OpenAI or a custom endpoint) that supplies anomaly scores.
How does the dashboard retrieve and display real‑time alerts from the anomaly detection engine?
The dashboard polls a REST endpoint that the detection service populates. It fetches JSON‑formatted alerts, stores them in the database, and uses AJAX (or WebSockets) to update the UI, rendering new entries and chart data without page reloads.
Can I customize the visualizations (charts, tables) to show additional log metrics?
Yes. The front‑end uses Chart.js, so you can add datasets by modifying the JavaScript config and adjusting the PHP query that supplies the data. Update the HTML template to include new canvas elements or table columns as needed.
Is the PHP dashboard secure enough for production use?
Implement HTTPS, sanitize all inputs, use prepared statements for database queries, and restrict API keys. Also enable authentication (e.g., JWT or session‑based login) and set proper file permissions to protect the dashboard in a production environment.
✍️ About the Author
Vijay Vinoth — Lead Programmer Analyst with expertise in PHP, Perl, Python, and Shell scripting. Passionate about AI, automation, and building scalable systems. Writing to share practical insights from real-world engineering experience.
Note: This technical analysis reflects my independent understanding as a Lead Programmer Analyst as of September 2026.
As AI ecosystems like Claude 4.1 evolve, actual implementation may vary. Refer to official documentation for final specs.
[…] AI-Enhanced Log Analysis and Anomaly Alert System — Part 6: Creating a PHP Dashboard for Alerts an… […]
[…] AI-Enhanced Log Analysis and Anomaly Alert System — Part 6: Creating a PHP Dashboard for Alerts an… […]
[…] AI-Enhanced Log Analysis and Anomaly Alert System — Part 6: Creating a PHP Dashboard for Alerts an… […]
[…] AI-Enhanced Log Analysis and Anomaly Alert System — Part 6: Creating a PHP Dashboard for Alerts an… […]