defmodule DalaDev.Server.DashboardLive do use Phoenix.LiveView, layout: {DalaDev.Server.Layouts, :app} alias DalaDev.Server.{LogFilter, WatchWorker} @log_limit 500 @elixir_limit 200 @log_topic "logs" @elixir_topic "elixir_logs" @device_topic "devices" @watch_topic "watch" @impl Phoenix.LiveView @spec mount(map(), map(), Phoenix.LiveView.Socket.t()) :: {:ok, Phoenix.LiveView.Socket.t()} def mount(_params, _session, socket) do if connected?(socket) do Phoenix.PubSub.subscribe(DalaDev.PubSub, @device_topic) Phoenix.PubSub.subscribe(DalaDev.PubSub, @log_topic) Phoenix.PubSub.subscribe(DalaDev.PubSub, @elixir_topic) Phoenix.PubSub.subscribe(DalaDev.PubSub, @watch_topic) end devices = DalaDev.Server.DevicePoller.get_devices() # newest-first list all_lines = DalaDev.Server.LogBuffer.get() elixir_lines = DalaDev.Server.ElixirLogBuffer.get() lan_url = Application.get_env(:dala_dev, :dashboard_lan_url) {qr_small, qr_large} = if lan_url do encoded = EQRCode.encode(lan_url) {EQRCode.svg(encoded, width: 32), EQRCode.svg(encoded, width: 160)} else {nil, nil} end watch = WatchWorker.status() socket = socket |> assign( devices: devices, all_log_lines: all_lines, log_filter: :app, text_filter: "", # serial => :update | :first_deploy deploying: %{}, # serial => [line, ...] deploy_output: %{}, lan_url: lan_url, qr_small: qr_small, qr_large: qr_large, watch_active: watch.watching, watch_nodes: watch.nodes, watch_last_push: watch.last_push, all_elixir_lines: elixir_lines, elixir_text_filter: "" ) |> stream(:log_lines, LogFilter.apply(all_lines, :app, "") |> Enum.reverse()) |> stream(:elixir_lines, Enum.reverse(elixir_lines)) {:ok, socket} end # ── PubSub handlers ────────────────────────────────────────────────────────── @impl Phoenix.LiveView @spec handle_info(term(), Phoenix.LiveView.Socket.t()) :: {:noreply, Phoenix.LiveView.Socket.t()} def handle_info({:devices_updated, devices}, socket) do {:noreply, assign(socket, :devices, devices)} end def handle_info({:log_line, _serial, line}, socket) do all_lines = [line | socket.assigns.all_log_lines] |> Enum.take(@log_limit) socket = assign(socket, :all_log_lines, all_lines) socket = if LogFilter.matches?(line, socket.assigns.log_filter, socket.assigns.text_filter) do stream_insert(socket, :log_lines, line, at: -1, limit: @log_limit) else socket end {:noreply, socket} end def handle_info({:elixir_log_line, line}, socket) do all = [line | socket.assigns.all_elixir_lines] |> Enum.take(@elixir_limit) socket = assign(socket, :all_elixir_lines, all) socket = if elixir_matches?(line, socket.assigns.elixir_text_filter) do stream_insert(socket, :elixir_lines, line, at: -1, limit: @elixir_limit) else socket end {:noreply, socket} end def handle_info({:watch_status, status}, socket) do {:noreply, assign(socket, watch_active: status == :watching)} end def handle_info({:watch_push, info}, socket) do {:noreply, assign(socket, watch_nodes: info.nodes, watch_last_push: info)} end def handle_info({:deploy_line, serial, line}, socket) do output = Map.get(socket.assigns.deploy_output, serial, []) {:noreply, assign( socket, :deploy_output, Map.put(socket.assigns.deploy_output, serial, [line | output]) )} end def handle_info({:deploy_done, serial}, socket) do deploying = Map.delete(socket.assigns.deploying, serial) {:noreply, assign(socket, :deploying, deploying)} end # ── Events ─────────────────────────────────────────────────────────────────── @impl Phoenix.LiveView @spec handle_event(String.t(), map(), Phoenix.LiveView.Socket.t()) :: {:noreply, Phoenix.LiveView.Socket.t()} def handle_event("deploy", %{"serial" => serial, "mode" => mode}, socket) do deploying = Map.put(socket.assigns.deploying, serial, String.to_atom(mode)) device = Enum.find(socket.assigns.devices, &(&1.serial == serial)) socket = assign(socket, deploying: deploying, deploy_output: Map.put(socket.assigns.deploy_output, serial, []) ) spawn_deploy(serial, String.to_atom(mode), device.platform, self()) {:noreply, socket} end def handle_event("toggle_watch", _, socket) do if socket.assigns.watch_active do WatchWorker.stop_watching() else WatchWorker.start_watching() end {:noreply, socket} end def handle_event("set_log_filter", %{"filter" => raw_filter}, socket) do filter = case raw_filter do "all" -> :all "app" -> :app serial -> serial end filtered = LogFilter.apply(socket.assigns.all_log_lines, filter, socket.assigns.text_filter) |> Enum.reverse() socket = socket |> assign(:log_filter, filter) |> stream(:log_lines, filtered, reset: true) {:noreply, socket} end # phx-change on a