140 lines
3.6 KiB
PHP
140 lines
3.6 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title><?= esc($file['name']) ?> — Log Viewer</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<style>
|
|
:root {
|
|
--brand: #0d9488;
|
|
--brand-dark: #0f766e;
|
|
--brand-light: #ccfbf1;
|
|
}
|
|
|
|
body {
|
|
background: #0f172a;
|
|
color: #e2e8f0;
|
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
margin: 0;
|
|
}
|
|
|
|
.topbar {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
background: #1e293b;
|
|
border-bottom: 1px solid #334155;
|
|
padding: 0.75rem 1rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 1rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.meta {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.15rem;
|
|
}
|
|
|
|
.meta h1 {
|
|
font-size: 1rem;
|
|
font-weight: 700;
|
|
margin: 0;
|
|
color: #f8fafc;
|
|
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
|
}
|
|
|
|
.meta span {
|
|
font-size: 0.8rem;
|
|
color: #94a3b8;
|
|
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
|
}
|
|
|
|
.actions {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.btn-brand {
|
|
background: var(--brand);
|
|
border-color: var(--brand);
|
|
color: #fff;
|
|
}
|
|
|
|
.btn-brand:hover,
|
|
.btn-brand:focus {
|
|
background: var(--brand-dark);
|
|
border-color: var(--brand-dark);
|
|
color: #fff;
|
|
}
|
|
|
|
.truncate-note {
|
|
background: #422006;
|
|
color: #fde68a;
|
|
border-bottom: 1px solid #854d0e;
|
|
padding: 0.55rem 1rem;
|
|
font-size: 0.85rem;
|
|
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
|
}
|
|
|
|
.log-body {
|
|
padding: 1rem;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
font-size: 0.82rem;
|
|
line-height: 1.45;
|
|
margin: 0;
|
|
}
|
|
|
|
.level-error { color: #fca5a5; }
|
|
.level-critical, .level-emergency, .level-alert { color: #fb7185; }
|
|
.level-warning { color: #fcd34d; }
|
|
.level-info, .level-notice { color: #7dd3fc; }
|
|
.level-debug { color: #a5b4fc; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="topbar">
|
|
<div class="meta">
|
|
<h1><?= esc($file['name']) ?></h1>
|
|
<span><?= esc($file['size_human']) ?> · modified <?= esc($file['modified']) ?></span>
|
|
</div>
|
|
<div class="actions">
|
|
<a class="btn btn-sm btn-outline-light" href="<?= esc(site_url('logs')) ?>">← All logs</a>
|
|
<a class="btn btn-sm btn-brand" href="<?= esc(site_url('logs/download?file=' . urlencode($file['name']))) ?>">Download</a>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if (! empty($truncated)): ?>
|
|
<div class="truncate-note">
|
|
Showing the last <?= number_format((int) $maxBytes) ?> bytes of this file. Use Download for the full log.
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<pre class="log-body" id="logContent"><?= esc($content) ?></pre>
|
|
|
|
<script>
|
|
// Soft-highlight log levels in the pre content without changing text
|
|
(function () {
|
|
const el = document.getElementById('logContent');
|
|
if (!el) return;
|
|
|
|
const html = el.textContent
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/\b(ERROR|CRITICAL|EMERGENCY|ALERT|WARNING|INFO|NOTICE|DEBUG)\b/g, (match) => {
|
|
return `<span class="level-${match.toLowerCase()}">${match}</span>`;
|
|
});
|
|
|
|
el.innerHTML = html;
|
|
window.scrollTo(0, document.body.scrollHeight);
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|