Statistik backend and favicon
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Admin Dashboard - NinjaCross</title>
|
<title>Admin Dashboard - NinjaCross</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/pictures/favicon.ico">
|
||||||
<link rel="stylesheet" href="/css/admin-dashboard.css">
|
<link rel="stylesheet" href="/css/admin-dashboard.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -40,6 +41,48 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Hauptseiten-Besuche -->
|
||||||
|
<div class="page-stats-section">
|
||||||
|
<h2>🏠 Hauptseiten-Besuche</h2>
|
||||||
|
<div class="page-stats-grid">
|
||||||
|
<div class="page-stat-card">
|
||||||
|
<h3>Heute</h3>
|
||||||
|
<div id="todayStats" class="page-stats-content">Lade...</div>
|
||||||
|
</div>
|
||||||
|
<div class="page-stat-card">
|
||||||
|
<h3>Diese Woche</h3>
|
||||||
|
<div id="weekStats" class="page-stats-content">Lade...</div>
|
||||||
|
</div>
|
||||||
|
<div class="page-stat-card">
|
||||||
|
<h3>Dieser Monat</h3>
|
||||||
|
<div id="monthStats" class="page-stats-content">Lade...</div>
|
||||||
|
</div>
|
||||||
|
<div class="page-stat-card">
|
||||||
|
<h3>Gesamt</h3>
|
||||||
|
<div id="totalStats" class="page-stats-content">Lade...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Verlinkungs-Statistiken -->
|
||||||
|
<div class="link-stats-section">
|
||||||
|
<h3>🔗 Account-Verknüpfungen</h3>
|
||||||
|
<div class="link-stats-grid">
|
||||||
|
<div class="link-stat-card">
|
||||||
|
<div class="link-stat-number" id="totalPlayersCount">-</div>
|
||||||
|
<div class="link-stat-label">Gesamt Spieler</div>
|
||||||
|
</div>
|
||||||
|
<div class="link-stat-card">
|
||||||
|
<div class="link-stat-number" id="linkedPlayersCount">-</div>
|
||||||
|
<div class="link-stat-label">Verknüpfte Spieler</div>
|
||||||
|
</div>
|
||||||
|
<div class="link-stat-card">
|
||||||
|
<div class="link-stat-number" id="linkPercentage">-</div>
|
||||||
|
<div class="link-stat-label">Verknüpfungsrate</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Dashboard Cards -->
|
<!-- Dashboard Cards -->
|
||||||
<div class="dashboard-grid">
|
<div class="dashboard-grid">
|
||||||
<!-- Benutzer-Verwaltung -->
|
<!-- Benutzer-Verwaltung -->
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Login - Lizenzgenerator</title>
|
<title>Login - Lizenzgenerator</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/pictures/favicon.ico">
|
||||||
<link rel="stylesheet" href="/css/adminlogin.css">
|
<link rel="stylesheet" href="/css/adminlogin.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -326,6 +326,121 @@ body {
|
|||||||
margin: 5% auto;
|
margin: 5% auto;
|
||||||
width: 95%;
|
width: 95%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-stats-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-stats-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Page Statistics Styles */
|
||||||
|
.page-stats-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 25px;
|
||||||
|
margin: 20px 0;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-stats-section h2 {
|
||||||
|
color: #1e3c72;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-stat-card {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
border-left: 4px solid #1e3c72;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-stat-card h3 {
|
||||||
|
color: #1e3c72;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-stats-content {
|
||||||
|
font-size: 0.9em;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-stats-content .page-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-stats-content .page-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-stats-content .page-name {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-stats-content .page-count {
|
||||||
|
background: #1e3c72;
|
||||||
|
color: white;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Link Statistics Styles */
|
||||||
|
.link-stats-section {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-stats-section h3 {
|
||||||
|
color: #1e3c72;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-stat-card {
|
||||||
|
text-align: center;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-stat-number {
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #1e3c72;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-stat-label {
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>SPEEDRUN ARENA - Admin Dashboard</title>
|
<title>SPEEDRUN ARENA - Admin Dashboard</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/pictures/favicon.ico">
|
||||||
<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
|
<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
|
||||||
<!-- QR Code Scanner Library -->
|
<!-- QR Code Scanner Library -->
|
||||||
<script src="https://unpkg.com/jsqr@1.4.0/dist/jsQR.js"></script>
|
<script src="https://unpkg.com/jsqr@1.4.0/dist/jsQR.js"></script>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Lizenzgenerator</title>
|
<title>Lizenzgenerator</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/pictures/favicon.ico">
|
||||||
<link rel="stylesheet" href="/css/generator.css">
|
<link rel="stylesheet" href="/css/generator.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Timer Leaderboard</title>
|
<title>Timer Leaderboard</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/pictures/favicon.ico">
|
||||||
|
<script src="/js/page-tracking.js"></script>
|
||||||
<link rel="stylesheet" href="/css/leaderboard.css">
|
<link rel="stylesheet" href="/css/leaderboard.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ let currentData = [];
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
checkAuth();
|
checkAuth();
|
||||||
loadStatistics();
|
loadStatistics();
|
||||||
|
loadPageStatistics();
|
||||||
setupEventListeners();
|
setupEventListeners();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -87,6 +88,66 @@ async function loadStatistics() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadPageStatistics() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/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 = '<div style="color: #dc3545;">Fehler beim Laden</div>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayPageStats(containerId, stats) {
|
||||||
|
const container = document.getElementById(containerId);
|
||||||
|
|
||||||
|
if (!stats || stats.length === 0) {
|
||||||
|
container.innerHTML = '<div style="color: #6c757d;">0</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find main page visits
|
||||||
|
const mainPageStat = stats.find(stat => stat.page === 'main_page_visit');
|
||||||
|
const count = mainPageStat ? mainPageStat.count : 0;
|
||||||
|
|
||||||
|
container.innerHTML = `<div style="font-size: 1.5em; font-weight: bold; color: #1e3c72;">${count}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
function showUserManagement() {
|
||||||
showDataSection('users', 'Benutzer-Verwaltung');
|
showDataSection('users', 'Benutzer-Verwaltung');
|
||||||
loadUsers();
|
loadUsers();
|
||||||
|
|||||||
33
public/js/page-tracking.js
Normal file
33
public/js/page-tracking.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// Page tracking functionality
|
||||||
|
function trackPageView(pageName) {
|
||||||
|
// Get user information
|
||||||
|
const userAgent = navigator.userAgent;
|
||||||
|
const referer = document.referrer || '';
|
||||||
|
|
||||||
|
// Send tracking data to server
|
||||||
|
fetch('/api/track-page-view', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
page: pageName,
|
||||||
|
userAgent: userAgent,
|
||||||
|
ipAddress: null, // Will be determined by server
|
||||||
|
referer: referer
|
||||||
|
})
|
||||||
|
}).catch(error => {
|
||||||
|
console.log('Page tracking failed:', error);
|
||||||
|
// Silently fail - don't interrupt user experience
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-track page on load - only track main page visits
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Only track the main page (index.html or root path)
|
||||||
|
const path = window.location.pathname;
|
||||||
|
|
||||||
|
if (path === '/' || path === '/index.html') {
|
||||||
|
trackPageView('main_page_visit');
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Ninja Server - Admin Login</title>
|
<title>Ninja Server - Admin Login</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/pictures/favicon.ico">
|
||||||
<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
|
<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
|
||||||
<link rel="stylesheet" href="/css/login.css">
|
<link rel="stylesheet" href="/css/login.css">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
BIN
public/pictures/favicon.ico
Normal file
BIN
public/pictures/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -4,6 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Passwort zurücksetzen - NinjaCross</title>
|
<title>Passwort zurücksetzen - NinjaCross</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/pictures/favicon.ico">
|
||||||
|
|
||||||
<!-- Supabase -->
|
<!-- Supabase -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
|
||||||
|
|||||||
102
routes/api.js
102
routes/api.js
@@ -1622,6 +1622,108 @@ router.delete('/admin-adminusers/:id', requireAdminAuth, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// PAGE VIEWS TRACKING
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Track page view
|
||||||
|
router.post('/track-page-view', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { page, userAgent, ipAddress, referer } = req.body;
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
INSERT INTO page_views (page, user_agent, ip_address, referer)
|
||||||
|
VALUES ($1, $2, $3, $4)
|
||||||
|
`, [page, userAgent, ipAddress, referer]);
|
||||||
|
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error tracking page view:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Fehler beim Tracking der Seitenaufrufe'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get page statistics
|
||||||
|
router.get('/admin-page-stats', requireAdminAuth, async (req, res) => {
|
||||||
|
try {
|
||||||
|
// Page views for today, this week, this month
|
||||||
|
const today = new Date();
|
||||||
|
const startOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
||||||
|
const startOfWeek = new Date(today);
|
||||||
|
startOfWeek.setDate(today.getDate() - today.getDay());
|
||||||
|
startOfWeek.setHours(0, 0, 0, 0);
|
||||||
|
const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||||
|
|
||||||
|
// Today's page views
|
||||||
|
const todayViews = await pool.query(`
|
||||||
|
SELECT page, COUNT(*) as count
|
||||||
|
FROM page_views
|
||||||
|
WHERE created_at >= $1
|
||||||
|
GROUP BY page
|
||||||
|
ORDER BY count DESC
|
||||||
|
`, [startOfDay]);
|
||||||
|
|
||||||
|
// This week's page views
|
||||||
|
const weekViews = await pool.query(`
|
||||||
|
SELECT page, COUNT(*) as count
|
||||||
|
FROM page_views
|
||||||
|
WHERE created_at >= $1
|
||||||
|
GROUP BY page
|
||||||
|
ORDER BY count DESC
|
||||||
|
`, [startOfWeek]);
|
||||||
|
|
||||||
|
// This month's page views
|
||||||
|
const monthViews = await pool.query(`
|
||||||
|
SELECT page, COUNT(*) as count
|
||||||
|
FROM page_views
|
||||||
|
WHERE created_at >= $1
|
||||||
|
GROUP BY page
|
||||||
|
ORDER BY count DESC
|
||||||
|
`, [startOfMonth]);
|
||||||
|
|
||||||
|
// Total page views
|
||||||
|
const totalViews = await pool.query(`
|
||||||
|
SELECT page, COUNT(*) as count
|
||||||
|
FROM page_views
|
||||||
|
GROUP BY page
|
||||||
|
ORDER BY count DESC
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Player/Supabase link statistics
|
||||||
|
const linkStats = await pool.query(`
|
||||||
|
SELECT
|
||||||
|
COUNT(*) as total_players,
|
||||||
|
COUNT(CASE WHEN supabase_user_id IS NOT NULL THEN 1 END) as linked_players,
|
||||||
|
CAST(
|
||||||
|
ROUND(
|
||||||
|
(COUNT(CASE WHEN supabase_user_id IS NOT NULL THEN 1 END)::numeric / COUNT(*)) * 100, 2
|
||||||
|
) AS DECIMAL(5,2)
|
||||||
|
) as link_percentage
|
||||||
|
FROM players
|
||||||
|
`);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
today: todayViews.rows,
|
||||||
|
week: weekViews.rows,
|
||||||
|
month: monthViews.rows,
|
||||||
|
total: totalViews.rows,
|
||||||
|
linkStats: linkStats.rows[0]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading page statistics:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Fehler beim Laden der Seitenstatistiken'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// POST/PUT ROUTES FÜR CRUD-OPERATIONEN
|
// POST/PUT ROUTES FÜR CRUD-OPERATIONEN
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user