defmodule ServerWeb.OverviewLive do use ServerWeb, :live_view alias Server.{Hosts, Metrics, Status} @impl true def mount(_params, _session, socket) do if connected?(socket), do: Phoenix.PubSub.subscribe(Server.PubSub, "metrics") {:ok, assign(socket, hosts: load_hosts(), page_title: "Overview")} end @impl true def handle_info({:metric_inserted, _host_id, _interval}, socket) do {:noreply, assign(socket, :hosts, load_hosts())} end defp load_hosts do for host <- Hosts.list_all() do sample = Metrics.latest_sample(host.id, "fast") payload = sample && sample.payload %{host: host, sample: sample, status: Status.compute(host.status, payload)} end end @impl true def render(assigns) do ~H"""

Hosts

{summary_line(@hosts)}
No hosts registered. Add one at <.link navigate={~p"/admin/hosts"}>/admin/hosts.
<.link :for={entry <- @hosts} navigate={~p"/hosts/#{entry.host.name}"} class="card" data-status={Atom.to_string(entry.status)} data-role="host-card">
{entry.host.name} {entry.status}
last seen {last_seen(entry.host.last_seen_at)}
<%= if entry.sample do %>
load{load(entry.sample.payload)}
ram{mem(entry.sample.payload)}
pools{pool_summary(entry.sample.payload)}
vms{vm_count(entry.sample.payload)}
<% else %>
no samples yet
<% end %>
""" end defp summary_line([]), do: "0 hosts" defp summary_line(hosts) do n = length(hosts) by = Enum.frequencies_by(hosts, & &1.status) parts = [:ok, :warning, :critical, :offline] |> Enum.filter(&Map.has_key?(by, &1)) |> Enum.map(fn s -> "#{by[s]} #{s}" end) "#{n} host#{if n == 1, do: "", else: "s"} · " <> Enum.join(parts, " · ") end defp last_seen(nil), do: "never" defp last_seen(%DateTime{} = dt) do secs = DateTime.diff(DateTime.utc_now(), dt, :second) cond do secs < 60 -> "#{secs}s ago" secs < 3600 -> "#{div(secs, 60)}m ago" true -> "#{div(secs, 3600)}h ago" end end defp load(payload) do case get_in(payload, ["host", "load1"]) do nil -> "—" l -> :io_lib.format("~.2f", [l]) |> to_string() end end defp mem(payload) do used = get_in(payload, ["host", "mem_used_bytes"]) total = get_in(payload, ["host", "mem_total_bytes"]) case {used, total} do {u, t} when is_integer(u) and is_integer(t) and t > 0 -> "#{Float.round(u / t * 100, 1)}%" _ -> "—" end end defp pool_summary(payload) do pools = get_in(payload, ["zfs_pools", "pools"]) || [] total = length(pools) bad = Enum.count(pools, &(&1["health"] != "ONLINE")) if total == 0, do: "—", else: "#{total - bad}/#{total}" end defp vm_count(payload) do vms = get_in(payload, ["vms_runtime", "vms"]) || [] length(vms) |> to_string() end end