Separieren vom css und js und fixes

This commit is contained in:
2025-09-04 14:14:59 +02:00
parent 0cfac629b5
commit f6cd5c734f
12 changed files with 3130 additions and 2462 deletions

View File

@@ -4,307 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard - NinjaCross</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
min-height: 100vh;
color: #333;
}
.header {
background: rgba(255, 255, 255, 0.95);
padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
color: #1e3c72;
font-size: 2em;
}
.user-info {
display: flex;
align-items: center;
gap: 15px;
}
.access-badge {
background: #4CAF50;
color: white;
padding: 5px 10px;
border-radius: 15px;
font-size: 0.8em;
font-weight: bold;
}
.access-badge.level-1 {
background: #ff9800;
}
.access-badge.level-2 {
background: #4CAF50;
}
.btn {
background: #2196F3;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
text-decoration: none;
display: inline-block;
font-size: 0.9em;
transition: all 0.3s ease;
}
.btn:hover {
background: #1976D2;
transform: translateY(-2px);
}
.btn-danger {
background: #f44336;
}
.btn-danger:hover {
background: #d32f2f;
}
.btn-warning {
background: #ff9800;
}
.btn-warning:hover {
background: #f57c00;
}
.btn-success {
background: #4CAF50;
}
.btn-success:hover {
background: #388E3C;
}
.container {
max-width: 1200px;
margin: 30px auto;
padding: 0 20px;
}
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.card {
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
}
.card h3 {
color: #1e3c72;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
}
.icon {
font-size: 1.5em;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 30px;
}
.stat-card {
background: white;
padding: 20px;
border-radius: 10px;
text-align: center;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.stat-number {
font-size: 2.5em;
font-weight: bold;
color: #1e3c72;
margin-bottom: 5px;
}
.stat-label {
color: #666;
font-size: 0.9em;
}
.data-table {
width: 100%;
border-collapse: collapse;
background: white;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.data-table th,
.data-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #eee;
}
.data-table th {
background: #f5f5f5;
font-weight: bold;
color: #333;
}
.data-table tr:hover {
background: #f9f9f9;
}
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-content {
background-color: white;
margin: 10% auto;
padding: 20px;
border-radius: 10px;
width: 90%;
max-width: 500px;
position: relative;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
position: absolute;
right: 15px;
top: 10px;
}
.close:hover {
color: #000;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #333;
}
.form-group input,
.form-group select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 1em;
}
.message {
padding: 10px;
border-radius: 5px;
margin-bottom: 15px;
display: none;
}
.message.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.message.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.loading {
text-align: center;
padding: 20px;
color: #666;
}
.no-data {
text-align: center;
padding: 40px;
color: #999;
font-style: italic;
}
.action-buttons {
display: flex;
gap: 5px;
}
.btn-small {
padding: 5px 10px;
font-size: 0.8em;
}
.table-container {
max-height: 400px;
overflow-y: auto;
margin-top: 15px;
}
.search-container {
margin-bottom: 20px;
display: flex;
gap: 10px;
align-items: center;
}
.search-input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
</style>
<link rel="stylesheet" href="/css/admin-dashboard.css">
</head>
<body>
<div class="header">
@@ -425,480 +125,7 @@
</div>
</div>
<script>
let currentUser = null;
let currentDataType = null;
let currentData = [];
// Beim Laden der Seite
document.addEventListener('DOMContentLoaded', function() {
checkAuth();
loadStatistics();
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/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/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/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);
}
}
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 = '<div class="no-data">Supabase-Benutzer werden über die Supabase-Console verwaltet</div>';
}
async function loadPlayers() {
try {
const response = await fetch('/api/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/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/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/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 = '<div class="table-container"><table class="data-table">';
html += '<thead><tr><th>ID</th><th>Name</th><th>RFID UID</th><th>Supabase User</th><th>Registriert</th><th>Aktionen</th></tr></thead><tbody>';
players.forEach(player => {
html += `<tr>
<td>${player.id}</td>
<td>${player.full_name || '-'}</td>
<td>${player.rfiduid || '-'}</td>
<td>${player.supabase_user_id ? '✅' : '❌'}</td>
<td>${new Date(player.created_at).toLocaleDateString('de-DE')}</td>
<td class="action-buttons">
<button class="btn btn-small btn-danger" onclick="deletePlayer('${player.id}')">Löschen</button>
</td>
</tr>`;
});
html += '</tbody></table></div>';
document.getElementById('dataContent').innerHTML = html;
}
function displayRunsTable(runs) {
let html = '<div class="table-container"><table class="data-table">';
html += '<thead><tr><th>ID</th><th>Spieler</th><th>Standort</th><th>Zeit</th><th>Datum</th><th>Aktionen</th></tr></thead><tbody>';
runs.forEach(run => {
// Use the time_seconds value from the backend
const timeInSeconds = parseFloat(run.time_seconds) || 0;
html += `<tr>
<td>${run.id}</td>
<td>${run.player_name || `Player ${run.player_id}`}</td>
<td>${run.location_name || `Location ${run.location_id}`}</td>
<td>${timeInSeconds.toFixed(3)}s</td>
<td>${new Date(run.created_at).toLocaleDateString('de-DE')} ${new Date(run.created_at).toLocaleTimeString('de-DE')}</td>
<td class="action-buttons">
<button class="btn btn-small btn-danger" onclick="deleteRun('${run.id}')">Löschen</button>
</td>
</tr>`;
});
html += '</tbody></table></div>';
document.getElementById('dataContent').innerHTML = html;
}
function displayLocationsTable(locations) {
let html = '<div class="table-container"><table class="data-table">';
html += '<thead><tr><th>ID</th><th>Name</th><th>Latitude</th><th>Longitude</th><th>Erstellt</th><th>Aktionen</th></tr></thead><tbody>';
locations.forEach(location => {
html += `<tr>
<td>${location.id}</td>
<td>${location.name}</td>
<td>${location.latitude}</td>
<td>${location.longitude}</td>
<td>${new Date(location.created_at).toLocaleDateString('de-DE')}</td>
<td class="action-buttons">
<button class="btn btn-small btn-danger" onclick="deleteLocation('${location.id}')">Löschen</button>
</td>
</tr>`;
});
html += '</tbody></table></div>';
document.getElementById('dataContent').innerHTML = html;
}
function displayAdminUsersTable(users) {
let html = '<div class="table-container"><table class="data-table">';
html += '<thead><tr><th>ID</th><th>Benutzername</th><th>Access Level</th><th>Aktiv</th><th>Letzter Login</th><th>Aktionen</th></tr></thead><tbody>';
users.forEach(user => {
const isCurrentUser = user.id === currentUser.id;
html += `<tr>
<td>${user.id}</td>
<td>${user.username} ${isCurrentUser ? '(Du)' : ''}</td>
<td>Level ${user.access_level}</td>
<td>${user.is_active ? '✅' : '❌'}</td>
<td>${user.last_login ? new Date(user.last_login).toLocaleDateString('de-DE') : 'Nie'}</td>
<td class="action-buttons">
${!isCurrentUser ? `<button class="btn btn-small btn-danger" onclick="deleteAdminUser(${user.id})">Löschen</button>` : ''}
</td>
</tr>`;
});
html += '</tbody></table></div>';
document.getElementById('dataContent').innerHTML = html;
}
function loadSystemInfo() {
document.getElementById('dataContent').innerHTML = `
<div style="padding: 20px;">
<h4>Server-Informationen</h4>
<p><strong>Node.js Version:</strong> ${navigator.userAgent}</p>
<p><strong>Aktuelle Zeit:</strong> ${new Date().toLocaleString('de-DE')}</p>
<p><strong>Angemeldeter Benutzer:</strong> ${currentUser.username} (Level ${currentUser.access_level})</p>
</div>
`;
}
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() {
// Implementierung für Add Modal je nach Datentyp
document.getElementById('addModal').style.display = 'block';
}
function closeModal() {
document.querySelectorAll('.modal').forEach(modal => {
modal.style.display = 'none';
});
}
function handleAddSubmit(e) {
e.preventDefault();
// Implementierung für das Hinzufügen von Daten
closeModal();
}
async function deletePlayer(id) {
if (await confirmDelete(`Spieler mit ID ${id} löschen?`)) {
try {
const response = await fetch(`/api/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/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/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/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);
}
</script>
<!-- Application JavaScript -->
<script src="/js/admin-dashboard.js"></script>
</body>
</html>