feat: complete MVP epics E7-E10 (provisioning, console proxy, audit, frontend)

This commit is contained in:
Philipp
2026-06-12 10:45:13 +02:00
parent 137c13fa98
commit ac30a19960
33 changed files with 3625 additions and 263 deletions
+133
View File
@@ -0,0 +1,133 @@
import { useState } from "react";
import { createVM } from "../api";
interface Props {
token: string;
projectID: string;
onCreated: () => void;
}
export function VMCreate({ token, projectID, onCreated }: Props) {
const [name, setName] = useState("");
const [templateID, setTemplateID] = useState("");
const [node, setNode] = useState("");
const [vcpu, setVCPU] = useState(2);
const [ramMB, setRAMMB] = useState(2048);
const [diskGB, setDiskGB] = useState(10);
const [sshKeyID, setSSHKeyID] = useState("");
const [error, setError] = useState("");
const [busy, setBusy] = useState(false);
const [open, setOpen] = useState(false);
async function handleCreate() {
setError("");
setBusy(true);
try {
await createVM(token, projectID, {
name,
template_id: templateID,
node,
vcpu,
ram_mb: ramMB,
disk_gb: diskGB,
ssh_key_id: sshKeyID || undefined,
});
setName("");
setTemplateID("");
setOpen(false);
onCreated();
} catch (err) {
setError(err instanceof Error ? err.message : "Fehler");
} finally {
setBusy(false);
}
}
if (!open) {
return (
<button className="primary-button" onClick={() => setOpen(true)}>
Neue VM erstellen
</button>
);
}
return (
<div className="panel">
<div className="panel-header">
<div>
<p className="eyebrow">Provisioning</p>
<h2>VM erstellen</h2>
</div>
<button className="ghost-button" onClick={() => setOpen(false)}>
Abbrechen
</button>
</div>
{error ? <p className="form-error">{error}</p> : null}
<div className="metric-list">
<label>
Name
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="meine-vm"
/>
</label>
<label>
Template-ID
<input
value={templateID}
onChange={(e) => setTemplateID(e.target.value)}
placeholder="UUID des Templates"
/>
</label>
<label>
Node
<input
value={node}
onChange={(e) => setNode(e.target.value)}
placeholder="pve"
/>
</label>
<label>
vCPU
<input
type="number"
value={vcpu}
onChange={(e) => setVCPU(Number(e.target.value))}
/>
</label>
<label>
RAM (MB)
<input
type="number"
value={ramMB}
onChange={(e) => setRAMMB(Number(e.target.value))}
/>
</label>
<label>
Disk (GB)
<input
type="number"
value={diskGB}
onChange={(e) => setDiskGB(Number(e.target.value))}
/>
</label>
<label>
SSH-Key-ID (optional)
<input
value={sshKeyID}
onChange={(e) => setSSHKeyID(e.target.value)}
placeholder="UUID"
/>
</label>
</div>
<button
className="primary-button"
disabled={busy}
onClick={handleCreate}
>
{busy ? "Erstelle..." : "VM erstellen"}
</button>
</div>
);
}