diff --git a/lib/freedive/api/service/cli.ex b/lib/freedive/api/service/cli.ex index f75c16e..098f989 100644 --- a/lib/freedive/api/service/cli.ex +++ b/lib/freedive/api/service/cli.ex @@ -18,6 +18,16 @@ defmodule Freedive.Api.Service.Cli do |> Enum.into(%{}, &{&1[:name], &1}) end + def service_command!(name, command, args \\ []) do + case service(name, command, args) do + {:ok, stdout} -> + {:ok, stdout |> String.trim()} + + error -> + error + end + end + def all_service_names() do case execute(@service_bin, ["-l"]) do {:ok, stdout} -> @@ -56,7 +66,8 @@ defmodule Freedive.Api.Service.Cli do running: if(enabled, do: service_is_running?(name), else: nil), description: if(enabled, do: service_description!(name), else: nil), commands: if(enabled, do: service_commands!(name), else: nil), - log: [] + log: [], + busy: false } end @@ -120,14 +131,14 @@ defmodule Freedive.Api.Service.Cli do end end - defp service(name, action, args) do - case execute(@service_bin, [name, action] ++ args, doas: true) do + defp service(name, command, args) do + case execute(@service_bin, [name, command] ++ args, doas: true) do {:ok, stdout} -> # Logger.debug("service, log: #{inspect(stdout)}") {:ok, stdout} {:error, {stderr, code}} -> - # Logger.warning("service #{name} #{action}: #{String.trim(stderr)}") + # Logger.warning("service #{name} #{command}: #{String.trim(stderr)}") {:error, {stderr, code}} end end diff --git a/lib/freedive/api/service/server.ex b/lib/freedive/api/service/server.ex index 1988ee8..4d42bfe 100644 --- a/lib/freedive/api/service/server.ex +++ b/lib/freedive/api/service/server.ex @@ -7,7 +7,11 @@ defmodule Freedive.Api.Service do import Freedive.Api.Service.Cli @topic "system:service" - @events [:started, :stopped, :restarted] + @events [ + :command, + :refreshed, + :stdlog + ] def start_link(opts) do GenServer.start_link(__MODULE__, opts, name: __MODULE__) @@ -30,15 +34,19 @@ defmodule Freedive.Api.Service do end def start(name) do - GenServer.cast(__MODULE__, {:start, name}) + GenServer.cast(__MODULE__, {:command, name, "start"}) end def stop(name) do - GenServer.cast(__MODULE__, {:stop, name}) + GenServer.cast(__MODULE__, {:command, name, "stop"}) end def restart(name) do - GenServer.cast(__MODULE__, {:restart, name}) + GenServer.cast(__MODULE__, {:command, name, "restart"}) + end + + def command(name, command) do + GenServer.cast(__MODULE__, {:command, name, command}) end @impl true @@ -60,12 +68,44 @@ defmodule Freedive.Api.Service do {:reply, services, state} end - def broadcast(event, service) do + @impl true + def handle_cast({:command, name, command}, state) do + broadcast(:command, %{name: name, command: command}) + broadcast(:stdlog, %{name: name, log: std_to_log("# service #{name} #{command}")}) + + case service_command!(name, command) do + {:ok, result} -> + broadcast(:stdlog, %{name: name, log: std_to_log(result)}) + + {:error, {reason, _code}} -> + broadcast(:stdlog, %{name: name, log: std_to_log(reason)}) + end + + if command in ["start", "stop", "restart"] do + broadcast(:refreshed, service_details(name)) + end + + {:noreply, state} + end + + def broadcast(event, payload) do if event in @events do - service = refresh(service) - Phoenix.PubSub.broadcast(Freedive.PubSub, @topic, {event, service}) + Phoenix.PubSub.broadcast( + Freedive.PubSub, + @topic, + {event, + Map.merge(payload, %{ + hostname: "unknown" + })} + ) else Logger.error("Service.Server broadcast: unknown event: #{event}") end end + + defp std_to_log(result) do + (result <> "\n") + |> String.split("\n") + |> Enum.map(&String.trim/1) + end end diff --git a/lib/freedive_web/live/home_live.ex b/lib/freedive_web/live/home_live.ex index 994811c..8f47697 100644 --- a/lib/freedive_web/live/home_live.ex +++ b/lib/freedive_web/live/home_live.ex @@ -13,6 +13,10 @@ defmodule FreediveWeb.HomeLive do """ end + def on_mount_connected(socket) do + {:ok, socket} + end + def items() do %{ "services" => %{ diff --git a/lib/freedive_web/live/liliform_live.ex b/lib/freedive_web/live/liliform_live.ex index 6bbbbac..2b0280f 100644 --- a/lib/freedive_web/live/liliform_live.ex +++ b/lib/freedive_web/live/liliform_live.ex @@ -6,6 +6,8 @@ defmodule FreediveWeb.LiliformLive do defmacro __using__(opts) do quote location: :keep, bind_quoted: [opts: opts] do use FreediveWeb, :live_view + require Logger + @behaviour FreediveWeb.LiliformLive @filters_all [ %{ @@ -36,7 +38,12 @@ defmodule FreediveWeb.LiliformLive do socket = assign(socket, :filters, @filters_all ++ filters) socket = assign(socket, :query, "") - {:ok, socket} + + if connected?(socket) do + apply(__MODULE__, :on_mount_connected, [socket]) + else + {:ok, socket} + end end def(handle_event("search", %{"value" => query}, socket)) do diff --git a/lib/freedive_web/live/service_live.ex b/lib/freedive_web/live/service_live.ex index 37be852..06eda36 100644 --- a/lib/freedive_web/live/service_live.ex +++ b/lib/freedive_web/live/service_live.ex @@ -1,6 +1,7 @@ defmodule FreediveWeb.ServiceLive do use FreediveWeb.LiliformLive alias Freedive.Api.Service + require Logger def render(assigns) do ~H""" @@ -14,6 +15,12 @@ defmodule FreediveWeb.ServiceLive do """ end + def on_mount_connected(socket) do + Logger.warning("Mounting ServiceLive") + Service.subscribe() + {:ok, socket} + end + def items() do Service.list() end @@ -42,6 +49,78 @@ defmodule FreediveWeb.ServiceLive do end) |> Enum.into(%{}, fn {name, item} -> {name, item} end) end + + def handle_event("action:" <> action, %{"name" => service_name}, socket) + when action in ["start", "stop", "restart"] do + case action do + "start" -> Service.start(service_name) + "stop" -> Service.stop(service_name) + "restart" -> Service.restart(service_name) + end + + {:noreply, socket} + end + + def handle_event("command:" <> command, %{"name" => service_name}, socket) do + Service.command(service_name, command) + {:noreply, socket} + end + + def handle_info({event, payload}, socket) do + case event do + :command -> + # Logger.info("ServiceLive: Command: #{inspect(payload)}") + + socket = + if socket.assigns.selected_item && payload.name == socket.assigns.selected_item.name do + socket |> assign(:selected_item, %{socket.assigns.selected_item | busy: true}) + else + socket + end + + {:noreply, socket} + + :refreshed -> + # Logger.info("ServiceLive: Refreshed: #{inspect(payload)}") + + socket = + if socket.assigns.selected_item && payload.name == socket.assigns.selected_item.name do + socket + |> assign(:selected_item, %{ + socket.assigns.selected_item + | running: payload.running, + enabled: payload.enabled, + busy: false + }) + |> assign(:items, socket.assigns.items |> Map.put(payload.name, payload)) + else + socket + end + + {:noreply, socket} + + :stdlog -> + # Logger.info("ServiceLive: Stdlog: #{inspect(payload)}") + + socket = + if socket.assigns.selected_item && payload.name == socket.assigns.selected_item.name do + socket + |> assign(:selected_item, %{ + socket.assigns.selected_item + | log: socket.assigns.selected_item.log ++ payload.log, + busy: false + }) + else + socket + end + + {:noreply, socket} + + _ -> + Logger.warning("ServiceLive: Unknown event: #{event}, payload: #{inspect(payload)}") + {:noreply, socket} + end + end end defmodule FreediveWeb.ServiceLive.Components do @@ -101,6 +180,16 @@ defmodule FreediveWeb.ServiceLive.Components do + <%= if Map.has_key?(@selected_item, :log) and @selected_item.log != [] do %> + <.panel_media> + <:icon> + <.icon for="terminal" color="gray" aria-hidden="true" /> + + <:actions> +
<%= Enum.join(@selected_item.log, "\n") %>
+ + <% end %> + <.panel_media> <:icon> <%!-- <.icon for="terminal" color="auto" size="3rem" aria-hidden="true" /> --%> @@ -112,18 +201,31 @@ defmodule FreediveWeb.ServiceLive.Components do
-
<% else %>
-
@@ -135,9 +237,10 @@ defmodule FreediveWeb.ServiceLive.Components do <%= for command <- @selected_item.commands do %>