From f05c20ed0b56cc57afec52ffd349ef0690ecdcd3 Mon Sep 17 00:00:00 2001 From: Carsten Date: Wed, 22 Apr 2026 17:55:07 +0200 Subject: [PATCH] feat(ui): detailed per-pool block with type, capacity bar, scrub state --- .../lib/server_web/live/host_detail_live.ex | 68 +++++++++++++++---- .../server_web/live/host_detail_live_test.exs | 21 +++++- 2 files changed, 75 insertions(+), 14 deletions(-) diff --git a/server/lib/server_web/live/host_detail_live.ex b/server/lib/server_web/live/host_detail_live.ex index 56d97e4..d921ab9 100644 --- a/server/lib/server_web/live/host_detail_live.ex +++ b/server/lib/server_web/live/host_detail_live.ex @@ -66,21 +66,35 @@ defmodule ServerWeb.HostDetailLive do
ZFS pools{length(pools(@fast))}
No data.
-
-
- - {pool["name"]} - - {pool["health"]} -
- cap {pool["capacity_percent"]}% · - frag {pool["fragmentation_percent"] || 0}% · - err {pool["error_count"] || 0} · - vdevs {pool["vdev_count"] || 0} (deg {pool["degraded_vdev_count"] || 0}) +
+
+
+ {pool["name"]} + {pool_layout(pool)}
+ {pool["health"]}
-
- scrub
{pool["last_scrub_end"] || "never"} + +
+ +
+ +
+ used {format_bytes(pool["allocated_bytes"] || 0)} · + free {format_bytes(pool["free_bytes"] || 0)} · + total {format_bytes(pool["size_bytes"] || 0)} + ({pool["capacity_percent"] || 0}%) +
+ +
+ frag {pool["fragmentation_percent"] || 0}% · + err {pool["error_count"] || 0} · + vdevs {pool["vdev_count"] || 0} (deg {pool["degraded_vdev_count"] || 0}) · + {pool_scrub_line(pool)} +
+ +
+ {v["name"]} {v["state"]} · r={v["read_errors"]} w={v["write_errors"]} cksum={v["checksum_errors"]}
@@ -155,6 +169,34 @@ defmodule ServerWeb.HostDetailLive do defp pool_badge_style("ONLINE"), do: "color: var(--ok);" defp pool_badge_style(_), do: "color: var(--crit);" + defp pool_layout(pool) do + case pool["pool_type"] do + nil -> "—" + "" -> "—" + t -> t + end + end + + defp capbar_level(cap) when is_number(cap) and cap >= 90, do: "crit" + defp capbar_level(cap) when is_number(cap) and cap >= 80, do: "warn" + defp capbar_level(_), do: "ok" + + defp pool_scrub_line(%{"scan_state" => "SCANNING"}), do: "scrub scanning" + + defp pool_scrub_line(%{"scan_state" => "FINISHED", "last_scrub_end" => end_time}) + when is_binary(end_time) and end_time != "", + do: "scrub #{end_time}" + + defp pool_scrub_line(%{"last_scrub_end" => end_time}) when is_binary(end_time) and end_time != "", + do: "scrub #{end_time}" + + defp pool_scrub_line(_), do: "scrub never" + + defp degraded_vdevs(pool) do + (pool["vdevs"] || []) + |> Enum.filter(fn v -> Map.get(v, "state") not in [nil, "ONLINE"] end) + end + defp sys_line(nil), do: "—" defp sys_line(%{payload: p}) do get_in(p, ["system_info", "pve_version"]) || "—" diff --git a/server/test/server_web/live/host_detail_live_test.exs b/server/test/server_web/live/host_detail_live_test.exs index a561f88..cec01b6 100644 --- a/server/test/server_web/live/host_detail_live_test.exs +++ b/server/test/server_web/live/host_detail_live_test.exs @@ -17,9 +17,22 @@ defmodule ServerWeb.HostDetailLiveTest do %{ "name" => "rpool", "health" => "ONLINE", + "pool_type" => "mirror", + "size_bytes" => 500_000_000_000, + "allocated_bytes" => 200_000_000_000, + "free_bytes" => 300_000_000_000, "capacity_percent" => 40, + "fragmentation_percent" => 17, "error_count" => 0, - "last_scrub_end" => "Sat Apr 19 02:00:00 2026" + "vdev_count" => 1, + "degraded_vdev_count" => 0, + "scan_function" => "scrub", + "scan_state" => "FINISHED", + "last_scrub_end" => "Sat Apr 19 02:00:00 2026", + "vdevs" => [ + %{"name" => "mirror-0", "type" => "mirror", "state" => "ONLINE", + "read_errors" => 0, "write_errors" => 0, "checksum_errors" => 0} + ] } ] }, @@ -75,6 +88,12 @@ defmodule ServerWeb.HostDetailLiveTest do assert html =~ "nginx" assert html =~ "rpool/data" assert html =~ "local" + assert html =~ "mirror" + assert html =~ "465.7 GB" # size_bytes formatted + assert html =~ "186.3 GB" # allocated_bytes formatted + assert html =~ "279.4 GB" # free_bytes formatted + assert html =~ "capbar" + assert html =~ "scrub" end test "404 for unknown host", %{conn: conn} do