freedive/lib/freedive_web/live/service_live.ex

301 lines
8.5 KiB
Elixir

defmodule FreediveWeb.ServiceLive do
use FreediveWeb.LiliformLive
alias Freedive.Api.Service
require Logger
def render(assigns) do
~H"""
<.page name="Services" filters={@filters} details={@details}>
<FreediveWeb.ServiceLive.Components.items_block
items={@items}
selected_item={@selected_item}
details={@details}
/>
</.page>
"""
end
def on_mount_connected(socket) do
Logger.warning("Mounting ServiceLive")
Service.subscribe()
{:ok, socket}
end
def items() do
Service.list()
end
def filters() do
[
%{
title: "Running",
key: :running,
icon: "circle-play",
active: true
},
%{
title: "Enabled",
key: :enabled,
icon: "circle-check",
active: false
}
]
end
def search(items, query) do
items
|> Enum.filter(fn {name, _item} ->
String.contains?(String.downcase(name), String.downcase(query))
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
})
else
socket
end
{:noreply, socket}
_ ->
Logger.warning("ServiceLive: Unknown event: #{event}, payload: #{inspect(payload)}")
{:noreply, socket}
end
end
end
defmodule FreediveWeb.ServiceLive.Components do
use Liliform.Component
import Liliform.Icon
import Liliform.Panel
import Liliform.Title
@doc """
Returns items as panel-blocks.
"""
attr :items, :list, default: [], doc: "items"
attr :selected_item, :string, default: nil, doc: "selected item"
attr :details, :string, default: nil, doc: "details"
def items_block(%{details: true} = assigns) do
~H"""
<.back_link selected_item={@selected_item} />
<.service_header selected_item={@selected_item} />
<.std_log selected_item={@selected_item} />
<.basic_commands selected_item={@selected_item} />
<.extra_commands selected_item={@selected_item} />
"""
end
def items_block(assigns) do
~H"""
<%= for {_name, item} <- @items do %>
<.service_preview item={item} selected_item={@selected_item} />
<% end %>
"""
end
def back_link(assigns) do
~H"""
<.link
class="panel-block pt-1 has-background-light"
phx-click="tap"
phx-value-name={@selected_item.name}
>
<span class="panel-icon">
<.icon for="arrow-left" aria-hidden="true" has-text-dark />
</span>
<div class="mt-2 ml-2">Back</div>
</.link>
"""
end
def service_header(assigns) do
~H"""
<.panel_block>
<.icon_raw for={@selected_item.icon} color="auto" size="3rem" aria-hidden="true" />
<div class="column">
<.title is-4>
<%= @selected_item.name %>
<%= if @selected_item.running do %>
<.icon for="circle-play" color="lightgreen" aria-hidden="true" />
<% else %>
<.icon for="circle-stop" color="gray" aria-hidden="true" />
<% end %>
<%= if @selected_item.enabled do %>
<.icon for="circle-check" color="lightgreen" aria-hidden="true" />
<% else %>
<.icon for="circle-x" color="gray" aria-hidden="true" />
<% end %>
</.title>
<.subtitle>
<%= @selected_item.description %>
</.subtitle>
</div>
</.panel_block>
"""
end
def service_preview(assigns) do
~H"""
<.panel_block
class={"#{if @selected_item != nil and @selected_item.name == @item.name, do: "pt-2 has-background-info-light", else: "pt-2"}"}
phx-click="tap"
phx-value-name={@item.name}
>
<.icon_raw for={@item.icon} color="auto" size="1.8rem" aria-hidden="true" />
<span class="is-size-5 px-4">
<%= @item.name %>
</span>
<%= if @item.running do %>
<.icon for="circle-play" size="1rem" color="lightgreen" aria-hidden="true" />
<% else %>
<.icon for="circle-stop" size="1rem" color="gray" aria-hidden="true" />
<% end %>
<%= if @item.enabled do %>
<.icon for="circle-check" size="1rem" color="lightgreen" aria-hidden="true" />
<% else %>
<.icon for="circle-x" size="1rem" color="gray" aria-hidden="true" />
<% end %>
</.panel_block>
"""
end
def basic_commands(assigns) do
~H"""
<.panel_block_div>
<%= if @selected_item.running do %>
<.icon_raw for="power" color="lightgreen" size="2rem" class="ml-2 mr-5" aria-hidden="true" />
<% else %>
<.icon_raw for="power" color="gray" size="2rem" class="ml-2 mr-5" aria-hidden="true" />
<% end %>
<div class="columns is-fullwidth mr-4">
<%= if @selected_item.running do %>
<button
class="button is-warning column is-2 m-2 is-fullwidth"
phx-click="action:restart"
phx-value-name={@selected_item.name}
{if @selected_item.busy, do: [disabled: true], else: []}
>
Restart
</button>
<button
class="button is-danger column is-2 m-2 is-fullwidth"
phx-click="action:stop"
phx-value-name={@selected_item.name}
{if @selected_item.busy, do: [disabled: true], else: []}
>
Stop
</button>
<% else %>
<button
class="button is-success column is-2 m-2 is-fullwidth"
phx-click="action:start"
phx-value-name={@selected_item.name}
{if @selected_item.busy, do: [disabled: true], else: []}
>
Start
</button>
<% end %>
</div>
</.panel_block_div>
"""
end
def extra_commands(assigns) do
~H"""
<%= if @selected_item.commands != [] and @selected_item.commands != nil do %>
<.panel_block_div>
<.icon_raw
for="inspection-panel"
color="auto"
size="2rem"
class="ml-1 mr-5"
aria-hidden="true"
/>
<div class="columns is-fullwidth mr-4">
<%= for command <- @selected_item.commands do %>
<button
class="button is-info column is-2 m-2 is-fullwidth"
phx-click={"command:#{command}"}
phx-value-name={@selected_item.name}
{if @selected_item.busy, do: [disabled: true], else: []}
>
<%= command %>
</button>
<% end %>
</div>
</.panel_block_div>
<% end %>
"""
end
def std_log(assigns) do
~H"""
<%= if Map.has_key?(@selected_item, :log) and @selected_item.log != [] do %>
<pre class="panel-block is-fullwidth"><code><%= Enum.join(@selected_item.log, "\n") %></code></pre>
<% end %>
"""
end
end