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())} 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"""

Proxmox Monitor

<.link navigate={~p"/vms"} class="text-sm text-zinc-600 hover:text-zinc-900">VMs <.link navigate={~p"/admin/hosts"} class="text-sm text-zinc-600 hover:text-zinc-900"> Admin
<.link href={~p"/logout"} method="delete" class="text-sm text-zinc-500">Sign out
border_class(entry.status)} > <.link navigate={~p"/hosts/#{entry.host.name}"} class="block space-y-2">
{entry.host.name} text_class(entry.status)}> {entry.status}
Last seen: {last_seen(entry.host.last_seen_at)}
Load: {format_load(entry.sample.payload)}
RAM used: {format_mem(entry.sample.payload)}
Pools: {pool_summary(entry.sample.payload)}
VMs: {vm_count(entry.sample.payload)}
No samples yet

No hosts registered yet. Add one via /admin/hosts.

""" end defp border_class(:ok), do: "border-green-500" defp border_class(:warning), do: "border-yellow-500" defp border_class(:critical), do: "border-red-500" defp border_class(:offline), do: "border-zinc-400" defp text_class(:ok), do: "text-green-600" defp text_class(:warning), do: "text-yellow-600" defp text_class(:critical), do: "text-red-600" defp text_class(:offline), do: "text-zinc-500" 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 format_load(payload) do case get_in(payload, ["host", "load1"]) do nil -> "—" l -> :io_lib.format("~.2f", [l]) |> to_string() end end defp format_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} ok" end defp vm_count(payload) do vms = get_in(payload, ["vms_runtime", "vms"]) || [] length(vms) end end