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>
106 lines
2.7 KiB
TypeScript
106 lines
2.7 KiB
TypeScript
import { clsx } from "clsx";
|
|
import { StatusSymbol } from "@/components/status";
|
|
|
|
/**
|
|
* Formular-Bausteine: echtes `<label>`, Fehlermeldung per `aria-describedby` verbunden,
|
|
* 44 px hoch auf dem Handy.
|
|
*/
|
|
export const EINGABE_KLASSE =
|
|
"bg-panel-hoch border-linie text-text h-11 w-full rounded-lg border px-3 placeholder:text-muted focus:border-akzent disabled:opacity-60 sm:h-10";
|
|
|
|
export function Feld({
|
|
id,
|
|
label,
|
|
hilfe,
|
|
fehler,
|
|
children,
|
|
className,
|
|
}: {
|
|
id: string;
|
|
label: string;
|
|
hilfe?: React.ReactNode;
|
|
fehler?: string;
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
}) {
|
|
return (
|
|
<div className={clsx("flex flex-col gap-1.5", className)}>
|
|
<label htmlFor={id} className="text-sm font-medium">
|
|
{label}
|
|
</label>
|
|
{children}
|
|
{fehler ? (
|
|
<p id={`${id}-fehler`} className="text-fehler text-sm">
|
|
<StatusSymbol art="fehler" className="mr-1 inline translate-y-[0.125em]" />
|
|
{fehler}
|
|
</p>
|
|
) : hilfe ? (
|
|
<p id={`${id}-hilfe`} className="text-muted text-sm">
|
|
{hilfe}
|
|
</p>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/** Verknüpft ein Eingabefeld mit Hilfe- oder Fehlertext. */
|
|
export function beschreibung(id: string, fehler?: string, hilfe?: unknown): Record<string, string | boolean> {
|
|
if (fehler) return { "aria-describedby": `${id}-fehler`, "aria-invalid": true };
|
|
if (hilfe) return { "aria-describedby": `${id}-hilfe` };
|
|
return {};
|
|
}
|
|
|
|
export function Schalter({
|
|
id,
|
|
label,
|
|
hilfe,
|
|
checked,
|
|
onChange,
|
|
disabled,
|
|
}: {
|
|
id: string;
|
|
label: string;
|
|
hilfe?: React.ReactNode;
|
|
checked: boolean;
|
|
onChange: (wert: boolean) => void;
|
|
disabled?: boolean;
|
|
}) {
|
|
return (
|
|
<div className="flex items-start gap-3">
|
|
<input
|
|
id={id}
|
|
type="checkbox"
|
|
checked={checked}
|
|
disabled={disabled}
|
|
onChange={(ereignis) => onChange(ereignis.target.checked)}
|
|
className="accent-akzent mt-0.5 h-5 w-5 shrink-0"
|
|
{...(hilfe ? { "aria-describedby": `${id}-hilfe` } : {})}
|
|
/>
|
|
<div>
|
|
<label htmlFor={id} className="text-sm font-medium">
|
|
{label}
|
|
</label>
|
|
{hilfe ? (
|
|
<p id={`${id}-hilfe`} className="text-muted text-sm">
|
|
{hilfe}
|
|
</p>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/** Text in JetBrains Mono mit Knopf zum Kopieren (Befehle, Fingerprints, Schlüssel). */
|
|
export function Code({ children, className }: { children: string; className?: string }) {
|
|
return (
|
|
<code
|
|
className={clsx(
|
|
"bg-grund border-linie block rounded-lg border px-3 py-2 font-mono text-xs break-all sm:text-sm",
|
|
className,
|
|
)}
|
|
>
|
|
{children}
|
|
</code>
|
|
);
|
|
}
|