- /logs und /get_log liefern nur noch die letzten 300 Zeilen (_tail_lines, effizientes Rueckwaerts-Lesen ohne komplette Datei einzulesen) statt der kompletten live.log -- behebt Ladezeit-Probleme auf groesseren Umgebungen. - Neuer Endpoint /logs/live/raw liefert das komplette Live-Log fuer ein Popup im Stil des bestehenden 'Ungespeicherte Aenderungen'-Modals (window.openRawLogModal in app.js, lazy nachgeladen erst bei Klick). - Neue Unterseite 'Verlauf' (/logs/history, /logs/history/raw) fuer aeltere, von logrotate rotierte Kopien des Live-Logs (live.log.1, .2, ...), auswaehlbar per Dropdown, ebenfalls mit 300-Zeilen-Tail + RAW-Popup. - Eigenes Recht logs_history.view (Kind von logs_group, direkt unter Live einsortiert) statt an logs_live.view gekoppelt -- Live-Status sehen und durch alte Logs blaettern sind bewusst getrennte Rechte. - Dateiname-Validierung gegen Path-Traversal in _resolve_log_history_file (striktes Regex + Verzeichnis-Check + Existenzpruefung). - VERSION 1.0.4 -> 1.0.5. Live getestet auf Testsystem-Update-Restart (192.168.82.51): Tail-Anzeige, RAW-Popup, Verlauf-Auswahl inkl. echter live.log.1, Permission-Matrix (Gruppen-Seite) sowie Rechtetrennung (Nutzer mit nur logs_live.view sieht /logs, aber weder Verlauf-Link noch Zugriff auf /logs/history bzw. /logs/history/raw) und Path-Traversal-Abwehr allesamt verifiziert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
90 lines
3.9 KiB
HTML
90 lines
3.9 KiB
HTML
{% extends "base.html" %}
|
|
{% set active_page = "logs" %}
|
|
{% block page_title %}Live{% endblock %}
|
|
{% block page_sub %}<div class="topbar-sub" data-log-name>{{ log_name or "kein Logfile" }}</div>{% endblock %}
|
|
|
|
{% block content %}
|
|
|
|
<div class="section-head">
|
|
<div>
|
|
<h2 style="font-size:16px;">Live-Log</h2>
|
|
<div class="hint">Laufende Erreichbarkeitsprüfung von poe.sh, farblich markiert (online/offline). Zeigt aus Performance-Gründen nur die letzten {{ tail_lines }} Zeilen.</div>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
{% if current_user.can_view_log_history %}
|
|
<a href="{{ url_for('logs_history') }}" class="btn btn-secondary">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 3"/></svg>
|
|
Verlauf
|
|
</a>
|
|
{% endif %}
|
|
<button type="button" class="btn btn-secondary" onclick="openRawLogModal('{{ url_for('get_log_raw') }}', 'Komplettes Live-Log (RAW)')">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><path d="M14 2v6h6"/></svg>
|
|
Komplettes Log (RAW)
|
|
</button>
|
|
<button id="refresh-btn" class="btn btn-secondary">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 11-3.2-6.9M21 4v5h-5"/></svg>
|
|
Aktualisieren
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="log-shell">
|
|
<div class="log-toolbar">
|
|
<div class="log-dots"><span></span><span></span><span></span></div>
|
|
<span class="text-faint mono" style="font-size:11.5px;" data-log-name>{{ log_name or "" }}</span>
|
|
<span class="text-faint mono" style="font-size:11.5px;" id="log-line-info">{% if total_lines %}letzte {{ tail_lines }} von {{ total_lines }} Zeilen{% endif %}</span>
|
|
</div>
|
|
<div id="log-box">{{ log_content or "Keine Logfiles gefunden." }}</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
function colorizeLine(line) {
|
|
let cls = "";
|
|
if (line.includes(" ist erreichbar!")) cls = "online";
|
|
else if (line.includes(" ist nicht erreichbar!")) cls = "offline";
|
|
else if (line.startsWith("----")) cls = "sep";
|
|
else if (line.toLowerCase().includes("manuell") || line.includes("PoE")) cls = "restart";
|
|
const span = document.createElement("span");
|
|
span.className = "log-line" + (cls ? " " + cls : "");
|
|
span.textContent = line;
|
|
return span;
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
const intervalMinutes = {{ global_check_interval | int }};
|
|
const intervalMilliseconds = intervalMinutes * 60 * 1000;
|
|
|
|
function renderLog(text, logName, totalLines) {
|
|
const box = document.getElementById("log-box");
|
|
box.innerHTML = "";
|
|
const lines = text.split("\n");
|
|
lines.forEach((line, i) => {
|
|
box.appendChild(colorizeLine(line));
|
|
if (i < lines.length - 1) box.appendChild(document.createElement("br"));
|
|
});
|
|
box.scrollTop = box.scrollHeight;
|
|
if (logName) {
|
|
document.querySelectorAll("[data-log-name]").forEach((el) => { el.textContent = logName; });
|
|
}
|
|
const info = document.getElementById("log-line-info");
|
|
if (info && totalLines) {
|
|
info.textContent = "letzte {{ tail_lines }} von " + totalLines + " Zeilen";
|
|
}
|
|
}
|
|
|
|
function fetchLog() {
|
|
fetch("{{ url_for('get_log') }}")
|
|
.then(r => r.text().then((text) => renderLog(text, r.headers.get("X-Log-Name"), r.headers.get("X-Total-Lines"))))
|
|
.catch(err => console.error(err));
|
|
}
|
|
|
|
document.getElementById("refresh-btn").addEventListener("click", fetchLog);
|
|
document.addEventListener("poe:check-triggered", fetchLog);
|
|
fetchLog();
|
|
if (intervalMilliseconds) setInterval(fetchLog, intervalMilliseconds);
|
|
});
|
|
</script>
|
|
{% endblock %}
|