Files
controls-web/data/latency-monitor.php
2026-02-17 12:44:37 -06:00

172 lines
5.3 KiB
PHP

<?php // phpcs:ignoreFile
require __DIR__ . '/../includes/db.php';
$appTimeZone = new DateTimeZone('America/Chicago');
$errorMessage = null;
$devices = [];
try {
$connection = controls_db_connect();
} catch (Throwable $exception) {
$connection = null;
$errorMessage = $exception->getMessage();
}
if ($connection instanceof mysqli) {
$sql = <<<SQL
SELECT d.id,
d.host,
d.label,
latest.status,
latest.latency_ms,
latest.checked_at
FROM monitoring_devices AS d
LEFT JOIN (
SELECT l1.device_id,
l1.status,
l1.latency_ms,
l1.checked_at
FROM monitoring_latency_log AS l1
INNER JOIN (
SELECT device_id, MAX(checked_at) AS max_checked
FROM monitoring_latency_log
GROUP BY device_id
) AS recent ON recent.device_id = l1.device_id AND recent.max_checked = l1.checked_at
) AS latest ON latest.device_id = d.id
WHERE d.is_active = 1
ORDER BY d.label;
SQL;
try {
$result = $connection->query($sql);
if ($result instanceof mysqli_result) {
while ($row = $result->fetch_assoc()) {
if (!empty($row['checked_at'])) {
$timestamp = DateTimeImmutable::createFromFormat(
'Y-m-d H:i:s',
$row['checked_at'],
new DateTimeZone('UTC')
);
if ($timestamp instanceof DateTimeImmutable) {
$timestamp = $timestamp->setTimezone($appTimeZone);
$row['checked_at_formatted'] = $timestamp->format('M j, Y g:i:s A T');
}
}
$devices[] = $row;
}
$result->free();
}
} catch (Throwable $exception) {
$errorMessage = $exception->getMessage();
} finally {
$connection->close();
}
}
ob_start();
?>
<header class="panel-header">
<h2>Device latency status</h2>
<p>Latest response times collected by the latency watcher service.</p>
</header>
<?php if (!empty($errorMessage ?? null)) : ?>
<div class="panel-error">
<strong>Unable to load data:</strong>
<span><?php echo htmlspecialchars($errorMessage); ?></span>
</div>
<?php elseif (empty($devices)) : ?>
<div class="panel-loading">
<span>No latency samples have been recorded yet.</span>
<span>Verify the watcher is running and devices are configured.</span>
</div>
<?php else : ?>
<div class="latency-table-wrapper">
<table class="latency-table">
<thead>
<tr>
<th scope="col">Device</th>
<th scope="col">Host</th>
<th scope="col">Status</th>
<th scope="col">Latency (ms)</th>
<th scope="col">Last checked</th>
</tr>
</thead>
<tbody>
<?php foreach ($devices as $device) : ?>
<?php
$status = $device['status'] ?? null;
$latency = $device['latency_ms'] ?? null;
$statusLabel = 'Unknown';
$statusClass = 'status-pill--unknown';
$trendUrl = 'trends/live/autochart.php?'
. http_build_query([
'primary' => 'latency:' . $device['id'],
'primary_label' => $device['label'],
'autostart' => '1',
'interval' => '5000',
]);
if ($status === 'up') {
$statusLabel = 'Online';
$statusClass = 'status-pill--up';
} elseif ($status === 'down') {
$statusLabel = 'Offline';
$statusClass = 'status-pill--down';
}
?>
<tr>
<td data-label="Device">
<a class="latency-link" href="<?php echo htmlspecialchars($trendUrl, ENT_QUOTES, 'UTF-8'); ?>" target="_blank" rel="noopener">
<?php echo htmlspecialchars($device['label']); ?>
</a>
</td>
<td data-label="Host"><?php echo htmlspecialchars($device['host']); ?></td>
<td data-label="Status">
<span class="status-pill <?php echo $statusClass; ?>">
<?php echo htmlspecialchars($statusLabel); ?>
</span>
</td>
<td data-label="Latency">
<?php echo $latency !== null ? number_format((float) $latency, 0) : '—'; ?>
</td>
<td data-label="Last checked">
<?php echo htmlspecialchars($device['checked_at_formatted'] ?? '—'); ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<footer class="latency-footnote">
<p>
Results update whenever <code>monitoring/latency_watcher.py</code> records a sample.
Configure devices in the <code>monitoring_devices</code> table and ensure the watcher
has database access.
</p>
</footer>
<?php
$payload = ob_get_clean();
$etag = '"' . md5($payload) . '"';
$clientEtag = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? trim($_SERVER['HTTP_IF_NONE_MATCH']) : null;
if ($clientEtag && $clientEtag === $etag) {
header('HTTP/1.1 304 Not Modified');
header('Cache-Control: no-cache, must-revalidate');
header('ETag: ' . $etag);
exit;
}
header('Cache-Control: no-cache, must-revalidate');
header('Content-Type: text/html; charset=utf-8');
header('ETag: ' . $etag);
echo $payload;