Files
proxdeploy/src/app/(app)/page.tsx
T
florian.wildeandClaude Opus 5.5 a9ea35cab5 fix(ui): Übersicht am Handy nicht breiter als der Bildschirm
Lange Zeilen unter „Letzte Deployments“ sprengten die Gitterspalte; der mobile Browser zoomte heraus, die Navigationsschublade verdeckte dann „Abmelden“. keinQuerscrollen misst jetzt gegen die eingestellte Breite – gegen innerWidth gemessen fiel das Herauszoomen nicht auf.

Refs #023

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 22:06:13 +02:00

113 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Metadata } from "next";
import Link from "next/link";
import { Karte } from "@/components/karte";
import { Status } from "@/components/status";
import { ART_TEXT, STATUS_ANZEIGE } from "@/lib/auftrag-text";
import { bytes, zeitpunkt } from "@/lib/formate";
import { seiteSchuetzen } from "@/server/auth/zugang";
import { auftraegeSuchen } from "@/server/services/auftraege";
import { serverListe } from "@/server/services/server";
import { ServerStatus } from "./server/server-status";
export const metadata: Metadata = { title: "Übersicht" };
/** Server mit Status und freien Ressourcen (aus dem letzten Verbindungstest), letzte Aufträge. */
export default async function Uebersicht() {
const benutzer = await seiteSchuetzen("lesen", "/");
const [server, { eintraege }] = await Promise.all([serverListe(), auftraegeSuchen({ seite: 1 })]);
const letzte = eintraege.slice(0, 8);
return (
<div className="flex flex-col gap-6">
<header>
<p className="text-muted text-sm">Willkommen, {benutzer.anzeigename}</p>
<h1 className="text-2xl font-semibold tracking-tight sm:text-3xl">Übersicht</h1>
</header>
{/* min-w-0: Gitterspalten schrumpfen sonst nicht unter lange Zeilen – am Handy zoomte der Browser dann heraus. */}
<div className="grid gap-6 lg:grid-cols-2 [&>*]:min-w-0">
<Karte
titel="Server"
aktion={
<Link href="/server" className="text-akzent text-sm">
Alle
</Link>
}
>
{server.length === 0 ? (
<Status art="offen">Noch kein Proxmox-Server angelegt.</Status>
) : (
<ul className="divide-linie flex flex-col divide-y">
{server.map((eintrag) => {
const knoten = eintrag.letzterTest?.knoten ?? [];
const online = knoten.filter((k) => k.online);
const frei = online.reduce(
(s, k) => s + ((k.speicherGesamt ?? 0) - (k.speicherBelegt ?? 0)),
0,
);
const cpus = online.reduce((s, k) => s + (k.cpus ?? 0), 0);
return (
<li key={eintrag.name} className="flex flex-col gap-1 py-2.5 first:pt-0 last:pb-0">
<div className="flex items-center justify-between gap-3">
<Link href={`/server/${eintrag.name}`} className="min-w-0 hover:underline">
<span className="font-medium">{eintrag.titel}</span>{" "}
<span className="text-muted font-mono text-sm">{eintrag.name}</span>
</Link>
<ServerStatus server={eintrag} />
</div>
{knoten.length > 0 ? (
<p className="text-muted text-xs">
{online.length} von {knoten.length} {knoten.length === 1 ? "Node" : "Nodes"} online
{online.length > 0 ? ` · ${cpus} CPUs · ${bytes(frei)} RAM frei` : ""}
{eintrag.letzterTest?.cluster ? ` · Cluster ${eintrag.letzterTest.cluster.name}` : ""}
</p>
) : null}
</li>
);
})}
</ul>
)}
</Karte>
<Karte
titel="Letzte Deployments"
aktion={
<Link href="/auftraege" className="text-akzent text-sm">
Verlauf
</Link>
}
>
{letzte.length === 0 ? (
<Status art="offen">Noch keine Deployments.</Status>
) : (
<ul className="divide-linie flex flex-col divide-y">
{letzte.map((a) => {
const [art, text] = STATUS_ANZEIGE[a.status];
return (
<li key={a.id}>
<Link
href={`/auftraege/${a.kurzId}`}
className="hover:bg-panel-hoch/60 -mx-2 flex items-center justify-between gap-3 rounded-lg px-2 py-2"
>
<span className="min-w-0 truncate">
<span className="font-medium">{a.name ?? ART_TEXT[a.art]}</span>
<span className="text-muted text-sm">
{" "}
{a.art !== "AUSROLLEN" && a.name ? `· ${ART_TEXT[a.art]} ` : ""}· {a.server.name}
{a.vmid ? ` · ${a.vmid}` : ""} · {zeitpunkt(a.erstelltAm)}
</span>
</span>
<Status art={art} className="shrink-0 text-sm">
{text}
</Status>
</Link>
</li>
);
})}
</ul>
)}
</Karte>
</div>
</div>
);
}