systant/dashboard/lib/dashboard_web/live/hosts_live.ex
2025-08-03 13:33:21 -07:00

112 lines
3.6 KiB
Elixir

defmodule DashboardWeb.HostsLive do
@moduledoc """
LiveView for real-time systant host monitoring.
"""
use DashboardWeb, :live_view
alias Phoenix.PubSub
@pubsub_topic "systant:hosts"
@impl true
def mount(_params, _session, socket) do
if connected?(socket) do
# Subscribe to host updates from MQTT
PubSub.subscribe(Dashboard.PubSub, @pubsub_topic)
end
# Start with empty hosts - will be populated by MQTT
hosts = %{}
socket =
socket
|> assign(:hosts, hosts)
|> assign(:page_title, "Systant Hosts")
{:ok, socket}
end
@impl true
def handle_info({:host_update, hostname, host_data}, socket) do
require Logger
Logger.info("LiveView received host update for #{hostname}: #{inspect(host_data)}")
updated_hosts = Map.put(socket.assigns.hosts, hostname, host_data)
{:noreply, assign(socket, :hosts, updated_hosts)}
end
@impl true
def render(assigns) do
~H"""
<div class="px-4 py-10 sm:px-6 sm:py-28 lg:px-8 xl:px-28 xl:py-32">
<div class="mx-auto max-w-xl lg:mx-0 lg:max-w-3xl">
<h1 class="text-brand mt-10 flex items-center text-sm font-semibold leading-6">
<.icon name="hero-computer-desktop" class="h-4 w-4" />
Systant Host Monitor
</h1>
<p class="text-[2rem] mt-4 font-semibold leading-10 tracking-tighter text-zinc-900">
Real-time system monitoring across all hosts
</p>
<p class="mt-4 text-base leading-7 text-zinc-600">
Live MQTT-powered dashboard showing statistics from all your systant-enabled hosts.
</p>
<div class="mt-10 grid gap-6">
<%= if Enum.empty?(@hosts) do %>
<div class="rounded-lg border border-zinc-200 p-8 text-center">
<.icon name="hero-signal-slash" class="mx-auto h-12 w-12 text-zinc-400" />
<h3 class="mt-4 text-lg font-semibold text-zinc-900">No hosts detected</h3>
<p class="mt-2 text-sm text-zinc-600">
Waiting for systant hosts to publish data via MQTT...
</p>
</div>
<% else %>
<%= for {hostname, host_data} <- @hosts do %>
<.host_card hostname={hostname} data={host_data} />
<% end %>
<% end %>
</div>
</div>
</div>
"""
end
attr :hostname, :string, required: true
attr :data, :map, required: true
defp host_card(assigns) do
~H"""
<div class="rounded-lg border border-zinc-200 bg-white p-6 shadow-sm">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<div class="rounded-full bg-green-100 p-2">
<.icon name="hero-server" class="h-5 w-5 text-green-600" />
</div>
<div>
<h3 class="text-lg font-semibold text-zinc-900"><%= @hostname %></h3>
<p class="text-sm text-zinc-600">
Last seen: <%= format_datetime(@data["last_seen"]) %>
</p>
</div>
</div>
<div class="rounded-full bg-green-100 px-3 py-1">
<span class="text-xs font-medium text-green-800">Online</span>
</div>
</div>
<div class="mt-4">
<h4 class="text-sm font-medium text-zinc-700 mb-2">Raw Data:</h4>
<pre class="text-xs bg-zinc-50 p-3 rounded border overflow-x-auto">
<%= Jason.encode!(@data, pretty: true) %>
</pre>
</div>
</div>
"""
end
defp format_datetime(%DateTime{} = datetime) do
Calendar.strftime(datetime, "%Y-%m-%d %H:%M:%S UTC")
end
defp format_datetime(_), do: "Unknown"
end