Greg Sarjeant a7e79796fa Add log viewer and tests for the logs and the viewer (#41)
Now that I'm adding more logging, I wanted to add a log viewer so people don't have to ssh to their servers to inspect logs. Also added tests around logging and the viewer.

Reviewed-on: https://gitea.subcultureofone.org/greg/tkr/pulls/41
Co-authored-by: Greg Sarjeant <greg@subcultureofone.org>
Co-committed-by: Greg Sarjeant <greg@subcultureofone.org>
2025-08-01 01:52:45 +00:00

90 lines
4.7 KiB
PHP

<?php /** @var ConfigModel $config */ ?>
<?php /** @var array $logEntries */ ?>
<?php /** @var array $availableRoutes */ ?>
<?php /** @var array $availableLevels */ ?>
<?php /** @var string $currentLevelFilter */ ?>
<?php /** @var string $currentRouteFilter */ ?>
<h1>System Logs</h1>
<main>
<!-- Filters -->
<div class="log-filters">
<form method="get" action="<?= Util::buildRelativeUrl($config->basePath, 'admin/logs') ?>">
<fieldset>
<legend>Filter Logs</legend>
<div class="fieldset-items">
<label for="level-filter">Level:</label>
<select id="level-filter" name="level">
<option value="">All Levels</option>
<?php foreach ($availableLevels as $level): ?>
<option value="<?= Util::escape_html($level) ?>"
<?= $currentLevelFilter === $level ? 'selected' : '' ?>>
<?= Util::escape_html($level) ?>
</option>
<?php endforeach; ?>
</select>
<label for="route-filter">Route:</label>
<select id="route-filter" name="route">
<option value="">All Routes</option>
<?php foreach ($availableRoutes as $route): ?>
<option value="<?= Util::escape_html($route) ?>"
<?= $currentRouteFilter === $route ? 'selected' : '' ?>>
<?= Util::escape_html($route) ?>
</option>
<?php endforeach; ?>
</select>
<div></div><button type="submit">Filter</button>
<div></div><a href="<?= Util::buildRelativeUrl($config->basePath, 'admin/logs') ?>">Clear</a>
</div>
</fieldset>
</form>
</div>
<!-- Log entries table -->
<div class="log-entries">
<?php if (empty($logEntries)): ?>
<p>No log entries found matching the current filters.</p>
<?php else: ?>
<table class="log-table">
<thead>
<tr>
<th>Time</th>
<th>Level</th>
<th>IP</th>
<th>Route</th>
<th>Message</th>
</tr>
</thead>
<tbody>
<?php foreach ($logEntries as $entry): ?>
<tr class="log-entry log-<?= strtolower($entry['level']) ?>">
<td class="log-timestamp log-monospace">
<time datetime="<?= Util::escape_html($entry['timestamp']) ?>">
<?= Util::escape_html($entry['timestamp']) ?>
</time>
</td>
<td class="log-level">
<span class="log-level-badge"><?= Util::escape_html($entry['level']) ?></span>
</td>
<td class="log-ip log-monospace"><?= Util::escape_html($entry['ip']) ?></td>
<td class="log-route log-monospace">
<?php if ($entry['route']): ?>
<?= Util::escape_html($entry['route']) ?>
<?php else: ?>
<span class="log-no-route">-</span>
<?php endif; ?>
</td>
<td class="log-message log-monospace"><?= Util::escape_html($entry['message']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<div class="log-info">
<p>Showing <?= count($logEntries) ?> recent log entries.
Log files are automatically rotated when they reach 1000 lines.</p>
</div>
</main>