diff --git a/src/api/networks.tsx b/src/api/networks.tsx index 60dafecd0..67d4b52cd 100644 --- a/src/api/networks.tsx +++ b/src/api/networks.tsx @@ -37,7 +37,7 @@ export const fetchNetworkState = ( }); }; -export const createClusterBridge = ( +export const createClusterNetwork = ( network: Partial, project: string, clusterMembers: LxdClusterMember[], @@ -47,6 +47,9 @@ export const createClusterBridge = ( name: network.name, description: network.description, type: network.type, + config: { + parent: network.config?.parent, + }, }; void Promise.allSettled( @@ -65,6 +68,8 @@ export const createClusterBridge = ( reject(error); return; } + // The network parent is cluster member specific, so we omit it on the cluster wide network configuration. + delete network.config?.parent; createNetwork(network, project).then(resolve).catch(reject); }) .catch(reject); diff --git a/src/pages/networks/CreateNetwork.tsx b/src/pages/networks/CreateNetwork.tsx index 8fb75a352..347867c2f 100644 --- a/src/pages/networks/CreateNetwork.tsx +++ b/src/pages/networks/CreateNetwork.tsx @@ -6,7 +6,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; import { queryKeys } from "util/queryKeys"; import { useNavigate, useParams } from "react-router-dom"; import { checkDuplicateName } from "util/helpers"; -import { createClusterBridge, createNetwork } from "api/networks"; +import { createClusterNetwork, createNetwork } from "api/networks"; import NetworkForm, { NetworkFormValues, toNetwork, @@ -81,7 +81,7 @@ const CreateNetwork: FC = () => { const mutation = isClustered && values.networkType !== "ovn" - ? () => createClusterBridge(network, project, clusterMembers) + ? () => createClusterNetwork(network, project, clusterMembers) : () => createNetwork(network, project); mutation() diff --git a/src/pages/networks/NetworkDetailOverview.tsx b/src/pages/networks/NetworkDetailOverview.tsx index a7ee8b544..c10b609a0 100644 --- a/src/pages/networks/NetworkDetailOverview.tsx +++ b/src/pages/networks/NetworkDetailOverview.tsx @@ -25,6 +25,9 @@ const NetworkDetailOverview: FC = ({ network }) => { return <>Missing project; } + const isPhysicalManagedNetwork = + network.type === "physical" && network.managed; + const { data: networkState, isLoading } = useQuery({ queryKey: [ queryKeys.projects, @@ -34,6 +37,7 @@ const NetworkDetailOverview: FC = ({ network }) => { queryKeys.state, ], queryFn: () => fetchNetworkState(network.name, project), + enabled: !isPhysicalManagedNetwork, }); const updateContentHeight = () => { @@ -92,39 +96,41 @@ const NetworkDetailOverview: FC = ({ network }) => { - - -

Status

- - - - - - - - - - - - - - - - - - - - - -
RX - {humanFileSize(networkState?.counters.bytes_received ?? 0)} ( - {networkState?.counters.packets_received ?? 0} packets) -
TX - {humanFileSize(networkState?.counters.bytes_sent ?? 0)} ( - {networkState?.counters.packets_sent ?? 0} packets) -
MAC address{networkState?.hwaddr ?? "-"}
MTU{networkState?.mtu ?? "-"}
- -
+ {!isPhysicalManagedNetwork && ( + + +

Status

+ + + + + + + + + + + + + + + + + + + + + +
RX + {humanFileSize(networkState?.counters.bytes_received ?? 0)}{" "} + ({networkState?.counters.packets_received ?? 0} packets) +
TX + {humanFileSize(networkState?.counters.bytes_sent ?? 0)} ( + {networkState?.counters.packets_sent ?? 0} packets) +
MAC address{networkState?.hwaddr ?? "-"}
MTU{networkState?.mtu ?? "-"}
+ +
+ )}

Usage ({usageCount})

diff --git a/src/pages/networks/forms/IpAddressSelector.tsx b/src/pages/networks/forms/IpAddressSelector.tsx index fcc14113a..e1ff1de24 100644 --- a/src/pages/networks/forms/IpAddressSelector.tsx +++ b/src/pages/networks/forms/IpAddressSelector.tsx @@ -19,7 +19,7 @@ const IpAddressSelector: FC = ({ id, address, setAddress }) => { onChange={() => setAddress("auto")} /> setAddress("none")} /> diff --git a/src/pages/networks/forms/NetworkForm.tsx b/src/pages/networks/forms/NetworkForm.tsx index 52fde3d50..c5858fd54 100644 --- a/src/pages/networks/forms/NetworkForm.tsx +++ b/src/pages/networks/forms/NetworkForm.tsx @@ -20,6 +20,7 @@ import NetworkFormMenu, { IPV4, IPV6, MAIN_CONFIGURATION, + OVN, YAML_CONFIGURATION, } from "pages/networks/forms/NetworkFormMenu"; import { FormikProps } from "formik/dist/types"; @@ -35,6 +36,7 @@ import { slugify } from "util/slugify"; import { useDocs } from "context/useDocs"; import YamlConfirmation from "components/forms/YamlConfirmation"; import { getHandledNetworkConfigKeys, getNetworkKey } from "util/networks"; +import NetworkFormOvn from "pages/networks/forms/NetworkFormOvn"; export interface NetworkFormValues { readOnly: boolean; @@ -47,6 +49,7 @@ export interface NetworkFormValues { bridge_mtu?: string; dns_domain?: string; dns_mode?: LxdNetworkDnsMode; + dns_nameservers?: string; dns_search?: string; ipv4_address?: string; ipv4_dhcp?: string; @@ -56,6 +59,9 @@ export interface NetworkFormValues { ipv4_nat?: string; ipv4_nat_address?: string; ipv4_ovn_ranges?: string; + ipv4_gateway?: string; + ipv4_routes?: string; + ipv4_routes_anycast?: string; ipv6_address?: string; ipv6_dhcp?: string; ipv6_dhcp_expiry?: string; @@ -65,7 +71,12 @@ export interface NetworkFormValues { ipv6_nat?: string; ipv6_nat_address?: string; ipv6_ovn_ranges?: string; + ipv6_gateway?: string; + ipv6_routes?: string; + ipv6_routes_anycast?: string; network?: string; + ovn_ingress_mode?: string; + parent?: string; yaml?: string; entityType: "network"; bareNetwork?: LxdNetwork; @@ -110,6 +121,7 @@ export const toNetwork = (values: NetworkFormValues): Partial => { [getNetworkKey("bridge_mtu")]: values.bridge_mtu, [getNetworkKey("dns_domain")]: values.dns_domain, [getNetworkKey("dns_mode")]: values.dns_mode, + [getNetworkKey("dns_nameservers")]: values.dns_nameservers, [getNetworkKey("dns_search")]: values.dns_search, [getNetworkKey("ipv4_address")]: values.ipv4_address, [getNetworkKey("ipv4_dhcp")]: values.ipv4_dhcp, @@ -119,6 +131,9 @@ export const toNetwork = (values: NetworkFormValues): Partial => { [getNetworkKey("ipv4_nat")]: values.ipv4_nat, [getNetworkKey("ipv4_nat_address")]: values.ipv4_nat_address, [getNetworkKey("ipv4_ovn_ranges")]: values.ipv4_ovn_ranges, + [getNetworkKey("ipv4_gateway")]: values.ipv4_gateway, + [getNetworkKey("ipv4_routes")]: values.ipv4_routes, + [getNetworkKey("ipv4_routes_anycast")]: values.ipv4_routes_anycast, [getNetworkKey("ipv6_address")]: values.ipv6_address, [getNetworkKey("ipv6_dhcp")]: values.ipv6_dhcp, [getNetworkKey("ipv6_dhcp_expiry")]: values.ipv6_dhcp_expiry, @@ -128,7 +143,12 @@ export const toNetwork = (values: NetworkFormValues): Partial => { [getNetworkKey("ipv6_nat")]: values.ipv6_nat, [getNetworkKey("ipv6_nat_address")]: values.ipv6_nat_address, [getNetworkKey("ipv6_ovn_ranges")]: values.ipv6_ovn_ranges, + [getNetworkKey("ipv6_gateway")]: values.ipv6_gateway, + [getNetworkKey("ipv6_routes")]: values.ipv6_routes, + [getNetworkKey("ipv6_routes_anycast")]: values.ipv6_routes_anycast, [getNetworkKey("network")]: values.network, + [getNetworkKey("ovn_ingress_mode")]: values.ovn_ingress_mode, + [getNetworkKey("parent")]: values.parent, }, }; }; @@ -196,6 +216,7 @@ const NetworkForm: FC = ({ {section === slugify(DNS) && } {section === slugify(IPV4) && } {section === slugify(IPV6) && } + {section === slugify(OVN) && } {section === slugify(YAML_CONFIGURATION) && ( = ({ formik }) => { return ( , - }), + ...(formik.values.networkType !== "physical" + ? [ + getConfigurationRow({ + formik, + name: "dns_domain", + label: "DNS domain", + defaultValue: "", + children: , + }), + ] + : []), ...(formik.values.networkType === "bridge" ? [ @@ -55,13 +59,25 @@ const NetworkFormDns: FC = ({ formik }) => { ] : []), - getConfigurationRow({ - formik, - name: "dns_search", - label: "DNS search", - defaultValue: "", - children: