let currentUser = null; let currentDataType = null; let currentData = []; // Beim Laden der Seite document.addEventListener('DOMContentLoaded', function () { checkAuth(); loadStatistics(); // Add cookie settings button functionality const cookieSettingsBtn = document.getElementById('cookie-settings-footer'); if (cookieSettingsBtn) { cookieSettingsBtn.addEventListener('click', function () { if (window.cookieConsent) { window.cookieConsent.resetConsent(); } }); } loadPageStatistics(); setupEventListeners(); }); function setupEventListeners() { // Logout Button document.getElementById('logoutBtn').addEventListener('click', logout); // Generator Button document.getElementById('generatorBtn').addEventListener('click', function () { window.location.href = '/generator'; }); // Modal Close Buttons document.querySelectorAll('.close').forEach(closeBtn => { closeBtn.addEventListener('click', closeModal); }); // Confirm Modal Buttons document.getElementById('confirmNo').addEventListener('click', closeModal); // Search Input document.getElementById('searchInput').addEventListener('input', filterData); // Add Form document.getElementById('addForm').addEventListener('submit', handleAddSubmit); } async function checkAuth() { try { const response = await fetch('/api/v1/web/check-session'); const result = await response.json(); if (!result.success) { window.location.href = '/adminlogin.html'; return; } currentUser = result.user; document.getElementById('username').textContent = currentUser.username; const accessBadge = document.getElementById('accessBadge'); accessBadge.textContent = `Level ${currentUser.access_level}`; accessBadge.className = `access-badge level-${currentUser.access_level}`; // Generator-Button nur für Level 2 if (currentUser.access_level >= 2) { document.getElementById('generatorBtn').style.display = 'inline-block'; } } catch (error) { console.error('Auth check failed:', error); window.location.href = '/adminlogin.html'; } } async function logout() { try { await fetch('/api/v1/public/logout', { method: 'POST' }); window.location.href = '/adminlogin.html'; } catch (error) { console.error('Logout failed:', error); window.location.href = '/adminlogin.html'; } } async function loadStatistics() { try { const response = await fetch('/api/v1/admin/stats'); const stats = await response.json(); if (stats.success) { document.getElementById('totalPlayers').textContent = stats.data.players || 0; document.getElementById('totalRuns').textContent = stats.data.runs || 0; document.getElementById('totalLocations').textContent = stats.data.locations || 0; document.getElementById('totalAdminUsers').textContent = stats.data.adminUsers || 0; } } catch (error) { console.error('Failed to load statistics:', error); } } async function loadPageStatistics() { try { const response = await fetch('/api/v1/admin/page-stats'); const result = await response.json(); if (result.success) { const data = result.data; // Display page view statistics displayPageStats('todayStats', data.today); displayPageStats('weekStats', data.week); displayPageStats('monthStats', data.month); displayPageStats('totalStats', data.total); // Display link statistics if (data.linkStats) { document.getElementById('totalPlayersCount').textContent = data.linkStats.total_players || 0; document.getElementById('linkedPlayersCount').textContent = data.linkStats.linked_players || 0; document.getElementById('linkPercentage').textContent = `${data.linkStats.link_percentage || 0}%`; } } } catch (error) { console.error('Failed to load page statistics:', error); // Show error in all stat containers ['todayStats', 'weekStats', 'monthStats', 'totalStats'].forEach(id => { document.getElementById(id).innerHTML = '
Fehler beim Laden
'; }); } } function displayPageStats(containerId, stats) { const container = document.getElementById(containerId); if (!stats || stats.length === 0) { container.innerHTML = '
0
'; return; } // Find main page visits const mainPageStat = stats.find(stat => stat.page === 'main_page_visit'); const count = mainPageStat ? mainPageStat.count : 0; container.innerHTML = `
${count}
`; } function getPageDisplayName(page) { const pageNames = { 'main_page_visit': '🏠 Hauptseiten-Besuche', 'home': '🏠 Hauptseite', 'login': '🔐 Login', 'dashboard': '📊 Dashboard', 'admin_login': '🛡️ Admin Login', 'admin_dashboard': '🛡️ Admin Dashboard', 'license_generator': '🔧 Lizenzgenerator', 'reset_password': '🔑 Passwort Reset' }; return pageNames[page] || page; } function showUserManagement() { showDataSection('users', 'Benutzer-Verwaltung'); loadUsers(); } function showPlayerManagement() { showDataSection('players', 'Spieler-Verwaltung'); loadPlayers(); } function showRunManagement() { showDataSection('runs', 'Läufe-Verwaltung'); loadRuns(); } function showLocationManagement() { showDataSection('locations', 'Standort-Verwaltung'); loadLocations(); } function showAdminUserManagement() { showDataSection('adminusers', 'Admin-Benutzer'); loadAdminUsers(); } function showSystemInfo() { showDataSection('system', 'System-Informationen'); loadSystemInfo(); } function showDataSection(type, title) { currentDataType = type; document.getElementById('dataTitle').textContent = title; document.getElementById('dataSection').style.display = 'block'; document.getElementById('searchInput').value = ''; } async function loadUsers() { // Implementation für Supabase-Benutzer document.getElementById('dataContent').innerHTML = '
Supabase-Benutzer werden über die Supabase-Console verwaltet
'; } async function loadPlayers() { try { const response = await fetch('/api/v1/admin/players'); const result = await response.json(); if (result.success) { currentData = result.data; displayPlayersTable(result.data); } else { showError('Fehler beim Laden der Spieler'); } } catch (error) { console.error('Failed to load players:', error); showError('Fehler beim Laden der Spieler'); } } async function loadRuns() { try { const response = await fetch('/api/v1/admin/runs'); const result = await response.json(); if (result.success) { currentData = result.data; displayRunsTable(result.data); } else { showError('Fehler beim Laden der Läufe'); } } catch (error) { console.error('Failed to load runs:', error); showError('Fehler beim Laden der Läufe'); } } async function loadLocations() { try { const response = await fetch('/api/v1/admin/locations'); const result = await response.json(); if (result.success) { currentData = result.data; displayLocationsTable(result.data); } else { showError('Fehler beim Laden der Standorte'); } } catch (error) { console.error('Failed to load locations:', error); showError('Fehler beim Laden der Standorte'); } } async function loadAdminUsers() { try { const response = await fetch('/api/v1/admin/adminusers'); const result = await response.json(); if (result.success) { currentData = result.data; displayAdminUsersTable(result.data); } else { showError('Fehler beim Laden der Admin-Benutzer'); } } catch (error) { console.error('Failed to load admin users:', error); showError('Fehler beim Laden der Admin-Benutzer'); } } function displayPlayersTable(players) { let html = '
'; html += ''; players.forEach(player => { html += ``; }); html += '
IDNameRFID UIDSupabase UserRegistriertAktionen
${player.id} ${player.full_name || '-'} ${player.rfiduid || '-'} ${player.supabase_user_id ? '✅' : '❌'} ${new Date(player.created_at).toLocaleDateString('de-DE')}
'; document.getElementById('dataContent').innerHTML = html; } function displayRunsTable(runs) { let html = '
'; html += ''; runs.forEach(run => { // Use the time_seconds value from the backend const timeInSeconds = parseFloat(run.time_seconds) || 0; html += ``; }); html += '
IDSpielerStandortZeitDatumAktionen
${run.id} ${run.player_name || `Player ${run.player_id}`} ${run.location_name || `Location ${run.location_id}`} ${timeInSeconds.toFixed(3)}s ${new Date(run.created_at).toLocaleDateString('de-DE')} ${new Date(run.created_at).toLocaleTimeString('de-DE')}
'; document.getElementById('dataContent').innerHTML = html; } function displayLocationsTable(locations) { let html = '
'; html += ''; locations.forEach(location => { html += ``; }); html += '
IDNameLatitudeLongitudeErstelltAktionen
${location.id} ${location.name} ${location.latitude} ${location.longitude} ${new Date(location.created_at).toLocaleDateString('de-DE')}
'; document.getElementById('dataContent').innerHTML = html; } function displayAdminUsersTable(users) { let html = '
'; html += ''; users.forEach(user => { const isCurrentUser = user.id === currentUser.id; html += ``; }); html += '
IDBenutzernameAccess LevelAktivLetzter LoginAktionen
${user.id} ${user.username} ${isCurrentUser ? '(Du)' : ''} Level ${user.access_level} ${user.is_active ? '✅' : '❌'} ${user.last_login ? new Date(user.last_login).toLocaleDateString('de-DE') : 'Nie'} ${!isCurrentUser ? `` : ''}
'; document.getElementById('dataContent').innerHTML = html; } function loadSystemInfo() { document.getElementById('dataContent').innerHTML = `

Server-Informationen

Node.js Version: ${navigator.userAgent}

Aktuelle Zeit: ${new Date().toLocaleString('de-DE')}

Angemeldeter Benutzer: ${currentUser.username} (Level ${currentUser.access_level})

`; } function filterData() { const searchTerm = document.getElementById('searchInput').value.toLowerCase(); if (!currentData) return; let filteredData = currentData.filter(item => { return Object.values(item).some(value => value && value.toString().toLowerCase().includes(searchTerm) ); }); switch (currentDataType) { case 'players': displayPlayersTable(filteredData); break; case 'runs': displayRunsTable(filteredData); break; case 'locations': displayLocationsTable(filteredData); break; case 'adminusers': displayAdminUsersTable(filteredData); break; } } function refreshData() { switch (currentDataType) { case 'players': loadPlayers(); break; case 'runs': loadRuns(); break; case 'locations': loadLocations(); break; case 'adminusers': loadAdminUsers(); break; case 'system': loadSystemInfo(); break; } } function showAddModal() { const modal = document.getElementById('addModal'); const modalTitle = document.getElementById('modalTitle'); const formFields = document.getElementById('formFields'); // Modal-Titel basierend auf aktuellem Datentyp setzen switch (currentDataType) { case 'players': modalTitle.textContent = 'Neuen Spieler hinzufügen'; formFields.innerHTML = `
`; break; case 'locations': modalTitle.textContent = 'Neuen Standort hinzufügen'; formFields.innerHTML = `
`; break; case 'adminusers': modalTitle.textContent = 'Neuen Admin-Benutzer hinzufügen'; formFields.innerHTML = `
`; break; case 'runs': modalTitle.textContent = 'Neuen Lauf hinzufügen'; formFields.innerHTML = `
`; break; default: modalTitle.textContent = 'Element hinzufügen'; formFields.innerHTML = '

Keine Felder für diesen Datentyp verfügbar.

'; } modal.style.display = 'block'; } function closeModal() { document.querySelectorAll('.modal').forEach(modal => { modal.style.display = 'none'; }); // Formular zurücksetzen document.getElementById('addForm').reset(); document.getElementById('modalMessage').style.display = 'none'; } async function handleAddSubmit(e) { e.preventDefault(); const formData = new FormData(e.target); const data = Object.fromEntries(formData.entries()); // Prüfen ob es sich um eine Edit-Operation handelt const isEdit = data.edit_id; const editId = data.edit_id; delete data.edit_id; // edit_id aus den Daten entfernen try { let endpoint = ''; let successMessage = ''; let method = 'POST'; switch (currentDataType) { case 'players': endpoint = isEdit ? `/api/v1/admin/players/${editId}` : '/api/v1/admin/players'; successMessage = isEdit ? 'Spieler erfolgreich aktualisiert' : 'Spieler erfolgreich hinzugefügt'; method = isEdit ? 'PUT' : 'POST'; break; case 'locations': endpoint = isEdit ? `/api/v1/admin/locations/${editId}` : '/api/v1/admin/locations'; successMessage = isEdit ? 'Standort erfolgreich aktualisiert' : 'Standort erfolgreich hinzugefügt'; method = isEdit ? 'PUT' : 'POST'; break; case 'adminusers': endpoint = isEdit ? `/api/v1/admin/adminusers/${editId}` : '/api/v1/admin/adminusers'; successMessage = isEdit ? 'Admin-Benutzer erfolgreich aktualisiert' : 'Admin-Benutzer erfolgreich hinzugefügt'; method = isEdit ? 'PUT' : 'POST'; break; case 'runs': endpoint = isEdit ? `/api/v1/admin/runs/${editId}` : '/api/v1/admin/runs'; successMessage = isEdit ? 'Lauf erfolgreich aktualisiert' : 'Lauf erfolgreich hinzugefügt'; method = isEdit ? 'PUT' : 'POST'; break; default: showError('Unbekannter Datentyp'); return; } const response = await fetch(endpoint, { method: method, headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data) }); const result = await response.json(); if (result.success) { showSuccess(successMessage); closeModal(); refreshData(); // Daten neu laden } else { showError(result.message || `Fehler beim ${isEdit ? 'Aktualisieren' : 'Hinzufügen'}`); } } catch (error) { console.error('Submit failed:', error); showError(`Fehler beim ${isEdit ? 'Aktualisieren' : 'Hinzufügen'}`); } } // Edit-Funktionen async function editPlayer(id) { const player = currentData.find(p => p.id == id); if (!player) return; const modal = document.getElementById('addModal'); const modalTitle = document.getElementById('modalTitle'); const formFields = document.getElementById('formFields'); modalTitle.textContent = 'Spieler bearbeiten'; formFields.innerHTML = `
`; modal.style.display = 'block'; } async function editLocation(id) { const location = currentData.find(l => l.id == id); if (!location) return; const modal = document.getElementById('addModal'); const modalTitle = document.getElementById('modalTitle'); const formFields = document.getElementById('formFields'); modalTitle.textContent = 'Standort bearbeiten'; formFields.innerHTML = `
`; modal.style.display = 'block'; } async function editRun(id) { try { // Lade die spezifischen Lauf-Daten direkt von der API const response = await fetch(`/api/admin-runs/${id}`); const result = await response.json(); if (!result.success) { showError('Fehler beim Laden der Lauf-Daten: ' + (result.message || 'Unbekannter Fehler')); return; } const run = result.data; if (!run) { showError('Lauf nicht gefunden'); return; } console.log('Editing run:', run); // Debug log const modal = document.getElementById('addModal'); const modalTitle = document.getElementById('modalTitle'); const formFields = document.getElementById('formFields'); modalTitle.textContent = 'Lauf bearbeiten'; formFields.innerHTML = `
`; modal.style.display = 'block'; } catch (error) { console.error('Error loading run data:', error); showError('Fehler beim Laden der Lauf-Daten'); } } async function deletePlayer(id) { if (await confirmDelete(`Spieler mit ID ${id} löschen?`)) { try { const response = await fetch(`/api/v1/admin/players/${id}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { showSuccess('Spieler erfolgreich gelöscht'); loadPlayers(); } else { showError('Fehler beim Löschen des Spielers'); } } catch (error) { console.error('Delete failed:', error); showError('Fehler beim Löschen des Spielers'); } } } async function deleteRun(id) { if (await confirmDelete(`Lauf mit ID ${id} löschen?`)) { try { const response = await fetch(`/api/v1/admin/runs/${id}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { showSuccess('Lauf erfolgreich gelöscht'); loadRuns(); } else { showError('Fehler beim Löschen des Laufs'); } } catch (error) { console.error('Delete failed:', error); showError('Fehler beim Löschen des Laufs'); } } } async function deleteLocation(id) { if (await confirmDelete(`Standort mit ID ${id} löschen?`)) { try { const response = await fetch(`/api/v1/admin/locations/${id}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { showSuccess('Standort erfolgreich gelöscht'); loadLocations(); } else { showError('Fehler beim Löschen des Standorts'); } } catch (error) { console.error('Delete failed:', error); showError('Fehler beim Löschen des Standorts'); } } } async function deleteAdminUser(id) { if (await confirmDelete(`Admin-Benutzer mit ID ${id} löschen?`)) { try { const response = await fetch(`/api/v1/admin/adminusers/${id}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { showSuccess('Admin-Benutzer erfolgreich gelöscht'); loadAdminUsers(); } else { showError('Fehler beim Löschen des Admin-Benutzers'); } } catch (error) { console.error('Delete failed:', error); showError('Fehler beim Löschen des Admin-Benutzers'); } } } function confirmDelete(message) { return new Promise((resolve) => { document.getElementById('confirmMessage').textContent = message; document.getElementById('confirmModal').style.display = 'block'; document.getElementById('confirmYes').onclick = () => { closeModal(); resolve(true); }; document.getElementById('confirmNo').onclick = () => { closeModal(); resolve(false); }; }); } function showSuccess(message) { const messageDiv = document.getElementById('modalMessage'); messageDiv.textContent = message; messageDiv.className = 'message success'; messageDiv.style.display = 'block'; setTimeout(() => { messageDiv.style.display = 'none'; }, 3000); } function showError(message) { const messageDiv = document.getElementById('modalMessage'); messageDiv.textContent = message; messageDiv.className = 'message error'; messageDiv.style.display = 'block'; setTimeout(() => { messageDiv.style.display = 'none'; }, 3000); } // ============================================================================ // BLACKLIST MANAGEMENT FUNCTIONS // ============================================================================ async function showBlacklistManagement() { currentDataType = 'blacklist'; document.getElementById('blacklistModal').style.display = 'block'; await loadBlacklist(); await loadBlacklistStats(); } async function loadBlacklist() { try { const response = await fetch('/api/v1/admin/blacklist'); const result = await response.json(); if (result.success) { displayBlacklist(result.data); } else { showBlacklistMessage('Fehler beim Laden der Blacklist: ' + result.message, 'error'); } } catch (error) { console.error('Error loading blacklist:', error); showBlacklistMessage('Fehler beim Laden der Blacklist', 'error'); } } function displayBlacklist(blacklist) { const content = document.getElementById('blacklistContent'); let html = '

📋 Blacklist-Inhalte

'; Object.entries(blacklist).forEach(([category, terms]) => { const categoryName = getCategoryDisplayName(category); const categoryIcon = getCategoryIcon(category); html += `

${categoryIcon} ${categoryName} ${terms.length} Einträge

`; if (terms.length === 0) { html += `
Keine Einträge in dieser Kategorie
`; } else { terms.forEach(term => { html += ` ${term} `; }); } html += `
`; }); content.innerHTML = html; } // Kategorie-Icons hinzufügen function getCategoryIcon(category) { const icons = { historical: '🏛️', offensive: '⚠️', titles: '👑', brands: '🏷️', inappropriate: '🚫', racial: '🌍', religious: '⛪', disability: '♿', leetspeak: '🔤', cyberbullying: '💻', drugs: '💊', violence: '⚔️' }; return icons[category] || '📝'; } function getCategoryDisplayName(category) { const names = { historical: 'Historisch belastet', offensive: 'Beleidigend/anstößig', titles: 'Titel/Berufsbezeichnung', brands: 'Markenname', inappropriate: 'Unpassend', racial: 'Rassistisch/ethnisch', religious: 'Religiös beleidigend', disability: 'Behinderungsbezogen', leetspeak: 'Verschleiert', cyberbullying: 'Cyberbullying', drugs: 'Drogenbezogen', violence: 'Gewalt/Bedrohung' }; return names[category] || category; } async function testNameAgainstBlacklist() { const firstname = document.getElementById('testFirstname').value.trim(); const lastname = document.getElementById('testLastname').value.trim(); const resultDiv = document.getElementById('testResult'); if (!firstname || !lastname) { showBlacklistMessage('Bitte gib Vorname und Nachname ein', 'error'); return; } try { const response = await fetch('/api/v1/admin/blacklist/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ firstname, lastname }) }); const result = await response.json(); if (result.success) { const testResult = result.data; if (testResult.isBlocked) { let matchTypeText = ''; let similarityText = ''; if (testResult.matchType === 'exact') { matchTypeText = 'Exakte Übereinstimmung'; } else if (testResult.matchType === 'similar') { matchTypeText = 'Ähnliche Übereinstimmung (Levenshtein)'; const similarityPercent = Math.round((1 - testResult.similarity) * 100); similarityText = `
Ähnlichkeit: ${similarityPercent}% (Distanz: ${testResult.levenshteinDistance})`; } resultDiv.innerHTML = `
❌ Name ist blockiert
Grund: ${testResult.reason}
Kategorie: ${getCategoryDisplayName(testResult.category)}
Gefundener Begriff: "${testResult.matchedTerm}"
Typ: ${matchTypeText}${similarityText}
`; } else { resultDiv.innerHTML = `
✅ Name ist erlaubt
Der Name "${firstname} ${lastname}" ist nicht in der Blacklist.
`; } resultDiv.style.display = 'block'; } else { showBlacklistMessage('Fehler beim Testen: ' + result.message, 'error'); } } catch (error) { console.error('Error testing name:', error); showBlacklistMessage('Fehler beim Testen des Namens', 'error'); } } async function addToBlacklist() { const term = document.getElementById('newTerm').value.trim(); const category = document.getElementById('newCategory').value; if (!term) { showBlacklistMessage('Bitte gib einen Begriff ein', 'error'); return; } try { const response = await fetch('/api/v1/admin/blacklist', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ term, category }) }); const result = await response.json(); if (result.success) { showBlacklistMessage('Begriff erfolgreich hinzugefügt', 'success'); document.getElementById('newTerm').value = ''; await loadBlacklist(); await loadBlacklistStats(); } else { showBlacklistMessage('Fehler beim Hinzufügen: ' + result.message, 'error'); } } catch (error) { console.error('Error adding to blacklist:', error); showBlacklistMessage('Fehler beim Hinzufügen zur Blacklist', 'error'); } } async function removeFromBlacklist(term, category) { if (!confirm(`Möchtest du "${term}" aus der Kategorie "${getCategoryDisplayName(category)}" entfernen?`)) { return; } try { const response = await fetch('/api/v1/admin/blacklist', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ term, category }) }); const result = await response.json(); if (result.success) { showBlacklistMessage('Begriff erfolgreich entfernt', 'success'); await loadBlacklist(); await loadBlacklistStats(); } else { showBlacklistMessage('Fehler beim Entfernen: ' + result.message, 'error'); } } catch (error) { console.error('Error removing from blacklist:', error); showBlacklistMessage('Fehler beim Entfernen aus der Blacklist', 'error'); } } function showBlacklistMessage(message, type) { const messageDiv = document.getElementById('blacklistMessage'); messageDiv.textContent = message; messageDiv.className = `message ${type}`; messageDiv.style.display = 'block'; setTimeout(() => { messageDiv.style.display = 'none'; }, 3000); } // Blacklist-Statistiken laden async function loadBlacklistStats() { try { const response = await fetch('/api/v1/admin/blacklist/stats'); const result = await response.json(); if (result.success) { displayBlacklistStats(result.data); } else { console.error('Error loading blacklist stats:', result.message); } } catch (error) { console.error('Error loading blacklist stats:', error); } } // Blacklist-Statistiken anzeigen function displayBlacklistStats(stats) { // Erstelle oder aktualisiere Statistiken-Bereich let statsDiv = document.getElementById('blacklistStats'); if (!statsDiv) { // Erstelle Statistiken-Bereich am Anfang des Modals const modalContent = document.querySelector('#blacklistModal .modal-content'); const firstChild = modalContent.firstChild; statsDiv = document.createElement('div'); statsDiv.id = 'blacklistStats'; statsDiv.style.cssText = 'border: 1px solid #4ade80; padding: 1rem; margin-bottom: 1rem; border-radius: 5px; background: rgba(34, 197, 94, 0.1);'; modalContent.insertBefore(statsDiv, firstChild); } let html = `

📊 Blacklist-Statistiken

`; stats.categories.forEach(category => { const categoryName = getCategoryDisplayName(category.category); const lastAdded = new Date(category.last_added).toLocaleDateString('de-DE'); html += `
${category.count}
${categoryName}
Letzte: ${lastAdded}
`; }); html += `
Gesamt: ${stats.total} Begriffe
`; statsDiv.innerHTML = html; }