"use client";
import { useEffect, useState } from "react";
import { createClient } from "@supabase/supabase-js";
import { motion } from "framer-motion";
import { format, isToday, isYesterday } from "date-fns";
import Image from "next/image";
import {
Activity,
Users,
Network,
Database,
Clock,
AlertTriangle,
RotateCw,
Search,
ChevronLeft,
ChevronRight,
} from "lucide-react";
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
} from "recharts";
// Initialize Supabase client
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
);
const ITEMS_PER_PAGE = 5;
export default function Dashboard() {
const [metrics, setMetrics] = useState([]);
const [activeNodes, setActiveNodes] = useState([]);
const [timeframe, setTimeframe] = useState("7d");
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [componentLoading, setComponentLoading] = useState({
metrics: true,
nodes: true,
versions: true,
peers: true,
});
// Pagination and Search states
const [versionPage, setVersionPage] = useState(1);
const [peerPage, setPeerPage] = useState(1);
const [searchQuery, setSearchQuery] = useState("");
const [searchResults, setSearchResults] = useState([]);
const [isSearching, setIsSearching] = useState(false);
useEffect(() => {
fetchData();
}, [timeframe]);
// Update search results when query changes
useEffect(() => {
if (searchQuery.trim()) {
setIsSearching(true);
const results = activePeerIds.filter(peerId =>
peerId.toLowerCase().includes(searchQuery.toLowerCase())
);
setSearchResults(results);
setPeerPage(1);
setIsSearching(false);
} else {
setSearchResults([]);
}
}, [searchQuery]);
const fetchData = async () => {
setLoading(true);
setError(null);
setComponentLoading({
metrics: true,
nodes: true,
versions: true,
peers: true,
});
try {
// Fetch metrics
const { data: metricsData, error: metricsError } = await supabase
.from("metrics")
.select("*")
.order("date", { ascending: true })
.limit(timeframe === "7d" ? 7 : timeframe === "30d" ? 30 : 365);
if (metricsError) throw metricsError;
setMetrics(metricsData || []);
setComponentLoading(prev => ({ ...prev, metrics: false }));
// Fetch nodes
const { data: nodesData, error: nodesError } = await supabase
.from("node_records")
.select("*")
.order("timestamp", { ascending: false });
if (nodesError) throw nodesError;
setActiveNodes(nodesData || []);
setComponentLoading(prev => ({ ...prev, nodes: false, versions: false, peers: false }));
if (!metricsData?.length && !nodesData?.length) {
throw new Error("No data available");
}
} catch (error) {
console.error("Error fetching data:", error);
setError(error.message);
} finally {
setLoading(false);
}
};
// Pagination helpers
const getPaginatedData = (data, page, itemsPerPage = ITEMS_PER_PAGE) => {
const startIndex = (page - 1) * itemsPerPage;
return data.slice(startIndex, startIndex + itemsPerPage);
};
const getPageCount = (totalItems, itemsPerPage = ITEMS_PER_PAGE) => {
return Math.ceil(totalItems / itemsPerPage);
};
const PaginationControls = ({ currentPage, totalPages, onPageChange, className = "" }) => (
Page {currentPage} of {totalPages}
);
// Component loading skeleton
const ComponentSkeleton = ({ className = "" }) => (
);
const formatLastUpdated = (timestamp) => {
if (!timestamp) return "N/A";
const date = new Date(timestamp);
const time = format(date, "HH:mm");
let dateText;
if (isToday(date)) {
dateText = "Today";
} else if (isYesterday(date)) {
dateText = "Yesterday";
} else {
dateText = format(date, "dd.MM.yyyy");
}
return { time, dateText };
};
// Calculate statistics
const currentActiveNodes = activeNodes.length;
const averagePeerCount = activeNodes.length
? (activeNodes.reduce((acc, node) => acc + node.peer_count, 0) / activeNodes.length).toFixed(1)
: 0;
const activePeerIds = [...new Set(activeNodes.map((node) => node.peer_id))];
const totalNodes = metrics.reduce((acc, day) => acc + day.new_records_count, 0);
const versionDistribution = activeNodes.reduce((acc, node) => {
acc[node.version] = (acc[node.version] || 0) + 1;
return acc;
}, {});
const lastUpdated = formatLastUpdated(activeNodes[0]?.timestamp);
// Get paginated data
const versionEntries = Object.entries(versionDistribution);
const paginatedVersions = getPaginatedData(versionEntries, versionPage);
const displayPeerIds = searchQuery ? searchResults : activePeerIds;
const paginatedPeerIds = getPaginatedData(displayPeerIds, peerPage);
// Calculate total pages
const totalVersionPages = getPageCount(versionEntries.length);
const totalPeerPages = getPageCount(displayPeerIds.length);
return (
{/* Header */}
Codex Metrics
{error ? (
) : (
{/* Top Section: Stats + Graph */}
{/* Left Column - Stats Cards */}
{[
{
title: "Active Nodes",
value: currentActiveNodes,
Icon: Users,
delay: 0,
isLoading: componentLoading.nodes,
},
{
title: "Average Peer Count",
value: averagePeerCount,
Icon: Network,
delay: 0.1,
isLoading: componentLoading.nodes,
},
{
title: "Total Nodes",
value: totalNodes,
Icon: Database,
delay: 0.2,
isLoading: componentLoading.metrics,
},
{
title: "Last Updated",
value: lastUpdated,
Icon: Clock,
delay: 0.3,
isLoading: componentLoading.nodes,
},
].map((stat) => (
{stat.title}
{stat.isLoading ? (
) : stat.title === "Last Updated" ? (
{lastUpdated.time}
{lastUpdated.dateText}
) : (
{stat.value}
)}
))}
{/* Right Column - Chart */}
Active Nodes Over Time
{componentLoading.metrics ? (
) : metrics.length === 0 ? (
No data available for the selected timeframe
) : (
format(new Date(date), "MMM d")}
fontSize={12}
tickMargin={10}
/>
)}
{/* Bottom Section: Version Distribution + Active Peers */}
{/* Version Distribution */}
Version Distribution
{componentLoading.versions ? (
) : Object.keys(versionDistribution).length === 0 ? (
No version data available
) : (
<>
{paginatedVersions.map(([version, count]) => (
))}
>
)}
{/* Active Peer IDs List */}
{componentLoading.peers ? (
) : activePeerIds.length === 0 ? (
No active peers available
) : isSearching ? (
) : searchQuery && searchResults.length === 0 ? (
No matching peer IDs found
) : (
<>
{paginatedPeerIds.map((peerId, index) => (
{peerId}
))}
>
)}
)}
);
}