Fix dashboard MQTT connection and remove simulation code

- Replace SimpleMqtt simulation with real MqttSubscriber
- Fix String.split bug when handling MQTT topic parsing
- Use hostname-based client ID to avoid MQTT client conflicts
- Add process management tools (hivemind, just) to development environment

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ryan 2025-08-03 19:39:59 -07:00
parent 400270ae52
commit c8e8e1dc24
4 changed files with 46 additions and 61 deletions

View File

@ -13,8 +13,8 @@ defmodule Dashboard.Application do
{Phoenix.PubSub, name: Dashboard.PubSub}, {Phoenix.PubSub, name: Dashboard.PubSub},
# Start the Finch HTTP client for sending emails # Start the Finch HTTP client for sending emails
{Finch, name: Dashboard.Finch}, {Finch, name: Dashboard.Finch},
# Start simple MQTT subscriber # Start real MQTT subscriber
Dashboard.SimpleMqtt, Dashboard.MqttSubscriber,
# Start to serve requests, typically the last entry # Start to serve requests, typically the last entry
DashboardWeb.Endpoint DashboardWeb.Endpoint
] ]

View File

@ -19,17 +19,27 @@ defmodule Dashboard.MqttSubscriber do
@impl true @impl true
def init(_opts) do def init(_opts) do
# Start MQTT connection in a supervised way # Start MQTT connection directly with hostname-based client ID to avoid conflicts
{:ok, _pid} = Tortoise.Supervisor.start_child( {:ok, hostname} = :inet.gethostname()
:dashboard_mqtt, client_id = "systant-dashboard-#{hostname}"
client_id: :dashboard_mqtt, connection_opts = [
client_id: client_id,
server: {Tortoise.Transport.Tcp, host: "mqtt.home", port: 1883}, server: {Tortoise.Transport.Tcp, host: "mqtt.home", port: 1883},
handler: {__MODULE__, []}, handler: {__MODULE__, []},
subscriptions: [{"systant/+/stats", 0}] subscriptions: [{"systant/+/stats", 0}]
) ]
Logger.info("Dashboard MQTT subscriber started") case Tortoise.Connection.start_link(connection_opts) do
{:ok, _pid} ->
Logger.info("Dashboard MQTT subscriber connected successfully")
{:ok, %{hosts: %{}}} {:ok, %{hosts: %{}}}
{:error, {:already_started, _pid}} ->
Logger.info("Dashboard MQTT connection already exists, reusing")
{:ok, %{hosts: %{}}}
{:error, reason} ->
Logger.error("Failed to connect to MQTT broker: #{inspect(reason)}")
{:stop, reason}
end
end end
@impl true @impl true
@ -43,11 +53,19 @@ defmodule Dashboard.MqttSubscriber do
end end
# Tortoise handler callbacks # Tortoise handler callbacks
def connection(_status, _state), do: [] def connection(status, state) do
def subscription(_status, _topic, _state), do: [] Logger.info("MQTT connection status: #{status}")
{:ok, state}
end
def subscription(status, topic, state) do
Logger.info("MQTT subscription status for #{topic}: #{status}")
{:ok, state}
end
def handle_message(topic, payload, _state) do def handle_message(topic, payload, _state) do
case String.split(topic, "/") do topic_parts = if is_binary(topic), do: String.split(topic, "/"), else: topic
case topic_parts do
["systant", hostname, "stats"] -> ["systant", hostname, "stats"] ->
case Jason.decode(payload) do case Jason.decode(payload) do
{:ok, data} -> {:ok, data} ->
@ -74,5 +92,6 @@ defmodule Dashboard.MqttSubscriber do
{:noreply, %{state | hosts: updated_hosts}} {:noreply, %{state | hosts: updated_hosts}}
end end
@impl true
def terminate(_reason, _state), do: [] def terminate(_reason, _state), do: []
end end

View File

@ -1,42 +0,0 @@
defmodule Dashboard.SimpleMqtt do
@moduledoc """
Simple GenServer that polls for MQTT data instead of complex subscriptions.
"""
use GenServer
require Logger
alias Phoenix.PubSub
def start_link(_opts) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
@impl true
def init(_) do
# Start a timer that simulates receiving MQTT data
# In a real implementation, you'd use a proper MQTT client here
Logger.info("Starting simple MQTT poller")
# For now, just generate fake data that matches what systant publishes
:timer.send_interval(5000, self(), :simulate_mqtt)
{:ok, %{}}
end
@impl true
def handle_info(:simulate_mqtt, state) do
# Simulate receiving an MQTT message from orion
hostname = "orion"
host_data = %{
"message" => "Hello from systant",
"hostname" => hostname,
"timestamp" => DateTime.utc_now() |> DateTime.to_iso8601(),
"last_seen" => DateTime.utc_now()
}
Logger.info("Simulating MQTT message from #{hostname}")
PubSub.broadcast(Dashboard.PubSub, "systant:hosts", {:host_update, hostname, host_data})
{:noreply, state}
end
end

View File

@ -27,13 +27,20 @@
elixir elixir
erlang erlang
# File watching for Phoenix live reload
inotifyTools
# Process management and task running
hivemind
just
# AI/Development tools # AI/Development tools
claude-code claude-code
aider-chat aider-chat
# Node.js for Phoenix assets # Node.js for Phoenix assets
nodejs_20 nodejs_20
npm nodePackages.npm
# Database for development # Database for development
postgresql postgresql
@ -69,7 +76,8 @@
}; };
}; };
} }
) // { )
// {
nixosModules.default = import ./nix/nixos-module.nix; nixosModules.default = import ./nix/nixos-module.nix;
}; };
} }