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:
@@ -6,7 +6,12 @@ import { Globe } from 'lucide-react';
|
||||
|
||||
export function GlobeCard() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const [visitorStats, setVisitorStats] = useState({ total: 0, active24h: 0, locations: [] });
|
||||
const [visitorStats, setVisitorStats] = useState({
|
||||
total: 0,
|
||||
active24h: 0,
|
||||
active7d: 0,
|
||||
locations: [],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchVisitors() {
|
||||
@@ -16,14 +21,15 @@ export function GlobeCard() {
|
||||
setVisitorStats({
|
||||
total: data.totalVisitors || 0,
|
||||
active24h: data.active24h || 0,
|
||||
locations: data.locations || []
|
||||
active7d: data.active7d || 0,
|
||||
locations: data.locations || [],
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to load visitors', e);
|
||||
}
|
||||
}
|
||||
fetchVisitors();
|
||||
const interval = setInterval(fetchVisitors, 30000); // 30s poll
|
||||
const interval = setInterval(fetchVisitors, 30000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
@@ -64,18 +70,22 @@ export function GlobeCard() {
|
||||
<Globe size={18} />
|
||||
<span className="text-sm font-medium">Visitor Map</span>
|
||||
</div>
|
||||
<div className="mt-4 space-y-2">
|
||||
<div className="mt-4 space-y-3">
|
||||
<div>
|
||||
<span className="text-2xl font-bold text-white tracking-tight block">{visitorStats.active24h}</span>
|
||||
<span className="text-xs text-neutral-500 font-mono">LAST 24H</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-lg font-bold text-neutral-300 block">{visitorStats.active7d}</span>
|
||||
<span className="text-xs text-neutral-500 font-mono">LAST 7D</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute top-6 right-6 z-10 text-right pointer-events-none">
|
||||
<div>
|
||||
<span className="text-xl font-bold text-neutral-300 block">{visitorStats.total}</span>
|
||||
<span className="text-xs text-neutral-500 font-mono">TOTAL</span>
|
||||
<span className="text-xs text-neutral-500 font-mono">ALL TIME</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -10,14 +10,9 @@ interface ServiceStatus {
|
||||
latency: number;
|
||||
uptime24h?: number;
|
||||
uptime7d?: number;
|
||||
history?: Array<{ hour: string; up: boolean }>;
|
||||
}
|
||||
|
||||
// We'd ideally fetch stats from API, but for now we calculate from live data or mock
|
||||
// To do this properly, we need an API endpoint returning stats.
|
||||
// Let's update `api/status` to also return stats or create `api/status/stats`.
|
||||
// For this step, I'll update the visual to SHOW where stats would be,
|
||||
// and we'll implement the backend stats aggregation in the next step.
|
||||
|
||||
export function UptimeCard() {
|
||||
const [services, setServices] = useState<ServiceStatus[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -72,11 +67,20 @@ export function UptimeCard() {
|
||||
<span className="text-xs text-neutral-500 font-mono w-10 text-right">{service.latency}ms</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Mini bars visualization for history - Mocked visual for now until API is ready */}
|
||||
<div className="flex gap-[2px] h-1.5 opacity-50">
|
||||
{[...Array(20)].map((_, i) => (
|
||||
<div key={i} className={`flex-1 rounded-full ${Math.random() > 0.95 ? 'bg-red-500' : 'bg-emerald-500'}`} />
|
||||
))}
|
||||
{service.history && service.history.length > 0 ? (
|
||||
service.history.slice(-24).map((h, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`flex-1 rounded-full ${h.up ? 'bg-emerald-500' : 'bg-red-500'}`}
|
||||
title={h.hour}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
[...Array(24)].map((_, i) => (
|
||||
<div key={i} className="flex-1 rounded-full bg-neutral-700" />
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-between text-[10px] text-neutral-600 font-mono">
|
||||
<span>24h: {service.uptime24h ?? 100}%</span>
|
||||
|
||||
Reference in New Issue
Block a user