forked from pisilinux-rs/yali-rs
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:
@@ -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 |
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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ı");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user