192 lines
4.7 KiB
TypeScript
192 lines
4.7 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import {
|
|
createVM,
|
|
fetchTemplates,
|
|
fetchSSHKeys,
|
|
type Template,
|
|
type SSHKey,
|
|
} from "../api";
|
|
|
|
interface Props {
|
|
token: string;
|
|
projectID: string;
|
|
tenantID: string;
|
|
onCreated: () => void;
|
|
}
|
|
|
|
export function VMCreate({ token, projectID, tenantID, 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);
|
|
|
|
const [templates, setTemplates] = useState<Template[]>([]);
|
|
const [sshKeys, setSSHKeys] = useState<SSHKey[]>([]);
|
|
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
fetchTemplates(token)
|
|
.then((res) => {
|
|
setTemplates(res.data ?? []);
|
|
if (res.data && res.data.length > 0 && !templateID) {
|
|
setTemplateID(res.data[0].id);
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
if (tenantID) {
|
|
fetchSSHKeys(token, tenantID)
|
|
.then((res) => setSSHKeys(res.data ?? []))
|
|
.catch(() => {});
|
|
}
|
|
}, [open, token, tenantID]);
|
|
|
|
useEffect(() => {
|
|
if (templateID) {
|
|
const tpl = templates.find((t) => t.id === templateID);
|
|
if (tpl && !node) {
|
|
setNode(tpl.proxmox_node);
|
|
}
|
|
}
|
|
}, [templateID, templates]);
|
|
|
|
async function handleCreate() {
|
|
setError("");
|
|
if (!name.trim()) {
|
|
setError("Name erforderlich");
|
|
return;
|
|
}
|
|
if (!templateID) {
|
|
setError("Template erforderlich");
|
|
return;
|
|
}
|
|
if (!node.trim()) {
|
|
setError("Node erforderlich");
|
|
return;
|
|
}
|
|
setBusy(true);
|
|
try {
|
|
await createVM(token, projectID, {
|
|
name: name.trim(),
|
|
template_id: templateID,
|
|
node: node.trim(),
|
|
vcpu,
|
|
ram_mb: ramMB,
|
|
disk_gb: diskGB,
|
|
ssh_key_id: sshKeyID || undefined,
|
|
});
|
|
setName("");
|
|
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
|
|
<select
|
|
value={templateID}
|
|
onChange={(e) => setTemplateID(e.target.value)}
|
|
>
|
|
{templates.map((t) => (
|
|
<option key={t.id} value={t.id}>
|
|
{t.name} (VMID {t.proxmox_template_vmid}, {t.proxmox_node})
|
|
</option>
|
|
))}
|
|
</select>
|
|
</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 (optional)
|
|
<select
|
|
value={sshKeyID}
|
|
onChange={(e) => setSSHKeyID(e.target.value)}
|
|
>
|
|
<option value="">Kein SSH-Key</option>
|
|
{sshKeys.map((k) => (
|
|
<option key={k.id} value={k.id}>
|
|
{k.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
</div>
|
|
<button
|
|
className="primary-button"
|
|
disabled={busy}
|
|
onClick={handleCreate}
|
|
style={{ marginTop: 14 }}
|
|
>
|
|
{busy ? "Erstelle..." : "VM erstellen"}
|
|
</button>
|
|
</div>
|
|
);
|
|
} |