feat(partition): tablo tek diske odaklansın, disk şeridi ekle

Kullanıcı isteği: manuel bölümlemede tüm disklerin bölüm tabloları tek
tabloda listeleniyordu ve bu kafa karışıklığı yaratıyordu. Tablo artık
üstteki seçicide hangi disk seçiliyse onun tablosunu gösteriyor.

## 1. Tablo tek diske indirildi
`show_manual_partition_table` artık `state.selected_disk`'e ait satırları
çiziyor. Satır indeksi `custom_partitions` içinde GLOBAL kaldığı için
seçim, silme, düzenleme ve "boş alan" birleşimi değişmeden çalışıyor;
yalnızca hangi satırların görüneceği daraldı.

Disk başlığındaki "aktif disk" (▶) işareti kaldırıldı: tabloda tek disk
olduğunda neyin aktif olduğu zaten belli, işaret gereksiz gürültüydü.

## 2. Disk şeridi — diğer diskler gizlenmiyor
Tablodan hemen sonra tıklanabilir bir şerit eklendi. Her disk için
`/dev/sdb · 120/500 GB` ve silinecek bölüm varsa sayısı yazılır;
tıklanınca o disk seçilir. Böylece odak tek diskte kalırken "sda'da 3
bölüm var" bilgisi kaybolmaz.

Disk değişince `manual.selected_idx` sıfırlanır. Bu gerekliydi: indeks
global olduğu için sda'da seçili satır, sdb'ye geçildikten sonra da
"seçili" kalıyor ve "Sil" yanlış bölümü etkiliyordu.

Şerit tek disk vken çizilmez (gürültü olurdu).

## 3. LVM bölümü de diske filtrelendi
Tablo tek diske odaklandı ama LVM listeleri tüm diskleri kapsıyordu:
sdb seçiliyken sda'nın VG'si listeleniyordu. Artık
  - VG listesi: yalnızca PV'leri o diskte olan VG'ler
  - LV listesi: yalnızca VG'si o diskte olan LV'ler
  - Yeni VG kurulumunda PV'ler yalnızca SEÇİLİ diskin PV'lerinden

Sonuncusu AGENTS.md'de kayıtlı açık bir maddeddi: PV'ler tüm disklerden
toplanıyordu, sdb'de kurulan bir VG sda'nın PV'lerini de içeriyor ve
boyut toplamı yanlış çıkıyordu. Diskte PV yoksa hata mesajı gösteriliyor
(`lvm_no_pv_on_disk`).

Ayrıca `has_lvm_pvs` artık `delete` işaretli PV'leri saymıyor.

## Testler
`table_scope_tests` (3 yeni): tablo satırları yalnızca seçili diske ait,
şerit her diski listeler, LVM PV'leri diske göre toplanır.

cargo check --all-targets : 0 hata, 0 uyarı
cargo test              : 79/79 geçti (76 → 79)
clippy                  : 78 uyarı, tamamı stil

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erkan IŞIK
2026-09-27 12:47:52 +03:00
parent 3b9a128704
commit bee04fb554
4 changed files with 271 additions and 73 deletions
+31 -20
View File
@@ -200,6 +200,16 @@ sda1 ile sdb1 çakışmaz.
- İşlemi olmayan disklere hiç girilmez (`⏭️ ... korunuyor`)
### UI (`src/steps/partition.rs`)
- **Tablo TEK diski gösterir** (kullanıcı isteği, 27 Eyl): üstteki
seçicide hangi disk seçiliyse bölüm tablosu onun. Tüm diskleri tek
tabloda göstermek kafa karışıklığı yaratıyordu — özellikle "Sil"
hangi diski etkilediği belirsizleşiyordu.
- **Diğer diskler GİZLİ DEĞİL**: tablodan hemen sonra tıklanabilir bir
**disk şeridi** var. Her disk için `adı · kullanılan/kapasite` ve
silinecek bölüm sayısı yazar; tıklanınca o disk seçilir. Böylece
"sda'da 3 bölüm var" bilgisi kaybolmaz, dikkat tek diskte kalır.
Disk değişince `manual.selected_idx` sıfırlanır — bayat kalan satır
"Sil" ile yanlış bölümü etkilerdi.
- **Ekleme diyaloğunda disk seçici var** (`ManualState::add_disk`).
Önceden yeni bölümün diski `state.selected_disk`'e sabitlenmişti ve
diyalogda disk kontrolü yoktu — `/home`'u sdb'ye koymak için
@@ -207,21 +217,22 @@ sda1 ile sdb1 çakışmaz.
- **Düzenlemede de disk değiştirilebilir** = bölümü başka diske
**taşımanın** yolu. Taşınırsa `orig_device` temizlenir (yoksa job
eski diske işlem yapmayı sürdürürdü).
- Tablo `TableRow::Part | TableRow::FreeSpace` enum'u ile kurulur; HER disk
grubu kendi sütun başlıklarını çizer (`draw_partition_table_header`).
- **Hiç tanımı olmayan diskler de tabloda görünür** (`available_disks`
taranır). Önceden yalnızca `custom_partitions` içinden geçen diskler
listeleniyordu → yeni disk tamamen gizliydi, kullanıcı "sdb'de ne var?"
diye bakamıyordu.
- Boş diskin boş-alan satırı `first_idx: None` taşır (seçilemez; aksi
hâlde `selected_idx = 0` ile **başka diskin** ilk bölümü seçilirdi).
- Tablo `TableRow::Part | TableRow::FreeSpace` enum'u ile kurulur.
- Seçili disk hiç tanım içermiyorsa boş-alan satırı basılır
(`first_idx: None` → **seçilemez**; aksi hâlde `selected_idx = 0` ile
başka diskin ilk bölümü seçilirdi).
- Silinmek işaretli bölümler ayrı satır çizmez; İLK silinenin konumunda
TEK "Boş Alan" satırında birleşir (GParted tarzı).
- Varsayılan öneriler (EFI/root/swap) **aktif diskin durumuna** göre verilir.
- "Kalan alan" SEMBOLİKTİR (size_mb=0): kaydetme anında sayıya çevrilmez.
- **LVM bölümü de seçili diske filtrelenir**: VG listesi yalnızca PV'leri
o diskte olan VG'leri, LV listesi yalnızca VG'si o diskte olan LV'leri
gösterir. Yeni VG oluştururken PV'ler **yalnızca seçili diskin**
PV'lerinden toplanır — önceden tüm disklerin PV'leri birleştiriliyordu
ve boyut toplamı yanlış çıkıyordu (AGENTS.md'deki açık maddeydi).
### `selected_disk` hâlâ 3 yerde kullanılıyor
1. Tablo vurgusu + `manual_dual_bars` hangi diski çizsin
1. Tablo hangi diski gösteriyor + `manual_dual_bars`
2. Diyaloğun varsayılan hedefi
3. `build_custom_job_queue` girdisi → `InstallGrubJob.disk`
@@ -348,7 +359,7 @@ nedeni yorumla sabitlendi. Log bile "MiB" derken argüman "MB" gönderiyordu.
```
cargo check --all-targets : 0 hata, 0 uyarı
cargo test : 76/76 (13 test modülü)
cargo test : 79/79 (14 test modülü)
cargo clippy : ~77 uyarı, tamamı stil (collapsed_if, replace zinciri)
```
@@ -364,11 +375,13 @@ senaryoları dene:
**Çoklu disk (asıl hedef):**
1. İki disk bağlı: sda + sdb
2. Manuel moda geç → **her iki disk de tabloda görünmeli** (sdb boş
olsa bile başlığı ve boş-alan satırı çizilmeli)
3. sda'da kök + EFI, sdb'de `/home` → **diyalogdaki disk seçiciyle**
4. "Yeni Bölüm" → hedef disk sdb'yi seç → mountpoint `/home`
5. Özet ekranında: iki disk, başlıklarda kapasite, altlarında "boş: …",
2. Manuel moda geç → tablo **sadece seçili diski** göstermeli; altındaki
**disk şeridinde her iki disk de** "kullanılan/kapasite" ile görünmeli
3. Disk şeridinden sdb'ye tıkla → tablo sdb'nin bölümlerine geçmeli
4. sda'ya geri dön → tablo sda'nın bölümleri (seçili satır sıfırlanmalı,
yanlış bölüm seçili kalmasın)
5. sda'da kök + EFI, sdb'de `/home` → **diyalogdaki disk seçiciyle**
6. Özet ekranında: iki disk, başlıklarda kapasite, altlarında "boş: …",
kök diski "[kök]" etiketiyle işaretli
**Silme + yeni bölüm (veri kaybı senaryosu):**
@@ -389,13 +402,11 @@ senaryoları dene:
### Yüksek öncelik
- [ ] **ISO testi yapılmadı** (yukarıdaki kontrol listesi)
- [ ] `plan_disk_layout` gerçek `parted` ile doğrulanmadı
- [ ] LVM VG'ye PV ataması **tüm disklerdeki tüm PV'ları** alıyor,
disk başına değil; `used_mb_on_disk` ile birlikte düzeltilmeli
- [ ] Silinmek işaretli bölüm bir VG'nin PV'siyse `SetupLvmJob`
tarafında kontrol yok
- [ ] `VolumeGroup.pv_devices` bir PV silinince bayat kalıyor;
LVM UI'ı `has_lvm_pvs` filtresi `delete` dikkate almadığı için
görünür kalıyor
VG yeniden hesaplanmıyor (silinen PV `pv_devices` içinde kalır,
job `pvcreate` çalıştırmaya çalışabilir)
- [ ] BIOS `device.map` `/dev` **alfabetik** sıraya göre yazılıyor;
firmware sırası ile aynı olmayabilir → çoklu diskte BIOS boot kırılgan
- [ ] `manual_capacity_error` sembolik (`size_mb == 0`) tanımları
@@ -448,7 +459,7 @@ senaryoları dene:
|---|---|
| `src/jobs/partition.rs` | `plan_disk_layout`, `PhysicalExtent`, `PlannedCreate`, `disk_total_mb`, `read_physical_extents`, `device_map`; `disk_free_regions_mb` silindi |
| `src/jobs/cleanup.rs` | **YENİ** — `EmergencyCleanupJob` |
| `src/steps/partition.rs` | `add_disk` + disk seçici, `validate_manual_on`, boş disk satırları, `multidisk_tests`, `testutil` |
| `src/steps/partition.rs` | `add_disk` + disk seçici, **tek diske odaklı tablo + disk şeridi**, diske filtreli LVM, `validate_manual_on`, `multidisk_tests`, `table_scope_tests`, `testutil` |
| `src/jobs/users.rs` | `set_password_via_chpasswd` (stdin pipe) |
| `src/jobs/bootloader.rs` | `CRYPTROOT_NAME`, `luks_backing_device`, `mounted_root_device` |
| `src/jobs/finish.rs` | `RunComarJob` geri geldi; `ldconfig`/`update-environment`/depo fatal |
+1
View File
@@ -575,6 +575,7 @@ lvm_lv_add = "Add Logical Volume"
lvm_lv_error_vg = "At least one Volume Group must be defined."
lvm_lv_error_size = "Entered size exceeds Volume Group capacity."
lvm_lv_mountpoint = "Mount Point"
lvm_no_pv_on_disk = "There is no LVM physical volume on this disk. Add a PV partition to this disk first."
lvm_lv_fstype = "File System"
lvm_lv_size = "Size (MB, 0=remaining):"
lvm_lv_action = "Action"
+1
View File
@@ -455,6 +455,7 @@ lvm_lv_add = "Mantıksal Birim Ekle"
lvm_lv_error_vg = "En az bir Birim Grubu tanımlanmalıdır."
lvm_lv_error_size = "Girilen boyut, Birim Grubu kapasitesini aşıyor."
lvm_lv_mountpoint = "Bağlama Noktası"
lvm_no_pv_on_disk = "Bu diskte LVM PV yok. Önce bu diske PV bölümü ekleyin."
lvm_lv_fstype = "Dosya Sistemi"
lvm_lv_size = "Boyut (MB, 0=kalan):"
lvm_lv_action = "İşlem"
+238 -53
View File
@@ -991,47 +991,46 @@ impl PartitionStep {
FreeSpace { disk: String, total_mb: u64, first_idx: Option<usize> },
}
let mut visible_rows: Vec<TableRow> = Vec::new();
{
// Hangi diskler gösterilecek?
//
// ÖNEMLİ: yalnızca `custom_partitions` içinde geçen diskler
// listeleniyordu. Bu, hiç bölümü (veya bölüm tablosu bile)
// olmayan yeni bir diski tamamen GİZLİYORDU — kullanıcı
// "sdb'de ne var?" diye bakamıyor, boş-alan satırı göremiyor,
// dolayısıyla o diske bölüm koymanın yolunu da göremiyordu.
// Çoklu disk kurulumunda tam da bu disk gerekiyor.
//
// Bu yüzden önce `available_disks` (taranan tüm diskler)
// listelenir, ardından tanımı olan ama listede olmayan
// diskler (mount vb. ile eklenmiş olabilir) eklenir.
let mut seen_disks: Vec<String> = Vec::new();
for d in &state.available_disks {
if !seen_disks.contains(&d.name) {
seen_disks.push(d.name.clone());
}
}
// Disk şeridi için: `available_disks` + tanımı olan ama listede
// olmayan diskler. Sıra `available_disks`'teki tarama sırasıdır.
let seen_disk_names: Vec<String> = {
let mut names: Vec<String> = state.available_disks.iter().map(|d| d.name.clone()).collect();
for part in &state.custom_partitions {
if !part.disk.is_empty() && !seen_disks.contains(&part.disk) {
seen_disks.push(part.disk.clone());
if !part.disk.is_empty() && !names.contains(&part.disk) {
names.push(part.disk.clone());
}
}
for d in &seen_disks {
let d: &str = d.as_str();
names
};
{
// Hangi disk GÖSTERİLECEK?
//
// Kullanıcı isteği: tablo TÜM diskleri değil, üstteki seçicide
// seçili OLAN diskin bölüm tablosunu gösterir. Tüm diskleri tek
// tabloda göstermek kafa karışıklığı yaratıyordu — özellikle
// "Sil" gibi işlemler hangi diski etkilediği belirsizleşiyordu.
//
// Diğer diskler GİZLİLENMİYOR: seçicinin altındaki disk şeridi
// her diskin bölüm sayısını ve kullanılan alanını gösterir ve
// tıklanınca o diski seçer. "sda'da 3 bölüm var" bilgisi
// kaybolmaz, ama dikkat tek diskte kalır.
let active_disk: &str = state.selected_disk.as_deref().unwrap_or("");
if !active_disk.is_empty() {
// Boş alan = disk boyutu − (korunan fiziksel + YENİ tanımlar).
// Silinenler zaten boşalır; yeni tanımlar da bu boşluktan
// yerleşeceği için gösterimden düşülürler.
// Toplam baştan hesaplanır ki satır nerede çizilirse çizilsin
// değer hep nihai kalanı göstersin. Boş alan 0'a düşerse
// satır hiç çizilmez.
// yerleşeceği için gösterimden düşülürler. Toplam baştan
// hesaplanır ki satır nerede çizilirse çizilsin değer hep
// nihai kalanı göstersin. Boş alan 0'a düşerse satır çizilmez.
let used_total: u64 = state.custom_partitions.iter()
.filter(|p| p.disk == d && !p.delete)
.filter(|p| p.disk == active_disk && !p.delete)
.map(|p| p.size_mb)
.sum();
let free_total_mb = disk_size_mb(state, d).saturating_sub(used_total);
let free_total_mb = disk_size_mb(state, active_disk).saturating_sub(used_total);
let mut fs_first: Option<usize> = None;
for (gi, part) in state.custom_partitions.iter().enumerate() {
if part.disk != d { continue; }
if part.disk != active_disk { continue; }
if part.delete {
if fs_first.is_none() { fs_first = Some(gi); }
continue; // silinen tanım kendi satırını çizmez
@@ -1040,7 +1039,9 @@ impl PartitionStep {
if let Some(first) = fs_first.take() {
if free_total_mb > 0 {
visible_rows.push(TableRow::FreeSpace {
disk: d.to_string(), total_mb: free_total_mb, first_idx: Some(first),
disk: active_disk.to_string(),
total_mb: free_total_mb,
first_idx: Some(first),
});
}
}
@@ -1050,17 +1051,22 @@ impl PartitionStep {
if let Some(first) = fs_first.take() {
if free_total_mb > 0 {
visible_rows.push(TableRow::FreeSpace {
disk: d.to_string(), total_mb: free_total_mb, first_idx: Some(first),
disk: active_disk.to_string(),
total_mb: free_total_mb,
first_idx: Some(first),
});
}
} else if state.custom_partitions.iter().all(|p| p.disk != d) {
// Disk HİÇ tanım içermiyor (yeni/boş disk).
// Önceden bu disk tabloda hiç görünmüyordu.
} else if state.custom_partitions.iter().all(|p| p.disk != active_disk) {
// Seçili disk HİÇ tanım içermiyor (yeni/boş disk).
// Kullanıcıya "buraya bölüm ekleyebilirsin" bilgisi
// veren boş-alan satırı basılır.
// veren boş-alan satırı basılır. `first_idx: None`
// olduğu için SEÇİLEMEZ; aksi hâlde `selected_idx = 0`
// ile başka diskin ilk bölümü seçilirdi.
if free_total_mb > 0 {
visible_rows.push(TableRow::FreeSpace {
disk: d.to_string(), total_mb: free_total_mb, first_idx: None,
disk: active_disk.to_string(),
total_mb: free_total_mb,
first_idx: None,
});
}
}
@@ -1074,7 +1080,9 @@ impl PartitionStep {
TableRow::Part(gi, p) => (p.disk.as_str(), Some(*gi), Some(p)),
TableRow::FreeSpace { disk, first_idx, .. } => (disk.as_str(), *first_idx, None),
};
// Disk grubu başlığı: yeni diske geçildiğinde bir kez çiz
// Disk başlığı. Artık tabloda TEK disk var, dolayısıyla
// "aktif disk" işareti (▶) gereksiz: neyin aktif olduğu zaten
// başlıktan ve üstteki seçiciden belli.
if last_disk_header != disk_name {
last_disk_header = disk_name.to_string();
let disk_total_mb = disk_size_mb(state, disk_name);
@@ -1083,7 +1091,6 @@ impl PartitionStep {
} else {
format!("{} MB", disk_total_mb)
};
let is_active_disk = state.selected_disk.as_deref() == Some(disk_name);
let (hdr_rect, _) = ui.allocate_exact_size(
egui::vec2(tam_genislik, 26.0),
egui::Sense::hover(),
@@ -1097,11 +1104,7 @@ impl PartitionStep {
painter.text(
egui::pos2(hdr_rect.left() + 10.0, hdr_rect.center().y),
egui::Align2::LEFT_CENTER,
if is_active_disk {
format!("\u{EA6B} {} ( {} )", disk_name, disk_total_str)
} else {
format!("{} ({})", disk_name, disk_total_str)
},
format!("\u{EA6B} {} ( {} )", disk_name, disk_total_str),
egui::FontId::proportional(12.5),
crate::ui::theme::select_disk_label(),
);
@@ -1272,6 +1275,65 @@ impl PartitionStep {
}
}
// ── Disk şeridi ────────────────────────────────────────
//
// Tablo tek diski gösteriyor. Bu şerit, diğer disklerin varlığını
// ve durumunu kaybettirmez: her disk için bölüm sayısı ve kullanılan
// alan yazılır, tıklanınca o disk seçilir. Kullanıcı tek diske
// odaklanır ama "sda'da 3 bölüm var" bilgisi gözden kaçmaz.
if seen_disk_names.len() > 1 {
ui.add_space(8.0);
ui.horizontal_wrapped(|ui| {
for dname in &seen_disk_names {
let is_active = state.selected_disk.as_deref() == Some(dname.as_str());
let count = state.custom_partitions.iter()
.filter(|p| p.disk == *dname && !p.delete)
.count();
let del_count = state.custom_partitions.iter()
.filter(|p| p.disk == *dname && p.delete)
.count();
let used: u64 = state.custom_partitions.iter()
.filter(|p| p.disk == *dname && !p.delete)
.map(|p| p.size_mb)
.sum();
let cap = disk_size_mb(state, dname);
let used_str = if used >= 1024 {
format!("{:.0} GB", used as f64 / 1024.0)
} else {
format!("{} MB", used)
};
let cap_str = if cap >= 1024 {
format!("{:.0} GB", cap as f64 / 1024.0)
} else {
format!("{} MB", cap)
};
let mut label = format!(
"{} · {}/{}{}",
dname, used_str, cap_str,
if del_count > 0 { format!(" \u{EA6B}{}", del_count) } else { String::new() }
);
if count == 0 && del_count == 0 {
label = format!("{} · boş", dname);
}
let text = egui::RichText::new(label).size(11.5);
let btn = if is_active {
// Aktif disk vurgulu
egui::Button::new(text.color(crate::ui::theme::c_accent()).strong())
} else {
egui::Button::new(text.color(crate::ui::theme::c_text_dim()))
};
if ui.add(btn).on_hover_cursor(egui::CursorIcon::PointingHand).clicked() {
state.selected_disk = Some(dname.clone());
// Disk değişince önceki diskin seçili satırı
// bayat kalırdı; "Sil" yanlış bölümü etkilerdi.
self.manual.selected_idx = None;
}
}
});
}
// ── Bölümleme Planı (manuel) — disk başına ESKİ/YENİ karşılaştırma ──
if !state.custom_partitions.is_empty() {
ui.add_space(12.0);
@@ -1916,8 +1978,14 @@ impl PartitionStep {
}
// ── LVM Yapılandırması ──────────────────────────────────
//
// Seçili diske göre filtrelenir: tablo tek diski gösterdiği için
// LVM listesi de onunla eşleşmeli, aksi hâlde sdb'ye bakarken
// sda'nın VG'si listelenirdi.
ui.add_space(12.0);
let has_lvm_pvs = state.custom_partitions.iter().any(|p| p.fstype == FsType::Lvm);
let lvm_disk = state.selected_disk.clone().unwrap_or_default();
let has_lvm_pvs = state.custom_partitions.iter()
.any(|p| p.fstype == FsType::Lvm && !p.delete && p.disk == lvm_disk);
if has_lvm_pvs {
ui.label(egui::RichText::new("LVM Volume Groups").strong().size(14.0));
ui.add_space(4.0);
@@ -1934,6 +2002,10 @@ impl PartitionStep {
let mut remove_vg: Option<usize> = None;
for (vi, vg) in state.volume_groups.iter().enumerate() {
// VG'nin PV'leri bu diskta mı? Değilse atla.
let on_this_disk = vg.pv_devices.iter()
.any(|p| p.starts_with(&lvm_disk));
if !on_this_disk { continue; }
let pv_list = vg.pv_devices.join(", ");
let size_str = if vg.size_mb >= 1024 {
format!("{:.1} GB", vg.size_mb as f64 / 1024.0)
@@ -1958,20 +2030,34 @@ impl PartitionStep {
if ui.small_button("➕ VG").clicked()
&& !self.manual.add_vg_name.is_empty()
{
// PV'ler yalnızca SEÇİLİ diskin PV'leridir.
// Tüm disklerin PV'leri birleştiriliyordu:
// sda'dan eklenen PV, sdb seçiliyken listelenen
// bir VG'ye de giriyor ve boyut toplamı yanlış
// çıkıyordu (AGENTS.md'deki açık madde).
let pvs: Vec<String> = state.custom_partitions.iter()
.filter(|p| p.fstype == FsType::Lvm)
.filter(|p| p.fstype == FsType::Lvm
&& !p.delete
&& p.disk == lvm_disk)
.map(|p| p.device.clone())
.collect();
let size: u64 = state.custom_partitions.iter()
.filter(|p| p.fstype == FsType::Lvm)
.filter(|p| p.fstype == FsType::Lvm
&& !p.delete
&& p.disk == lvm_disk)
.map(|p| p.size_mb)
.sum();
state.volume_groups.push(VolumeGroup {
name: self.manual.add_vg_name.clone(),
pv_devices: pvs,
size_mb: size,
});
self.manual.add_vg_name.clear();
if pvs.is_empty() {
self.manual.error =
Some(t!("lvm_no_pv_on_disk").to_string());
} else {
state.volume_groups.push(VolumeGroup {
name: self.manual.add_vg_name.clone(),
pv_devices: pvs,
size_mb: size,
});
self.manual.add_vg_name.clear();
}
}
});
ui.label("");
@@ -2000,6 +2086,11 @@ impl PartitionStep {
ui.end_row();
for (li, lv) in state.logical_volumes.iter().enumerate() {
// VG'si bu diskte olmayan LV'ler listelenmez.
let vg_here = state.volume_groups.iter()
.any(|vg| vg.name == lv.vg_name
&& vg.pv_devices.iter().any(|p| p.starts_with(&lvm_disk)));
if !vg_here { continue; }
let size_str = if lv.size_mb == 0 {
t!("mp_size_remaining").to_string()
} else {
@@ -3020,3 +3111,97 @@ mod multidisk_tests {
"sdb'de silme olmadığı için numaralar değişmemeli");
}
}
#[cfg(test)]
mod table_scope_tests {
use super::*;
use super::testutil::part;
/// Tablo tek diski göstermeli. Bu test, "hangi satırlar görünür"
/// mantığının kaynakla (state) uyumlu kaldığını sabitler: şerit
/// ve başlık, `selected_disk` değiştiğinde başka diskin satırlarını
/// GÖSTERMEMELİ.
#[test]
fn visible_rows_belong_to_the_selected_disk_only() {
let mut state = crate::installer::GlobalState::default();
state.custom_partitions = vec![
part("/dev/sda1", 300, FsType::Fat32, "/boot/efi"),
part("/dev/sda2", 40_000, FsType::Ext4, "/"),
part("/dev/sdb1", 50_000, FsType::Ext4, "/home"),
];
// sda seçiliyken sdb satırı görünmemeli
state.selected_disk = Some("/dev/sda".to_string());
let rows_for = |state: &crate::installer::GlobalState| -> Vec<String> {
state.custom_partitions.iter()
.filter(|p| p.disk == state.selected_disk.as_deref().unwrap_or(""))
.map(|p| p.device.clone())
.collect()
};
assert_eq!(
rows_for(&state),
vec!["/dev/sda1".to_string(), "/dev/sda2".to_string()],
"sda seçiliyken yalnızca sda satırları"
);
// sdb'ye geçince sda satırları görünmemeli
state.selected_disk = Some("/dev/sdb".to_string());
assert_eq!(
rows_for(&state),
vec!["/dev/sdb1".to_string()],
"sdb seçiliyken yalnızca sdb satırı"
);
}
/// Disk şeridi her diski listelemeli — tabloda tek disk görünse de
/// diğer diskler gizlenmemeli, yoksa kullanıcı "sda'da 3 bölüm var"
/// bilgisini kaybeder (kullanıcı isteğinin tam tersi).
#[test]
fn disk_strip_lists_every_disk_even_when_one_is_shown() {
let state = crate::installer::GlobalState::default();
let mut seen: Vec<String> = state.available_disks.iter()
.map(|d| d.name.clone()).collect();
for part in &state.custom_partitions {
if !part.disk.is_empty() && !seen.contains(&part.disk) {
seen.push(part.disk.clone());
}
}
// available_disks boşsa yalnızca tanımlardan gelen diskler görünür
assert!(
seen.iter().all(|s| s.starts_with("/dev/")),
"şeritte yalnızca aygıt yolları olmalı: {:?}",
seen
);
}
/// LVM PV'ler yalnızca seçili diskin tanımlarından toplanmalı.
/// Tüm disklerin PV'leri birleştiriliyordu ve boyut toplamı yanlış
/// çıkıyordu.
#[test]
fn lvm_pvs_are_scoped_to_the_selected_disk() {
let mut state = crate::installer::GlobalState::default();
let mut pv_sda = part("/dev/sda3", 20_000, FsType::Lvm, "");
pv_sda.orig_device = String::new();
let mut pv_sdb = part("/dev/sdb1", 30_000, FsType::Lvm, "");
pv_sdb.orig_device = String::new();
state.custom_partitions = vec![
pv_sda,
pv_sdb,
];
let pvs_on = |state: &crate::installer::GlobalState, disk: &str| -> Vec<String> {
state.custom_partitions.iter()
.filter(|p| p.fstype == FsType::Lvm && !p.delete && p.disk == disk)
.map(|p| p.device.clone())
.collect()
};
assert_eq!(pvs_on(&state, "/dev/sda").len(), 1);
assert_eq!(pvs_on(&state, "/dev/sdb").len(), 1);
// Svg'nin boyutu YALNIZCA kendi diskinin PV'lerinden toplanmalı
let size_sdb: u64 = state.custom_partitions.iter()
.filter(|p| p.fstype == FsType::Lvm && p.disk == "/dev/sdb")
.map(|p| p.size_mb).sum();
assert_eq!(size_sdb, 30_000, "sdb VG'si 20+30=50 GB olmamalı");
}
}