]>
Commit | Line | Data |
---|---|---|
86c48f76 | 1 | use std::{cell::RefCell, marker::PhantomData, rc::Rc}; |
32368ac3 | 2 | |
af0dfe0e | 3 | use cursive::{ |
ed191e60 | 4 | view::{Nameable, Resizable, ViewWrapper}, |
45082a3c | 5 | views::{ |
2379338c CH |
6 | Button, Dialog, DummyView, LinearLayout, NamedView, PaddedView, Panel, ScrollView, |
7 | SelectView, TextView, | |
45082a3c | 8 | }, |
7393ed88 | 9 | Cursive, View, |
af0dfe0e | 10 | }; |
32368ac3 WB |
11 | |
12 | use super::{DiskSizeEditView, FormView, IntegerEditView}; | |
86c48f76 AL |
13 | use crate::options::FS_TYPES; |
14 | use crate::InstallerState; | |
15 | ||
16 | use proxmox_installer_common::{ | |
feea90d4 CH |
17 | disk_checks::{ |
18 | check_btrfs_raid_config, check_disks_4kn_legacy_boot, check_for_duplicate_disks, | |
19 | check_zfs_raid_config, | |
20 | }, | |
ed37980f | 21 | options::{ |
feea90d4 CH |
22 | AdvancedBootdiskOptions, BootdiskOptions, BtrfsBootdiskOptions, Disk, FsType, |
23 | LvmBootdiskOptions, ZfsBootdiskOptions, ZFS_CHECKSUM_OPTIONS, ZFS_COMPRESS_OPTIONS, | |
ed37980f | 24 | }, |
13490d13 | 25 | setup::{BootType, ProductConfig, ProxmoxProduct, RuntimeInfo}, |
32368ac3 | 26 | }; |
af0dfe0e | 27 | |
521dfff5 CH |
28 | /// OpenZFS specifies 64 MiB as the absolute minimum: |
29 | /// https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max | |
30 | const ZFS_ARC_MIN_SIZE_MIB: usize = 64; // MiB | |
31 | ||
d81cffcb CH |
32 | /// Convience wrapper when needing to take a (interior-mutable) reference to `BootdiskOptions`. |
33 | /// Interior mutability is safe for this case, as it is completely single-threaded. | |
34 | pub type BootdiskOptionsRef = Rc<RefCell<BootdiskOptions>>; | |
35 | ||
ed191e60 | 36 | pub struct BootdiskOptionsView { |
af0dfe0e | 37 | view: LinearLayout, |
d81cffcb | 38 | advanced_options: BootdiskOptionsRef, |
fea97303 | 39 | boot_type: BootType, |
ed191e60 CH |
40 | } |
41 | ||
42 | impl BootdiskOptionsView { | |
521dfff5 | 43 | pub fn new(siv: &mut Cursive, runinfo: &RuntimeInfo, options: &BootdiskOptions) -> Self { |
b977af78 CH |
44 | let bootdisk_form = FormView::new() |
45 | .child( | |
46 | "Target harddisk", | |
47 | SelectView::new() | |
48 | .popup() | |
521dfff5 | 49 | .with_all(runinfo.disks.iter().map(|d| (d.to_string(), d.clone()))), |
b977af78 | 50 | ) |
66baa275 | 51 | .with_name("bootdisk-options-target-disk"); |
ed191e60 | 52 | |
e14adfe3 CH |
53 | let product_conf = siv |
54 | .user_data::<InstallerState>() | |
55 | .map(|state| state.setup_info.config.clone()) | |
56 | .unwrap(); // Safety: InstallerState must always be set | |
57 | ||
7393ed88 | 58 | let advanced_options = Rc::new(RefCell::new(options.clone())); |
ed191e60 CH |
59 | |
60 | let advanced_button = LinearLayout::horizontal() | |
61 | .child(DummyView.full_width()) | |
62 | .child(Button::new("Advanced options", { | |
521dfff5 | 63 | let runinfo = runinfo.clone(); |
ed191e60 CH |
64 | let options = advanced_options.clone(); |
65 | move |siv| { | |
e14adfe3 | 66 | siv.add_layer(advanced_options_view( |
521dfff5 | 67 | &runinfo, |
e14adfe3 CH |
68 | options.clone(), |
69 | product_conf.clone(), | |
70 | )); | |
ed191e60 CH |
71 | } |
72 | })); | |
73 | ||
74 | let view = LinearLayout::vertical() | |
b977af78 | 75 | .child(bootdisk_form) |
ed191e60 CH |
76 | .child(DummyView) |
77 | .child(advanced_button); | |
78 | ||
fea97303 CH |
79 | let boot_type = siv |
80 | .user_data::<InstallerState>() | |
81 | .map(|state| state.runtime_info.boot_type) | |
82 | .unwrap_or(BootType::Bios); | |
83 | ||
ed191e60 CH |
84 | Self { |
85 | view, | |
86 | advanced_options, | |
fea97303 | 87 | boot_type, |
ed191e60 CH |
88 | } |
89 | } | |
90 | ||
994c4ff0 | 91 | pub fn get_values(&mut self) -> Result<BootdiskOptions, String> { |
7393ed88 | 92 | let mut options = (*self.advanced_options).clone().into_inner(); |
ed191e60 | 93 | |
7393ed88 CH |
94 | if [FsType::Ext4, FsType::Xfs].contains(&options.fstype) { |
95 | let disk = self | |
96 | .view | |
994c4ff0 CH |
97 | .get_child_mut(0) |
98 | .and_then(|v| v.downcast_mut::<NamedView<FormView>>()) | |
99 | .map(NamedView::<FormView>::get_mut) | |
100 | .and_then(|v| v.get_value::<SelectView<Disk>, _>(0)) | |
69f162a6 | 101 | .ok_or("failed to retrieve bootdisk")?; |
ed191e60 | 102 | |
7393ed88 CH |
103 | options.disks = vec![disk]; |
104 | } | |
105 | ||
fea97303 | 106 | check_disks_4kn_legacy_boot(self.boot_type, &options.disks)?; |
994c4ff0 | 107 | Ok(options) |
ed191e60 | 108 | } |
af0dfe0e CH |
109 | } |
110 | ||
ed191e60 CH |
111 | impl ViewWrapper for BootdiskOptionsView { |
112 | cursive::wrap_impl!(self.view: LinearLayout); | |
113 | } | |
114 | ||
115 | struct AdvancedBootdiskOptionsView { | |
116 | view: LinearLayout, | |
117 | } | |
af0dfe0e | 118 | |
ed191e60 | 119 | impl AdvancedBootdiskOptionsView { |
521dfff5 | 120 | fn new(runinfo: &RuntimeInfo, options: &BootdiskOptions, product_conf: ProductConfig) -> Self { |
e14adfe3 CH |
121 | let filter_btrfs = |
122 | |fstype: &&FsType| -> bool { product_conf.enable_btrfs || !fstype.is_btrfs() }; | |
a96dc916 | 123 | |
66baa275 CH |
124 | let fstype_select = SelectView::new() |
125 | .popup() | |
a96dc916 TL |
126 | .with_all( |
127 | FS_TYPES | |
128 | .iter() | |
129 | .filter(filter_btrfs) | |
130 | .map(|t| (t.to_string(), *t)), | |
131 | ) | |
66baa275 CH |
132 | .selected( |
133 | FS_TYPES | |
134 | .iter() | |
a96dc916 | 135 | .filter(filter_btrfs) |
66baa275 CH |
136 | .position(|t| *t == options.fstype) |
137 | .unwrap_or_default(), | |
138 | ) | |
521dfff5 | 139 | .on_submit(Self::fstype_on_submit); |
af0dfe0e | 140 | |
7393ed88 | 141 | let mut view = LinearLayout::vertical() |
ed191e60 | 142 | .child(DummyView.full_width()) |
66baa275 | 143 | .child(FormView::new().child("Filesystem", fstype_select)) |
7393ed88 CH |
144 | .child(DummyView.full_width()); |
145 | ||
146 | match &options.advanced { | |
e8a7b9ba CH |
147 | AdvancedBootdiskOptions::Lvm(lvm) => { |
148 | view.add_child(LvmBootdiskOptionsView::new(lvm, &product_conf)) | |
149 | } | |
7393ed88 | 150 | AdvancedBootdiskOptions::Zfs(zfs) => { |
521dfff5 | 151 | view.add_child(ZfsBootdiskOptionsView::new(runinfo, zfs, &product_conf)) |
7393ed88 | 152 | } |
56a304d5 | 153 | AdvancedBootdiskOptions::Btrfs(btrfs) => { |
521dfff5 | 154 | view.add_child(BtrfsBootdiskOptionsView::new(&runinfo.disks, btrfs)) |
56a304d5 | 155 | } |
7393ed88 | 156 | }; |
af0dfe0e CH |
157 | |
158 | Self { view } | |
159 | } | |
160 | ||
521dfff5 | 161 | fn fstype_on_submit(siv: &mut Cursive, fstype: &FsType) { |
e8a7b9ba | 162 | let state = siv.user_data::<InstallerState>().unwrap(); |
13490d13 | 163 | let runinfo = state.runtime_info.clone(); |
e8a7b9ba | 164 | let product_conf = state.setup_info.config.clone(); |
e14adfe3 | 165 | |
7393ed88 CH |
166 | siv.call_on_name("advanced-bootdisk-options-dialog", |view: &mut Dialog| { |
167 | if let Some(AdvancedBootdiskOptionsView { view }) = | |
ce0b5865 | 168 | view.get_content_mut().downcast_mut() |
7393ed88 CH |
169 | { |
170 | view.remove_child(3); | |
171 | match fstype { | |
e8a7b9ba | 172 | FsType::Ext4 | FsType::Xfs => view.add_child( |
521dfff5 | 173 | LvmBootdiskOptionsView::new_with_defaults(&runinfo.disks[0], &product_conf), |
e8a7b9ba | 174 | ), |
13490d13 CH |
175 | FsType::Zfs(_) => view.add_child(ZfsBootdiskOptionsView::new_with_defaults( |
176 | &runinfo, | |
177 | &product_conf, | |
178 | )), | |
e8a7b9ba | 179 | FsType::Btrfs(_) => { |
521dfff5 | 180 | view.add_child(BtrfsBootdiskOptionsView::new_with_defaults(&runinfo.disks)) |
e8a7b9ba | 181 | } |
7393ed88 CH |
182 | } |
183 | } | |
184 | }); | |
185 | ||
186 | siv.call_on_name( | |
187 | "bootdisk-options-target-disk", | |
66baa275 CH |
188 | |view: &mut FormView| match fstype { |
189 | FsType::Ext4 | FsType::Xfs => { | |
190 | view.replace_child( | |
191 | 0, | |
192 | SelectView::new() | |
193 | .popup() | |
521dfff5 | 194 | .with_all(runinfo.disks.iter().map(|d| (d.to_string(), d.clone()))), |
66baa275 CH |
195 | ); |
196 | } | |
197 | other => view.replace_child(0, TextView::new(other.to_string())), | |
7393ed88 CH |
198 | }, |
199 | ); | |
200 | } | |
201 | ||
fea97303 | 202 | fn get_values(&mut self) -> Result<BootdiskOptions, String> { |
ed191e60 CH |
203 | let fstype = self |
204 | .view | |
597d10f6 CH |
205 | .get_child(1) |
206 | .and_then(|v| v.downcast_ref::<FormView>()) | |
207 | .and_then(|v| v.get_value::<SelectView<FsType>, _>(0)) | |
208 | .ok_or("Failed to retrieve filesystem type".to_owned())?; | |
ed191e60 | 209 | |
597d10f6 CH |
210 | let advanced = self |
211 | .view | |
212 | .get_child_mut(3) | |
213 | .ok_or("Failed to retrieve advanced bootdisk options view".to_owned())?; | |
7393ed88 CH |
214 | |
215 | if let Some(view) = advanced.downcast_mut::<LvmBootdiskOptionsView>() { | |
597d10f6 CH |
216 | let advanced = view |
217 | .get_values() | |
218 | .map(AdvancedBootdiskOptions::Lvm) | |
219 | .ok_or("Failed to retrieve advanced bootdisk options")?; | |
220 | ||
221 | Ok(BootdiskOptions { | |
7393ed88 CH |
222 | disks: vec![], |
223 | fstype, | |
597d10f6 | 224 | advanced, |
7393ed88 CH |
225 | }) |
226 | } else if let Some(view) = advanced.downcast_mut::<ZfsBootdiskOptionsView>() { | |
597d10f6 CH |
227 | let (disks, advanced) = view |
228 | .get_values() | |
229 | .ok_or("Failed to retrieve advanced bootdisk options")?; | |
ed191e60 | 230 | |
ed37980f | 231 | if let FsType::Zfs(level) = fstype { |
fea97303 | 232 | check_zfs_raid_config(level, &disks).map_err(|err| format!("{fstype}: {err}"))?; |
ed37980f CH |
233 | } |
234 | ||
597d10f6 | 235 | Ok(BootdiskOptions { |
7393ed88 CH |
236 | disks, |
237 | fstype, | |
238 | advanced: AdvancedBootdiskOptions::Zfs(advanced), | |
239 | }) | |
56a304d5 | 240 | } else if let Some(view) = advanced.downcast_mut::<BtrfsBootdiskOptionsView>() { |
597d10f6 CH |
241 | let (disks, advanced) = view |
242 | .get_values() | |
243 | .ok_or("Failed to retrieve advanced bootdisk options")?; | |
56a304d5 | 244 | |
ed37980f CH |
245 | if let FsType::Btrfs(level) = fstype { |
246 | check_btrfs_raid_config(level, &disks).map_err(|err| format!("{fstype}: {err}"))?; | |
247 | } | |
248 | ||
597d10f6 | 249 | Ok(BootdiskOptions { |
56a304d5 CH |
250 | disks, |
251 | fstype, | |
252 | advanced: AdvancedBootdiskOptions::Btrfs(advanced), | |
253 | }) | |
7393ed88 | 254 | } else { |
597d10f6 | 255 | Err("Invalid bootdisk view state".to_owned()) |
7393ed88 | 256 | } |
af0dfe0e CH |
257 | } |
258 | } | |
259 | ||
ed191e60 | 260 | impl ViewWrapper for AdvancedBootdiskOptionsView { |
af0dfe0e CH |
261 | cursive::wrap_impl!(self.view: LinearLayout); |
262 | } | |
263 | ||
264 | struct LvmBootdiskOptionsView { | |
cd6d2d24 | 265 | view: FormView, |
e14adfe3 | 266 | has_extra_fields: bool, |
af0dfe0e CH |
267 | } |
268 | ||
269 | impl LvmBootdiskOptionsView { | |
e8a7b9ba CH |
270 | fn new(options: &LvmBootdiskOptions, product_conf: &ProductConfig) -> Self { |
271 | let show_extra_fields = product_conf.product == ProxmoxProduct::PVE; | |
272 | ||
ed191e60 | 273 | // TODO: Set maximum accordingly to disk size |
cd6d2d24 CH |
274 | let view = FormView::new() |
275 | .child( | |
276 | "Total size", | |
859e3478 CH |
277 | DiskSizeEditView::new() |
278 | .content(options.total_size) | |
279 | .max_value(options.total_size), | |
cd6d2d24 | 280 | ) |
af0dfe0e | 281 | .child( |
cd6d2d24 | 282 | "Swap size", |
409fc0fd | 283 | DiskSizeEditView::new_emptyable().content_maybe(options.swap_size), |
af0dfe0e | 284 | ) |
6719eda6 | 285 | .child_conditional( |
e14adfe3 | 286 | show_extra_fields, |
cd6d2d24 | 287 | "Maximum root volume size", |
409fc0fd | 288 | DiskSizeEditView::new_emptyable().content_maybe(options.max_root_size), |
af0dfe0e | 289 | ) |
6719eda6 | 290 | .child_conditional( |
e14adfe3 | 291 | show_extra_fields, |
cd6d2d24 | 292 | "Maximum data volume size", |
409fc0fd | 293 | DiskSizeEditView::new_emptyable().content_maybe(options.max_data_size), |
cd6d2d24 CH |
294 | ) |
295 | .child( | |
296 | "Minimum free LVM space", | |
409fc0fd | 297 | DiskSizeEditView::new_emptyable().content_maybe(options.min_lvm_free), |
af0dfe0e CH |
298 | ); |
299 | ||
e14adfe3 CH |
300 | Self { |
301 | view, | |
302 | has_extra_fields: show_extra_fields, | |
303 | } | |
af0dfe0e CH |
304 | } |
305 | ||
e8a7b9ba CH |
306 | fn new_with_defaults(disk: &Disk, product_conf: &ProductConfig) -> Self { |
307 | Self::new(&LvmBootdiskOptions::defaults_from(disk), product_conf) | |
308 | } | |
309 | ||
af0dfe0e | 310 | fn get_values(&mut self) -> Option<LvmBootdiskOptions> { |
e14adfe3 CH |
311 | let min_lvm_free_id = if self.has_extra_fields { 4 } else { 2 }; |
312 | ||
313 | let max_root_size = self | |
314 | .has_extra_fields | |
315 | .then(|| self.view.get_value::<DiskSizeEditView, _>(2)) | |
316 | .flatten(); | |
317 | let max_data_size = self | |
318 | .has_extra_fields | |
319 | .then(|| self.view.get_value::<DiskSizeEditView, _>(3)) | |
320 | .flatten(); | |
321 | ||
af0dfe0e | 322 | Some(LvmBootdiskOptions { |
cd6d2d24 | 323 | total_size: self.view.get_value::<DiskSizeEditView, _>(0)?, |
409fc0fd | 324 | swap_size: self.view.get_value::<DiskSizeEditView, _>(1), |
6719eda6 TL |
325 | max_root_size, |
326 | max_data_size, | |
327 | min_lvm_free: self.view.get_value::<DiskSizeEditView, _>(min_lvm_free_id), | |
af0dfe0e CH |
328 | }) |
329 | } | |
330 | } | |
331 | ||
332 | impl ViewWrapper for LvmBootdiskOptionsView { | |
cd6d2d24 | 333 | cursive::wrap_impl!(self.view: FormView); |
af0dfe0e | 334 | } |
ed191e60 | 335 | |
ee1437bb | 336 | struct MultiDiskOptionsView<T> { |
56a304d5 | 337 | view: LinearLayout, |
ee1437bb | 338 | phantom: PhantomData<T>, |
56a304d5 CH |
339 | } |
340 | ||
ee1437bb | 341 | impl<T: View> MultiDiskOptionsView<T> { |
9e5cf6b6 | 342 | fn new(avail_disks: &[Disk], selected_disks: &[usize], options_view: T) -> Self { |
429718b8 CH |
343 | let mut selectable_disks = avail_disks |
344 | .iter() | |
345 | .map(|d| (d.to_string(), Some(d.clone()))) | |
346 | .collect::<Vec<(String, Option<Disk>)>>(); | |
347 | ||
348 | selectable_disks.push(("-- do not use --".to_owned(), None)); | |
349 | ||
d36c96af | 350 | let mut disk_form = FormView::new(); |
9e5cf6b6 | 351 | for (i, _) in avail_disks.iter().enumerate() { |
d36c96af | 352 | disk_form.add_child( |
56a304d5 CH |
353 | &format!("Harddisk {i}"), |
354 | SelectView::new() | |
355 | .popup() | |
429718b8 | 356 | .with_all(selectable_disks.clone()) |
cc655f8b | 357 | .selected(selected_disks[i]), |
d36c96af | 358 | ); |
56a304d5 CH |
359 | } |
360 | ||
2379338c | 361 | let mut disk_select_view = LinearLayout::vertical() |
cdba54ce | 362 | .child(TextView::new("Disk setup").center()) |
d36c96af | 363 | .child(DummyView) |
2379338c CH |
364 | .child(ScrollView::new(disk_form.with_name("multidisk-disk-form"))); |
365 | ||
366 | if avail_disks.len() > 3 { | |
367 | let do_not_use_index = selectable_disks.len() - 1; | |
368 | let deselect_all_button = Button::new("Deselect all", move |siv| { | |
369 | siv.call_on_name("multidisk-disk-form", |view: &mut FormView| { | |
370 | view.call_on_childs(&|v: &mut SelectView<Option<Disk>>| { | |
371 | // As there is no .on_select() callback defined on the | |
372 | // SelectView's, the returned callback here can be safely | |
373 | // ignored. | |
374 | v.set_selection(do_not_use_index); | |
375 | }); | |
376 | }); | |
377 | }); | |
378 | ||
379 | disk_select_view.add_child(PaddedView::lrtb( | |
380 | 0, | |
381 | 0, | |
382 | 1, | |
383 | 0, | |
384 | LinearLayout::horizontal() | |
385 | .child(DummyView.full_width()) | |
386 | .child(deselect_all_button), | |
387 | )); | |
388 | } | |
d36c96af | 389 | |
56a304d5 | 390 | let options_view = LinearLayout::vertical() |
cdba54ce | 391 | .child(TextView::new("Advanced options").center()) |
56a304d5 | 392 | .child(DummyView) |
ee1437bb | 393 | .child(options_view); |
56a304d5 CH |
394 | |
395 | let view = LinearLayout::horizontal() | |
396 | .child(disk_select_view) | |
397 | .child(DummyView.fixed_width(3)) | |
398 | .child(options_view); | |
399 | ||
ee1437bb | 400 | Self { |
82cc9fc4 | 401 | view: LinearLayout::vertical().child(view), |
ee1437bb CH |
402 | phantom: PhantomData, |
403 | } | |
56a304d5 CH |
404 | } |
405 | ||
7f273738 | 406 | fn top_panel(mut self, view: impl View) -> Self { |
82cc9fc4 CH |
407 | if self.has_top_panel() { |
408 | self.view.remove_child(0); | |
409 | } | |
7f273738 | 410 | |
82cc9fc4 | 411 | self.view.insert_child(0, Panel::new(view)); |
7f273738 CH |
412 | self |
413 | } | |
414 | ||
cc655f8b SS |
415 | /// |
416 | /// This function returns a tuple of vectors. The first vector contains the currently selected | |
417 | /// disks in order of their selection slot. Empty slots are filtered out. The second vector | |
418 | /// contains indices of each slot's selection, which enables us to restore the selection even | |
419 | /// for empty slots. | |
420 | /// | |
421 | fn get_disks_and_selection(&mut self) -> Option<(Vec<Disk>, Vec<usize>)> { | |
56a304d5 | 422 | let mut disks = vec![]; |
82cc9fc4 CH |
423 | let view_top_index = usize::from(self.has_top_panel()); |
424 | ||
d36c96af CH |
425 | let disk_form = self |
426 | .view | |
2379338c CH |
427 | .get_child_mut(view_top_index)? |
428 | .downcast_mut::<LinearLayout>()? | |
429 | .get_child_mut(0)? | |
430 | .downcast_mut::<LinearLayout>()? | |
431 | .get_child_mut(2)? | |
432 | .downcast_mut::<ScrollView<NamedView<FormView>>>()? | |
433 | .get_inner_mut() | |
434 | .get_mut(); | |
56a304d5 | 435 | |
cc655f8b SS |
436 | let mut selected_disks = Vec::new(); |
437 | ||
d36c96af | 438 | for i in 0..disk_form.len() { |
429718b8 | 439 | let disk = disk_form.get_value::<SelectView<Option<Disk>>, _>(i)?; |
56a304d5 | 440 | |
429718b8 CH |
441 | // `None` means no disk was selected for this slot |
442 | if let Some(disk) = disk { | |
443 | disks.push(disk); | |
444 | } | |
cc655f8b SS |
445 | |
446 | selected_disks.push( | |
447 | disk_form | |
448 | .get_child::<SelectView<Option<Disk>>>(i)? | |
449 | .selected_id()?, | |
450 | ); | |
56a304d5 CH |
451 | } |
452 | ||
cc655f8b | 453 | Some((disks, selected_disks)) |
ee1437bb | 454 | } |
56a304d5 | 455 | |
ee1437bb | 456 | fn inner_mut(&mut self) -> Option<&mut T> { |
82cc9fc4 CH |
457 | let view_top_index = usize::from(self.has_top_panel()); |
458 | ||
ee1437bb | 459 | self.view |
82cc9fc4 CH |
460 | .get_child_mut(view_top_index)? |
461 | .downcast_mut::<LinearLayout>()? | |
56a304d5 | 462 | .get_child_mut(2)? |
ee1437bb CH |
463 | .downcast_mut::<LinearLayout>()? |
464 | .get_child_mut(2)? | |
465 | .downcast_mut::<T>() | |
466 | } | |
82cc9fc4 CH |
467 | |
468 | fn has_top_panel(&self) -> bool { | |
469 | // The root view should only ever have one or two children | |
470 | assert!([1, 2].contains(&self.view.len())); | |
471 | ||
472 | self.view.len() == 2 | |
473 | } | |
ee1437bb CH |
474 | } |
475 | ||
476 | impl<T: 'static> ViewWrapper for MultiDiskOptionsView<T> { | |
477 | cursive::wrap_impl!(self.view: LinearLayout); | |
478 | } | |
479 | ||
480 | struct BtrfsBootdiskOptionsView { | |
d36c96af | 481 | view: MultiDiskOptionsView<FormView>, |
ee1437bb CH |
482 | } |
483 | ||
484 | impl BtrfsBootdiskOptionsView { | |
ee1437bb CH |
485 | fn new(disks: &[Disk], options: &BtrfsBootdiskOptions) -> Self { |
486 | let view = MultiDiskOptionsView::new( | |
487 | disks, | |
cc655f8b | 488 | &options.selected_disks, |
d36c96af | 489 | FormView::new().child("hdsize", DiskSizeEditView::new().content(options.disk_size)), |
7f273738 CH |
490 | ) |
491 | .top_panel(TextView::new("Btrfs integration is a technology preview!").center()); | |
ee1437bb CH |
492 | |
493 | Self { view } | |
494 | } | |
495 | ||
e8a7b9ba CH |
496 | fn new_with_defaults(disks: &[Disk]) -> Self { |
497 | Self::new(disks, &BtrfsBootdiskOptions::defaults_from(disks)) | |
498 | } | |
499 | ||
ee1437bb | 500 | fn get_values(&mut self) -> Option<(Vec<Disk>, BtrfsBootdiskOptions)> { |
cc655f8b | 501 | let (disks, selected_disks) = self.view.get_disks_and_selection()?; |
d36c96af | 502 | let disk_size = self.view.inner_mut()?.get_value::<DiskSizeEditView, _>(0)?; |
56a304d5 | 503 | |
cc655f8b SS |
504 | Some(( |
505 | disks, | |
506 | BtrfsBootdiskOptions { | |
507 | disk_size, | |
508 | selected_disks, | |
509 | }, | |
510 | )) | |
56a304d5 CH |
511 | } |
512 | } | |
513 | ||
514 | impl ViewWrapper for BtrfsBootdiskOptionsView { | |
d36c96af | 515 | cursive::wrap_impl!(self.view: MultiDiskOptionsView<FormView>); |
56a304d5 CH |
516 | } |
517 | ||
7393ed88 | 518 | struct ZfsBootdiskOptionsView { |
d36c96af | 519 | view: MultiDiskOptionsView<FormView>, |
7393ed88 CH |
520 | } |
521 | ||
522 | impl ZfsBootdiskOptionsView { | |
523 | // TODO: Re-apply previous disk selection from `options` correctly | |
521dfff5 CH |
524 | fn new( |
525 | runinfo: &RuntimeInfo, | |
526 | options: &ZfsBootdiskOptions, | |
527 | product_conf: &ProductConfig, | |
528 | ) -> Self { | |
529 | let is_pve = product_conf.product == ProxmoxProduct::PVE; | |
530 | ||
d36c96af CH |
531 | let inner = FormView::new() |
532 | .child("ashift", IntegerEditView::new().content(options.ashift)) | |
533 | .child( | |
7393ed88 CH |
534 | "compress", |
535 | SelectView::new() | |
536 | .popup() | |
537 | .with_all(ZFS_COMPRESS_OPTIONS.iter().map(|o| (o.to_string(), *o))) | |
538 | .selected( | |
539 | ZFS_COMPRESS_OPTIONS | |
540 | .iter() | |
541 | .position(|o| *o == options.compress) | |
542 | .unwrap_or_default(), | |
543 | ), | |
d36c96af CH |
544 | ) |
545 | .child( | |
7393ed88 CH |
546 | "checksum", |
547 | SelectView::new() | |
548 | .popup() | |
549 | .with_all(ZFS_CHECKSUM_OPTIONS.iter().map(|o| (o.to_string(), *o))) | |
550 | .selected( | |
551 | ZFS_CHECKSUM_OPTIONS | |
552 | .iter() | |
553 | .position(|o| *o == options.checksum) | |
554 | .unwrap_or_default(), | |
555 | ), | |
d36c96af CH |
556 | ) |
557 | .child("copies", IntegerEditView::new().content(options.copies)) | |
521dfff5 CH |
558 | .child_conditional( |
559 | is_pve, | |
560 | "ARC max size", | |
561 | IntegerEditView::new_with_suffix("MiB") | |
562 | .max_value(runinfo.total_memory) | |
563 | .content(options.arc_max), | |
564 | ) | |
d36c96af | 565 | .child("hdsize", DiskSizeEditView::new().content(options.disk_size)); |
7393ed88 | 566 | |
521dfff5 | 567 | let view = MultiDiskOptionsView::new(&runinfo.disks, &options.selected_disks, inner) |
7f273738 CH |
568 | .top_panel(TextView::new( |
569 | "ZFS is not compatible with hardware RAID controllers, for details see the documentation." | |
570 | ).center()); | |
571 | ||
572 | Self { view } | |
7393ed88 CH |
573 | } |
574 | ||
13490d13 CH |
575 | fn new_with_defaults(runinfo: &RuntimeInfo, product_conf: &ProductConfig) -> Self { |
576 | Self::new( | |
521dfff5 | 577 | runinfo, |
13490d13 | 578 | &ZfsBootdiskOptions::defaults_from(runinfo, product_conf), |
521dfff5 | 579 | product_conf, |
13490d13 | 580 | ) |
e8a7b9ba CH |
581 | } |
582 | ||
7393ed88 | 583 | fn get_values(&mut self) -> Option<(Vec<Disk>, ZfsBootdiskOptions)> { |
cc655f8b | 584 | let (disks, selected_disks) = self.view.get_disks_and_selection()?; |
d36c96af | 585 | let view = self.view.inner_mut()?; |
521dfff5 CH |
586 | let has_arc_max = view.len() >= 6; |
587 | let disk_size_index = if has_arc_max { 5 } else { 4 }; | |
7393ed88 | 588 | |
d36c96af CH |
589 | let ashift = view.get_value::<IntegerEditView, _>(0)?; |
590 | let compress = view.get_value::<SelectView<_>, _>(1)?; | |
591 | let checksum = view.get_value::<SelectView<_>, _>(2)?; | |
592 | let copies = view.get_value::<IntegerEditView, _>(3)?; | |
521dfff5 CH |
593 | let disk_size = view.get_value::<DiskSizeEditView, _>(disk_size_index)?; |
594 | ||
595 | let arc_max = if has_arc_max { | |
596 | view.get_value::<IntegerEditView, _>(4)? | |
597 | .max(ZFS_ARC_MIN_SIZE_MIB) | |
598 | } else { | |
599 | 0 // use built-in ZFS default value | |
600 | }; | |
7393ed88 CH |
601 | |
602 | Some(( | |
603 | disks, | |
604 | ZfsBootdiskOptions { | |
605 | ashift, | |
606 | compress, | |
607 | checksum, | |
608 | copies, | |
521dfff5 | 609 | arc_max, |
7393ed88 | 610 | disk_size, |
cc655f8b | 611 | selected_disks, |
7393ed88 CH |
612 | }, |
613 | )) | |
614 | } | |
615 | } | |
616 | ||
617 | impl ViewWrapper for ZfsBootdiskOptionsView { | |
d36c96af | 618 | cursive::wrap_impl!(self.view: MultiDiskOptionsView<FormView>); |
7393ed88 CH |
619 | } |
620 | ||
e14adfe3 | 621 | fn advanced_options_view( |
521dfff5 | 622 | runinfo: &RuntimeInfo, |
d81cffcb | 623 | options_ref: BootdiskOptionsRef, |
e14adfe3 CH |
624 | product_conf: ProductConfig, |
625 | ) -> impl View { | |
7393ed88 | 626 | Dialog::around(AdvancedBootdiskOptionsView::new( |
521dfff5 | 627 | runinfo, |
d81cffcb | 628 | &(*options_ref).borrow(), |
e14adfe3 | 629 | product_conf, |
7393ed88 CH |
630 | )) |
631 | .title("Advanced bootdisk options") | |
632 | .button("Ok", { | |
d81cffcb | 633 | let options_ref = options_ref.clone(); |
7393ed88 CH |
634 | move |siv| { |
635 | let options = siv | |
636 | .call_on_name("advanced-bootdisk-options-dialog", |view: &mut Dialog| { | |
637 | view.get_content_mut() | |
ed37980f | 638 | .downcast_mut::<AdvancedBootdiskOptionsView>() |
fea97303 | 639 | .map(AdvancedBootdiskOptionsView::get_values) |
7393ed88 CH |
640 | }) |
641 | .flatten(); | |
642 | ||
597d10f6 CH |
643 | let options = match options { |
644 | Some(Ok(options)) => options, | |
645 | Some(Err(err)) => { | |
646 | siv.add_layer(Dialog::info(err)); | |
647 | return; | |
648 | } | |
649 | None => { | |
650 | siv.add_layer(Dialog::info("Failed to retrieve bootdisk options view")); | |
651 | return; | |
652 | } | |
653 | }; | |
654 | ||
4f2c43ee CH |
655 | if let Err(duplicate) = check_for_duplicate_disks(&options.disks) { |
656 | siv.add_layer(Dialog::info(format!( | |
657 | "Cannot select same disk twice: {duplicate}" | |
658 | ))); | |
659 | return; | |
5ed7f495 TL |
660 | } |
661 | ||
7393ed88 | 662 | siv.pop_layer(); |
597d10f6 | 663 | *(*options_ref).borrow_mut() = options; |
7393ed88 CH |
664 | } |
665 | }) | |
666 | .with_name("advanced-bootdisk-options-dialog") | |
fccf6bb0 | 667 | .max_size((120, 40)) |
ed191e60 | 668 | } |