imap-mail-filter-service/app/templates/logs.html

203 lines
6.8 KiB
HTML

{% extends "base.html" %}
{% block title %}Verarbeitungslog — IMAP Mail Filter{% endblock %}
{% block content %}
<h1>Verarbeitungslog</h1>
<div style="display:flex; gap:1rem; align-items:end; flex-wrap:wrap; margin-bottom:1rem;">
<label style="margin-bottom:0;">
Konto
<select id="log-account" onchange="loadLogs()" style="margin-bottom:0;">
<option value="">Alle Konten</option>
{% for acc in accounts %}
<option value="{{ acc.id }}">{{ acc.name }}</option>
{% endfor %}
</select>
</label>
<label style="margin-bottom:0;">
Level
<select id="log-level" onchange="loadLogs()" style="margin-bottom:0;">
<option value="">Alle</option>
<option value="success">Erfolg</option>
<option value="info">Info</option>
<option value="warning">Warnung</option>
<option value="error">Fehler</option>
</select>
</label>
<div role="group" style="margin-bottom:0;">
<button class="outline small" onclick="loadLogs()">Aktualisieren</button>
<button class="outline small contrast" onclick="clearLogs()">Log leeren</button>
</div>
<label style="margin-bottom:0; margin-left:auto;">
<input type="checkbox" id="auto-refresh" onchange="toggleAutoRefresh()" style="margin-bottom:0;">
Auto-Refresh (5s)
</label>
</div>
<div id="log-stats" style="margin-bottom:1rem;"></div>
<div id="log-container">
<p aria-busy="true">Logs werden geladen...</p>
</div>
<div id="log-paging" style="display:flex; justify-content:center; gap:1rem; margin-top:1rem;"></div>
{% endblock %}
{% block scripts %}
<style>
.log-table {
width: 100%;
font-size: 0.9rem;
}
.log-table th, .log-table td {
padding: 0.4rem 0.6rem;
vertical-align: top;
}
.log-row { border-left: 3px solid transparent; }
.log-row[data-level="success"] { border-left-color: #28a745; }
.log-row[data-level="info"] { border-left-color: #17a2b8; }
.log-row[data-level="warning"] { border-left-color: #ffc107; }
.log-row[data-level="error"] { border-left-color: #dc3545; }
.log-level {
display: inline-block;
padding: 0.1rem 0.4rem;
border-radius: 3px;
font-size: 0.75rem;
font-weight: bold;
text-transform: uppercase;
}
.log-level-success { background: #d4edda; color: #155724; }
.log-level-info { background: #d1ecf1; color: #0c5460; }
.log-level-warning { background: #fff3cd; color: #856404; }
.log-level-error { background: #f8d7da; color: #721c24; }
.log-detail { font-size: 0.8rem; opacity: 0.7; }
.log-mail-info { font-size: 0.8rem; color: var(--pico-muted-color); }
</style>
<script>
let currentOffset = 0;
const PAGE_SIZE = 50;
let refreshTimer = null;
async function loadLogs(offset = 0) {
currentOffset = offset;
const accountId = document.getElementById('log-account').value;
const level = document.getElementById('log-level').value;
const container = document.getElementById('log-container');
let url = `/api/logs/?limit=${PAGE_SIZE}&offset=${offset}`;
if (accountId) url += `&account_id=${accountId}`;
if (level) url += `&level=${level}`;
try {
const resp = await fetch(url);
const data = await resp.json();
renderStats(data.total);
renderLogs(data.logs);
renderPaging(data.total, offset);
} catch(e) {
container.innerHTML = `<p>Fehler beim Laden: ${e.message}</p>`;
}
}
function renderStats(total) {
document.getElementById('log-stats').innerHTML = `<small>${total} Einträge gesamt</small>`;
}
function renderLogs(logs) {
const container = document.getElementById('log-container');
if (!logs.length) {
container.innerHTML = '<article><p>Keine Log-Einträge vorhanden.</p></article>';
return;
}
let html = `<table class="log-table">
<thead><tr>
<th style="width:140px;">Zeit</th>
<th style="width:60px;">Level</th>
<th style="width:120px;">Konto</th>
<th>Nachricht</th>
<th style="width:120px;">Regel</th>
</tr></thead><tbody>`;
for (const log of logs) {
const time = log.created_at ? new Date(log.created_at).toLocaleString('de-DE', {
day: '2-digit', month: '2-digit', year: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit'
}) : '';
const levelClass = `log-level-${log.level}`;
const levelLabel = {success: 'OK', info: 'Info', warning: 'Warn', error: 'Fehler'}[log.level] || log.level;
let message = escapeHtml(log.message);
if (log.mail_subject || log.mail_from) {
message += `<div class="log-mail-info">`;
if (log.mail_from) message += `Von: ${escapeHtml(log.mail_from)}<br>`;
if (log.mail_subject) message += `Betreff: ${escapeHtml(log.mail_subject)}`;
message += `</div>`;
}
if (log.details) {
message += `<div class="log-detail">${escapeHtml(log.details)}</div>`;
}
html += `<tr class="log-row" data-level="${log.level}">
<td><small>${time}</small></td>
<td><span class="log-level ${levelClass}">${levelLabel}</span></td>
<td><small>${escapeHtml(log.account_name || '')}</small></td>
<td>${message}</td>
<td><small>${escapeHtml(log.rule_name || '')}</small></td>
</tr>`;
}
html += '</tbody></table>';
container.innerHTML = html;
}
function renderPaging(total, offset) {
const paging = document.getElementById('log-paging');
if (total <= PAGE_SIZE) {
paging.innerHTML = '';
return;
}
const page = Math.floor(offset / PAGE_SIZE) + 1;
const totalPages = Math.ceil(total / PAGE_SIZE);
let html = '';
if (offset > 0) {
html += `<button class="outline small" onclick="loadLogs(${offset - PAGE_SIZE})">Neuere</button>`;
}
html += `<small>Seite ${page} / ${totalPages}</small>`;
if (offset + PAGE_SIZE < total) {
html += `<button class="outline small" onclick="loadLogs(${offset + PAGE_SIZE})">Ältere</button>`;
}
paging.innerHTML = html;
}
async function clearLogs() {
if (!confirm('Log wirklich leeren?')) return;
const accountId = document.getElementById('log-account').value;
let url = '/api/logs/';
if (accountId) url += `?account_id=${accountId}`;
await fetch(url, {method: 'DELETE'});
loadLogs();
}
function toggleAutoRefresh() {
if (document.getElementById('auto-refresh').checked) {
refreshTimer = setInterval(() => loadLogs(currentOffset), 5000);
} else {
clearInterval(refreshTimer);
refreshTimer = null;
}
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Initial load
loadLogs();
</script>
{% endblock %}