diff --git a/lib/freedive/api/service/cli.ex b/lib/freedive/api/service/cli.ex index b4d52e6..d6eebe1 100644 --- a/lib/freedive/api/service/cli.ex +++ b/lib/freedive/api/service/cli.ex @@ -134,11 +134,11 @@ defmodule Freedive.Api.Service.Cli do defp service(name, command, args) do case execute(@service_bin, [name, command] ++ args, doas: true) do {:ok, stdout} -> - Logger.debug("service #{name} #{command} #{args}: #{inspect(stdout)}") + # Logger.debug("service #{name} #{command} #{args}: #{inspect(stdout)}") {:ok, stdout} {:error, {stderr, code}} -> - Logger.debug("service #{name} #{command} #{args}: (#{code}) #{String.trim(stderr)}") + # Logger.debug("service #{name} #{command} #{args}: (#{code}) #{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 51a359b..8da5c47 100644 --- a/lib/freedive/api/service/server.ex +++ b/lib/freedive/api/service/server.ex @@ -10,6 +10,7 @@ defmodule Freedive.Api.Service do @events [ :command, :refreshed, + :refreshed_services, :stdlog ] @@ -53,12 +54,25 @@ defmodule Freedive.Api.Service do def init(opts) do state = %{opts: opts, services: []} Logger.info("Starting Service.Server with opts: #{inspect(opts)}") + {:ok, state, {:continue, opts}} end @impl true def handle_continue(_opts, state) do - state = %{state | services: list_services!()} + {:ok, watcher_pid} = + FileSystem.start_link( + dirs: ["/etc/", "/usr/local/etc/rc.d"], + name: :rc_dirs_changed + ) + + FileSystem.subscribe(:rc_dirs_changed) + + state = + state + |> Map.put(:services, list_services!()) + |> Map.put(:watcher_pid, watcher_pid) + {:noreply, state} end @@ -88,16 +102,40 @@ defmodule Freedive.Api.Service do {:noreply, state} end + @impl true + def handle_info( + {:file_event, watcher_pid, {path, _events}}, + %{watcher_pid: watcher_pid} = state + ) do + name = Path.basename(path) + Logger.debug("Service.Server: handle_info file_event name: #{name}") + + state = + if name in state[:services] do + curr_service = state[:services][name] + refreshed_service = refresh_service!(curr_service) + IO.inspect(refreshed_service, label: "refreshed_service") + broadcast(:refreshed, refreshed_service) + %{state | services: Map.put(state[:services], name, refreshed_service)} + else + if name == "rc.conf" do + Logger.info("Service.Server: rc.conf changed, refreshing enabled services.") + services = list_services!() + broadcast(:refreshed_services, services) + + state + |> Map.put(:services, services) + else + state + end + end + + {:noreply, state} + end + def broadcast(event, payload) do if event in @events do - Phoenix.PubSub.broadcast( - Freedive.PubSub, - @topic, - {event, - Map.merge(payload, %{ - hostname: "unknown" - })} - ) + Phoenix.PubSub.broadcast(Freedive.PubSub, @topic, {event, payload}) else Logger.error("Service.Server broadcast: unknown event: #{event}") end diff --git a/lib/freedive_web/live/service_live.ex b/lib/freedive_web/live/service_live.ex index 6bcf1a3..38b1cd8 100644 --- a/lib/freedive_web/live/service_live.ex +++ b/lib/freedive_web/live/service_live.ex @@ -99,6 +99,26 @@ defmodule FreediveWeb.ServiceLive do {: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)}") @@ -171,20 +191,15 @@ defmodule FreediveWeb.ServiceLive.Components do def service_header(assigns) do ~H""" <.panel_block> - <.icon_raw for={@selected_item.icon} color="auto" size="3rem" aria-hidden="true" /> + <.icon_raw for={@selected_item.icon} color="auto" size="2.5rem" aria-hidden="true" />
<.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 %> + + + <.icon_running item={@selected_item} size="1.2rem" /> + <.icon_enabled item={@selected_item} size="1.2rem" /> + <.subtitle> <%= @selected_item.description %> @@ -201,23 +216,14 @@ defmodule FreediveWeb.ServiceLive.Components do phx-click="tap" phx-value-name={@item.name} > - <.icon_raw for={@item.icon} color="auto" size="1.5rem" aria-hidden="true" /> <%= @item.name %> - <%= 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 %> + <.icon_running item={@item} size="1rem" /> + <.icon_enabled item={@item} size="1rem" /> """ end @@ -298,4 +304,24 @@ defmodule FreediveWeb.ServiceLive.Components do <% 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 diff --git a/lib/liliform/icon.ex b/lib/liliform/icon.ex index 770025b..f39c2a4 100644 --- a/lib/liliform/icon.ex +++ b/lib/liliform/icon.ex @@ -50,6 +50,7 @@ defmodule Liliform.Icon do <.icon for="info" size="2rem" /> """ attr :for, :string, required: true, doc: "icon name" + attr :title, :string, default: nil, doc: "icon title" attr :size, :string, default: nil, doc: "size of icon" attr :color, :string, default: nil, doc: "color of icon" attr :class, :string, default: "", doc: "additional classes" @@ -72,7 +73,14 @@ defmodule Liliform.Icon do ~H""" - <.icon_svg for={@for} height={@size} width={@size} {icon_color(assigns)} /> + <.icon_svg + for={@for} + title={@title} + height={@size} + width={@size} + {icon_color(assigns)} + {@rest} + /> <%= if @inner_block != [] do %> @@ -87,6 +95,7 @@ defmodule Liliform.Icon do Renders an icon without the icon-text wrapper. """ attr :for, :string, required: true, doc: "icon name" + attr :title, :string, default: nil, doc: "icon title" attr :size, :string, default: nil, doc: "size of icon" attr :color, :string, default: nil, doc: "color of icon" attr :rest, :global @@ -97,7 +106,7 @@ defmodule Liliform.Icon do |> set_bulma_classes() ~H""" - <.icon_svg for={@for} height={@size} width={@size} {icon_color(assigns)} {@rest} /> + <.icon_svg for={@for} title={@title} height={@size} width={@size} {icon_color(assigns)} {@rest} /> """ end @@ -112,38 +121,35 @@ defmodule Liliform.Icon do defp icon_color(assigns) do if Features.enabled?(:colorhash) do - color = - case assigns.color do - nil -> "" - "auto" -> Colorhash.hsl(assigns.for) - color -> color - end - - auto_color = - case assigns.for do - "alert" -> [class: "has-text-danger"] - "info" -> [class: "has-text-info"] - "success" -> [class: "has-text-success"] - "warning" -> [class: "has-text-warning"] - "error" -> [class: "has-text-danger"] - "all" -> [color: "magenta"] - "compute" -> [color: "blue"] - "storage" -> [color: "green"] - "network" -> [color: "orange"] - "system" -> [color: "purple"] - "account" -> [color: "darkblue"] - _ -> [color: color] - end - - case color do - "auto" -> - auto_color - - "" -> + case assigns.color do + nil -> [] - _ -> - [color: color] + clr -> + case String.downcase(clr) do + "auto" -> + case String.downcase(assigns.for) do + "alert" -> [class: "has-text-danger"] + "info" -> [class: "has-text-info"] + "success" -> [class: "has-text-success"] + "warning" -> [class: "has-text-warning"] + "error" -> [class: "has-text-danger"] + "all" -> [color: "magenta"] + "compute" -> [color: "blue"] + "storage" -> [color: "green"] + "network" -> [color: "orange"] + "system" -> [color: "purple"] + "account" -> [color: "darkblue"] + "running" -> [color: "lime"] + "stopped" -> [color: "gray"] + "enabled" -> [color: "blue"] + "disabled" -> [color: "gray"] + _ -> [color: Colorhash.hsl(assigns.for)] + end + + _ -> + [color: assigns.color] + end end else [class: "has-text-dark"] @@ -170,7 +176,9 @@ defmodule Liliform.Icon do "ntpdate" -> :clock "httpd" -> :globe "running" -> :circle_play + "stopped" -> :circle_stop "enabled" -> :circle_check + "disabled" -> :circle_x lucide_name -> String.to_atom(lucide_name) end end diff --git a/lib/liliform/page.ex b/lib/liliform/page.ex index 7275d6e..43698e1 100644 --- a/lib/liliform/page.ex +++ b/lib/liliform/page.ex @@ -53,7 +53,7 @@ defmodule Liliform.Page do phx-value-key={filter.key} > <.icon_raw - for={filter.icon} + for={filter.title} size="2rem" color={if filter.active, do: "gray", else: "auto"} /> diff --git a/mix.exs b/mix.exs index 3b76755..e53489f 100644 --- a/mix.exs +++ b/mix.exs @@ -19,7 +19,7 @@ defmodule Freedive.MixProject do defp freebsd_pkg do [ - service_commands: ["init", "account_register", "password_reset"], + service_commands: ["init", "account_register", "password_reset"] ] end @@ -73,7 +73,8 @@ defmodule Freedive.MixProject do {:lucide_icons, "~> 1.1"}, {:phx_component_helpers, "~> 1.4"}, {:mix_freebsd_pkg, github: "hiway/mix_freebsd_pkg", runtime: Mix.env() == :dev}, - {:phx_config_util, "~> 0.1.0"} + {:phx_config_util, "~> 0.1.0"}, + {:file_system, "~> 1.0"} ] end