From: Christoph Heiss <c.heiss@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH installer 15/15] tui: network: add bond setup options to advanced network dialog
Date: Tue, 8 Sep 2026 12:16:29 +0200 [thread overview]
Message-ID: <20260908101647.1057780-16-c.heiss@proxmox.com> (raw)
In-Reply-To: <20260908101647.1057780-1-c.heiss@proxmox.com>
Partially fixes #2164 [0].
Adds a second panel to the advanced network options dialog, allowing to
enable bonding and setting all the required options for it; such as
members, mode, hash policy and primary bond member, as required.
[0] https://bugzilla.proxmox.com/show_bug.cgi?id=2164
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
proxmox-tui-installer/Cargo.toml | 1 +
proxmox-tui-installer/src/main.rs | 7 +-
proxmox-tui-installer/src/options.rs | 45 ++-
proxmox-tui-installer/src/setup.rs | 5 +
proxmox-tui-installer/src/views/network.rs | 415 ++++++++++++++++++---
5 files changed, 424 insertions(+), 49 deletions(-)
diff --git a/proxmox-tui-installer/Cargo.toml b/proxmox-tui-installer/Cargo.toml
index 56395a4..cb950b3 100644
--- a/proxmox-tui-installer/Cargo.toml
+++ b/proxmox-tui-installer/Cargo.toml
@@ -9,6 +9,7 @@ homepage = "https://www.proxmox.com"
[dependencies]
proxmox-installer-common.workspace = true
+proxmox-network-api.workspace = true
proxmox-network-types.workspace = true
proxmox-installer-types.workspace = true
anyhow.workspace = true
diff --git a/proxmox-tui-installer/src/main.rs b/proxmox-tui-installer/src/main.rs
index 61a06f7..6e7c691 100644
--- a/proxmox-tui-installer/src/main.rs
+++ b/proxmox-tui-installer/src/main.rs
@@ -177,6 +177,7 @@ fn main() {
None,
// We enable network interface pinning by default in the TUI
Some(&NetworkInterfacePinningOptions::default()),
+ None,
),
autoreboot: true,
},
@@ -578,7 +579,11 @@ fn summary_dialog(siv: &mut Cursive) -> InstallerView {
("name".to_owned(), "Option".to_owned()),
("value".to_owned(), "Selected value".to_owned()),
])
- .items(state.options.to_summary(&state.locales)),
+ .items(
+ state
+ .options
+ .to_summary(&state.runtime_info, &state.locales),
+ ),
))
.child(
LinearLayout::horizontal()
diff --git a/proxmox-tui-installer/src/options.rs b/proxmox-tui-installer/src/options.rs
index 2c156e8..8b650f1 100644
--- a/proxmox-tui-installer/src/options.rs
+++ b/proxmox-tui-installer/src/options.rs
@@ -2,9 +2,9 @@ use crate::SummaryOption;
use proxmox_installer_common::{
options::{BootdiskOptions, NetworkOptions, TimezoneOptions},
- setup::LocaleInfo,
+ setup::{LocaleInfo, NetworkInfo, RuntimeInfo},
};
-use proxmox_installer_types::EMAIL_DEFAULT_PLACEHOLDER;
+use proxmox_installer_types::{EMAIL_DEFAULT_PLACEHOLDER, answer::NetworkBondOptions};
#[derive(Clone)]
pub struct PasswordOptions {
@@ -31,13 +31,27 @@ pub struct InstallerOptions {
}
impl InstallerOptions {
- pub fn to_summary(&self, locales: &LocaleInfo) -> Vec<SummaryOption> {
+ pub fn to_summary(
+ &self,
+ runtime_info: &RuntimeInfo,
+ locales: &LocaleInfo,
+ ) -> Vec<SummaryOption> {
let kb_layout = locales
.kmap
.get(&self.timezone.kb_layout)
.map(|l| &l.name)
.unwrap_or(&self.timezone.kb_layout);
+ let mngmt_interface = if let Some(opts) = &self.network.bond_opts {
+ let members = self
+ .resolve_bond_member_names(opts, &runtime_info.network)
+ .join(" | ");
+
+ format!("bond0 (mode: {}, members: {members})", opts.mode)
+ } else {
+ self.network.ifname.to_owned()
+ };
+
vec![
SummaryOption::new("Bootdisk filesystem", self.bootdisk.fstype.to_string()),
SummaryOption::new(
@@ -52,11 +66,34 @@ impl InstallerOptions {
SummaryOption::new("Timezone", &self.timezone.timezone),
SummaryOption::new("Keyboard layout", kb_layout),
SummaryOption::new("Administrator email", &self.password.email),
- SummaryOption::new("Management interface", &self.network.ifname),
+ SummaryOption::new("Management interface", &mngmt_interface),
SummaryOption::new("Hostname", self.network.fqdn.to_string()),
SummaryOption::new("Host IP (CIDR)", self.network.address.to_string()),
SummaryOption::new("Gateway", self.network.gateway.to_string()),
SummaryOption::new("DNS", self.network.dns_server.to_string()),
]
}
+
+ fn resolve_bond_member_names(
+ &self,
+ bond: &NetworkBondOptions,
+ network: &NetworkInfo,
+ ) -> Vec<String> {
+ let pin_opts = self.network.pinning_opts.as_ref();
+
+ bond.members
+ .iter()
+ .map(|mac| {
+ // Resolve either to the pinned name, if enabled, otherwise to the kernel-assigned one.
+ pin_opts
+ .and_then(|o| o.mapping.get(mac).cloned())
+ .or_else(|| {
+ network.interfaces.iter().find_map(|(_, iface)| {
+ (iface.mac == *mac).then_some(iface.name.to_owned())
+ })
+ })
+ .unwrap_or("<unknown>".to_owned())
+ })
+ .collect()
+ }
}
diff --git a/proxmox-tui-installer/src/setup.rs b/proxmox-tui-installer/src/setup.rs
index ae4b717..fb9a305 100644
--- a/proxmox-tui-installer/src/setup.rs
+++ b/proxmox-tui-installer/src/setup.rs
@@ -5,6 +5,7 @@ use proxmox_installer_common::{
options::AdvancedBootdiskOptions,
setup::{InstallConfig, InstallFirstBootSetup, InstallRootPassword},
};
+use proxmox_installer_types::answer::NetworkBondOptions;
impl From<InstallerOptions> for InstallConfig {
fn from(options: InstallerOptions) -> Self {
@@ -35,6 +36,10 @@ impl From<InstallerOptions> for InstallConfig {
subscription_key: None,
mngmt_nic: options.network.ifname,
+ mngmt_bond: options
+ .network
+ .bond_opts
+ .unwrap_or_else(NetworkBondOptions::disabled),
network_interface_pin_map: pinning_opts.map(|o| o.mapping.clone()).unwrap_or_default(),
hostname: options.network.fqdn.host().to_owned(),
diff --git a/proxmox-tui-installer/src/views/network.rs b/proxmox-tui-installer/src/views/network.rs
index f040881..f1b677d 100644
--- a/proxmox-tui-installer/src/views/network.rs
+++ b/proxmox-tui-installer/src/views/network.rs
@@ -18,16 +18,24 @@ use proxmox_installer_common::{
options::{NetworkInterfacePinningOptions, NetworkOptions},
setup::{Interface, NetworkInfo},
};
+use proxmox_installer_types::answer::NetworkBondOptions;
+use proxmox_network_api::{BondXmitHashPolicy, LinuxBondMode};
use proxmox_network_types::{MacAddress, fqdn::Fqdn, ip_address::Cidr};
-use super::{CidrAddressEditView, FormView};
+use super::{CheckboxGroup, CidrAddressEditView, FormView};
struct NetworkViewOptions {
pinning_enabled: bool,
+ bond_enabled: bool,
interfaces: Vec<Interface>,
+ /// Last selected interface (by its MAC address) in the [SelectView].
+ /// Keeps track of the selected interface across enabling/disabling bonding, which replaces the
+ /// view.
+ last_selected_iface: MacAddress,
// For UI purposes, we want to always save the state for the below options, even if not enabled
// by the user - to preserve the state between toggling the checkbox
pinning_options: NetworkInterfacePinningOptions,
+ bond_options: NetworkBondOptions,
}
/// Convenience wrapper when needing to take a (interior-mutable) reference to
@@ -46,6 +54,7 @@ impl NetworkOptionsView {
const ADVANCED_OPTIONS_BUTTON_NAME: &str = "network-pinning-options-button";
const INTERFACE_PINNING_VIEW_NAME: &str = "network-name-pinning-view";
+ const BOND_OPTIONS_VIEW_NAME: &str = "network-bond-options-view";
pub fn new(options: &NetworkOptions, network_info: &NetworkInfo) -> Self {
let mut interfaces = network_info
@@ -57,13 +66,30 @@ impl NetworkOptionsView {
// First, sort interfaces by their link state and then name
interfaces.sort_unstable_by(|a, b| (&a.state, &a.name).cmp(&(&b.state, &b.name)));
+ let selected_mac = network_info
+ .interfaces
+ .get(&options.ifname)
+ .map(|iface| iface.mac)
+ .unwrap_or_else(|| {
+ interfaces
+ .first()
+ .expect("at least one network interface")
+ .mac
+ });
+
let options_ref = Arc::new(Mutex::new(NetworkViewOptions {
pinning_enabled: options.pinning_opts.is_some(),
+ bond_enabled: options.bond_opts.is_some(),
interfaces,
+ last_selected_iface: selected_mac,
pinning_options: options.pinning_opts.clone().unwrap_or_default(),
+ bond_options: options
+ .bond_opts
+ .clone()
+ .unwrap_or_else(NetworkBondOptions::disabled),
}));
- let iface_selection = Self::build_mgmt_ifname_selectview(options_ref.clone(), None);
+ let iface_selection = Self::build_mgmt_ifname_selectview(options_ref.clone());
let form = FormView::<()>::new()
.child(
@@ -148,18 +174,26 @@ impl NetworkOptionsView {
.parse::<IpAddr>()
.map_err(|err| err.to_string())?;
- let pinning_opts = self
- .options
- .lock()
- .map(|opt| opt.pinning_enabled.then_some(opt.pinning_options.clone()))
- .map_err(|err| err.to_string())?;
+ let options = self.options.lock().map_err(|err| err.to_string())?;
- let ifname = if let Some(opts) = &pinning_opts
- && let Some(pinned) = iface.to_pinned(opts)
- {
- pinned.name
+ let pinning_opts = options
+ .pinning_enabled
+ .then_some(options.pinning_options.clone());
+
+ let bond_opts = options.bond_enabled.then_some(options.bond_options.clone());
+
+ let ifname = if options.bond_enabled {
+ "bond0".to_owned()
} else {
- iface.name
+ let iface = form
+ .get_value::<NamedView<SelectView<Interface>>, _>(0)
+ .ok_or("failed to retrieve management interface name")?;
+
+ pinning_opts
+ .as_ref()
+ .and_then(|opts| iface.to_pinned(opts))
+ .unwrap_or(iface)
+ .name
};
if address.address().is_ipv4() != gateway.is_ipv4() {
@@ -176,42 +210,51 @@ impl NetworkOptionsView {
gateway,
dns_server,
pinning_opts,
+ bond_opts,
})
}
}
fn advanced_options_view(options_ref: NetworkViewOptionsRef) -> impl View {
let inner = ScrollView::new(
- LinearLayout::vertical().child(Panel::new(
- InterfacePinningOptionsView::new(options_ref.clone())
- .with_name(Self::INTERFACE_PINNING_VIEW_NAME),
- )),
+ LinearLayout::vertical()
+ .child(Panel::new(
+ InterfacePinningOptionsView::new(options_ref.clone())
+ .with_name(Self::INTERFACE_PINNING_VIEW_NAME),
+ ))
+ .child(Panel::new(
+ BondOptionsView::new(options_ref.clone())
+ .with_name(Self::BOND_OPTIONS_VIEW_NAME),
+ )),
);
Dialog::around(inner)
.title("Advanced Network Options")
.button("Ok", {
move |siv| {
- match Self::get_view_values(siv) {
- Ok(pin_opts) => {
+ match Self::get_view_values(siv, options_ref.clone()) {
+ Ok((pin_opts, bond_opts)) => {
siv.pop_layer();
let options = &mut options_ref.lock().expect("unpoisoned lock");
options.pinning_options = pin_opts;
+ options.bond_options = bond_opts;
}
Err(err) => {
- siv.pop_layer();
siv.add_layer(Dialog::info(err.to_string()));
return;
}
}
- Self::refresh_ifname_selectview(siv, options_ref.clone());
+ Self::refresh_ifname_view(siv, options_ref.clone());
}
})
.max_size((80, 40))
}
- fn get_view_values(siv: &mut Cursive) -> Result<NetworkInterfacePinningOptions> {
+ fn get_view_values(
+ siv: &mut Cursive,
+ options_ref: NetworkViewOptionsRef,
+ ) -> Result<(NetworkInterfacePinningOptions, NetworkBondOptions)> {
let pin_opts = siv
.call_on_name(
Self::INTERFACE_PINNING_VIEW_NAME,
@@ -221,23 +264,42 @@ impl NetworkOptionsView {
bail!("failed to retrieve network interface name pinning options")
})?;
- Ok(pin_opts)
+ let bond_opts = siv
+ .call_on_name(Self::BOND_OPTIONS_VIEW_NAME, BondOptionsView::get_values)
+ .unwrap_or_else(|| bail!("failed to retrieve management network bond options"))?;
+
+ if options_ref
+ .lock()
+ .map(|opts| opts.bond_enabled)
+ .unwrap_or(false)
+ {
+ bond_opts.verify()?;
+ }
+
+ Ok((pin_opts, bond_opts))
}
- fn refresh_ifname_selectview(siv: &mut Cursive, options_ref: NetworkViewOptionsRef) {
- siv.call_on_name(
- Self::MGMT_IFNAME_SELECTVIEW_NAME,
- |view: &mut SelectView<Interface>| {
- let selected_mac = view.selection().map(|iface| iface.mac);
- *view = Self::build_mgmt_ifname_selectview(options_ref, selected_mac);
- },
- );
+ fn refresh_ifname_view(siv: &mut Cursive, options_ref: NetworkViewOptionsRef) {
+ siv.call_on_name(Self::FORM_VIEW_NAME, |form: &mut FormView<()>| {
+ let options = options_ref.lock().expect("unpoisoned lock");
+
+ if options.bond_enabled {
+ form.replace_child(
+ 0,
+ TextView::new(format!(
+ "bond0 (mode: {}, {} members)",
+ options.bond_options.mode,
+ options.bond_options.members.len()
+ )),
+ );
+ } else {
+ drop(options);
+ form.replace_child(0, Self::build_mgmt_ifname_selectview(options_ref.clone()));
+ }
+ });
}
- fn build_mgmt_ifname_selectview(
- options_ref: NetworkViewOptionsRef,
- selected_mac: Option<MacAddress>,
- ) -> SelectView<Interface> {
+ fn build_mgmt_ifname_selectview(options_ref: NetworkViewOptionsRef) -> SelectView<Interface> {
let options = options_ref.lock().expect("unpoisoned lock");
// Map all interfaces to a list of (human-readable interface name, [Interface]) pairs
@@ -255,20 +317,21 @@ impl NetworkOptionsView {
})
.collect::<Vec<(String, Interface)>>();
- let mut view = SelectView::new().popup().with_all(ifnames.clone());
-
- let selected_mac = selected_mac.unwrap_or_else(|| {
- options
- .interfaces
- .first()
- .expect("at least one network interface")
- .mac
- });
+ let mut view = SelectView::new()
+ .popup()
+ .with_all(ifnames.clone())
+ .on_submit({
+ let options_ref = options_ref.clone();
+ move |_, iface| {
+ let mut options = options_ref.lock().expect("unpoisoned lock");
+ options.last_selected_iface = iface.mac;
+ }
+ });
// Finally, (try to) select the current one
let selected = view
.iter()
- .position(|(_label, iface)| iface.mac == selected_mac)
+ .position(|(_label, iface)| iface.mac == options.last_selected_iface)
.unwrap_or(0); // we sort UP interfaces first, so select the first UP interface
//
view.set_selection(selected);
@@ -313,6 +376,7 @@ impl InterfacePinningOptionsView {
enabled;
Self::toggle_all(siv, enabled);
+ BondOptionsView::refresh_ifnames(siv, options_ref.clone());
}
})
.with_name(Self::ENABLED_CHECKBOX_NAME),
@@ -414,3 +478,266 @@ impl InterfacePinningOptionsView {
impl ViewWrapper for InterfacePinningOptionsView {
cursive::wrap_impl!(self.view: LinearLayout);
}
+
+struct BondOptionsView {
+ view: LinearLayout,
+ options_ref: NetworkViewOptionsRef,
+}
+
+impl BondOptionsView {
+ const FORM_NAME: &str = "network-bond-form-view";
+ const MEMBERS_VIEW_NAME: &str = "network-bond-members-checkboxes-view";
+
+ fn new(options_ref: NetworkViewOptionsRef) -> Self {
+ let options = &options_ref.lock().expect("unpoisoned lock");
+
+ let mut view = LinearLayout::vertical()
+ .child(
+ LinearLayout::horizontal()
+ .child(
+ Checkbox::new()
+ .with_checked(options.bond_enabled)
+ .on_change({
+ let options_ref = options_ref.clone();
+ move |siv, enabled| {
+ options_ref.lock().expect("unpoisoned lock").bond_enabled =
+ enabled;
+
+ Self::toggle_all(siv, enabled);
+ }
+ }),
+ )
+ .child(TextView::new(" Enable bonding for management interface").no_wrap()),
+ )
+ .child(DummyView::new())
+ .child(
+ FormView::<()>::new()
+ .child(
+ "Bond Mode",
+ SelectView::new()
+ .popup()
+ .with_all(
+ NetworkBondOptions::MODES
+ .iter()
+ .map(|m| (m.to_string(), *m)),
+ )
+ .selected(
+ NetworkBondOptions::MODES
+ .iter()
+ .position(|m| *m == options.bond_options.mode)
+ .unwrap_or_default(),
+ )
+ .on_submit(Self::toggle_mode_dependent_options)
+ .with_enabled(options.bond_enabled),
+ )
+ .child(
+ "Hash Policy",
+ SelectView::new()
+ .popup()
+ .with_all(
+ NetworkBondOptions::HASH_POLICIES
+ .iter()
+ .map(|m| (m.to_string(), *m)),
+ )
+ .selected(
+ NetworkBondOptions::HASH_POLICIES
+ .iter()
+ .position(|m| *m == options.bond_options.hash_policy)
+ .unwrap_or_default(),
+ )
+ .with_enabled(
+ options.bond_enabled
+ && Self::hash_policy_view_enabled(options.bond_options.mode),
+ ),
+ )
+ .child(
+ "Primary interface",
+ EditView::new()
+ .content({
+ options
+ .bond_options
+ .primary_interface
+ .and_then(|mac| {
+ options.interfaces.iter().find(|iface| iface.mac == mac)
+ })
+ .and_then(|iface| iface.to_pinned(&options.pinning_options))
+ .map(|iface| iface.name)
+ .unwrap_or_default()
+ })
+ .with_enabled(
+ options.bond_enabled
+ && Self::primary_interface_view_enable(
+ options.bond_options.mode,
+ ),
+ ),
+ )
+ .with_name(Self::FORM_NAME),
+ )
+ .child(DummyView::new());
+
+ let mut checkboxes = CheckboxGroup::<MacAddress>::new();
+ for iface in options
+ .interfaces
+ .iter()
+ .filter(|iface| iface.pinned_id.is_some())
+ {
+ let label = Self::format_interface_label(options, iface);
+ let is_member = options.bond_options.members.contains(&iface.mac);
+
+ checkboxes.add(&label, is_member, iface.mac);
+ }
+ checkboxes.set_enabled(options.bond_enabled);
+ view.add_child(checkboxes.with_name(Self::MEMBERS_VIEW_NAME));
+
+ Self {
+ view,
+ options_ref: options_ref.clone(),
+ }
+ }
+
+ fn hash_policy_view_enabled(mode: LinuxBondMode) -> bool {
+ matches!(mode, LinuxBondMode::Ieee802_3ad | LinuxBondMode::BalanceXor)
+ }
+
+ fn primary_interface_view_enable(mode: LinuxBondMode) -> bool {
+ mode == LinuxBondMode::ActiveBackup
+ }
+
+ fn toggle_all(siv: &mut Cursive, enabled: bool) {
+ siv.call_on_name(Self::FORM_NAME, |form: &mut FormView| {
+ if let Some(v) = form.get_child_mut::<SelectView<LinuxBondMode>>(0) {
+ v.set_enabled(enabled);
+ }
+ if let Some(v) = form.get_child_mut::<SelectView<BondXmitHashPolicy>>(1) {
+ v.set_enabled(enabled);
+ }
+ if let Some(v) = form.get_child_mut::<EditView>(2) {
+ v.set_enabled(enabled);
+ }
+ });
+
+ siv.call_on_name(
+ Self::MEMBERS_VIEW_NAME,
+ |group: &mut CheckboxGroup<MacAddress>| {
+ group.set_enabled(enabled);
+ },
+ );
+ }
+
+ fn toggle_mode_dependent_options(siv: &mut Cursive, selected: &LinuxBondMode) {
+ siv.call_on_name(Self::FORM_NAME, |form: &mut FormView| {
+ if let Some(v) = form.get_child_mut::<SelectView<BondXmitHashPolicy>>(1) {
+ v.set_enabled(Self::hash_policy_view_enabled(*selected));
+ }
+ if let Some(v) = form.get_child_mut::<EditView>(2) {
+ v.set_enabled(Self::primary_interface_view_enable(*selected));
+ }
+ });
+ }
+
+ fn refresh_ifnames(siv: &mut Cursive, options_ref: NetworkViewOptionsRef) {
+ siv.call_on_name(
+ Self::MEMBERS_VIEW_NAME,
+ |group: &mut CheckboxGroup<MacAddress>| {
+ let options = options_ref.lock().expect("unpoisoned lock");
+ group.update_labels(&|_, label, mac| {
+ if let Some(iface) = options.interfaces.iter().find(|iface| iface.mac == *mac) {
+ label.set_content(Self::format_interface_label(&options, iface));
+ }
+ });
+ },
+ );
+ }
+
+ fn get_values(&mut self) -> Result<NetworkBondOptions> {
+ let form = self
+ .view
+ .get_child_mut(2)
+ .and_then(|v| v.downcast_mut::<NamedView<FormView<()>>>())
+ .ok_or(anyhow!("failed to retrieve bond options form"))?
+ .get_mut();
+
+ let mode = form
+ .get_value::<SelectView<LinuxBondMode>, _>(0)
+ .ok_or(anyhow!("failed to retrieve bond mode"))?;
+
+ let hash_policy = form
+ .get_value::<SelectView<BondXmitHashPolicy>, _>(1)
+ .ok_or(anyhow!("failed to retrieve bond hash policy"))?;
+
+ let primary_interface = form
+ .get_value::<EditView, String>(2)
+ // translate the interface name back to the mac address, which is used as identifier
+ .ok_or(anyhow!("failed to retrieve bond primary interface"))
+ .and_then(|name| {
+ if name.is_empty() {
+ Ok(None)
+ } else {
+ Ok(Some(self.get_mac_address_for_interface_name(&name)?))
+ }
+ })?;
+
+ let members = self
+ .view
+ .get_child_mut(4)
+ .and_then(|v| v.downcast_mut::<NamedView<CheckboxGroup<MacAddress>>>())
+ .ok_or(anyhow!("failed to retrieve bond member interface group"))?
+ .get_mut()
+ .entries()
+ .filter_map(|(checked, mac)| checked.then_some(*mac))
+ .collect();
+
+ Ok(NetworkBondOptions {
+ members,
+ mode,
+ hash_policy,
+ primary_interface,
+ })
+ }
+
+ fn format_interface_label(options: &NetworkViewOptions, iface: &Interface) -> String {
+ let name = if options.pinning_enabled {
+ iface
+ .to_pinned(&options.pinning_options)
+ .map(|p| p.name)
+ .unwrap_or_else(|| iface.name.clone())
+ } else {
+ iface.name.clone()
+ };
+
+ let mut label = format!("{name} ({}, {}, {})", iface.mac, iface.driver, iface.state);
+
+ if !iface.addresses.is_empty() {
+ label += "\n ";
+ label += iface
+ .addresses
+ .iter()
+ .fold(String::new(), |acc, addr| format!("{acc}, {addr}"))
+ .trim_start_matches(", ");
+ }
+
+ label
+ }
+
+ fn get_mac_address_for_interface_name(&self, ifname: &str) -> Result<MacAddress> {
+ let options = &self.options_ref.lock().expect("unpoisoned lock");
+
+ if options.pinning_enabled {
+ options
+ .pinning_options
+ .mapping
+ .iter()
+ .find_map(|(mac, name)| (*name == ifname).then_some(*mac))
+ } else {
+ options
+ .interfaces
+ .iter()
+ .find_map(|iface| (iface.name == ifname).then_some(iface.mac))
+ }
+ .ok_or_else(|| anyhow!("interface '{ifname}' not found"))
+ }
+}
+
+impl ViewWrapper for BondOptionsView {
+ cursive::wrap_impl!(self.view: LinearLayout);
+}
--
2.55.0
prev parent reply other threads:[~2026-09-08 10:19 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-08 10:16 [PATCH installer/proxmox 00/15] partially fix #2164: add bond setup across all installers Christoph Heiss
2026-09-08 10:16 ` [PATCH proxmox 01/15] installer-types: use `MacAddress` type where applicable Christoph Heiss
2026-09-08 10:16 ` [PATCH proxmox 02/15] installer-types: answer: add options for configuring management bond Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 03/15] install: run make tidy Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 04/15] tree-wide: use `MacAddress` type instead of string for MAC addresses Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 05/15] install: factor out /etc/network/interfaces setup into own subroutine Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 06/15] install: do not rely on interface name map to be fully populated Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 07/15] install: config: add option for setting up bond on management interface Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 08/15] install: network: set up management bond if requested Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 09/15] gui: network: factor out name pinning into advanced options dialog Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 10/15] gui: network: add bond setup options to " Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 11/15] auto: pass trough management network bond options to low-level installer Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 12/15] tui: drop long obsolete comment about logo formatting Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 13/15] tui: views: add widget for displaying a group of checkboxes Christoph Heiss
2026-09-08 10:16 ` [PATCH installer 14/15] tui: network: factor out name pinning into advanced options dialog Christoph Heiss
2026-09-08 10:16 ` Christoph Heiss [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260908101647.1057780-16-c.heiss@proxmox.com \
--to=c.heiss@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.