Sistem Asistanı (v1.1) / sayfalar/disk_sagligi.py
disk_sagligi.py 249 satır • 11.47 KB
# sayfalar/disk_sagligi.py
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, 
                             QComboBox, QTextEdit, QProgressBar, QMessageBox, QApplication)
from gorsel_araclar import SayfaBasligi, SvgIkonOlusturucu
import subprocess
import os

class DiskSagligiSayfasi(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        layout = QVBoxLayout(self)
        icon = SvgIkonOlusturucu.health_ikonu("#33AADD", 32)
        layout.addWidget(SayfaBasligi("Disk Sağlığı Analizi", icon))

        if os.path.exists("/usr/sbin/smartctl"):
            self.smart_bin = "/usr/sbin/smartctl"
        elif os.path.exists("/sbin/smartctl"):
            self.smart_bin = "/sbin/smartctl"
        else:
            self.smart_bin = "/usr/sbin/smartctl"

        h_sel = QHBoxLayout()
        self.combo_disk = QComboBox()
        self.diskleri_bul()
        h_sel.addWidget(QLabel("Disk Seç:"))
        h_sel.addWidget(self.combo_disk)
        
        btn_tara = QPushButton("🩺 Sağlık Taraması Yap")
        btn_tara.setStyleSheet("background-color: #33AADD; color: white; font-weight: bold;")
        btn_tara.clicked.connect(self.tara)
        h_sel.addWidget(btn_tara)
        layout.addLayout(h_sel)

        self.lbl_sonuc = QLabel("Durum: Bekleniyor")
        self.lbl_sonuc.setStyleSheet("font-size: 14pt; font-weight: bold; margin: 10px;")
        layout.addWidget(self.lbl_sonuc)

        self.txt_detay = QTextEdit()
        self.txt_detay.setReadOnly(True)
        # Daha modern, konsol benzeri font ve hafif koyu arka plan (eğer tema açıksa bile okunur olsun)
        self.txt_detay.setStyleSheet("font-family: 'Consolas', 'Monospace'; font-size: 10pt; line-height: 1.2;")
        layout.addWidget(self.txt_detay)

        # Onarım Butonu
        btn_onar = QPushButton("🛠️ Disk Hatalarını Onar (fsck - Reboot Gerekir)")
        btn_onar.setStyleSheet("background-color: #e67e22; color: white; font-weight: bold; padding: 10px;")
        btn_onar.clicked.connect(self.onarim_baslat)
        layout.addWidget(btn_onar)

    def diskleri_bul(self):
        self.combo_disk.clear() 
        try:
            out = subprocess.check_output("lsblk -d -n -o NAME,MODEL,TYPE", shell=True, text=True)
            for line in out.splitlines():
                if "disk" in line:
                    parts = line.split()
                    name = parts[0]
                    model = " ".join(parts[1:-1])
                    self.combo_disk.addItem(f"/dev/{name} - {model}", f"/dev/{name}")
        except: pass

    def raporu_detayli_turkcelestir(self, ham_metin):
        """Smartctl çıktısını analiz edip modern, detaylı Türkçe rapor çıkarır."""
        kimlik = []
        saglik = []
        istatistik = []
        hatalar = []
        
        lines = ham_metin.splitlines()
        disk_turu = "SATA/HDD"
        if "NVMe" in ham_metin: disk_turu = "NVMe/SSD"

        for line in lines:
            line = line.strip()
            
            # --- 1. KİMLİK BİLGİLERİ ---
            if line.startswith("Model Family:") or line.startswith("Model Number:"):
                kimlik.append(f"• Model:         {line.split(':', 1)[1].strip()}")
            elif line.startswith("Serial Number:"):
                kimlik.append(f"• Seri No:       {line.split(':', 1)[1].strip()}")
            elif line.startswith("User Capacity:"):
                cap = line.split(':', 1)[1].strip()
                if "[" in cap: cap = cap.split("[")[1].replace("]", "")
                kimlik.append(f"• Kapasite:      {cap}")
            elif line.startswith("Firmware Version:"):
                kimlik.append(f"• Yazılım Sür.:  {line.split(':', 1)[1].strip()}")

            # --- 2. SAĞLIK DURUMU ---
            elif "SMART overall-health" in line:
                stat = line.split(':', 1)[1].strip()
                saglik.append(f"• GENEL DURUM:   {'✅ SAĞLAM' if 'PASSED' in stat else '❌ BAŞARISIZ'}")
            elif "SMART Health Status:" in line:
                stat = line.split(':', 1)[1].strip()
                saglik.append(f"• GENEL DURUM:   {'✅ SAĞLAM' if 'OK' in stat else '❌ HATA'}")
            elif "Critical Warning:" in line:
                warn = line.split(':', 1)[1].strip()
                durum = "✅ Yok" if warn == "0x00" else f"⚠️ VAR ({warn})"
                saglik.append(f"• Kritik Uyarı:  {durum}")

            # --- 3. İSTATİSTİKLER (SATA ve NVMe Karışık) ---
            elif "Power_On_Hours" in line:
                # SATA formatı: ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE
                parts = line.split()
                if len(parts) > 9: istatistik.append(f"• Çalışma Süresi: {parts[-1]} Saat")
            elif "Power On Hours:" in line: # NVMe formatı
                istatistik.append(f"• Çalışma Süresi: {line.split(':', 1)[1].strip().replace(',','')} Saat")
                
            elif "Power_Cycle_Count" in line:
                parts = line.split()
                if len(parts) > 9: istatistik.append(f"• Açma/Kapama:    {parts[-1]} Kez")
            elif "Power Cycles:" in line: # NVMe
                istatistik.append(f"• Açma/Kapama:    {line.split(':', 1)[1].strip().replace(',','')} Kez")

            elif "Temperature_Celsius" in line:
                parts = line.split()
                if len(parts) > 9: istatistik.append(f"• Sıcaklık:       {parts[-1]} °C")
            elif "Temperature:" in line and "Celsius" in line:
                temp = line.split()[1]
                if temp != "0": istatistik.append(f"• Sıcaklık:       {temp} °C")

            # NVMe Özel Veri (Toplam Yazılan)
            elif "Data Units Written:" in line:
                # Genelde 512 byte units verilir, kabaca TB'a çevirelim
                raw_val = line.split(':', 1)[1].split('[')[0].strip().replace(',', '')
                try:
                    tb_val = int(raw_val) * 512 / (1024**4) # TB hesabı
                    istatistik.append(f"• Toplam Yazılan: {tb_val:.2f} TB")
                except: pass

            # --- 4. KRİTİK HATALAR (Eğer 0 değilse göster) ---
            # Reallocated_Sector_Ct
            if "Reallocated_Sector_Ct" in line:
                parts = line.split()
                raw = int(parts[-1])
                if raw > 0: hatalar.append(f"⚠️ Bozuk Sektör (Reallocated): {raw}")
            
            # Current_Pending_Sector
            elif "Current_Pending_Sector" in line:
                parts = line.split()
                raw = int(parts[-1])
                if raw > 0: hatalar.append(f"⚠️ Bekleyen Sektör (Pending): {raw}")
            
            # UDMA_CRC_Error_Count (Kablo hatası)
            elif "UDMA_CRC_Error_Count" in line:
                parts = line.split()
                raw = int(parts[-1])
                if raw > 0: hatalar.append(f"ℹ️ İletişim Hatası (CRC): {raw} (Kabloyu kontrol et)")
            
            # Media_and_Data_Integrity_Errors (NVMe)
            elif "Media and Data Integrity Errors:" in line:
                val = line.split(':', 1)[1].strip()
                if val != "0": hatalar.append(f"⚠️ Veri Bütünlüğü Hatası: {val}")

        # --- RAPOR OLUŞTURMA ---
        final_report = []
        final_report.append(f"=== {disk_turu} SAĞLIK RAPORU ===")
        final_report.append("")
        
        final_report.append("--- [1] CİHAZ BİLGİLERİ ---")
        final_report.extend(kimlik)
        final_report.append("")
        
        final_report.append("--- [2] SAĞLIK DURUMU ---")
        final_report.extend(saglik)
        if not hatalar:
            final_report.append("• Disk yüzeyinde kritik hata tespit edilmedi.")
        final_report.append("")
        
        if hatalar:
            final_report.append("--- [!] TESPİT EDİLEN SORUNLAR ---")
            final_report.extend(hatalar)
            final_report.append("")

        final_report.append("--- [3] İSTATİSTİKLER ---")
        final_report.extend(istatistik)
        
        return "\n".join(final_report)

    def tara(self):
        if not os.path.exists(self.smart_bin):
            QMessageBox.critical(self, "Eksik Bileşen", "smartmontools paketi eksik.\nKurmak için: sudo apt install smartmontools"); return

        dev = self.combo_disk.currentData()
        if not dev: return
        
        self.lbl_sonuc.setText("Taranıyor...")
        self.txt_detay.clear()
        QApplication.processEvents()
        
        final_output = ""
        
        try:
            # TEK SEFERDE YETKİLENDİRME (Auto -> SCSI Fallback)
            shell_cmd = (
                f"output=$({self.smart_bin} -a -d auto {dev} 2>&1); "
                f"if echo \"$output\" | grep -E -q 'Unknown USB bridge|specify device type'; then "
                f"{self.smart_bin} -a -d scsi {dev}; "
                f"else echo \"$output\"; fi"
            )
            
            cmd = ["pkexec", "sh", "-c", shell_cmd]
            process = subprocess.run(cmd, capture_output=True, text=True)
            final_output = process.stdout
            
            if not final_output.strip() and process.stderr:
                final_output = process.stderr

        except Exception as e:
            final_output = str(e)

        # Çıktıyı DETAYLI Türkçeleştir ve Göster
        # Eğer çok kısa bir çıktıysa (hata veya desteklenmiyor) direkt göster
        if len(final_output) < 300 and "Unknown" in final_output:
             self.txt_detay.setText(f"Disk verisi tam okunamadı.\nHata:\n{final_output}")
        else:
             turkce_cikti = self.raporu_detayli_turkcelestir(final_output)
             self.txt_detay.setText(turkce_cikti)
        
        # --- DURUM BELİRLEME (Görsel Renklendirme) ---
        durum = "BELIRSIZ"
        
        if "SMART Health Status: OK" in final_output: durum = "SAGLAM"
        elif "test result: PASSED" in final_output: durum = "SAGLAM"
        elif "Health Status: OK" in final_output: durum = "SAGLAM"
        elif "Critical Warning: 0x00" in final_output: durum = "SAGLAM"
        elif "test result: FAILED" in final_output or "Health Status: BAD" in final_output: durum = "HATALI"
        elif "Unknown USB bridge" in final_output: 
            self.lbl_sonuc.setText("⚠️ USB Tanınamadı")
            self.lbl_sonuc.setStyleSheet("font-size: 14pt; font-weight: bold; color: #f1c40f;")
            return

        if durum == "SAGLAM":
            self.lbl_sonuc.setText("✅ Durum: SAĞLAM")
            self.lbl_sonuc.setStyleSheet("font-size: 14pt; font-weight: bold; color: #2ecc71;")
        elif durum == "HATALI":
            self.lbl_sonuc.setText("❌ Durum: HATALI")
            self.lbl_sonuc.setStyleSheet("font-size: 14pt; font-weight: bold; color: #e74c3c;")
        else:
            self.lbl_sonuc.setText("⚠️ Durum: Veri Yok / Belirsiz")
            self.lbl_sonuc.setStyleSheet("font-size: 14pt; font-weight: bold; color: #f1c40f;")

    def onarim_baslat(self):
        dev = self.combo_disk.currentData()
        if not dev: return
        if QMessageBox.question(self, "Onarım Planla", f"{dev} için fsck onarımı planlanacak (Yeniden başlatma gerekir).", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) == QMessageBox.StandardButton.Yes:
            try:
                subprocess.run(["pkexec", "touch", "/forcefsck"], check=True)
                QMessageBox.information(self, "Başarılı", "Onarım planlandı. Bilgisayarı yeniden başlatın.")
            except: QMessageBox.critical(self, "Hata", "Onarım planlanamadı.")