docker.videokonverter/app/templates/base.html
data ff04bb2e9e Startseite auf Bibliothek geaendert
/ leitet jetzt auf /library weiter, Dashboard unter /dashboard erreichbar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 17:43:03 +01:00

164 lines
5.7 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}VideoKonverter{% endblock %}</title>
<link rel="stylesheet" href="/static/css/style.css">
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
{% block head %}{% endblock %}
</head>
<body>
<header>
<div class="header-left">
<h1>VideoKonverter</h1>
</div>
<nav>
<a href="/dashboard" class="nav-link {% if request.path == '/dashboard' %}active{% endif %}">Dashboard</a>
<a href="/library" class="nav-link {% if request.path.startswith('/library') %}active{% endif %}">Bibliothek</a>
<a href="/admin" class="nav-link {% if request.path == '/admin' %}active{% endif %}">Einstellungen</a>
<a href="/statistics" class="nav-link {% if request.path == '/statistics' %}active{% endif %}">Statistik</a>
</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
<div id="toast-container"></div>
<!-- Benachrichtigungs-Glocke -->
<div id="notification-bell" class="notification-bell" onclick="toggleNotificationPanel()">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
</svg>
<span id="notification-badge" class="notification-badge" style="display:none">0</span>
</div>
<!-- Log-Panel -->
<div id="notification-panel" class="notification-panel" style="display:none">
<div class="notification-header">
<span>Server-Log</span>
<div>
<button class="btn-small btn-secondary" onclick="clearNotifications()">Alle loeschen</button>
<button class="btn-close" onclick="toggleNotificationPanel()">&times;</button>
</div>
</div>
<div id="notification-list" class="notification-list">
<div class="notification-empty">Keine Nachrichten</div>
</div>
</div>
<script>
// === Benachrichtigungs-System ===
const notifications = [];
let unreadErrors = 0;
function toggleNotificationPanel() {
const panel = document.getElementById("notification-panel");
const isOpen = panel.style.display !== "none";
panel.style.display = isOpen ? "none" : "flex";
if (!isOpen) {
// Panel geoeffnet - Fehler als gelesen markieren
unreadErrors = 0;
updateBadge();
}
}
function updateBadge() {
const badge = document.getElementById("notification-badge");
const bell = document.getElementById("notification-bell");
if (unreadErrors > 0) {
badge.textContent = unreadErrors > 99 ? "99+" : unreadErrors;
badge.style.display = "";
bell.classList.add("has-error");
} else {
badge.style.display = "none";
bell.classList.remove("has-error");
}
}
function addNotification(msg, level = "info") {
const time = new Date().toLocaleTimeString("de-DE", {hour: "2-digit", minute: "2-digit", second: "2-digit"});
notifications.unshift({msg, level, time});
if (notifications.length > 100) notifications.pop();
if (level === "error" || level === "ERROR") {
unreadErrors++;
updateBadge();
}
renderNotifications();
}
function renderNotifications() {
const list = document.getElementById("notification-list");
if (!notifications.length) {
list.innerHTML = '<div class="notification-empty">Keine Nachrichten</div>';
return;
}
list.innerHTML = notifications.map(n => {
const cls = n.level.toLowerCase() === "error" ? "notification-item error" :
n.level.toLowerCase() === "warning" ? "notification-item warning" :
"notification-item";
return `<div class="${cls}">
<span class="notification-time">${n.time}</span>
<span class="notification-msg">${escapeHtmlSimple(n.msg)}</span>
</div>`;
}).join("");
}
function clearNotifications() {
notifications.length = 0;
unreadErrors = 0;
updateBadge();
renderNotifications();
}
function escapeHtmlSimple(str) {
return String(str)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
// Log-Empfang per WebSocket (kein Polling mehr)
// WebSocket sendet {data_log: {level, message}} - wird in websocket.js
// oder hier abgefangen, je nachdem welche Seite geladen ist.
let _logWs = null;
function connectLogWebSocket() {
const proto = location.protocol === "https:" ? "wss:" : "ws:";
const url = `${proto}//${location.host}/ws`;
_logWs = new WebSocket(url);
_logWs.onmessage = function(event) {
try {
const packet = JSON.parse(event.data);
if (packet.data_log) {
addNotification(packet.data_log.message, packet.data_log.level);
}
} catch (e) {
// JSON-Parse-Fehler ignorieren
}
};
_logWs.onclose = function() {
setTimeout(connectLogWebSocket, 5000);
};
}
// Nur Log-WebSocket starten wenn kein globaler WS existiert (Dashboard hat eigenen)
if (!window.WS_URL) {
connectLogWebSocket();
}
</script>
{% block scripts %}{% endblock %}
</body>
</html>