ui enhancements and changes in db tracking everyday active nodes

This commit is contained in:
Kumaraguru 2025-01-13 18:13:52 +00:00
parent 8b867d10cf
commit 25a9a000ef
No known key found for this signature in database
GPG Key ID: 4E4555A84ECD28F7
2 changed files with 154 additions and 68 deletions

View File

@ -83,8 +83,8 @@ export default function Dashboard() {
useEffect(() => {
if (searchQuery.trim()) {
setIsSearching(true);
const results = activePeerIds.filter(peerId =>
peerId.toLowerCase().includes(searchQuery.toLowerCase())
const results = activeNodeIds.filter(nodeId =>
nodeId.toLowerCase().includes(searchQuery.toLowerCase())
);
setSearchResults(results);
setPeerPage(1);
@ -105,12 +105,24 @@ export default function Dashboard() {
});
try {
// Calculate date range based on timeframe
const now = new Date();
const startDate = new Date();
if (timeframe === "7d") {
startDate.setDate(now.getDate() - 7);
} else if (timeframe === "30d") {
startDate.setDate(now.getDate() - 30);
} else if (timeframe === "1y") {
startDate.setDate(now.getDate() - 365);
}
// Fetch metrics
const { data: metricsData, error: metricsError } = await supabase
.from("metrics")
.select("*")
.order("date", { ascending: true })
.limit(timeframe === "7d" ? 7 : timeframe === "30d" ? 30 : 365);
.gte('date', startDate.toISOString().split('T')[0])
.lte('date', now.toISOString().split('T')[0])
.order("date", { ascending: true });
if (metricsError) throw metricsError;
setMetrics(metricsData || []);
@ -201,12 +213,21 @@ export default function Dashboard() {
};
// Calculate statistics
const currentActiveNodes = activeNodes.length;
const totalUniqueNodes = [...new Set(activeNodes.map(node => node.node_id))].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);
// Calculate today's active nodes
const todayStart = new Date();
todayStart.setHours(0, 0, 0, 0);
const todayActiveNodes = [...new Set(
activeNodes
.filter(node => new Date(node.timestamp) >= todayStart)
.map(node => node.node_id)
)].length;
const versionDistribution = activeNodes.reduce((acc, node) => {
acc[node.version] = (acc[node.version] || 0) + 1;
return acc;
@ -223,6 +244,40 @@ export default function Dashboard() {
const totalVersionPages = getPageCount(versionEntries.length);
const totalPeerPages = getPageCount(displayPeerIds.length);
// Prepare chart data
const chartData = metrics.map((day) => ({
date: new Date(day.date),
"Active Nodes": day.active_nodes_count
}));
// Get unique node IDs and their latest records
const nodeRecords = activeNodes.reduce((acc, node) => {
if (!acc[node.node_id] || new Date(acc[node.node_id].timestamp) < new Date(node.timestamp)) {
acc[node.node_id] = node;
}
return acc;
}, {});
const activeNodeIds = Object.keys(nodeRecords);
const displayNodeIds = searchQuery ? searchResults : activeNodeIds;
const paginatedNodeIds = getPaginatedData(displayNodeIds, peerPage);
const totalNodePages = getPageCount(displayNodeIds.length);
// Node details dialog state
const [selectedNode, setSelectedNode] = useState(null);
// Format timestamp for node details
const formatNodeTimestamp = (timestamp) => {
if (!timestamp) return "N/A";
const date = new Date(timestamp);
if (isToday(date)) {
return `Today at ${format(date, "HH:mm")}`;
} else if (isYesterday(date)) {
return `Yesterday at ${format(date, "HH:mm")}`;
}
return format(date, "MMM d, yyyy 'at' HH:mm");
};
return (
<>
<Head>
@ -328,9 +383,9 @@ export default function Dashboard() {
difficulty in getting onboarded to Codex.
</p>
</DialogDescription>
<div className="mt-6 sm:mt-8 space-y-4 sm:space-y-6 border-t border-neutral-800 pt-4 sm:pt-6">
<div className="mt-6 sm:mt-8 space-y-4 sm:space-y-6 pt-4 sm:pt-6">
<div>
<h4 className="text-sm sm:text-base font-semibold text-white mb-2 sm:mb-3">Don't wish to provide data?</h4>
<h4 className="text-sm sm:text-base font-semibold text-white mb-2 sm:mb-3 border-t border-neutral-800 pt-4 sm:pt-6">Don't wish to provide data?</h4>
<p className="text-sm text-neutral-400">
You can still run a Codex node without providing any data. To do this, please follow the steps mentioned in the{' '}
<a
@ -384,8 +439,8 @@ export default function Dashboard() {
<div className="grid grid-cols-2 lg:grid-cols-1 lg:grid-rows-4 gap-4 lg:h-[450px]">
{[
{
title: "Active Nodes",
value: currentActiveNodes,
title: "Total Unique Nodes",
value: totalUniqueNodes,
Icon: Users,
delay: 0,
isLoading: componentLoading.nodes,
@ -398,11 +453,11 @@ export default function Dashboard() {
isLoading: componentLoading.nodes,
},
{
title: "Total Nodes",
value: totalNodes,
title: "Active Today",
value: todayActiveNodes,
Icon: Database,
delay: 0.2,
isLoading: componentLoading.metrics,
isLoading: componentLoading.nodes,
},
{
title: "Last Updated",
@ -481,7 +536,7 @@ export default function Dashboard() {
) : (
<div className="h-full">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={metrics}>
<LineChart data={chartData}>
<CartesianGrid
strokeDasharray="3 3"
stroke="#333"
@ -515,7 +570,7 @@ export default function Dashboard() {
/>
<Line
type="monotone"
dataKey="new_records_count"
dataKey="Active Nodes"
stroke="#7afbaf"
strokeWidth={2}
dot={false}
@ -571,7 +626,7 @@ export default function Dashboard() {
<div className="w-full bg-neutral-800 rounded-full h-2 overflow-hidden">
<motion.div
initial={{ width: 0 }}
animate={{ width: `${(count / currentActiveNodes) * 100}%` }}
animate={{ width: `${(count / totalUniqueNodes) * 100}%` }}
transition={{ duration: 0.5, ease: "easeOut" }}
className="bg-[#7afbaf] h-2 rounded-full"
/>
@ -589,7 +644,7 @@ export default function Dashboard() {
)}
</motion.div>
{/* Active Peer IDs List */}
{/* Active Node IDs List */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
@ -597,58 +652,89 @@ export default function Dashboard() {
className="bg-neutral-900 p-4 sm:p-6 rounded-xl border border-neutral-800
hover:border-neutral-700 transition-colors h-[300px] lg:h-[350px] flex flex-col"
>
<div className="flex flex-col gap-4 sm:gap-6">
<h3 className="text-neutral-400 font-medium flex items-center gap-2">
<Network className="w-5 h-5 opacity-60" />
Active Peer IDs
<div className="flex items-center justify-between mb-4">
<h3 className="text-sm font-medium text-neutral-400 flex items-center gap-2">
<Database className="w-4 h-4 opacity-60" />
Active Node IDs
</h3>
<div className="relative">
<Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-neutral-500" />
<input
type="text"
placeholder="Search peer IDs..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-neutral-800 border border-neutral-700 rounded-lg px-4 py-2
text-sm placeholder-neutral-500 focus:border-[#7afbaf] focus:ring-1
focus:ring-[#7afbaf] transition-colors outline-none"
placeholder="Search node IDs..."
className="pl-9 pr-4 py-2 bg-neutral-800 border border-neutral-700 rounded-lg text-sm
placeholder:text-neutral-500 focus:border-[#7afbaf] focus:ring-1 focus:ring-[#7afbaf]
transition-colors outline-none w-[200px]"
/>
<Search className="w-4 h-4 text-neutral-500 absolute right-3 top-1/2 -translate-y-1/2" />
</div>
</div>
{componentLoading.peers ? (
<ComponentSkeleton className="mt-4" />
) : activePeerIds.length === 0 ? (
<div className="flex items-center justify-center flex-1">
<p className="text-neutral-400">No active peers available</p>
</div>
) : isSearching ? (
<div className="flex items-center justify-center flex-1">
<RotateCw className="w-6 h-6 text-neutral-400 animate-spin" />
</div>
) : searchQuery && searchResults.length === 0 ? (
<div className="flex items-center justify-center flex-1">
<p className="text-neutral-400">No matching peer IDs found</p>
{componentLoading.nodes ? (
<ComponentSkeleton />
) : paginatedNodeIds.length === 0 ? (
<div className="flex-1 flex items-center justify-center">
<p className="text-neutral-400">
{searchQuery ? "No matching node IDs found" : "No active node IDs"}
</p>
</div>
) : (
<>
<div className="space-y-2 overflow-y-auto flex-1 pr-2 mt-4 scrollbar-thin
<div className="space-y-2 overflow-y-auto flex-1 pr-2 scrollbar-thin
scrollbar-thumb-neutral-700 scrollbar-track-neutral-800">
{paginatedPeerIds.map((peerId, index) => (
<motion.div
key={peerId}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.1 * (index % 5) }}
className="bg-neutral-800 p-3 rounded-lg text-xs sm:text-sm font-medium
break-all hover:bg-neutral-700/50 transition-colors"
{paginatedNodeIds.map((nodeId) => {
const node = nodeRecords[nodeId];
return (
<Dialog key={nodeId}>
<DialogTrigger asChild>
<button
className="w-full p-3 bg-neutral-800/50 hover:bg-neutral-800
rounded-lg transition-colors text-left text-sm flex items-center
justify-between group"
>
{peerId}
</motion.div>
))}
<span className="truncate flex-1">{nodeId}</span>
<span className="text-[#7afbaf] opacity-0 group-hover:opacity-100
transition-opacity text-xs">View Details</span>
</button>
</DialogTrigger>
<DialogContent className="p-4 sm:p-6">
<DialogHeader>
<div className="flex items-center gap-3 mb-4">
<img src="/logo.svg" alt="Codex" className="w-8 h-8 sm:w-10 sm:h-10" />
<div className="flex items-center gap-2">
<h1 className="text-lg sm:text-xl font-bold text-white">Codex</h1>
<span className="text-xs text-[#7afbaf] font-bold border border-[#7afbaf] rounded-full px-2 py-0.5">Testnet</span>
</div>
</div>
<div className="space-y-4">
<div>
<h4 className="text-sm text-neutral-400 mb-1">NODE ID</h4>
<p className="text-sm sm:text-base font-medium break-all text-[#7afbaf]">{nodeId}</p>
</div>
<div>
<h4 className="text-sm text-neutral-400 mb-1">VERSION</h4>
<p className="text-sm sm:text-base font-medium text-[#7afbaf]">{node.version}</p>
</div>
<div>
<h4 className="text-sm text-neutral-400 mb-1">LAST ONLINE</h4>
<p className="text-sm sm:text-base font-medium text-[#7afbaf]">
{formatNodeTimestamp(node.timestamp)}
</p>
</div>
<div>
<h4 className="text-sm text-neutral-400 mb-1">CONNECTED DHT PEERS</h4>
<p className="text-sm sm:text-base font-medium text-[#7afbaf]">{node.peer_count}</p>
</div>
</div>
</DialogHeader>
</DialogContent>
</Dialog>
);
})}
</div>
<PaginationControls
currentPage={peerPage}
totalPages={totalPeerPages}
totalPages={totalNodePages}
onPageChange={setPeerPage}
className="mt-4 pt-4 border-t border-neutral-800"
/>

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB