Can create NAT networks
This commit is contained in:
@ -30,6 +30,12 @@ export interface VMDisk {
|
||||
deleteType?: "keepfile" | "deletefile";
|
||||
}
|
||||
|
||||
export type VMNetInterface = VMNetUserspaceSLIRPStack;
|
||||
|
||||
export interface VMNetUserspaceSLIRPStack {
|
||||
type: "UserspaceSLIRPStack";
|
||||
}
|
||||
|
||||
interface VMInfoInterface {
|
||||
name: string;
|
||||
uuid?: string;
|
||||
@ -43,6 +49,7 @@ interface VMInfoInterface {
|
||||
vnc_access: boolean;
|
||||
iso_file?: string;
|
||||
disks: VMDisk[];
|
||||
networks: VMNetInterface[];
|
||||
}
|
||||
|
||||
export class VMInfo implements VMInfoInterface {
|
||||
@ -58,6 +65,7 @@ export class VMInfo implements VMInfoInterface {
|
||||
vnc_access: boolean;
|
||||
iso_file?: string;
|
||||
disks: VMDisk[];
|
||||
networks: VMNetUserspaceSLIRPStack[];
|
||||
|
||||
constructor(int: VMInfoInterface) {
|
||||
this.name = int.name;
|
||||
@ -72,6 +80,7 @@ export class VMInfo implements VMInfoInterface {
|
||||
this.vnc_access = int.vnc_access;
|
||||
this.iso_file = int.iso_file;
|
||||
this.disks = int.disks;
|
||||
this.networks = int.networks;
|
||||
}
|
||||
|
||||
static NewEmpty(): VMInfo {
|
||||
@ -83,6 +92,7 @@ export class VMInfo implements VMInfoInterface {
|
||||
number_vcpu: 1,
|
||||
vnc_access: true,
|
||||
disks: [],
|
||||
networks: [],
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,16 @@
|
||||
import { FormControl, InputLabel, MenuItem, Select } from "@mui/material";
|
||||
import {
|
||||
FormControl,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Select,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { TextInput } from "./TextInput";
|
||||
|
||||
export interface SelectOption {
|
||||
value?: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export function SelectInput(p: {
|
||||
@ -33,7 +40,18 @@ export function SelectInput(p: {
|
||||
value={e.value}
|
||||
style={{ fontStyle: e.value === undefined ? "italic" : undefined }}
|
||||
>
|
||||
{e.label}
|
||||
<div>
|
||||
{e.label}
|
||||
{e.description && (
|
||||
<Typography
|
||||
component={"div"}
|
||||
variant="caption"
|
||||
style={{ whiteSpace: "normal" }}
|
||||
>
|
||||
{e.description}
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
|
116
virtweb_frontend/src/widgets/forms/VMNetworksList.tsx
Normal file
116
virtweb_frontend/src/widgets/forms/VMNetworksList.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
import { mdiNetworkOutline } from "@mdi/js";
|
||||
import Icon from "@mdi/react";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
IconButton,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemText,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import { VMInfo, VMNetInterface } from "../../api/VMApi";
|
||||
import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
|
||||
import { SelectInput } from "./SelectInput";
|
||||
|
||||
export function VMNetworksList(p: {
|
||||
vm: VMInfo;
|
||||
onChange?: () => void;
|
||||
editable: boolean;
|
||||
}): React.ReactElement {
|
||||
const addNew = () => {
|
||||
p.vm.networks.push({ type: "UserspaceSLIRPStack" });
|
||||
p.onChange?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* networks list */}
|
||||
{p.vm.networks.map((n, num) => (
|
||||
<NetworkInfo
|
||||
key={num}
|
||||
editable={p.editable}
|
||||
network={n}
|
||||
onChange={p.onChange}
|
||||
removeFromList={() => {
|
||||
p.vm.networks.splice(num, 1);
|
||||
p.onChange?.();
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{p.editable && (
|
||||
<Button onClick={addNew}>Add a new network interface</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function NetworkInfo(p: {
|
||||
editable: boolean;
|
||||
network: VMNetInterface;
|
||||
onChange?: () => void;
|
||||
removeFromList: () => void;
|
||||
}): React.ReactElement {
|
||||
const confirm = useConfirm();
|
||||
const deleteNetwork = async () => {
|
||||
if (
|
||||
!(await confirm("Do you really want to remove this network interface?"))
|
||||
)
|
||||
return;
|
||||
|
||||
p.removeFromList();
|
||||
p.onChange?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ListItem
|
||||
secondaryAction={
|
||||
p.editable && (
|
||||
<IconButton
|
||||
edge="end"
|
||||
aria-label="remove network"
|
||||
onClick={deleteNetwork}
|
||||
>
|
||||
<Tooltip title="Remove network">
|
||||
<DeleteIcon />
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
)
|
||||
}
|
||||
>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<Icon path={mdiNetworkOutline} />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={
|
||||
p.editable ? (
|
||||
<SelectInput
|
||||
label=""
|
||||
editable
|
||||
value={p.network.type}
|
||||
onValueChange={(v) => {
|
||||
p.network.type = v as any;
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
label: "Userspace SLIRP stack",
|
||||
value: "UserspaceSLIRPStack",
|
||||
description:
|
||||
"Provides a virtual LAN with NAT to the outside world. The virtual network has DHCP & DNS services",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
p.network.type
|
||||
)
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -13,6 +13,7 @@ import { VMDisksList } from "../forms/VMDisksList";
|
||||
import { VMSelectIsoInput } from "../forms/VMSelectIsoInput";
|
||||
import { VMScreenshot } from "./VMScreenshot";
|
||||
import { ResAutostartInput } from "../forms/ResAutostartInput";
|
||||
import { VMNetworksList } from "../forms/VMNetworksList";
|
||||
|
||||
interface DetailsProps {
|
||||
vm: VMInfo;
|
||||
@ -202,6 +203,11 @@ function VMDetailsInner(
|
||||
/>
|
||||
<VMDisksList vm={p.vm} editable={p.editable} onChange={p.onChange} />
|
||||
</EditSection>
|
||||
|
||||
{/* Networks section */}
|
||||
<EditSection title="Networks">
|
||||
<VMNetworksList vm={p.vm} editable={p.editable} onChange={p.onChange} />
|
||||
</EditSection>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user