85 lines
2.7 KiB
HTML
85 lines
2.7 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Logs - HAMeter{% endblock %}
|
|
{% block page_title %}Logs{% endblock %}
|
|
|
|
{% block top_actions %}
|
|
<div class="log-controls">
|
|
<select id="log-level-filter" onchange="filterLogs()">
|
|
<option value="">All Levels</option>
|
|
<option value="DEBUG">DEBUG</option>
|
|
<option value="INFO">INFO</option>
|
|
<option value="WARNING">WARNING</option>
|
|
<option value="ERROR">ERROR</option>
|
|
</select>
|
|
<input type="text" id="log-search" placeholder="Filter..." oninput="filterLogs()">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="log-autoscroll" checked> Auto-scroll
|
|
</label>
|
|
<button class="btn btn-sm btn-secondary" onclick="clearLogs()">Clear</button>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="log-viewer" id="log-viewer"></div>
|
|
|
|
<script>
|
|
const logViewer = document.getElementById('log-viewer');
|
|
let allLogs = [];
|
|
|
|
// Load initial logs
|
|
fetch('/api/logs?count=500')
|
|
.then(r => r.json())
|
|
.then(logs => {
|
|
allLogs = logs;
|
|
renderLogs();
|
|
});
|
|
|
|
function addLogEntry(entry) {
|
|
allLogs.push(entry);
|
|
if (allLogs.length > 2000) allLogs = allLogs.slice(-1000);
|
|
renderLogs();
|
|
}
|
|
|
|
function renderLogs() {
|
|
const level = document.getElementById('log-level-filter').value;
|
|
const search = document.getElementById('log-search').value.toLowerCase();
|
|
|
|
const filtered = allLogs.filter(log => {
|
|
if (level && log.level !== level) return false;
|
|
if (search && !log.message.toLowerCase().includes(search) && !log.name.toLowerCase().includes(search)) return false;
|
|
return true;
|
|
});
|
|
|
|
logViewer.innerHTML = filtered.map(log => {
|
|
const cls = 'log-' + (log.level || 'INFO').toLowerCase();
|
|
return '<div class="log-line ' + cls + '">' +
|
|
'<span class="log-ts">' + (log.timestamp || '') + '</span> ' +
|
|
'<span class="log-level">[' + (log.level || '?') + ']</span> ' +
|
|
'<span class="log-name">' + (log.name || '') + ':</span> ' +
|
|
'<span class="log-msg">' + escapeHtml(log.message || '') + '</span>' +
|
|
'</div>';
|
|
}).join('');
|
|
|
|
if (document.getElementById('log-autoscroll').checked) {
|
|
logViewer.scrollTop = logViewer.scrollHeight;
|
|
}
|
|
}
|
|
|
|
function filterLogs() { renderLogs(); }
|
|
function clearLogs() { allLogs = []; renderLogs(); }
|
|
|
|
function escapeHtml(s) {
|
|
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
}
|
|
|
|
// SSE log handler
|
|
if (typeof window._onLogUpdate === 'undefined') {
|
|
window._onLogUpdate = function(logs) {
|
|
if (Array.isArray(logs)) {
|
|
logs.forEach(addLogEntry);
|
|
}
|
|
};
|
|
}
|
|
</script>
|
|
{% endblock %}
|