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