212 lines
8.6 KiB
TypeScript
212 lines
8.6 KiB
TypeScript
import { useQuery } from "@tanstack/react-query";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from "recharts";
|
|
import { analyticsService } from "@/services";
|
|
import type { StorageByUser, StorageAnalytics } from "@/types/analytics.types";
|
|
import {
|
|
HardDrive,
|
|
FileText,
|
|
Database,
|
|
Users,
|
|
ChevronRight,
|
|
} from "lucide-react";
|
|
|
|
const COLORS = ["#111827", "#4B5563", "#9CA3AF", "#D1D5DB", "#E2E8F0"];
|
|
|
|
interface ChartDataItem {
|
|
name: string;
|
|
value: number;
|
|
}
|
|
|
|
export default function AnalyticsStoragePage() {
|
|
const { data: storage, isLoading } = useQuery<StorageAnalytics>({
|
|
queryKey: ["admin", "analytics", "storage"],
|
|
queryFn: () => analyticsService.getStorageAnalytics(),
|
|
});
|
|
|
|
const formatBytes = (bytes: number) => {
|
|
if (bytes === 0) return "0 Bytes";
|
|
const k = 1024;
|
|
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
|
|
};
|
|
|
|
const chartData: ChartDataItem[] =
|
|
storage?.byCategory?.map((cat) => ({
|
|
name: cat.category,
|
|
value: cat.size,
|
|
})) || [];
|
|
|
|
return (
|
|
<div className="space-y-8 max-w-7xl mx-auto bg-white p-4 min-h-screen">
|
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-gray-900 tracking-tight">
|
|
Storage Intelligence
|
|
</h1>
|
|
<p className="text-gray-500 mt-1">
|
|
Infrastructure resource allocation and data distribution registry.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
<Card className="border shadow-none rounded-none bg-white">
|
|
<CardHeader className="border-b pb-4 bg-gray-50/30 flex flex-row items-center justify-between">
|
|
<CardTitle className="text-xs font-bold uppercase tracking-widest text-gray-400">
|
|
Resource Consumption
|
|
</CardTitle>
|
|
<HardDrive className="w-4 h-4 text-gray-300" />
|
|
</CardHeader>
|
|
<CardContent className="p-8 space-y-8">
|
|
{isLoading ? (
|
|
<div className="h-[300px] flex items-center justify-center text-gray-400 animate-pulse font-medium uppercase tracking-widest text-[10px]">
|
|
Quantifying resources...
|
|
</div>
|
|
) : (
|
|
<>
|
|
<div className="grid grid-cols-2 gap-8">
|
|
<div className="space-y-1">
|
|
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest">
|
|
Aggregate Payload
|
|
</p>
|
|
<p className="text-3xl font-bold text-gray-900 tracking-tighter">
|
|
{storage?.total ? formatBytes(storage.total.size) : "0 B"}
|
|
</p>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest">
|
|
Object Registry
|
|
</p>
|
|
<p className="text-3xl font-bold text-gray-900 tracking-tighter">
|
|
{storage?.total?.files?.toLocaleString() || 0}{" "}
|
|
<span className="text-xs text-gray-400 uppercase ml-1">
|
|
Items
|
|
</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="pt-8 border-t">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<span className="text-[10px] font-bold text-gray-900 uppercase tracking-widest">
|
|
Efficiency Status
|
|
</span>
|
|
<span className="px-2 py-0.5 text-[9px] font-bold uppercase bg-emerald-50 text-emerald-600 border border-emerald-100">
|
|
Optimal
|
|
</span>
|
|
</div>
|
|
<div className="w-full bg-gray-100 h-2">
|
|
<div className="bg-gray-900 h-full w-[42%]" />
|
|
</div>
|
|
<p className="text-[10px] text-gray-400 font-medium mt-2">
|
|
42% Cluster Capacity Utilized
|
|
</p>
|
|
</div>
|
|
</>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="border shadow-none rounded-none bg-white">
|
|
<CardHeader className="border-b pb-4 bg-gray-50/30 flex flex-row items-center justify-between">
|
|
<CardTitle className="text-xs font-bold uppercase tracking-widest text-gray-400">
|
|
Distribution by Taxonomy
|
|
</CardTitle>
|
|
<Database className="w-4 h-4 text-gray-300" />
|
|
</CardHeader>
|
|
<CardContent className="p-8">
|
|
{isLoading ? (
|
|
<div className="h-[300px] flex items-center justify-center">
|
|
...
|
|
</div>
|
|
) : chartData.length > 0 ? (
|
|
<ResponsiveContainer width="100%" height={300}>
|
|
<PieChart>
|
|
<Pie
|
|
data={chartData}
|
|
cx="50%"
|
|
cy="50%"
|
|
innerRadius={60}
|
|
outerRadius={100}
|
|
paddingAngle={4}
|
|
stroke="none"
|
|
dataKey="value"
|
|
>
|
|
{chartData.map((_entry: ChartDataItem, index: number) => (
|
|
<Cell
|
|
key={`cell-${index}`}
|
|
fill={COLORS[index % COLORS.length]}
|
|
/>
|
|
))}
|
|
</Pie>
|
|
<Tooltip
|
|
contentStyle={{
|
|
backgroundColor: "#fff",
|
|
border: "1px solid #E2E8F0",
|
|
borderRadius: "0px",
|
|
boxShadow: "none",
|
|
fontSize: "10px",
|
|
fontWeight: "700",
|
|
textTransform: "uppercase",
|
|
}}
|
|
formatter={(value: number) => formatBytes(value)}
|
|
/>
|
|
</PieChart>
|
|
</ResponsiveContainer>
|
|
) : (
|
|
<div className="h-[300px] flex items-center justify-center text-gray-400 italic font-medium uppercase tracking-widest text-[10px]">
|
|
No category distribution.
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{storage?.topUsers && storage.topUsers.length > 0 && (
|
|
<Card className="border shadow-none rounded-none bg-white">
|
|
<CardHeader className="border-b pb-4 bg-gray-50/30 flex flex-row items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<Users className="w-4 h-4 text-gray-400" />
|
|
<CardTitle className="text-xs font-bold uppercase tracking-widest text-gray-400">
|
|
High-Consumption Operators
|
|
</CardTitle>
|
|
</div>
|
|
<FileText className="w-4 h-4 text-gray-300" />
|
|
</CardHeader>
|
|
<CardContent className="p-0">
|
|
<div className="divide-y divide-gray-100">
|
|
{storage.topUsers.map((user: StorageByUser, index: number) => (
|
|
<div
|
|
key={index}
|
|
className="flex items-center justify-between p-6 hover:bg-gray-50 transition-colors group"
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
<div className="w-8 h-8 flex items-center justify-center bg-gray-50 border text-[10px] font-bold text-gray-400">
|
|
0{index + 1}
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-bold text-gray-900 tracking-tighter">
|
|
{user.userName || user.email}
|
|
</p>
|
|
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest mt-0.5">
|
|
{user.documentCount.toLocaleString()} Object References
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-8">
|
|
<span className="text-sm font-bold text-gray-900 tabular-nums">
|
|
{formatBytes(user.storageUsed)}
|
|
</span>
|
|
<ChevronRight className="w-4 h-4 text-gray-300 opacity-0 group-hover:opacity-100 transition-all" />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|