Wire up visitor metrics from webserver DB and fix uptime monitoring

- Read visitors from /server_storage/visitors.db (webserver's DB) instead of
  admin dash's own table; geoip lookups at query time for globe markers
- Globe card now shows 24h, 7d, and all-time unique visitor counts
- Uptime monitor: Nextcloud via host.docker.internal for Docker networking,
  Website and Gitea monitored on public domains
- UptimeCard uses real hourly history bars instead of Math.random() mock
- docker-compose: mount /server_storage:ro, add extra_hosts for Linux compat

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Shivam Patel
2026-02-09 00:50:40 -05:00
parent 4416e35580
commit d5ab2db926
9 changed files with 259 additions and 47 deletions

View File

@@ -1,35 +1,76 @@
import { NextResponse } from 'next/server';
import { getDb } from '@/lib/db';
import { getVisitorsDb } from '@/lib/visitors-db';
import geoip from 'geoip-lite';
export const dynamic = 'force-dynamic';
export async function GET() {
try {
const db = await getDb();
const db = await getVisitorsDb();
// Get active/recent visitors (last 24h)
const recent = await db.all(`
SELECT lat, lon, city, country, count(*) as count
FROM visitors
WHERE timestamp > datetime('now', '-24 hours')
GROUP BY lat, lon
`);
if (!db) {
return NextResponse.json({
locations: [],
totalVisitors: 0,
active24h: 0,
active7d: 0,
});
}
// Get total count
const total = await db.get(`SELECT count(*) as count FROM visitors`);
// Count unique visitors across time windows
const [total, last24h, last7d] = await Promise.all([
db.get(`SELECT COUNT(DISTINCT ip_address) as count FROM visits`),
db.get(`SELECT COUNT(DISTINCT ip_address) as count FROM visits WHERE visited_at >= datetime('now', '-24 hours')`),
db.get(`SELECT COUNT(DISTINCT ip_address) as count FROM visits WHERE visited_at >= datetime('now', '-7 days')`),
]);
// Get unique IPs from last 7 days for globe markers
const recentIps = await db.all(
`SELECT ip_address, COUNT(*) as hits FROM visits WHERE visited_at >= datetime('now', '-7 days') GROUP BY ip_address`
);
// Geo-locate IPs and group by location
const locationMap = new Map<string, { lat: number; lon: number; city: string; country: string; count: number }>();
for (const row of recentIps) {
const geo = geoip.lookup(row.ip_address);
if (geo && geo.ll) {
const key = `${geo.ll[0]},${geo.ll[1]}`;
const existing = locationMap.get(key);
if (existing) {
existing.count += row.hits;
} else {
locationMap.set(key, {
lat: geo.ll[0],
lon: geo.ll[1],
city: geo.city || 'Unknown',
country: geo.country || 'Unknown',
count: row.hits,
});
}
}
}
const locations = Array.from(locationMap.values()).map(loc => ({
location: [loc.lat, loc.lon],
size: Math.min(0.1, 0.05 + loc.count * 0.005),
city: loc.city,
country: loc.country,
}));
return NextResponse.json({
locations: recent.map(r => ({
location: [r.lat, r.lon],
size: Math.min(0.1, 0.05 + (r.count * 0.005)), // Scale size by visitor count
city: r.city,
country: r.country
})),
totalVisitors: total.count,
active24h: recent.reduce((sum, r) => sum + r.count, 0)
locations,
totalVisitors: total?.count || 0,
active24h: last24h?.count || 0,
active7d: last7d?.count || 0,
});
} catch (error) {
console.error('Visitor fetch error:', error);
return NextResponse.json({ locations: [], totalVisitors: 0, active24h: 0 });
return NextResponse.json({
locations: [],
totalVisitors: 0,
active24h: 0,
active7d: 0,
});
}
}