From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 3080A1FF09C for ; Mon, 02 Nov 2026 13:50:52 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 0121321894; Mon, 02 Nov 2026 13:50:38 +0100 (CET) From: Christoph Heiss To: pdm-devel@lists.proxmox.com Subject: [PATCH datacenter-manager 5/5] ui: auto-installer: add vlan and bond config as advanced options Date: Mon, 2 Nov 2026 13:49:37 +0100 Message-ID: <20261102124939.1023342-6-c.heiss@proxmox.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261102124939.1023342-1-c.heiss@proxmox.com> References: <20261102124939.1023342-1-c.heiss@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1793623815059 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.174 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: FULBOCUI6QWWCFF2TJLGG23L2FYFPTMS X-Message-ID-Hash: FULBOCUI6QWWCFF2TJLGG23L2FYFPTMS X-MailFrom: c.heiss@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: This adds a new advanced section in the "Network" tab of the prepared answer add/edit dialog. With this, the user can the VLAN tag and/or bonding for newly installed system. Bond member selection uses udev filters, to provide a way to dynamically select network interfaces for target systems. Signed-off-by: Christoph Heiss --- ui/Cargo.toml | 1 + .../prepared_answer_add_wizard.rs | 6 + .../prepared_answer_edit_window.rs | 5 + .../auto_installer/prepared_answer_form.rs | 134 +++++++++++++++++- 4 files changed, 140 insertions(+), 6 deletions(-) diff --git a/ui/Cargo.toml b/ui/Cargo.toml index d3053802..0b00914c 100644 --- a/ui/Cargo.toml +++ b/ui/Cargo.toml @@ -40,6 +40,7 @@ proxmox-client = "1" proxmox-human-byte = "1" proxmox-installer-types = "0.3" proxmox-login = "1" +proxmox-network-api = "1" proxmox-network-types = "1.1" proxmox-schema = "5" proxmox-subscription = { version = "1.0.1", features = ["api-types"], default-features = false } diff --git a/ui/src/remotes/auto_installer/prepared_answer_add_wizard.rs b/ui/src/remotes/auto_installer/prepared_answer_add_wizard.rs index 03d78be8..63f10dcf 100644 --- a/ui/src/remotes/auto_installer/prepared_answer_add_wizard.rs +++ b/ui/src/remotes/auto_installer/prepared_answer_add_wizard.rs @@ -72,6 +72,12 @@ impl AddAnswerWizardProperties { dns: None, netdev_filter: BTreeMap::new(), netif_name_pinning_enabled: true, + bond_enabled: false, + bond_members_filter: BTreeMap::new(), + bond_members_filter_match: None, + bond_mode: None, + bond_hash_policy: None, + vlan_id: None, // disk options filesystem: answer::FilesystemOptions::Ext4(answer::LvmOptions::default()), disk_mode: DiskSelectionMode::default(), diff --git a/ui/src/remotes/auto_installer/prepared_answer_edit_window.rs b/ui/src/remotes/auto_installer/prepared_answer_edit_window.rs index 33a9ce00..5b4108ba 100644 --- a/ui/src/remotes/auto_installer/prepared_answer_edit_window.rs +++ b/ui/src/remotes/auto_installer/prepared_answer_edit_window.rs @@ -157,6 +157,11 @@ async fn submit( "post-hook-cert-fp", "disk-filter", "netdev-filter", + "bond-members-filter", + "bond-mode", + "bond-hash-policy", + "bond-primary-interface", + "vlan-id", "target-filter", "subscription-key", "template-counters", diff --git a/ui/src/remotes/auto_installer/prepared_answer_form.rs b/ui/src/remotes/auto_installer/prepared_answer_form.rs index 0b01baf1..e24d23aa 100644 --- a/ui/src/remotes/auto_installer/prepared_answer_form.rs +++ b/ui/src/remotes/auto_installer/prepared_answer_form.rs @@ -18,11 +18,12 @@ use proxmox_installer_types::{ answer::{ BTRFS_COMPRESS_OPTIONS, BtrfsCompressOption, BtrfsOptions, DiskSetup, FILESYSTEM_TYPE_OPTIONS, Filesystem, FilesystemOptions, FilesystemType, FilterMatch, - KeyboardLayout, LvmOptions, ROOT_PASSWORD_SCHEMA, RebootMode, SUBSCRIPTION_KEY_SCHEMA, - ZFS_CHECKSUM_OPTIONS, ZFS_COMPRESS_OPTIONS, ZfsChecksumOption, ZfsCompressOption, - ZfsOptions, email_validate, + KeyboardLayout, LvmOptions, NetworkBondOptions, ROOT_PASSWORD_SCHEMA, RebootMode, + SUBSCRIPTION_KEY_SCHEMA, ZFS_CHECKSUM_OPTIONS, ZFS_COMPRESS_OPTIONS, ZfsChecksumOption, + ZfsCompressOption, ZfsOptions, email_validate, }, }; +use proxmox_network_api::{BondXmitHashPolicy, LinuxBondMode}; use proxmox_network_types::fqdn::Fqdn; use proxmox_schema::api_types::{CIDR_SCHEMA, IP_SCHEMA}; use proxmox_yew_comp::{KeyValueList, SchemaValidation, utils::copy_text_to_clipboard}; @@ -355,7 +356,20 @@ pub fn render_network_options_form( .and_then(|v| v.as_bool()) .unwrap_or(true); - InputPanel::new() + let bond_enabled = form_ctx + .read() + .get_field_value("bond-enabled") + .and_then(|v| v.as_bool()) + .unwrap_or(!config.bond_members_filter.is_empty()); + + let bond_mode = form_ctx + .read() + .get_field_value("bond-mode") + .and_then(|v| v.as_str().and_then(|s| s.parse().ok())) + .or(config.bond_mode) + .unwrap_or(LinuxBondMode::ActiveBackup); + + let panel = InputPanel::new() .class(Flex::Fill) .class(Overflow::Auto) .padding(4) @@ -451,7 +465,99 @@ pub fn render_network_options_form( .disabled(use_dhcp_network) .required(!use_dhcp_network), ) - .into() + // Bonding and VLAN tagging are supported for both the DHCP and the static + // configuration, so they are independent of the 'Use DHCP' setting. + .with_advanced_spacer() + .with_advanced_field( + tr!("VLAN ID"), + Number::::new() + .name("vlan-id") + .min(2) + .max(4094) + .step(1) + .submit_empty(false) + .placeholder(tr!("Optional, 2-4094. Disabled when empty.")) + .value(config.vlan_id.map(|v| v.to_string())), + ) + .with_advanced_field( + tr!("Enable Bonding"), + Checkbox::new() + .name("bond-enabled") + .tip(tr!( + "Bond the member interfaces for the management network." + )) + .default(config.bond_enabled), + ) + .with_field( + tr!("Bond Members udev Filter Mode"), + filter_match_combobox() + .name("bond-members-filter-match") + .value(config.bond_members_filter_match.map(|v| v.to_string())) + .disabled(!bond_enabled), + ) + .with_advanced_field( + tr!("Bond Members udev Filters"), + KeyValueList::new() + .name("bond-members-filter") + .class(FlexFit) + .value( + config + .bond_members_filter + .iter() + .map(|(k, v)| (k.clone(), Value::String(v.clone()))) + .collect(), + ) + .key_label(tr!("Property Name")) + .value_label(tr!("Value To Match")) + .key_placeholder(tr!("udev Property Name, e.g. ID_MODEL")) + .value_renderer(render_udev_filter_value.into()) + .submit_validate(kv_list_to_udev_filter_map_validate) + .submit_empty(false) + .disabled(!bond_enabled) + .required(bond_enabled), + ) + .with_advanced_field( + tr!("Bond Mode"), + Combobox::new() + .name("bond-mode") + .items(Rc::new( + NetworkBondOptions::MODES + .iter() + .map(|mode| mode.to_string().into()) + .collect(), + )) + .default(LinuxBondMode::ActiveBackup.to_string()) + .value(config.bond_mode.map(|v| v.to_string())) + .disabled(!bond_enabled) + .required(bond_enabled) + .show_filter(false), + ) + .with_advanced_field( + tr!("Bond Hash Policy"), + Combobox::new() + .name("bond-hash-policy") + .items(Rc::new( + NetworkBondOptions::HASH_POLICIES + .iter() + .map(|policy| policy.to_string().into()) + .collect(), + )) + .default(BondXmitHashPolicy::Layer2.to_string()) + .value(config.bond_hash_policy.map(|v| v.to_string())) + .disabled( + !bond_enabled + || !matches!( + bond_mode, + LinuxBondMode::Ieee802_3ad | LinuxBondMode::BalanceXor + ), + ) + .show_filter(false), + ); + + // the bond and VLAN options live in the advanced section, report that to the dialog so it can + // offer the toggle + form_ctx.set_has_advanced(panel.has_advanced()); + panel.into() } pub fn render_disk_setup_form( @@ -523,7 +629,7 @@ pub fn render_disk_setup_form( .with_spacer() .with_field( tr!("Disk udev Filter Mode"), - Combobox::new() + filter_match_combobox() .name("disk-filter-match") .items(Rc::new( [FilterMatch::Any, FilterMatch::All] @@ -1147,6 +1253,22 @@ fn render_template_counter_value( .into() } +fn filter_match_combobox() -> Combobox { + Combobox::new() + .items(Rc::new( + [FilterMatch::Any, FilterMatch::All] + .iter() + .map(|opt| opt.to_string().into()) + .collect(), + )) + .render_value(|v: &AttrValue| match v.parse::() { + Ok(FilterMatch::Any) => tr!("Match Any Filter").into(), + Ok(FilterMatch::All) => tr!("Match All Filters").into(), + _ => v.into(), + }) + .default(FilterMatch::default().to_string()) +} + /// Validate against a schema, but accept any value carrying a MiniJinja placeholder, as the /// backend substitutes those before parsing the field. fn templated_schema_validate(schema: &'static proxmox_schema::Schema) -> ValidateFn { -- 2.55.0