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>
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import { benoetigteRechte, versionLesen } from "@/engine/proxmox/faehigkeiten";
|
||||
import type { ProxmoxMock } from "./mock";
|
||||
|
||||
/**
|
||||
* Typische Proxmox-Zustände für Tests. Die Antworten folgen dem Format der echten API
|
||||
* (geprüft gegen das API-Schema von PVE 9.2).
|
||||
*/
|
||||
export type Szenario = {
|
||||
version: string;
|
||||
cluster?: { name: string; nodes: Array<{ name: string; ip: string; online?: boolean }> };
|
||||
/** Storage `local` mit Inhaltstyp import? (ab 8.4 nötig für Template-Bau per API) */
|
||||
importStorage?: boolean;
|
||||
/** Rechte, die dem Token fehlen sollen. */
|
||||
ohneRechte?: string[];
|
||||
/** Ohne Sys.Audit auf / antwortet /cluster/status mit 403. */
|
||||
ohneClusterStatus?: boolean;
|
||||
};
|
||||
|
||||
export function szenarioEinrichten(mock: ProxmoxMock, szenario: Szenario): void {
|
||||
const version = versionLesen(szenario.version)!;
|
||||
const nodes = szenario.cluster?.nodes ?? [{ name: "pve01", ip: "127.0.0.1", online: true }];
|
||||
const ohne = new Set(szenario.ohneRechte ?? []);
|
||||
const alle = benoetigteRechte(version).map((r) => r.recht);
|
||||
const rechte = Object.fromEntries(alle.filter((r) => !ohne.has(r)).map((r) => [r, 1]));
|
||||
|
||||
mock.route("GET", "/version", () => ({
|
||||
data: { version: szenario.version, release: `${version.major}.${version.minor}`, repoid: "abcdef" },
|
||||
}));
|
||||
mock.route("GET", "/cluster/status", () =>
|
||||
szenario.ohneClusterStatus
|
||||
? { status: 403, statusText: "Permission check failed (/, Sys.Audit)" }
|
||||
: {
|
||||
data: [
|
||||
...(szenario.cluster
|
||||
? [
|
||||
{
|
||||
type: "cluster",
|
||||
name: szenario.cluster.name,
|
||||
quorate: 1,
|
||||
nodes: nodes.length,
|
||||
version: 3,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...nodes.map((n, i) => ({
|
||||
type: "node",
|
||||
name: n.name,
|
||||
online: n.online === false ? 0 : 1,
|
||||
ip: n.ip,
|
||||
local: i === 0 ? 1 : 0,
|
||||
nodeid: i + 1,
|
||||
})),
|
||||
],
|
||||
},
|
||||
);
|
||||
mock.route("GET", "/nodes", () => ({
|
||||
data: nodes.map((n) => ({
|
||||
node: n.name,
|
||||
status: n.online === false ? "offline" : "online",
|
||||
cpu: 0.05,
|
||||
maxcpu: 16,
|
||||
mem: 8 * 1024 ** 3,
|
||||
maxmem: 64 * 1024 ** 3,
|
||||
uptime: 3600,
|
||||
})),
|
||||
}));
|
||||
mock.route("GET", /^\/nodes\/([^/]+)\/version$/, () => ({
|
||||
data: { version: szenario.version, release: `${version.major}.${version.minor}`, repoid: "abcdef" },
|
||||
}));
|
||||
mock.route("GET", /^\/nodes\/([^/]+)\/storage$/, () => ({
|
||||
data: [
|
||||
{
|
||||
storage: "local",
|
||||
type: "dir",
|
||||
content: ["iso", "vztmpl", "backup", "snippets", ...(szenario.importStorage ? ["import"] : [])].join(
|
||||
",",
|
||||
),
|
||||
active: 1,
|
||||
enabled: 1,
|
||||
shared: 0,
|
||||
avail: 80 * 1024 ** 3,
|
||||
total: 100 * 1024 ** 3,
|
||||
},
|
||||
{
|
||||
storage: "local-zfs",
|
||||
type: "zfspool",
|
||||
content: "images,rootdir",
|
||||
active: 1,
|
||||
enabled: 1,
|
||||
shared: 0,
|
||||
avail: 800 * 1024 ** 3,
|
||||
total: 1000 * 1024 ** 3,
|
||||
},
|
||||
],
|
||||
}));
|
||||
mock.route("GET", /^\/nodes\/([^/]+)\/network$/, () => ({
|
||||
data: [
|
||||
{ iface: "eno1", type: "eth", active: 1 },
|
||||
{
|
||||
iface: "vmbr0",
|
||||
type: "bridge",
|
||||
active: 1,
|
||||
bridge_vlan_aware: 1,
|
||||
cidr: "10.0.10.2/24",
|
||||
comments: "LAN\n",
|
||||
},
|
||||
{ iface: "vmbr1", type: "bridge", active: 1 },
|
||||
],
|
||||
}));
|
||||
mock.route("GET", "/cluster/sdn/vnets", () =>
|
||||
ohne.has("SDN.Audit")
|
||||
? { status: 403, statusText: "Permission check failed (/sdn, SDN.Audit)" }
|
||||
: { data: [{ vnet: "dmz", zone: "zone1", tag: 30, alias: "DMZ" }] },
|
||||
);
|
||||
mock.route("GET", "/access/permissions", (anfrage) => {
|
||||
const p = anfrage.abfrage.get("path") ?? "/";
|
||||
return { data: { [p]: rechte } };
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user