Files
proxdeploy/src/app/(app)/server/[name]/page.tsx
T
florian.wildeandClaude Opus 5.5 86b650e20a feat(server): Verbindungstest mit Fähigkeitserkennung
Der Test liest Version, Cluster, Nodes, Storages, Bridges, VNets und die
effektiven Rechte des Tokens und leitet daraus ab, welcher Vorgang per
API, per SSH oder gar nicht geht – je PVE-Version: Template-Bau per API
erst ab 8.4 mit import-Storage, Snippets und LXC-Exec nur per SSH.

Fehlende Rechte erscheinen einzeln mit Pfad und Zweck; die Rollenliste
in docs/proxmox-rechte.md prüft ein Test gegen den Code. SSH-Host-Keys
werden wie Zertifikate erst nach Bestätigung genutzt, und bestätigt
werden kann nur ein Schlüssel, den der letzte Test gesehen hat.

Statussymbole in der GUI sind jetzt gezeichnet: ◐ und ○ fehlen im
Web-Subset von JetBrains Mono.

Refs #003

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:44:27 +02:00

113 lines
4.6 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 { Pencil } from "lucide-react";
import type { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import { Code } from "@/components/formular";
import { Karte } from "@/components/karte";
import { Status } from "@/components/status";
import { prisma } from "@/lib/db";
import { darf } from "@/server/auth/rechte";
import { seiteSchuetzen } from "@/server/auth/zugang";
import { serverSicht } from "@/server/services/server";
import { ServerStatus } from "../server-status";
import { ServerAktionen } from "./server-aktionen";
import { Verbindungsbericht } from "./verbindungsbericht";
export async function generateMetadata({ params }: { params: Promise<{ name: string }> }): Promise<Metadata> {
return { title: (await params).name };
}
export default async function ServerDetail({ params }: { params: Promise<{ name: string }> }) {
const { name } = await params;
const benutzer = await seiteSchuetzen("lesen", `/server/${name}`);
const datensatz = await prisma.server.findUnique({ where: { name } });
if (!datensatz) notFound();
const server = serverSicht(datensatz);
const verwalten = darf(benutzer.rolle, "serverVerwalten");
return (
<div className="flex flex-col gap-6">
<header className="flex flex-wrap items-end justify-between gap-4">
<div className="min-w-0">
<p className="text-muted font-mono text-sm">{server.name}</p>
<h1 className="text-2xl font-semibold tracking-tight sm:text-3xl">{server.titel}</h1>
</div>
{verwalten ? (
<Link
href={`/server/${server.name}/bearbeiten`}
className="border-linie bg-panel-hoch hover:border-muted inline-flex h-11 items-center gap-2 rounded-lg border px-4 text-sm font-semibold sm:h-10"
>
<Pencil size={16} aria-hidden="true" /> Bearbeiten
</Link>
) : null}
</header>
<Karte titel="Verbindung" aktion={<ServerStatus server={server} />}>
<dl className="grid gap-x-6 gap-y-2 text-sm sm:grid-cols-[max-content_1fr]">
<dt className="text-muted">Adresse</dt>
<dd className="font-mono break-all">{server.url}</dd>
<dt className="text-muted">Token-ID</dt>
<dd className="font-mono break-all">{server.tokenId}</dd>
<dt className="text-muted">TLS</dt>
<dd>
{server.fingerabdruck ? (
<span className="flex flex-col gap-1">
<span>Fingerprint gepinnt{server.tlsPruefen ? " und Kette geprüft" : ""}</span>
<span className="text-muted font-mono text-xs break-all">{server.fingerabdruck}</span>
</span>
) : (
"Kette gegen Systemzertifikate geprüft"
)}
</dd>
<dt className="text-muted">Templates</dt>
<dd className="font-mono">
{server.templateVmidVon}–{server.templateVmidBis}
</dd>
<dt className="text-muted">Gäste ab</dt>
<dd className="font-mono">{server.gastVmidVon}</dd>
</dl>
</Karte>
<Verbindungsbericht
name={server.name}
bericht={server.letzterTest}
geprueftAm={server.geprueftAm}
verwalten={verwalten}
/>
<Karte titel="SSH zu den Nodes (optional)">
{!server.sshAktiv ? (
<Status art="offen">Nicht genutzt. Deployments laufen dann nur über die API.</Status>
) : server.sshOeffentlich ? (
<div className="flex flex-col gap-3 text-sm">
<p>
Diesen Schlüssel auf jedem Node für <span className="font-mono">{server.sshBenutzer}</span>{" "}
hinterlegen. Im Cluster genügt ein Node:{" "}
<span className="font-mono">/root/.ssh/authorized_keys</span> zeigt dort auf die gemeinsame
Datei in <span className="font-mono">/etc/pve/priv/</span>.
</p>
<Code>{`echo '${server.sshOeffentlich}' >> ~/.ssh/authorized_keys`}</Code>
<p className="text-muted">
Host-Keys erscheinen nach dem Verbindungstest beim jeweiligen Node und werden erst nach
Bestätigung genutzt.
</p>
</div>
) : (
<Status art="laeuft">SSH ist eingeschaltet, aber es gibt noch keinen Schlüssel.</Status>
)}
{verwalten && server.sshAktiv ? (
<div className="mt-4">
<ServerAktionen name={server.name} art="ssh" erneuern={Boolean(server.sshOeffentlich)} />
</div>
) : null}
</Karte>
{verwalten ? (
<Karte titel="Gefahrenzone">
<ServerAktionen name={server.name} art="loeschen" />
</Karte>
) : null}
</div>
);
}