generated from Dicken/dickendock
85 lines
3.2 KiB
TypeScript
85 lines
3.2 KiB
TypeScript
import { useServices } from "../../hooks/useServices.js";
|
||
import { useDevices } from "../../hooks/useDevices.js";
|
||
import { useCategories } from "../../hooks/useCategories.js";
|
||
import { AdminPageHeader } from "./AdminPageHeader.js";
|
||
|
||
function StatCard({ label, value }: { label: string; value: number | string }) {
|
||
return (
|
||
<div className="rounded-2xl border border-black/10 bg-white p-5 dark:border-white/10 dark:bg-white/5">
|
||
<div className="text-3xl font-semibold text-black dark:text-white">{value}</div>
|
||
<div className="mt-1 text-sm text-black/50 dark:text-white/50">{label}</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function DashboardPage() {
|
||
const { data: services } = useServices();
|
||
const { data: devices } = useDevices();
|
||
const { data: categories } = useCategories();
|
||
|
||
const onlineDevices = devices?.filter((d) => d.online).length ?? 0;
|
||
const favoriteServices = services?.filter((s) => s.favorite).length ?? 0;
|
||
|
||
const recentlyScanned = [...(devices ?? [])]
|
||
.filter((d) => d.lastScan)
|
||
.sort((a, b) => (b.lastScan ?? "").localeCompare(a.lastScan ?? ""))
|
||
.slice(0, 5);
|
||
|
||
return (
|
||
<div>
|
||
<AdminPageHeader
|
||
title="Dashboard"
|
||
description="Überblick über dein Homelab."
|
||
/>
|
||
|
||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-4">
|
||
<StatCard label="Geräte" value={devices?.length ?? 0} />
|
||
<StatCard label="davon online" value={onlineDevices} />
|
||
<StatCard label="Dienste" value={services?.length ?? 0} />
|
||
<StatCard label="Favoriten" value={favoriteServices} />
|
||
</div>
|
||
|
||
<div className="mt-8">
|
||
<h2 className="mb-3 text-sm font-semibold text-black/60 dark:text-white/60">
|
||
Zuletzt gescannte Geräte
|
||
</h2>
|
||
{recentlyScanned.length === 0 ? (
|
||
<p className="text-sm text-black/40 dark:text-white/40">
|
||
Noch keine Scans durchgeführt. Starte einen Scan unter „Geräte“ oder „Scanner“.
|
||
</p>
|
||
) : (
|
||
<div className="overflow-hidden rounded-2xl border border-black/10 dark:border-white/10">
|
||
<table className="w-full text-sm">
|
||
<tbody>
|
||
{recentlyScanned.map((device) => (
|
||
<tr
|
||
key={device.id}
|
||
className="border-b border-black/5 last:border-0 dark:border-white/5"
|
||
>
|
||
<td className="px-4 py-3 font-medium text-black dark:text-white">
|
||
{device.hostname}
|
||
</td>
|
||
<td className="px-4 py-3 text-black/40 dark:text-white/40">{device.ip}</td>
|
||
<td className="px-4 py-3 text-black/40 dark:text-white/40">
|
||
{device.lastScan ? new Date(device.lastScan).toLocaleString("de-DE") : "–"}
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="mt-8">
|
||
<h2 className="mb-3 text-sm font-semibold text-black/60 dark:text-white/60">
|
||
Kategorien
|
||
</h2>
|
||
<p className="text-sm text-black/40 dark:text-white/40">
|
||
{categories?.length ?? 0} Kategorie(n) angelegt.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|