"use client"; import { ArrowUpDown, Box, CheckCircle2, ChevronLeft, ChevronRight, List, Loader2, Trophy, XCircle, } from "lucide-react"; import { Bar, BarChart, CartesianGrid, Cell, LabelList, XAxis, } from "recharts"; import React, { useEffect, useMemo, useState } from "react"; import type { ChartConfig } from "@/components/ui/chart"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { ChartContainer, ChartTooltip, ChartTooltipContent, } from "@/components/ui/chart"; import { formattedBadge } from "@/components/command-menu"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; type DataModel = { id: number; ct_type: number; disk_size: number; core_count: number; ram_size: number; os_type: string; os_version: string; disableip6: string; nsapp: string; created_at: string; method: string; pve_version: string; status: string; error: string; type: string; [key: string]: any; }; type SummaryData = { total_entries: number; status_count: Record; nsapp_count: Record; }; // Chart colors optimized for both light and dark modes // Medium-toned colors that are visible and not too flashy in both themes const CHART_COLORS = [ "#5B8DEF", // blue - medium tone "#4ECDC4", // teal - medium tone "#FF8C42", // orange - medium tone "#A78BFA", // purple - medium tone "#F472B6", // pink - medium tone "#38BDF8", // cyan - medium tone "#4ADE80", // green - medium tone "#FBBF24", // yellow - medium tone "#818CF8", // indigo - medium tone "#FB7185", // rose - medium tone "#2DD4BF", // turquoise - medium tone "#C084FC", // violet - medium tone "#60A5FA", // sky blue - medium tone "#84CC16", // lime - medium tone "#F59E0B", // amber - medium tone "#A855F7", // purple - medium tone "#10B981", // emerald - medium tone "#EAB308", // gold - medium tone "#3B82F6", // royal blue - medium tone "#EF4444", // red - medium tone ]; const chartConfigApps = { count: { label: "Installations", color: "hsl(var(--chart-1))", }, } satisfies ChartConfig; export default function DataPage() { const [data, setData] = useState([]); const [summary, setSummary] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(25); const [sortConfig, setSortConfig] = useState<{ key: string; direction: "ascending" | "descending"; } | null>(null); const nf = new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }); useEffect(() => { const fetchData = async () => { setLoading(true); try { const [summaryRes, dataRes] = await Promise.all([ fetch("https://api.htl-braunau.at/data/summary"), fetch( `https://api.htl-braunau.at/data/paginated?page=${currentPage}&limit=${itemsPerPage === 0 ? "" : itemsPerPage }`, ), ]); if (!summaryRes.ok) { throw new Error(`Failed to fetch summary: ${summaryRes.statusText}`); } if (!dataRes.ok) { throw new Error(`Failed to fetch data: ${dataRes.statusText}`); } const summaryData: SummaryData = await summaryRes.json(); const pageData: DataModel[] = await dataRes.json(); setSummary(summaryData); setData(pageData); } catch (err) { setError((err as Error).message); } finally { setLoading(false); } }; fetchData(); }, [currentPage, itemsPerPage]); const sortedData = useMemo(() => { if (!sortConfig) return data; return [...data].sort((a, b) => { if (a[sortConfig.key] < b[sortConfig.key]) { return sortConfig.direction === "ascending" ? -1 : 1; } if (a[sortConfig.key] > b[sortConfig.key]) { return sortConfig.direction === "ascending" ? 1 : -1; } return 0; }); }, [data, sortConfig]); const requestSort = (key: string) => { let direction: "ascending" | "descending" = "ascending"; if ( sortConfig && sortConfig.key === key && sortConfig.direction === "ascending" ) { direction = "descending"; } setSortConfig({ key, direction }); }; const formatDate = (dateString: string): string => { const date = new Date(dateString); return new Intl.DateTimeFormat("en-US", { dateStyle: "medium", timeStyle: "short", }).format(date); }; const getTypeBadge = (type: string) => { if (type === "lxc") return formattedBadge("ct"); if (type === "vm") return formattedBadge("vm"); return null; }; // Stats calculations const successCount = summary?.status_count.done ?? 0; const failureCount = summary?.status_count.failed ?? 0; const totalCount = summary?.total_entries ?? 0; const successRate = totalCount > 0 ? (successCount / totalCount) * 100 : 0; const allApps = useMemo(() => { if (!summary?.nsapp_count) return []; return Object.entries(summary.nsapp_count).sort(([, a], [, b]) => b - a); }, [summary]); const topApps = useMemo(() => { return allApps.slice(0, 15); }, [allApps]); const mostPopularApp = topApps[0]; // Chart Data const appsChartData = topApps.map(([name, count], index) => ({ app: name, count, fill: CHART_COLORS[index % CHART_COLORS.length], })); if (error) { return (

Error loading data: {error}

); } return (
{/* Header */}

Analytics

Overview of container installations and system statistics.

{/* Widgets */}
Total Created
{nf.format(totalCount)}

Total LXC/VM entries found

Success Rate
{successRate.toFixed(1)} %

{nf.format(successCount)} {" "} successful installations

Failures
{nf.format(failureCount)}

Installations encountered errors

Most Popular
{mostPopularApp ? mostPopularApp[0] : "N/A"}

{mostPopularApp ? nf.format(mostPopularApp[1]) : 0} {" "} installations

{/* Graphs */}
Top Applications The most frequently installed applications.
Application Statistics Installation counts for all {" "} {allApps.length} {" "} applications.
{allApps.map(([name, count], index) => (
{index + 1} . {name}
{nf.format(count)}
))}
{loading ? (
) : ( (value.length > 8 ? `${value.slice(0, 8)}...` : value)} /> } /> {appsChartData.map((entry, index) => ( ))} )}
{/* Data Table */}
Installation Log Detailed records of all container creation attempts.
requestSort("status")} > Status {sortConfig?.key === "status" && ( )} requestSort("type")} > Type {sortConfig?.key === "type" && ( )} requestSort("nsapp")} > Application {sortConfig?.key === "nsapp" && ( )} requestSort("os_type")} > OS {sortConfig?.key === "os_type" && ( )} requestSort("disk_size")} > Disk Size {sortConfig?.key === "disk_size" && ( )} requestSort("core_count")} > Core Count {sortConfig?.key === "core_count" && ( )} requestSort("ram_size")} > RAM Size {sortConfig?.key === "ram_size" && ( )} requestSort("created_at")} > Created At {sortConfig?.key === "created_at" && ( )} {loading ? (
{" "} Loading data...
) : sortedData.length > 0 ? ( sortedData.map((item, idx) => ( {item.status === "done" ? ( Success ) : item.status === "failed" ? ( Failed ) : item.status === "installing" ? ( Installing ) : ( {item.status} )} {getTypeBadge(item.type) || ( {item.type} )} {item.nsapp} {item.os_type} {" "} {item.os_version} {item.disk_size} MB {item.core_count} {item.ram_size} MB {formatDate(item.created_at)} )) ) : ( No results found. )}
Page {" "} {currentPage}
); }