"use client"; import { useEffect, useState } from "react"; import { useSession } from "next-auth/react"; import { LocationResponse } from "@/lib/types"; interface DeviceInfo { id: string; name: string; color: string; } export default function AdminDashboard() { const { data: session } = useSession(); const userRole = (session?.user as any)?.role; const username = session?.user?.name || ''; const isAdmin = userRole === 'ADMIN'; const isSuperAdmin = username === 'admin'; const [stats, setStats] = useState({ totalDevices: 0, totalPoints: 0, lastUpdated: "", onlineDevices: 0, }); const [devices, setDevices] = useState([]); const [cleanupStatus, setCleanupStatus] = useState<{ loading: boolean; message: string; type: 'success' | 'error' | ''; }>({ loading: false, message: '', type: '', }); const [optimizeStatus, setOptimizeStatus] = useState<{ loading: boolean; message: string; type: 'success' | 'error' | ''; }>({ loading: false, message: '', type: '', }); const [dbStats, setDbStats] = useState(null); const [systemStatus, setSystemStatus] = useState(null); const [geofenceStats, setGeofenceStats] = useState(null); // Fetch devices from API useEffect(() => { const fetchDevices = async () => { try { const response = await fetch("/api/devices/public"); if (response.ok) { const data = await response.json(); setDevices(data.devices); } } catch (err) { console.error("Failed to fetch devices:", err); } }; fetchDevices(); // Refresh devices every 30 seconds const interval = setInterval(fetchDevices, 30000); return () => clearInterval(interval); }, []); useEffect(() => { const fetchStats = async () => { try { // Fetch from local API (reads from SQLite cache) const response = await fetch("/api/locations?sync=false"); // sync=false for faster response const data: LocationResponse = await response.json(); // Calculate online devices (last location < 10 minutes) const now = Date.now(); const tenMinutesAgo = now - 10 * 60 * 1000; const recentDevices = new Set( data.history .filter((loc) => { const locationTime = new Date(loc.timestamp).getTime(); return locationTime > tenMinutesAgo; }) .map((loc) => loc.username) ); setStats({ totalDevices: devices.length, totalPoints: data.total_points || data.history.length, lastUpdated: data.last_updated || new Date().toISOString(), onlineDevices: recentDevices.size, }); } catch (err) { console.error("Failed to fetch stats", err); } }; if (devices.length > 0) { fetchStats(); const interval = setInterval(fetchStats, 10000); return () => clearInterval(interval); } }, [devices]); // Fetch detailed database statistics useEffect(() => { const fetchDbStats = async () => { try { const response = await fetch('/api/locations/stats'); if (response.ok) { const data = await response.json(); setDbStats(data); } } catch (err) { console.error('Failed to fetch DB stats:', err); } }; fetchDbStats(); // Refresh DB stats every 30 seconds const interval = setInterval(fetchDbStats, 30000); return () => clearInterval(interval); }, []); // Fetch system status (uptime, memory) useEffect(() => { const fetchSystemStatus = async () => { try { const response = await fetch('/api/system/status'); if (response.ok) { const data = await response.json(); setSystemStatus(data); } } catch (err) { console.error('Failed to fetch system status:', err); } }; fetchSystemStatus(); // Refresh every 10 seconds for live uptime const interval = setInterval(fetchSystemStatus, 10000); return () => clearInterval(interval); }, []); // Fetch geofence statistics useEffect(() => { const fetchGeofenceStats = async () => { try { const [geofencesRes, eventsRes] = await Promise.all([ fetch('/api/geofences'), fetch('/api/geofences/events?limit=100'), ]); if (geofencesRes.ok && eventsRes.ok) { const geofencesData = await geofencesRes.json(); const eventsData = await eventsRes.json(); const now = Date.now(); const oneDayAgo = now - 24 * 60 * 60 * 1000; const events24h = eventsData.events?.filter((e: any) => new Date(e.timestamp).getTime() > oneDayAgo ) || []; setGeofenceStats({ total: geofencesData.total || 0, active: geofencesData.geofences?.filter((g: any) => g.is_active === 1).length || 0, events24h: events24h.length, recentEvents: eventsData.events?.slice(0, 5) || [], }); } } catch (err) { console.error('Failed to fetch geofence stats:', err); } }; fetchGeofenceStats(); // Refresh every 30 seconds const interval = setInterval(fetchGeofenceStats, 30000); return () => clearInterval(interval); }, []); // Cleanup old locations const handleCleanup = async (retentionHours: number) => { if (!confirm(`Delete all locations older than ${Math.round(retentionHours / 24)} days?`)) { return; } setCleanupStatus({ loading: true, message: '', type: '' }); try { const response = await fetch('/api/locations/cleanup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ retentionHours }), }); const data = await response.json(); if (response.ok) { setCleanupStatus({ loading: false, message: `βœ“ Deleted ${data.deleted} records. Freed ${data.freedKB} KB.`, type: 'success', }); // Refresh stats setTimeout(() => { window.location.reload(); }, 2000); } else { setCleanupStatus({ loading: false, message: `Error: ${data.error}`, type: 'error', }); } } catch (error) { setCleanupStatus({ loading: false, message: 'Failed to cleanup locations', type: 'error', }); } // Clear message after 5 seconds setTimeout(() => { setCleanupStatus({ loading: false, message: '', type: '' }); }, 5000); }; // Optimize database const handleOptimize = async () => { if (!confirm('Optimize database? This may take a few seconds.')) { return; } setOptimizeStatus({ loading: true, message: '', type: '' }); try { const response = await fetch('/api/locations/optimize', { method: 'POST', }); const data = await response.json(); if (response.ok) { setOptimizeStatus({ loading: false, message: `βœ“ Database optimized. Freed ${data.freedMB} MB. (${data.before.sizeMB} β†’ ${data.after.sizeMB} MB)`, type: 'success', }); // Refresh stats setTimeout(() => { window.location.reload(); }, 2000); } else { setOptimizeStatus({ loading: false, message: `Error: ${data.error}`, type: 'error', }); } } catch (error) { setOptimizeStatus({ loading: false, message: 'Failed to optimize database', type: 'error', }); } // Clear message after 5 seconds setTimeout(() => { setOptimizeStatus({ loading: false, message: '', type: '' }); }, 5000); }; const statCards = [ { title: "Total Devices", value: stats.totalDevices, icon: "πŸ“±", }, { title: "Online Devices", value: stats.onlineDevices, icon: "🟒", }, { title: "Total Locations", value: stats.totalPoints, icon: "πŸ“", }, ]; return (
{/* Hero Section with Gradient */}

Dashboard

Willkommen zurück! Hier ist ein Überblick über dein System.

{/* Stats Grid */}
{statCards.map((stat, index) => { const gradients = [ 'from-emerald-500 to-teal-600', 'from-blue-500 to-indigo-600', 'from-violet-500 to-purple-600' ]; return (
{stat.icon}

{stat.title}

{stat.value}

); })}
{/* System Status */} {systemStatus && (
βš™οΈ

System Status

⏱️

App Uptime

{systemStatus.uptime.formatted}

Running since server start

πŸ’Ύ

Memory Usage

{systemStatus.memory.heapUsed} MB

Heap: {systemStatus.memory.heapTotal} MB / RSS: {systemStatus.memory.rss} MB

πŸš€

Runtime

{systemStatus.nodejs}

Platform: {systemStatus.platform}

)} {/* Database Statistics */} {dbStats && (
πŸ“Š

Database Statistics

πŸ’½

Database Size

{dbStats.sizeMB} MB

WAL Mode: {dbStats.walMode}

πŸ“…

Time Range

{dbStats.oldest ? new Date(dbStats.oldest).toLocaleDateString() : 'N/A'}

to

{dbStats.newest ? new Date(dbStats.newest).toLocaleDateString() : 'N/A'}

πŸ“ˆ

Average Per Day

{dbStats.avgPerDay}

locations (last 7 days)

{/* Locations per Device */} {dbStats.perDevice && dbStats.perDevice.length > 0 && (

πŸ“± Locations per Device

{dbStats.perDevice.map((device: any, idx: number) => (
{idx + 1}
Device {device.username}
{device.count.toLocaleString()} locations
))}
)}
)} {/* Geofence Statistics */} {geofenceStats && (geofenceStats.total > 0 || geofenceStats.events24h > 0) && (
πŸ“

Geofence Overview

Manage Geofences
πŸ—ΊοΈ

Active Geofences

{geofenceStats.active}

of {geofenceStats.total} total zones

πŸ“Š

Events (24h)

{geofenceStats.events24h}

enter/exit events today

⚑

Status

{geofenceStats.active > 0 ? 'βœ“ Active' : 'Inactive'}

monitoring enabled

{/* Recent Events */} {geofenceStats.recentEvents && geofenceStats.recentEvents.length > 0 && (

πŸ“‹ Recent Events

View All β†’
{geofenceStats.recentEvents.map((event: any, idx: number) => (
{event.geofenceName || 'Unknown Zone'}

Device {event.device_id} β€’{' '} {new Date(event.timestamp).toLocaleString()}

{event.event_type === 'enter' ? ( ↓ Enter ) : ( ↑ Exit )}
))}
)}
)} {/* Device List */}
πŸ“±

Configured Devices

{devices.map((device, idx) => ( ))}
ID Name Color
{device.id} {device.name}
{device.color}
{/* Database Maintenance - SUPER ADMIN ONLY (username "admin") */} {isSuperAdmin && (
πŸ› οΈ

Database Maintenance

SUPER ADMIN ONLY

{/* Cleanup Section */}

🧹 Clean up old data

Delete old location data to keep the database size manageable.

{/* Cleanup Status Message */} {cleanupStatus.message && (
{cleanupStatus.message}
)} {/* Cleanup Buttons */}

Current database size: {stats.totalPoints.toLocaleString()} locations

{/* Optimize Section */}

⚑ Optimize Database

Run VACUUM and ANALYZE to reclaim disk space and improve query performance. Recommended after cleanup.

{/* Optimize Status Message */} {optimizeStatus.message && (
{optimizeStatus.message}
)}
)} {/* Last Updated */}
πŸ•’ Last Updated
{new Date(stats.lastUpdated).toLocaleString()}
); }