Full-stack real-time server monitoring dashboard with: - Express.js backend with SSH-based metrics collection - React + TypeScript + Tailwind CSS + shadcn/ui frontend - CPU, memory, disk, Docker container, and Nginx monitoring - Pure SVG charts (CPU area chart + memory donut) - Gilroy font, 10s auto-refresh polling - Multi-page dashboard with sidebar navigation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
46 lines
1.3 KiB
TypeScript
46 lines
1.3 KiB
TypeScript
import { useMemo } from "react"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
interface RefreshControlProps {
|
|
lastUpdated: string | null
|
|
isConnected: boolean
|
|
}
|
|
|
|
function formatRelativeTime(timestamp: string): string {
|
|
const diff = Date.now() - new Date(timestamp).getTime()
|
|
const seconds = Math.floor(diff / 1000)
|
|
if (seconds < 5) return "just now"
|
|
if (seconds < 60) return `${seconds}s ago`
|
|
const minutes = Math.floor(seconds / 60)
|
|
return `${minutes}m ago`
|
|
}
|
|
|
|
export function RefreshControl({ lastUpdated, isConnected }: RefreshControlProps) {
|
|
const relativeTime = useMemo(() => {
|
|
if (!lastUpdated) return "..."
|
|
return formatRelativeTime(lastUpdated)
|
|
}, [lastUpdated])
|
|
|
|
return (
|
|
<div className="flex items-center gap-2 rounded-lg border px-3 py-1.5">
|
|
<span
|
|
className={cn(
|
|
"relative flex size-2",
|
|
isConnected ? "text-green-500" : "text-muted-foreground"
|
|
)}
|
|
>
|
|
{isConnected && (
|
|
<span className="absolute inline-flex size-full animate-ping rounded-full bg-green-400 opacity-75" />
|
|
)}
|
|
<span
|
|
className={cn(
|
|
"relative inline-flex size-2 rounded-full",
|
|
isConnected ? "bg-green-500" : "bg-muted-foreground"
|
|
)}
|
|
/>
|
|
</span>
|
|
<span className="text-xs text-muted-foreground">{relativeTime}</span>
|
|
</div>
|
|
)
|
|
}
|