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}> """ 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 ["enable", "disable", "start", "stop", "restart"] do case action do "enable" -> Service.enable(service_name) "disable" -> Service.disable(service_name) "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} :refreshed_services -> Logger.info("ServiceLive: Refreshed services.") searched_and_filtered_items = payload |> search(socket.assigns.query) |> FreediveWeb.LiliformLive.filter( FreediveWeb.LiliformLive.get_active_filter(socket.assigns.filters) ) socket = if socket.assigns.selected_item do selected_item = payload[socket.assigns.selected_item.name] assign(socket, :selected_item, selected_item) else socket end {:noreply, assign(socket, %{items_all: payload, items: searched_and_filtered_items})} :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} /> <.extra_commands selected_item={@selected_item} /> <.start_stop_commands selected_item={@selected_item} /> <.enable_disable_commands selected_item={@selected_item} /> """ end def items_block(assigns) do ~H""" <%= for {_name, item} <- Enum.sort(@items, fn {a, _}, {b, _} -> a < b end) 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} > <.icon for="arrow-left" aria-hidden="true" has-text-dark />
Back
""" end def service_header(assigns) do ~H""" <.panel_block> <.icon_raw for={@selected_item.icon} color="auto" size="2.5rem" aria-hidden="true" />
<.title is-4> <%= @selected_item.name %> <.icon_running item={@selected_item} size="1.2rem" /> <.icon_enabled item={@selected_item} size="1.2rem" /> <.subtitle> <%= @selected_item.description %>
""" end def service_preview(assigns) do ~H""" <.panel_block class={"#{if @selected_item != nil and @selected_item.name == @item.name, do: "pt-3 has-background-info-light", else: "pt-3"}"} phx-click="tap" phx-value-name={@item.name} > <.icon_raw for={@item.icon} color="auto" size="1.5rem" aria-hidden="true" /> <%= @item.name %> <.icon_running item={@item} size="1rem" /> <.icon_enabled item={@item} size="1rem" /> """ end def enable_disable_commands(assigns) do ~H""" <.panel_block_div> <%= if @selected_item.enabled do %> <.icon_raw for="enabled" color="lightgreen" size="1.5rem" class="ml-2 mr-5" aria-hidden="true" /> <% else %> <.icon_raw for="disabled" color="gray" size="1.5rem" class="ml-2 mr-5" aria-hidden="true" /> <% end %>
<%= if @selected_item.enabled do %> <% else %> <% end %>
""" end def start_stop_commands(assigns) do ~H""" <.panel_block_div> <%= if @selected_item.running do %> <.icon_raw for="power" color="lightgreen" size="1.5rem" class="ml-2 mr-5" aria-hidden="true" /> <% else %> <.icon_raw for="power" color="gray" size="1.5rem" class="ml-2 mr-5" aria-hidden="true" /> <% end %>
<%= if @selected_item.running do %> <% else %> <% end %>
""" 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="1.5rem" class="ml-1 mr-5" aria-hidden="true" />
<%= for command <- @selected_item.commands do %> <% end %>
<% end %> """ end def std_log(assigns) do ~H""" <%= if Map.has_key?(@selected_item, :log) and @selected_item.log != [] do %>
<%= Enum.join(@selected_item.log, "\n") %>
<% end %> """ end def icon_running(assigns) do ~H""" <%= if @item.running do %> <.icon for="running" title="Running" size={@size} color="auto" aria-hidden="true" /> <% else %> <.icon for="stopped" title="Stopped" size={@size} color="auto" aria-hidden="true" /> <% end %> """ end def icon_enabled(assigns) do ~H""" <%= if @item.enabled do %> <.icon for="enabled" title="Enabled" size={@size} color="auto" aria-hidden="true" /> <% else %> <.icon for="disabled" title="Disabled" size={@size} color="auto" aria-hidden="true" /> <% end %> """ end end