import { createSignal, Show, For, createEffect, ErrorBoundary } from "solid-js"; import { PageHead } from "~/components/PageHead"; import { redirect, query, createAsync, useNavigate } from "@solidjs/router"; import { api } from "~/lib/api"; const checkAdmin = query(async (): Promise => { "use server"; const { getUserState } = await import("~/lib/auth-query"); const userState = await getUserState(); if (!userState.isAdmin) { console.log("redirect"); throw redirect("/"); } return true; }, "checkAdminAccess"); const getSummaryData = query(async (days: number) => { "use server"; const { createCaller } = await import("~/server/api/root"); const { getEvent } = await import("vinxi/http"); const caller = await createCaller(getEvent()); return await caller.analytics.getSummary({ days }); }, "getSummaryData"); const getPerformanceData = query(async (days: number) => { "use server"; const { createCaller } = await import("~/server/api/root"); const { getEvent } = await import("vinxi/http"); const caller = await createCaller(getEvent()); return await caller.analytics.getPerformanceStats({ days }); }, "getPerformanceData"); const getPathData = query(async (path: string, days: number) => { "use server"; const { createCaller } = await import("~/server/api/root"); const { getEvent } = await import("vinxi/http"); const caller = await createCaller(getEvent()); return await caller.analytics.getPathStats({ path, days }); }, "getPathData"); export const route = { load: async () => { await checkAdmin(); // Preload initial data with default timeWindow of 7 days void getSummaryData(7); void getPerformanceData(7); } }; interface PerformanceTarget { good: number; acceptable: number; label: string; unit: string; } const PERFORMANCE_TARGETS: Record = { lcp: { good: 1500, acceptable: 2500, label: "LCP", unit: "ms" }, fcp: { good: 1000, acceptable: 1800, label: "FCP", unit: "ms" }, ttfb: { good: 500, acceptable: 800, label: "TTFB", unit: "ms" }, cls: { good: 0.05, acceptable: 0.1, label: "CLS", unit: "" }, avgDuration: { good: 2000, acceptable: 3000, label: "Avg Duration", unit: "ms" } }; function getPerformanceRating( metric: string, value: number ): "good" | "acceptable" | "poor" { const target = PERFORMANCE_TARGETS[metric]; if (!target) return "acceptable"; if (value <= target.good) return "good"; if (value <= target.acceptable) return "acceptable"; return "poor"; } function getRatingColor(rating: "good" | "acceptable" | "poor"): string { switch (rating) { case "good": return "text-green"; case "acceptable": return "text-yellow"; case "poor": return "text-red"; } } function getRatingBgColor(rating: "good" | "acceptable" | "poor"): string { switch (rating) { case "good": return "bg-green/10"; case "acceptable": return "bg-yellow/10"; case "poor": return "bg-red/10"; } } function formatBytes(bytes: number): string { if (bytes < 1024) return `${bytes.toFixed(0)}B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`; return `${(bytes / (1024 * 1024)).toFixed(2)}MB`; } function formatNumber(num: number): string { return new Intl.NumberFormat().format(Math.round(num)); } export default function AnalyticsPage() { const [timeWindow, setTimeWindow] = createSignal(7); const [selectedPath, setSelectedPath] = createSignal(null); const [error, setError] = createSignal(null); const summary = createAsync(() => getSummaryData(timeWindow())); const performanceStats = createAsync(() => getPerformanceData(timeWindow())); const pathStats = createAsync(() => { const path = selectedPath(); if (!path) return Promise.resolve(null); return getPathData(path, timeWindow()); }); return ( <>

Analytics Dashboard

Visitor analytics and performance metrics

{/* Time Window Selector */}
{(days) => ( )}

Error loading analytics

{error()}

{(data) => ( <> {/* Overview Cards */}
Total Requests
{formatNumber(data().totalVisits)}
{formatNumber(data().totalPageVisits)} pages,{" "} {formatNumber(data().totalApiCalls)} API
Unique Visitors
{formatNumber(data().uniqueVisitors)}
Authenticated Users
{formatNumber(data().uniqueUsers)}
Avg. Visits/Day
{formatNumber(data().totalVisits / timeWindow())}
{/* Performance Metrics Section */} 0 } >

Core Web Vitals

{/* Performance Overview Cards */}
LCP (Largest Contentful Paint)
{Math.round(performanceStats()!.avgLcp!)}ms
Target: <1.5s (good), <2.5s (ok)
FCP (First Contentful Paint)
{Math.round(performanceStats()!.avgFcp!)}ms
Target: <1s (good), <1.8s (ok)
CLS (Cumulative Layout Shift)
{performanceStats()!.avgCls!.toFixed(3)}
Target: <0.05 (good), <0.1 (ok)
TTFB (Time to First Byte)
{Math.round(performanceStats()!.avgTtfb!)}ms
Target: <500ms (good), <800ms (ok)
{/* Performance by Page */} 0 } >

Performance by Page

{performanceStats()!.totalWithMetrics} page loads with performance data

{(page) => ( )}
Page LCP FCP CLS TTFB Samples
{page.path} {Math.round(page.avgLcp)}ms {Math.round(page.avgFcp)}ms {page.avgCls.toFixed(3)} {Math.round(page.avgTtfb)}ms {page.count}
{/* Top Pages */}

Top Pages

{(pathData) => { const percentage = (pathData.count / data().totalPageVisits) * 100; return (
setSelectedPath(pathData.path)} >
{pathData.path} {formatNumber(pathData.count)} visits
{percentage.toFixed(1)}% of page traffic
); }}
{/* Top API Calls */}

Top API Calls

{(apiData) => { const percentage = (apiData.count / data().totalApiCalls) * 100; return (
{apiData.path} {formatNumber(apiData.count)}
{percentage.toFixed(1)}% of API traffic
); }}
{/* Device & Browser Stats */}
{/* Device Types */}

Device Types

{(device) => { const totalDevices = data().deviceTypes.reduce( (sum, d) => sum + d.count, 0 ); const percentage = totalDevices > 0 ? (device.count / totalDevices) * 100 : 0; return (
{device.type} {formatNumber(device.count)} ( {percentage.toFixed(1)}%)
); }}
{/* Browsers */}

Browsers

{(browser) => { const totalBrowsers = data().browsers.reduce( (sum, b) => sum + b.count, 0 ); const percentage = totalBrowsers > 0 ? (browser.count / totalBrowsers) * 100 : 0; return (
{browser.browser} {formatNumber(browser.count)} ( {percentage.toFixed(1)}%)
); }}
{/* Top Referrers */} 0}>

Top Referrers

{(referrer) => (
{referrer.referrer} {formatNumber(referrer.count)}
)}
)} {/* Path Details Modal/Section */} {(stats) => (

Path Details: {selectedPath()}

Total Visits
{formatNumber(stats().totalVisits)}
Unique Visitors
{formatNumber(stats().uniqueVisitors)}
Avg. Duration
{stats().avgDurationMs ? `${(stats().avgDurationMs! / 1000).toFixed(1)}s` : "N/A"}
{/* Visits by Day */} 0}>

Visits by Day

{(day) => { const maxVisits = Math.max( ...stats().visitsByDay.map((d) => d.count) ); const percentage = (day.count / maxVisits) * 100; return (
{new Date(day.date).toLocaleDateString()} {formatNumber(day.count)}
); }}
)}
); }