Can define IP reservations for networks
This commit is contained in:
parent
ed1bd806d7
commit
afebe97395
@ -35,6 +35,7 @@ struct ServerConstraints {
|
|||||||
disk_size: LenConstraints,
|
disk_size: LenConstraints,
|
||||||
net_name_size: LenConstraints,
|
net_name_size: LenConstraints,
|
||||||
net_title_size: LenConstraints,
|
net_title_size: LenConstraints,
|
||||||
|
dhcp_reservation_host_name: LenConstraints,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder {
|
pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder {
|
||||||
@ -66,6 +67,8 @@ pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder {
|
|||||||
|
|
||||||
net_name_size: LenConstraints { min: 2, max: 50 },
|
net_name_size: LenConstraints { min: 2, max: 50 },
|
||||||
net_title_size: LenConstraints { min: 0, max: 50 },
|
net_title_size: LenConstraints { min: 0, max: 50 },
|
||||||
|
|
||||||
|
dhcp_reservation_host_name: LenConstraints { min: 2, max: 250 },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -432,10 +432,49 @@ pub struct NetworkIPXML {
|
|||||||
pub dhcp: Option<NetworkDHCPXML>,
|
pub dhcp: Option<NetworkDHCPXML>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl NetworkIPXML {
|
||||||
|
pub fn into_xml(mut self) -> anyhow::Result<String> {
|
||||||
|
let mut hosts_xml = vec![];
|
||||||
|
|
||||||
|
if let Some(dhcp) = &mut self.dhcp {
|
||||||
|
for host in &dhcp.hosts {
|
||||||
|
let mut host_xml = serde_xml_rs::to_string(&host)?;
|
||||||
|
|
||||||
|
// In case of IPv6, mac address should not be specified
|
||||||
|
host_xml = host_xml.replace("mac=\"\"", "");
|
||||||
|
|
||||||
|
// strip xml tag
|
||||||
|
let start_offset = host_xml.find("<host").unwrap();
|
||||||
|
hosts_xml.push(host_xml[start_offset..].to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
dhcp.hosts = vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut res = serde_xml_rs::to_string(&self)?;
|
||||||
|
let hosts_xml = hosts_xml.join("\n");
|
||||||
|
res = res.replace("</dhcp>", &format!("{hosts_xml}</dhcp>"));
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
|
#[derive(serde::Serialize, serde::Deserialize, Debug)]
|
||||||
#[serde(rename = "dhcp")]
|
#[serde(rename = "dhcp")]
|
||||||
pub struct NetworkDHCPXML {
|
pub struct NetworkDHCPXML {
|
||||||
pub range: NetworkDHCPRangeXML,
|
pub range: NetworkDHCPRangeXML,
|
||||||
|
#[serde(default, rename = "host", skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub hosts: Vec<NetworkDHCPHostXML>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Debug)]
|
||||||
|
#[serde(rename = "host")]
|
||||||
|
pub struct NetworkDHCPHostXML {
|
||||||
|
#[serde(rename(serialize = "@mac"), default)]
|
||||||
|
pub mac: String,
|
||||||
|
#[serde(rename(serialize = "@name"))]
|
||||||
|
pub name: String,
|
||||||
|
#[serde(rename(serialize = "@ip"))]
|
||||||
|
pub ip: IpAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
|
#[derive(serde::Serialize, serde::Deserialize, Debug)]
|
||||||
@ -474,7 +513,8 @@ impl NetworkXML {
|
|||||||
let mut ips_xml = Vec::with_capacity(self.ips.len());
|
let mut ips_xml = Vec::with_capacity(self.ips.len());
|
||||||
for ip in self.ips {
|
for ip in self.ips {
|
||||||
log::debug!("Serialize {ip:?}");
|
log::debug!("Serialize {ip:?}");
|
||||||
let ip_xml = serde_xml_rs::to_string(&ip)?;
|
let ip_xml = ip.into_xml()?;
|
||||||
|
// strip xml tag
|
||||||
let start_offset = ip_xml.find("<ip").unwrap();
|
let start_offset = ip_xml.find("<ip").unwrap();
|
||||||
ips_xml.push(ip_xml[start_offset..].to_string());
|
ips_xml.push(ip_xml[start_offset..].to_string());
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,9 @@ use crate::libvirt_lib_structures::{
|
|||||||
DevicesXML, DiskBootXML, DiskDriverXML, DiskReadOnlyXML, DiskSourceXML, DiskTargetXML, DiskXML,
|
DevicesXML, DiskBootXML, DiskDriverXML, DiskReadOnlyXML, DiskSourceXML, DiskTargetXML, DiskXML,
|
||||||
DomainCPUTopology, DomainCPUXML, DomainInputXML, DomainMemoryXML, DomainNetInterfaceXML,
|
DomainCPUTopology, DomainCPUXML, DomainInputXML, DomainMemoryXML, DomainNetInterfaceXML,
|
||||||
DomainVCPUXML, DomainXML, FeaturesXML, GraphicsXML, NetIntSourceXML, NetMacAddress,
|
DomainVCPUXML, DomainXML, FeaturesXML, GraphicsXML, NetIntSourceXML, NetMacAddress,
|
||||||
NetworkDHCPRangeXML, NetworkDHCPXML, NetworkDNSForwarderXML, NetworkDNSXML, NetworkDomainXML,
|
NetworkDHCPHostXML, NetworkDHCPRangeXML, NetworkDHCPXML, NetworkDNSForwarderXML, NetworkDNSXML,
|
||||||
NetworkForwardXML, NetworkIPXML, NetworkXML, OSLoaderXML, OSTypeXML, TPMBackendXML,
|
NetworkDomainXML, NetworkForwardXML, NetworkIPXML, NetworkXML, OSLoaderXML, OSTypeXML,
|
||||||
TPMDeviceXML, VideoModelXML, VideoXML, XMLUuid, ACPIXML, OSXML,
|
TPMBackendXML, TPMDeviceXML, VideoModelXML, VideoXML, XMLUuid, ACPIXML, OSXML,
|
||||||
};
|
};
|
||||||
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
|
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
|
||||||
use crate::utils::disks_utils::Disk;
|
use crate::utils::disks_utils::Disk;
|
||||||
@ -442,18 +442,45 @@ pub enum NetworkForwardMode {
|
|||||||
Isolated,
|
Isolated,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
||||||
|
pub struct DHCPv4HostReservation {
|
||||||
|
mac: String,
|
||||||
|
name: String,
|
||||||
|
ip: Ipv4Addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
||||||
|
pub struct IPv4DHCPConfig {
|
||||||
|
start: Ipv4Addr,
|
||||||
|
end: Ipv4Addr,
|
||||||
|
hosts: Vec<DHCPv4HostReservation>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
||||||
pub struct IPV4Config {
|
pub struct IPV4Config {
|
||||||
bridge_address: Ipv4Addr,
|
bridge_address: Ipv4Addr,
|
||||||
prefix: u32,
|
prefix: u32,
|
||||||
dhcp_range: Option<[Ipv4Addr; 2]>,
|
dhcp: Option<IPv4DHCPConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
||||||
|
pub struct DHCPv6HostReservation {
|
||||||
|
name: String,
|
||||||
|
ip: Ipv6Addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
||||||
|
pub struct IPv6DHCPConfig {
|
||||||
|
start: Ipv6Addr,
|
||||||
|
end: Ipv6Addr,
|
||||||
|
hosts: Vec<DHCPv6HostReservation>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
||||||
pub struct IPV6Config {
|
pub struct IPV6Config {
|
||||||
bridge_address: Ipv6Addr,
|
bridge_address: Ipv6Addr,
|
||||||
prefix: u32,
|
prefix: u32,
|
||||||
dhcp_range: Option<[Ipv6Addr; 2]>,
|
dhcp: Option<IPv6DHCPConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Network configuration
|
/// Network configuration
|
||||||
@ -510,11 +537,20 @@ impl NetworkInfo {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.mask()
|
.mask()
|
||||||
.into(),
|
.into(),
|
||||||
dhcp: ipv4.dhcp_range.map(|[start, end]| NetworkDHCPXML {
|
dhcp: ipv4.dhcp.map(|dhcp| NetworkDHCPXML {
|
||||||
range: NetworkDHCPRangeXML {
|
range: NetworkDHCPRangeXML {
|
||||||
start: IpAddr::V4(start),
|
start: IpAddr::V4(dhcp.start),
|
||||||
end: IpAddr::V4(end),
|
end: IpAddr::V4(dhcp.end),
|
||||||
},
|
},
|
||||||
|
hosts: dhcp
|
||||||
|
.hosts
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| NetworkDHCPHostXML {
|
||||||
|
mac: c.mac,
|
||||||
|
name: c.name,
|
||||||
|
ip: c.ip.into(),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -528,11 +564,20 @@ impl NetworkInfo {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.mask()
|
.mask()
|
||||||
.into(),
|
.into(),
|
||||||
dhcp: ipv6.dhcp_range.map(|[start, end]| NetworkDHCPXML {
|
dhcp: ipv6.dhcp.map(|dhcp| NetworkDHCPXML {
|
||||||
range: NetworkDHCPRangeXML {
|
range: NetworkDHCPRangeXML {
|
||||||
start: IpAddr::V6(start),
|
start: IpAddr::V6(dhcp.start),
|
||||||
end: IpAddr::V6(end),
|
end: IpAddr::V6(dhcp.end),
|
||||||
},
|
},
|
||||||
|
hosts: dhcp
|
||||||
|
.hosts
|
||||||
|
.into_iter()
|
||||||
|
.map(|h| NetworkDHCPHostXML {
|
||||||
|
mac: "".to_string(),
|
||||||
|
name: h.name,
|
||||||
|
ip: h.ip.into(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -588,10 +633,19 @@ impl NetworkInfo {
|
|||||||
as u32,
|
as u32,
|
||||||
p => p,
|
p => p,
|
||||||
},
|
},
|
||||||
dhcp_range: i
|
dhcp: i.dhcp.as_ref().map(|d| IPv4DHCPConfig {
|
||||||
.dhcp
|
start: extract_ipv4(d.range.start),
|
||||||
.as_ref()
|
end: extract_ipv4(d.range.end),
|
||||||
.map(|d| [extract_ipv4(d.range.start), extract_ipv4(d.range.end)]),
|
hosts: d
|
||||||
|
.hosts
|
||||||
|
.iter()
|
||||||
|
.map(|h| DHCPv4HostReservation {
|
||||||
|
mac: h.mac.to_string(),
|
||||||
|
name: h.name.to_string(),
|
||||||
|
ip: extract_ipv4(h.ip),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
ip_v6: xml
|
ip_v6: xml
|
||||||
.ips
|
.ips
|
||||||
@ -605,10 +659,18 @@ impl NetworkInfo {
|
|||||||
as u32,
|
as u32,
|
||||||
p => p,
|
p => p,
|
||||||
},
|
},
|
||||||
dhcp_range: i
|
dhcp: i.dhcp.as_ref().map(|d| IPv6DHCPConfig {
|
||||||
.dhcp
|
start: extract_ipv6(d.range.start),
|
||||||
.as_ref()
|
end: extract_ipv6(d.range.end),
|
||||||
.map(|d| [extract_ipv6(d.range.start), extract_ipv6(d.range.end)]),
|
hosts: d
|
||||||
|
.hosts
|
||||||
|
.iter()
|
||||||
|
.map(|h| DHCPv6HostReservation {
|
||||||
|
name: h.name.to_string(),
|
||||||
|
ip: extract_ipv6(h.ip),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,22 @@
|
|||||||
import { APIClient } from "./ApiClient";
|
import { APIClient } from "./ApiClient";
|
||||||
|
|
||||||
|
export interface DHCPHost {
|
||||||
|
// This field is unspecified in IPv6 configurations
|
||||||
|
mac: string | undefined;
|
||||||
|
name: string;
|
||||||
|
ip: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DHCPConfig {
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
|
hosts: DHCPHost[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface IpConfig {
|
export interface IpConfig {
|
||||||
bridge_address: string;
|
bridge_address: string;
|
||||||
prefix: number;
|
prefix: number;
|
||||||
dhcp_range?: [string, string];
|
dhcp?: DHCPConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NetworkInfo {
|
export interface NetworkInfo {
|
||||||
|
@ -19,6 +19,7 @@ export interface ServerConstraints {
|
|||||||
disk_size: LenConstraint;
|
disk_size: LenConstraint;
|
||||||
net_name_size: LenConstraint;
|
net_name_size: LenConstraint;
|
||||||
net_title_size: LenConstraint;
|
net_title_size: LenConstraint;
|
||||||
|
dhcp_reservation_host_name: LenConstraint;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LenConstraint {
|
export interface LenConstraint {
|
||||||
|
141
virtweb_frontend/src/widgets/net/DHCPHostReservations.tsx
Normal file
141
virtweb_frontend/src/widgets/net/DHCPHostReservations.tsx
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import { mdiIp } from "@mdi/js";
|
||||||
|
import Icon from "@mdi/react";
|
||||||
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Button,
|
||||||
|
IconButton,
|
||||||
|
ListItem,
|
||||||
|
ListItemAvatar,
|
||||||
|
ListItemText,
|
||||||
|
Paper,
|
||||||
|
Tooltip,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { DHCPConfig, DHCPHost } from "../../api/NetworksApi";
|
||||||
|
import { ServerApi } from "../../api/ServerApi";
|
||||||
|
import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
|
||||||
|
import { IPInput } from "../forms/IPInput";
|
||||||
|
import { MACInput } from "../forms/MACInput";
|
||||||
|
import { TextInput } from "../forms/TextInput";
|
||||||
|
|
||||||
|
export function DHCPHostReservations(p: {
|
||||||
|
editable: boolean;
|
||||||
|
dhcp: DHCPConfig;
|
||||||
|
version: 4 | 6;
|
||||||
|
onChange?: (d: DHCPConfig) => void;
|
||||||
|
}): React.ReactElement {
|
||||||
|
const addHost = () => {
|
||||||
|
p.dhcp.hosts.push({
|
||||||
|
ip: p.version === 4 ? "192.168.1.30" : "fd00::b200",
|
||||||
|
name: "host",
|
||||||
|
mac: p.version === 4 ? "00:00:00:00:00:00" : undefined,
|
||||||
|
});
|
||||||
|
p.onChange?.(p.dhcp);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{p.dhcp.hosts.map((h, num) => (
|
||||||
|
<HostReservationWidget
|
||||||
|
key={num}
|
||||||
|
{...p}
|
||||||
|
onChange={() => {
|
||||||
|
p.onChange?.(p.dhcp);
|
||||||
|
}}
|
||||||
|
host={h}
|
||||||
|
onRemove={() => {
|
||||||
|
p.dhcp.hosts.splice(num, 1);
|
||||||
|
p.onChange?.(p.dhcp);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{p.editable && (
|
||||||
|
<Button onClick={addHost}>Add new host reservation</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function HostReservationWidget(p: {
|
||||||
|
editable: boolean;
|
||||||
|
host: DHCPHost;
|
||||||
|
version: 4 | 6;
|
||||||
|
onChange: () => void;
|
||||||
|
onRemove: () => void;
|
||||||
|
}): React.ReactElement {
|
||||||
|
const confirm = useConfirm();
|
||||||
|
const deleteReservation = async () => {
|
||||||
|
if (
|
||||||
|
!(await confirm("Do you really want to remove this host IP reservation?"))
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
p.onRemove();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper elevation={3} style={{ padding: "10px", marginTop: "20px" }}>
|
||||||
|
<ListItem
|
||||||
|
secondaryAction={
|
||||||
|
p.editable && (
|
||||||
|
<IconButton
|
||||||
|
edge="end"
|
||||||
|
aria-label="remove network"
|
||||||
|
onClick={deleteReservation}
|
||||||
|
>
|
||||||
|
<Tooltip title="Remove host IP allocation">
|
||||||
|
<DeleteIcon />
|
||||||
|
</Tooltip>
|
||||||
|
</IconButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar>
|
||||||
|
<Icon path={mdiIp} />
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
<TextInput
|
||||||
|
editable={p.editable}
|
||||||
|
label="Host name"
|
||||||
|
value={p.host.name}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
p.host.name = v!;
|
||||||
|
p.onChange();
|
||||||
|
}}
|
||||||
|
type="text"
|
||||||
|
size={ServerApi.Config.constraints.dhcp_reservation_host_name}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
<div style={{ marginLeft: "70px" }}>
|
||||||
|
{p.version === 4 && (
|
||||||
|
<MACInput
|
||||||
|
editable={p.editable}
|
||||||
|
label="MAC Address"
|
||||||
|
value={p.host.mac}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
p.host.mac = v!;
|
||||||
|
p.onChange?.();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<IPInput
|
||||||
|
editable={p.editable}
|
||||||
|
label="IP address"
|
||||||
|
version={p.version}
|
||||||
|
value={p.host.ip}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
p.host.ip = v!;
|
||||||
|
p.onChange?.();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { Checkbox, Grid } from "@mui/material";
|
import { Checkbox, Grid, Paper } from "@mui/material";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { IpConfig, NetworkApi, NetworkInfo } from "../../api/NetworksApi";
|
import { IpConfig, NetworkApi, NetworkInfo } from "../../api/NetworksApi";
|
||||||
import { ServerApi } from "../../api/ServerApi";
|
import { ServerApi } from "../../api/ServerApi";
|
||||||
@ -10,6 +10,7 @@ import { TextInput } from "../forms/TextInput";
|
|||||||
import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
|
import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
|
||||||
import { CheckboxInput } from "../forms/CheckboxInput";
|
import { CheckboxInput } from "../forms/CheckboxInput";
|
||||||
import { ResAutostartInput } from "../forms/ResAutostartInput";
|
import { ResAutostartInput } from "../forms/ResAutostartInput";
|
||||||
|
import { DHCPHostReservations } from "./DHCPHostReservations";
|
||||||
|
|
||||||
interface DetailsProps {
|
interface DetailsProps {
|
||||||
net: NetworkInfo;
|
net: NetworkInfo;
|
||||||
@ -239,44 +240,59 @@ function IPSection(p: {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
checked={!!p.config.dhcp_range}
|
checked={!!p.config.dhcp}
|
||||||
editable={p.editable}
|
editable={p.editable}
|
||||||
label="Enable DHCP"
|
label="Enable DHCP"
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
if (v)
|
if (v)
|
||||||
p.config!.dhcp_range =
|
p.config!.dhcp =
|
||||||
p.version === 4
|
p.version === 4
|
||||||
? ["192.168.1.100", "192.168.1.200"]
|
? {
|
||||||
: ["fd00::100", "fd00::f00"];
|
start: "192.168.1.100",
|
||||||
else p.config!.dhcp_range = undefined;
|
end: "192.168.1.200",
|
||||||
|
hosts: [],
|
||||||
|
}
|
||||||
|
: { start: "fd00::100", end: "fd00::f00", hosts: [] };
|
||||||
|
else p.config!.dhcp = undefined;
|
||||||
p.onChange?.(p.config);
|
p.onChange?.(p.config);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{p.config?.dhcp_range && (
|
{p.config?.dhcp && (
|
||||||
<>
|
<>
|
||||||
<IPInput
|
<Paper elevation={3} style={{ padding: "10px" }}>
|
||||||
label="DHCP allocation start"
|
<IPInput
|
||||||
editable={p.editable}
|
label="DHCP allocation start"
|
||||||
version={p.version}
|
editable={p.editable}
|
||||||
value={p.config.dhcp_range[0]}
|
version={p.version}
|
||||||
onValueChange={(v) => {
|
value={p.config.dhcp.start}
|
||||||
p.config!.dhcp_range![0] = v!;
|
onValueChange={(v) => {
|
||||||
p.onChange(p.config);
|
p.config!.dhcp!.start = v!;
|
||||||
}}
|
p.onChange(p.config);
|
||||||
/>
|
}}
|
||||||
<IPInput
|
/>
|
||||||
label="DHCP allocation end"
|
<IPInput
|
||||||
editable={p.editable}
|
label="DHCP allocation end"
|
||||||
version={p.version}
|
editable={p.editable}
|
||||||
value={p.config.dhcp_range[1]}
|
version={p.version}
|
||||||
onValueChange={(v) => {
|
value={p.config.dhcp.end}
|
||||||
p.config!.dhcp_range![1] = v!;
|
onValueChange={(v) => {
|
||||||
p.onChange(p.config);
|
p.config!.dhcp!.end = v!;
|
||||||
}}
|
p.onChange(p.config);
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DHCPHostReservations
|
||||||
|
{...p}
|
||||||
|
dhcp={p.config.dhcp}
|
||||||
|
onChange={(d) => {
|
||||||
|
p.config!.dhcp = d;
|
||||||
|
p.onChange?.(p.config);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</EditSection>
|
</EditSection>
|
||||||
|
Loading…
Reference in New Issue
Block a user