Can define IP reservations for networks
This commit is contained in:
		| @@ -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> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user