112 lines
3.6 KiB
Elixir
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 |