Fixed and changes

This commit is contained in:
Akshay Kolli
2026-02-08 03:03:53 -05:00
parent 3f72118348
commit dd1a2ea200
13 changed files with 1948 additions and 125 deletions

View File

@@ -1,11 +1,31 @@
'use client';
import createGlobe from 'cobe';
import { useEffect, useRef } from 'react';
import { useEffect, useRef, useState } from 'react';
import { Globe } from 'lucide-react';
export function GlobeCard() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [visitorStats, setVisitorStats] = useState({ total: 0, active24h: 0, locations: [] });
useEffect(() => {
async function fetchVisitors() {
try {
const res = await fetch('/api/visitors');
const data = await res.json();
setVisitorStats({
total: data.totalVisitors || 0,
active24h: data.active24h || 0,
locations: data.locations || []
});
} catch (e) {
console.error('Failed to load visitors', e);
}
}
fetchVisitors();
const interval = setInterval(fetchVisitors, 30000); // 30s poll
return () => clearInterval(interval);
}, []);
useEffect(() => {
let phi = 0;
@@ -25,17 +45,8 @@ export function GlobeCard() {
baseColor: [0.3, 0.3, 0.3],
markerColor: [0.1, 0.8, 1],
glowColor: [0.1, 0.1, 0.2],
markers: [
// Mock data points
{ location: [40.7128, -74.0060], size: 0.05 }, // NY
{ location: [51.5074, -0.1278], size: 0.05 }, // London
{ location: [35.6762, 139.6503], size: 0.05 }, // Tokyo
{ location: [22.3193, 114.1694], size: 0.05 }, // HK
{ location: [-33.8688, 151.2093], size: 0.05 }, // Sydney
],
markers: visitorStats.locations,
onRender: (state) => {
// Called on every animation frame.
// `state` will be an empty object, return updated params.
state.phi = phi;
phi += 0.01;
},
@@ -44,18 +55,27 @@ export function GlobeCard() {
return () => {
globe.destroy();
};
}, []);
}, [visitorStats.locations]);
return (
<div className="col-span-1 md:col-span-2 lg:col-span-2 row-span-2 bg-neutral-900 border border-neutral-800 rounded-xl relative overflow-hidden group hover:border-neutral-700 transition-colors">
<div className="absolute top-6 left-6 z-10 pointer-events-none">
<div className="flex items-center gap-2 text-neutral-400">
<Globe size={18} />
<span className="text-sm font-medium">Active Visitors</span>
<span className="text-sm font-medium">Visitor Map</span>
</div>
<div className="mt-1">
<span className="text-2xl font-bold text-white tracking-tight">1,240</span>
<span className="text-xs text-neutral-500 ml-2 font-mono">LIVE</span>
<div className="mt-4 space-y-2">
<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>
</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>
</div>
</div>

View File

@@ -8,15 +8,22 @@ interface ServiceStatus {
url: string;
status: 'up' | 'down';
latency: number;
uptime24h?: number;
uptime7d?: number;
}
// 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);
const [error, setError] = useState(false);
const fetchStatus = async () => {
setLoading(true);
try {
const res = await fetch('/api/status');
if (!res.ok) throw new Error('Failed to fetch');
@@ -33,15 +40,10 @@ export function UptimeCard() {
useEffect(() => {
fetchStatus();
// Poll every 30 seconds
const interval = setInterval(fetchStatus, 30000);
return () => clearInterval(interval);
}, []);
// Calculate overall health
const upCount = services.filter(s => s.status === 'up').length;
const healthPercentage = services.length > 0 ? Math.round((upCount / services.length) * 100) : 0;
return (
<div className="col-span-1 md:col-span-2 lg:col-span-2 row-span-1 bg-neutral-900 border border-neutral-800 rounded-xl p-6 flex flex-col justify-between hover:border-neutral-700 transition-colors">
<div className="flex justify-between items-start">
@@ -50,11 +52,6 @@ export function UptimeCard() {
<span className="text-sm font-medium">Uptime Monitor</span>
{loading && <RefreshCcw size={12} className="animate-spin ml-2 opacity-50" />}
</div>
{!loading && !error && (
<span className={`text-xs px-2 py-1 rounded-full font-mono ${healthPercentage === 100 ? 'bg-emerald-500/10 text-emerald-500' : 'bg-red-500/10 text-red-500'}`}>
{healthPercentage}%
</span>
)}
</div>
{error ? (
@@ -63,26 +60,30 @@ export function UptimeCard() {
<span className="text-xs">Failed to load status</span>
</div>
) : (
<div className="space-y-3 mt-4">
{services.length === 0 && loading ? (
// Skeleton loader
[1, 2, 3].map(i => (
<div key={i} className="flex items-center justify-between animate-pulse">
<div className="h-4 w-20 bg-neutral-800 rounded"></div>
<div className="h-2 w-2 bg-neutral-800 rounded-full"></div>
</div>
))
) : (
services.map((service) => (
<div key={service.name} className="flex items-center justify-between group">
<div className="space-y-4 mt-4">
{services.map((service) => (
<div key={service.name} className="flex flex-col gap-1">
<div className="flex items-center justify-between group">
<span className="text-sm text-neutral-300 font-medium group-hover:text-white transition-colors">{service.name}</span>
<div className="flex items-center gap-2">
<span className="text-xs text-neutral-500 font-mono">{service.status === 'up' ? `${service.latency}ms` : 'DOWN'}</span>
<div className={`w-2 h-2 rounded-full shadow-[0_0_8px_rgba(0,0,0,0.4)] ${service.status === 'up' ? 'bg-emerald-500 shadow-emerald-500/40' : 'bg-red-500 shadow-red-500/40'}`} />
<span className={`text-xs px-1.5 py-0.5 rounded ${service.status === 'up' ? 'bg-emerald-500/10 text-emerald-500' : 'bg-red-500/10 text-red-500'}`}>
{service.status === 'up' ? 'UP' : 'DOWN'}
</span>
<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'}`} />
))}
</div>
<div className="flex justify-between text-[10px] text-neutral-600 font-mono">
<span>24h: {service.uptime24h ?? 100}%</span>
<span>7d: {service.uptime7d ?? 100}%</span>
</div>
</div>
))}
</div>
)}
</div>

View File

@@ -15,7 +15,7 @@ export function WeatherCard() {
async function fetchWeather() {
try {
const res = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${LAT}&longitude=${LNG}&current=temperature_2m,relative_humidity_2m,weather_code&daily=temperature_2m_max,temperature_2m_min&temperature_unit=fahrenheit&timezone=auto`
`https://api.open-meteo.com/v1/forecast?latitude=${LAT}&longitude=${LNG}&current=temperature_2m,relative_humidity_2m,weather_code&daily=temperature_2m_max,temperature_2m_min&temperature_unit=celsius&timezone=auto`
);
const data = await res.json();
setWeather(data);
@@ -62,7 +62,7 @@ export function WeatherCard() {
</div>
<div className="flex items-end gap-4 mt-2">
<div className="text-4xl font-bold text-white tracking-tighter">{Math.round(current.temperature_2m)}°F</div>
<div className="text-4xl font-bold text-white tracking-tighter">{Math.round(current.temperature_2m)}°C</div>
<div className="pb-1 text-sm text-neutral-400 font-medium">{getWeatherDescription(current.weather_code)}</div>
</div>