Add service commands
This commit is contained in:
parent
b452dc87d3
commit
01d9c54023
5 changed files with 181 additions and 16 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -13,6 +13,10 @@ defmodule FreediveWeb.HomeLive do
|
|||
"""
|
||||
end
|
||||
|
||||
def on_mount_connected(socket) do
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
def items() do
|
||||
%{
|
||||
"services" => %{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|||
</:actions>
|
||||
</.panel_media>
|
||||
|
||||
<%= if Map.has_key?(@selected_item, :log) and @selected_item.log != [] do %>
|
||||
<.panel_media>
|
||||
<:icon>
|
||||
<.icon for="terminal" color="gray" aria-hidden="true" />
|
||||
</:icon>
|
||||
<:actions></:actions>
|
||||
<pre><code><%= Enum.join(@selected_item.log, "\n") %></code></pre>
|
||||
</.panel_media>
|
||||
<% end %>
|
||||
|
||||
<.panel_media>
|
||||
<:icon>
|
||||
<%!-- <.icon for="terminal" color="auto" size="3rem" aria-hidden="true" /> --%>
|
||||
|
@ -112,18 +201,31 @@ defmodule FreediveWeb.ServiceLive.Components do
|
|||
<div class="column is-narrow">
|
||||
<button
|
||||
class="button is-warning"
|
||||
phx-click="action:restart"
|
||||
phx-value-name={@selected_item.name}
|
||||
{if @selected_item.busy, do: [disabled: true], else: []}
|
||||
>
|
||||
Restart
|
||||
</button>
|
||||
</div>
|
||||
<div class="column">
|
||||
<button class="button is-danger">
|
||||
<button
|
||||
class="button is-danger"
|
||||
phx-click="action:stop"
|
||||
phx-value-name={@selected_item.name}
|
||||
{if @selected_item.busy, do: [disabled: true], else: []}
|
||||
>
|
||||
Stop
|
||||
</button>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="column">
|
||||
<button class="button is-success">
|
||||
<button
|
||||
class="button is-success"
|
||||
phx-click="action:start"
|
||||
phx-value-name={@selected_item.name}
|
||||
{if @selected_item.busy, do: [disabled: true], else: []}
|
||||
>
|
||||
Start
|
||||
</button>
|
||||
</div>
|
||||
|
@ -135,9 +237,10 @@ defmodule FreediveWeb.ServiceLive.Components do
|
|||
<%= for command <- @selected_item.commands do %>
|
||||
<button
|
||||
class="button is-info"
|
||||
phx-click="command"
|
||||
phx-disable-with="..."
|
||||
phx-click={"command:#{command}"}
|
||||
phx-value-name={@selected_item.name}
|
||||
phx-value-command={command}
|
||||
{if @selected_item.busy, do: [disabled: true], else: []}
|
||||
>
|
||||
<%= command %>
|
||||
</button>
|
||||
|
|
Loading…
Reference in a new issue