diff --git a/tests/test_drives.py b/tests/test_drives.py index 1d13157..7603c81 100644 --- a/tests/test_drives.py +++ b/tests/test_drives.py @@ -1,4 +1,5 @@ from drives import ( + get_drive_type_label, get_drive_type_score, get_size_score, parse_lsblk_output, @@ -61,12 +62,36 @@ def test_score_device_sums_type_and_size(monkeypatch): def test_parse_lsblk_drops_loop_and_rom(monkeypatch): import drives - monkeypatch.setattr(drives, "get_drive_health", lambda _: 10) + monkeypatch.setattr(drives, "_smart_status", lambda _: "passed") output = "loop0 2.5G loop\nsr0 1024M rom\nsda 500G disk\nnvme0n1 1T disk\n" devices = parse_lsblk_output(output) names = [d["name"] for d in devices] assert names == ["/dev/nvme0n1", "/dev/sda"] +def test_parse_lsblk_attaches_human_labels(monkeypatch): + import drives + + monkeypatch.setattr(drives, "_smart_status", lambda _: "passed") + output = "nvme0n1 1T disk\n" + [dev] = parse_lsblk_output(output) + assert dev["type_label"] == "NVMe" + assert dev["health_label"] == "Healthy" + + +def test_parse_lsblk_surfaces_smart_warning(monkeypatch): + import drives + + monkeypatch.setattr(drives, "_smart_status", lambda _: "failed") + [dev] = parse_lsblk_output("sda 500G disk\n") + assert dev["health_label"] == "SMART warning" + + +def test_drive_type_label_nvme_ssd_hdd(): + assert get_drive_type_label("/dev/nvme0n1") == "NVMe" + assert get_drive_type_label("/dev/ssd0") == "SSD" + assert get_drive_type_label("/dev/sda") == "HDD" + + def test_parse_lsblk_handles_empty_output(): assert parse_lsblk_output("") == [] diff --git a/webinstaller/drives.py b/webinstaller/drives.py index 6011ad8..4d82cc0 100644 --- a/webinstaller/drives.py +++ b/webinstaller/drives.py @@ -1,7 +1,7 @@ import subprocess -def get_drive_health(device): +def _smart_status(device): try: result = subprocess.run( ["smartctl", "-H", device], @@ -9,13 +9,25 @@ def get_drive_health(device): ) output = result.stdout.decode() if "PASSED" in output: - return 10 + return "passed" elif "FAILED" in output: - return 0 - return 5 + return "failed" + return "unknown" except Exception as e: print(f"Error checking SMART status for {device}: {e}") - return 5 + return "unknown" + + +_HEALTH_SCORE = {"passed": 10, "failed": 0, "unknown": 5} +_HEALTH_LABEL = { + "passed": "Healthy", + "failed": "SMART warning", + "unknown": "Status unknown", +} + + +def get_drive_health(device): + return _HEALTH_SCORE[_smart_status(device)] def get_drive_type_score(device): @@ -27,6 +39,15 @@ def get_drive_type_score(device): return 5 +def get_drive_type_label(device): + name = device.lower() + if "nvme" in name: + return "NVMe" + if "ssd" in name: + return "SSD" + return "HDD" + + def parse_size_gb(size_str): size_str = size_str.strip().upper().replace(",", ".") if not size_str: @@ -71,11 +92,20 @@ def parse_lsblk_output(output): if dev_type != "disk": continue device = f"/dev/{name}" + size_gb = parse_size_gb(size) + status = _smart_status(device) + score = ( + get_drive_type_score(device) + + _HEALTH_SCORE[status] + + get_size_score(size_gb) + ) devices.append( { "name": device, "size": size, - "score": score_device(device, parse_size_gb(size)), + "type_label": get_drive_type_label(device), + "health_label": _HEALTH_LABEL[status], + "score": score, } ) devices.sort(key=lambda d: d["score"], reverse=True) diff --git a/webinstaller/static/style.css b/webinstaller/static/style.css index 057e626..094af5c 100644 --- a/webinstaller/static/style.css +++ b/webinstaller/static/style.css @@ -217,13 +217,22 @@ select:focus { font-size: 0.85rem; color: var(--fg-muted); } -.drive .score { +.drive .chip { background: var(--bg-subtle); padding: 0.15rem 0.5rem; border-radius: 999px; - font-family: var(--font-mono); font-size: 0.78rem; } +.drive .badge-recommended { + background: color-mix(in srgb, var(--accent) 18%, var(--bg-card)); + color: var(--accent); + padding: 0.15rem 0.55rem; + border-radius: 999px; + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.02em; + text-transform: uppercase; +} /* ── Summary table (overview) ───────────────────────────────── */ diff --git a/webinstaller/templates/install/step2.html b/webinstaller/templates/install/step2.html index 6733213..0309e02 100644 --- a/webinstaller/templates/install/step2.html +++ b/webinstaller/templates/install/step2.html @@ -5,7 +5,7 @@ {% block content %}
Pick the disk Furtka will install onto. The highest-scored drive is recommended.
+Pick the disk Furtka will install onto. We pre-select the drive that looks fastest and healthiest.
{% if not drives %}