'use client' import { useEffect, useState } from 'react' import dynamic from 'next/dynamic' import { useParams } from 'next/navigation' import Link from 'next/link' const DottedMap = dynamic(() => import('@/components/dashboard/DottedMap'), { ssr: false }) import { getDateRange, formatDate, Select } from '@ciphera-net/ui' import { ArrowSquareOut, CloudArrowUp } from '@phosphor-icons/react' import { ResponsiveContainer, AreaChart, Area, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, } from 'recharts' import { useDashboard, useBunnyStatus, useBunnyOverview, useBunnyDailyStats, useBunnyTopCountries } from '@/lib/swr/dashboard' import { SkeletonLine, StatCardSkeleton, useMinimumLoading, useSkeletonFade } from '@/components/skeletons' // ─── Helpers ──────────────────────────────────────────────────── function formatBytes(bytes: number): string { if (bytes === 0) return '0 B' const units = ['B', 'KB', 'MB', 'GB', 'TB'] const i = Math.floor(Math.log(bytes) / Math.log(1024)) const value = bytes / Math.pow(1024, i) return value.toFixed(i === 0 ? 0 : 1) + ' ' + units[i] } function formatNumber(n: number): string { if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M' if (n >= 1_000) return (n / 1_000).toFixed(1) + 'K' return n.toLocaleString() } function formatDateShort(date: string): string { const d = new Date(date + 'T00:00:00') return d.getDate() + ' ' + d.toLocaleString('en-US', { month: 'short' }) } function changePercent( current: number, prev: number ): { value: number; positive: boolean } | null { if (prev === 0) return null const pct = ((current - prev) / prev) * 100 return { value: pct, positive: pct >= 0 } } // ─── Page ─────────────────────────────────────────────────────── export default function CDNPage() { const params = useParams() const siteId = params.id as string // Date range const [period, setPeriod] = useState('7') const [dateRange, setDateRange] = useState(() => getDateRange(7)) // Data fetching const { data: bunnyStatus } = useBunnyStatus(siteId) const { data: dashboard } = useDashboard(siteId, dateRange.start, dateRange.end) const { data: overview } = useBunnyOverview(siteId, dateRange.start, dateRange.end) const { data: dailyStats } = useBunnyDailyStats(siteId, dateRange.start, dateRange.end) const { data: topCountries } = useBunnyTopCountries(siteId, dateRange.start, dateRange.end) const showSkeleton = useMinimumLoading(!bunnyStatus) const fadeClass = useSkeletonFade(showSkeleton) // Document title useEffect(() => { const domain = dashboard?.site?.domain document.title = domain ? `CDN \u00b7 ${domain} | Pulse` : 'CDN | Pulse' }, [dashboard?.site?.domain]) // ─── Loading skeleton ───────────────────────────────────── if (showSkeleton) { return (
) } // ─── Not connected state ────────────────────────────────── if (bunnyStatus && !bunnyStatus.connected) { return (

Connect BunnyCDN

Monitor your CDN performance including bandwidth usage, cache hit rates, request volumes, and geographic distribution.

Connect in Settings
) } // ─── Connected — main view ──────────────────────────────── const bandwidthChange = overview ? changePercent(overview.total_bandwidth, overview.prev_total_bandwidth) : null const requestsChange = overview ? changePercent(overview.total_requests, overview.prev_total_requests) : null const cacheHitChange = overview ? changePercent(overview.cache_hit_rate, overview.prev_cache_hit_rate) : null const originChange = overview ? changePercent(overview.avg_origin_response, overview.prev_avg_origin_response) : null const errorsChange = overview ? changePercent(overview.total_errors, overview.prev_total_errors) : null const daily = dailyStats?.daily_stats ?? [] const countries = topCountries?.countries ?? [] const maxCountryBandwidth = countries.length > 0 ? countries[0].bandwidth : 1 return (
{/* Header */}

CDN Analytics

BunnyCDN performance, bandwidth, and cache metrics