diff --git a/public/admin-dashboard.html b/public/admin-dashboard.html
index 7278c5a..e38d7d9 100644
--- a/public/admin-dashboard.html
+++ b/public/admin-dashboard.html
@@ -4,6 +4,7 @@
diff --git a/public/adminlogin.html b/public/adminlogin.html
index 87412a1..3742489 100644
--- a/public/adminlogin.html
+++ b/public/adminlogin.html
@@ -4,6 +4,7 @@
Login - Lizenzgenerator
+
diff --git a/public/css/admin-dashboard.css b/public/css/admin-dashboard.css
index 5903be4..6b89da1 100644
--- a/public/css/admin-dashboard.css
+++ b/public/css/admin-dashboard.css
@@ -326,6 +326,121 @@ body {
margin: 5% auto;
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) {
diff --git a/public/dashboard.html b/public/dashboard.html
index 37e9010..eeb58cc 100644
--- a/public/dashboard.html
+++ b/public/dashboard.html
@@ -4,6 +4,7 @@
SPEEDRUN ARENA - Admin Dashboard
+
diff --git a/public/generator.html b/public/generator.html
index 216097c..737eb7f 100644
--- a/public/generator.html
+++ b/public/generator.html
@@ -4,6 +4,7 @@
Lizenzgenerator
+
diff --git a/public/index.html b/public/index.html
index 37a522b..dd4c32e 100644
--- a/public/index.html
+++ b/public/index.html
@@ -4,6 +4,8 @@
Timer Leaderboard
+
+
diff --git a/public/js/admin-dashboard.js b/public/js/admin-dashboard.js
index 0cea831..95bb68f 100644
--- a/public/js/admin-dashboard.js
+++ b/public/js/admin-dashboard.js
@@ -6,6 +6,7 @@ let currentData = [];
document.addEventListener('DOMContentLoaded', function() {
checkAuth();
loadStatistics();
+ loadPageStatistics();
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 = '
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();
diff --git a/public/js/page-tracking.js b/public/js/page-tracking.js
new file mode 100644
index 0000000..327d95a
--- /dev/null
+++ b/public/js/page-tracking.js
@@ -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');
+ }
+});
diff --git a/public/login.html b/public/login.html
index 97d50b0..7aff8cf 100644
--- a/public/login.html
+++ b/public/login.html
@@ -4,6 +4,7 @@
Ninja Server - Admin Login
+
diff --git a/public/pictures/favicon.ico b/public/pictures/favicon.ico
new file mode 100644
index 0000000..5f2904b
Binary files /dev/null and b/public/pictures/favicon.ico differ
diff --git a/public/reset-password.html b/public/reset-password.html
index 0699a8c..993c114 100644
--- a/public/reset-password.html
+++ b/public/reset-password.html
@@ -4,6 +4,7 @@
Passwort zurücksetzen - NinjaCross
+
diff --git a/routes/api.js b/routes/api.js
index 4cfa411..625d694 100644
--- a/routes/api.js
+++ b/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
// ============================================================================