Fix dashboard BigInt crash when storage fields are missing.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jos Vooges | STH 2026-08-25 01:03:04 +02:00
parent 39ecf1af56
commit 9b557cee54
2 changed files with 39 additions and 15 deletions

View file

@ -15,10 +15,17 @@ interface Dashboard {
currentBandwidth: string; currentBandwidth: string;
} }
function formatBytes(bytes: string): string { function formatBytes(bytes: string | number | null | undefined): string {
const n = BigInt(bytes); if (bytes === null || bytes === undefined || bytes === "") return "0 B";
let n: bigint;
try {
n = typeof bytes === "bigint" ? bytes : BigInt(String(bytes));
} catch {
return "0 B";
}
const units = ["B", "KB", "MB", "GB", "TB"]; const units = ["B", "KB", "MB", "GB", "TB"];
let val = Number(n); let val = Number(n);
if (!Number.isFinite(val)) return "0 B";
let i = 0; let i = 0;
while (val >= 1024 && i < units.length - 1) { while (val >= 1024 && i < units.length - 1) {
val /= 1024; val /= 1024;
@ -30,12 +37,21 @@ function formatBytes(bytes: string): string {
export default function DashboardPage() { export default function DashboardPage() {
useAuth(); useAuth();
const [data, setData] = useState<Dashboard | null>(null); const [data, setData] = useState<Dashboard | null>(null);
const [error, setError] = useState("");
useEffect(() => { useEffect(() => {
fetch("/api/v1/admin/dashboard", { credentials: "include" }) fetch("/api/v1/admin/dashboard", { credentials: "include" })
.then((r) => r.json()) .then(async (r) => {
.then(setData) const json = await r.json();
.catch(console.error); if (!r.ok) {
throw new Error(json.error?.message ?? `HTTP ${r.status}`);
}
setData(json);
})
.catch((err) => {
console.error(err);
setError(err instanceof Error ? err.message : "Laden mislukt");
});
}, []); }, []);
return ( return (
@ -43,42 +59,48 @@ export default function DashboardPage() {
<Nav /> <Nav />
<div className="container"> <div className="container">
<h1 style={{ marginBottom: "2rem", fontSize: "1.75rem" }}>Dashboard</h1> <h1 style={{ marginBottom: "2rem", fontSize: "1.75rem" }}>Dashboard</h1>
{error && <div className="error">{error}</div>}
{data && ( {data && (
<div className="grid"> <div className="grid">
<div className="card"> <div className="card">
<h2>Nodes Online</h2> <h2>Nodes Online</h2>
<div className="value">{data.nodesOnline}</div> <div className="value">{data.nodesOnline ?? 0}</div>
</div> </div>
<div className="card"> <div className="card">
<h2>Nodes Offline</h2> <h2>Nodes Offline</h2>
<div className="value">{data.nodesOffline}</div> <div className="value">{data.nodesOffline ?? 0}</div>
</div> </div>
<div className="card"> <div className="card">
<h2>Movies</h2> <h2>Movies</h2>
<div className="value">{data.movies}</div> <div className="value">{data.movies ?? 0}</div>
</div> </div>
<div className="card"> <div className="card">
<h2>Series</h2> <h2>Series</h2>
<div className="value">{data.series}</div> <div className="value">{data.series ?? 0}</div>
</div> </div>
<div className="card"> <div className="card">
<h2>Episodes</h2> <h2>Episodes</h2>
<div className="value">{data.episodes}</div> <div className="value">{data.episodes ?? 0}</div>
</div> </div>
<div className="card"> <div className="card">
<h2>Active Streams</h2> <h2>Active Streams</h2>
<div className="value">{data.activeStreams}</div> <div className="value">{data.activeStreams ?? 0}</div>
</div> </div>
<div className="card"> <div className="card">
<h2>Total Storage</h2> <h2>Total Storage</h2>
<div className="value" style={{ fontSize: "1.5rem" }}>{formatBytes(data.totalStorage)}</div> <div className="value" style={{ fontSize: "1.5rem" }}>
{formatBytes(data.totalStorage)}
</div>
</div> </div>
<div className="card"> <div className="card">
<h2>Free Storage</h2> <h2>Free Storage</h2>
<div className="value" style={{ fontSize: "1.5rem" }}>{formatBytes(data.freeStorage)}</div> <div className="value" style={{ fontSize: "1.5rem" }}>
{formatBytes(data.freeStorage)}
</div>
</div> </div>
</div> </div>
)} )}
{!data && !error && <p style={{ color: "#71717a" }}>Laden...</p>}
</div> </div>
</> </>
); );

View file

@ -29,8 +29,10 @@ interface Movie {
files: MovieFile[]; files: MovieFile[];
} }
function formatBytes(bytes: string): string { function formatBytes(bytes: string | number | null | undefined): string {
let val = Number(bytes); const valNum = Number(bytes ?? 0);
if (!Number.isFinite(valNum) || valNum < 0) return "0 B";
let val = valNum;
const units = ["B", "KB", "MB", "GB", "TB"]; const units = ["B", "KB", "MB", "GB", "TB"];
let i = 0; let i = 0;
while (val >= 1024 && i < units.length - 1) { while (val >= 1024 && i < units.length - 1) {