"use client"; import { useEffect, useState } from "react"; import { useSession } from "next-auth/react"; import { LocationResponse } from "@/types/location"; 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); // 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); }, []); // 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 (

Dashboard

{/* Stats Grid */}
{statCards.map((stat) => (
{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) => (
Device {device.username} {device.count.toLocaleString()} locations
))}
)}
)} {/* Device List */}

Configured Devices

{devices.map((device) => ( ))}
ID Name Color
{device.id} {device.name}
{device.color}
{/* Database Maintenance - SUPER ADMIN ONLY (username "admin") */} {isSuperAdmin && (

Database Maintenance

{/* 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} 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()}
); }