Compare commits

..

No commits in common. "2c9fab28a48f556d13e69b5baa6e5fa5aab8d663" and "2781748626520527bb8804683ae123ed446ecfdf" have entirely different histories.

10 changed files with 145 additions and 226 deletions

View file

@ -134,11 +134,11 @@ defmodule Freedive.Api.Service.Cli do
defp service(name, command, args) do defp service(name, command, args) do
case execute(@service_bin, [name, command] ++ args, doas: true) do case execute(@service_bin, [name, command] ++ args, doas: true) do
{:ok, stdout} -> {:ok, stdout} ->
# Logger.debug("service #{name} #{command} #{args}: #{inspect(stdout)}") Logger.debug("service #{name} #{command} #{args}: #{inspect(stdout)}")
{:ok, stdout} {:ok, stdout}
{:error, {stderr, code}} -> {: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}} {:error, {stderr, code}}
end end
end end

View file

@ -10,7 +10,6 @@ defmodule Freedive.Api.Service do
@events [ @events [
:command, :command,
:refreshed, :refreshed,
:refreshed_services,
:stdlog :stdlog
] ]
@ -54,25 +53,12 @@ defmodule Freedive.Api.Service do
def init(opts) do def init(opts) do
state = %{opts: opts, services: []} state = %{opts: opts, services: []}
Logger.info("Starting Service.Server with opts: #{inspect(opts)}") Logger.info("Starting Service.Server with opts: #{inspect(opts)}")
{:ok, state, {:continue, opts}} {:ok, state, {:continue, opts}}
end end
@impl true @impl true
def handle_continue(_opts, state) do def handle_continue(_opts, state) do
{:ok, watcher_pid} = state = %{state | services: list_services!()}
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} {:noreply, state}
end end
@ -102,40 +88,16 @@ defmodule Freedive.Api.Service do
{:noreply, state} {:noreply, state}
end 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 def broadcast(event, payload) do
if event in @events do if event in @events do
Phoenix.PubSub.broadcast(Freedive.PubSub, @topic, {event, payload}) Phoenix.PubSub.broadcast(
Freedive.PubSub,
@topic,
{event,
Map.merge(payload, %{
hostname: "unknown"
})}
)
else else
Logger.error("Service.Server broadcast: unknown event: #{event}") Logger.error("Service.Server broadcast: unknown event: #{event}")
end end

View file

@ -4,7 +4,7 @@
<.navbar_brand class="ml-1"> <.navbar_brand class="ml-1">
<.navbar_item> <.navbar_item>
<.title is-4> <.title is-4>
<.link navigate={~p"/"} class="has-text-dark"> <.link patch={~p"/"} class="has-text-dark">
Freedive Freedive
</.link> </.link>
</.title> </.title>
@ -103,7 +103,7 @@
<.link href={~p"/packages"} class="navbar-item"> <.link href={~p"/packages"} class="navbar-item">
Packages Packages
</.link> --%> </.link> --%>
<.link navigate={~p"/services"} class="navbar-item"> <.link patch={~p"/services"} class="navbar-item">
Services Services
</.link> </.link>
</.navbar_dropdown> </.navbar_dropdown>
@ -118,7 +118,7 @@
<.navbar_dropdown> <.navbar_dropdown>
<%= if @current_user do %> <%= if @current_user do %>
<.link navigate={~p"/users/settings"} class="navbar-item"> <.link patch={~p"/users/settings"} class="navbar-item">
Settings Settings
</.link> </.link>
<.link href={~p"/users/log_out"} method="delete" class="navbar-item"> <.link href={~p"/users/log_out"} method="delete" class="navbar-item">

View file

@ -94,7 +94,7 @@ defmodule FreediveWeb.HomeLive.Components do
def items_block(assigns) do def items_block(assigns) do
~H""" ~H"""
<%= for {_name, item} <- @items do %> <%= for {_name, item} <- @items do %>
<.link class="panel-block pt-1" navigate={item.path}> <.link class="panel-block pt-1" patch={item.path}>
<span class="panel-icon"> <span class="panel-icon">
<.icon for={item.icon} color="auto" aria-hidden="true" /> <.icon for={item.icon} color="auto" aria-hidden="true" />
</span> </span>

View file

@ -99,26 +99,6 @@ defmodule FreediveWeb.ServiceLive do
{:noreply, socket} {: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 -> :stdlog ->
# Logger.info("ServiceLive: Stdlog: #{inspect(payload)}") # Logger.info("ServiceLive: Stdlog: #{inspect(payload)}")
@ -191,15 +171,20 @@ defmodule FreediveWeb.ServiceLive.Components do
def service_header(assigns) do def service_header(assigns) do
~H""" ~H"""
<.panel_block> <.panel_block>
<.icon_raw for={@selected_item.icon} color="auto" size="2.5rem" aria-hidden="true" /> <.icon_raw for={@selected_item.icon} color="auto" size="3rem" aria-hidden="true" />
<div class="column"> <div class="column">
<.title is-4> <.title is-4>
<%= @selected_item.name %> <%= @selected_item.name %>
<%= if @selected_item.running do %>
<span class="ml-1"> <.icon for="circle-play" color="lightgreen" aria-hidden="true" />
<.icon_running item={@selected_item} size="1.2rem" /> <% else %>
<.icon_enabled item={@selected_item} size="1.2rem" /> <.icon for="circle-stop" color="gray" aria-hidden="true" />
</span> <% 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> </.title>
<.subtitle> <.subtitle>
<%= @selected_item.description %> <%= @selected_item.description %>
@ -212,18 +197,27 @@ defmodule FreediveWeb.ServiceLive.Components do
def service_preview(assigns) do def service_preview(assigns) do
~H""" ~H"""
<.panel_block <.panel_block
class={"#{if @selected_item != nil and @selected_item.name == @item.name, do: "pt-3 has-background-info-light", else: "pt-3"}"} 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-click="tap"
phx-value-name={@item.name} phx-value-name={@item.name}
> >
<.icon_raw for={@item.icon} color="auto" size="1.5rem" aria-hidden="true" />
<span class="px-4"> <.icon_raw for={@item.icon} color="auto" size="1.8rem" aria-hidden="true" />
<span class="is-size-5 px-4">
<%= @item.name %> <%= @item.name %>
</span> </span>
<.icon_running item={@item} size="1rem" /> <%= if @item.running do %>
<.icon_enabled item={@item} size="1rem" /> <.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> </.panel_block>
""" """
end end
@ -232,9 +226,9 @@ defmodule FreediveWeb.ServiceLive.Components do
~H""" ~H"""
<.panel_block_div> <.panel_block_div>
<%= if @selected_item.running do %> <%= if @selected_item.running do %>
<.icon_raw for="power" color="lightgreen" size="1.5rem" class="ml-2 mr-5" aria-hidden="true" /> <.icon_raw for="power" color="lightgreen" size="2rem" class="ml-2 mr-5" aria-hidden="true" />
<% else %> <% else %>
<.icon_raw for="power" color="gray" size="1.5rem" class="ml-2 mr-5" aria-hidden="true" /> <.icon_raw for="power" color="gray" size="2rem" class="ml-2 mr-5" aria-hidden="true" />
<% end %> <% end %>
<div class="columns is-fullwidth mr-4"> <div class="columns is-fullwidth mr-4">
<%= if @selected_item.running do %> <%= if @selected_item.running do %>
@ -276,7 +270,7 @@ defmodule FreediveWeb.ServiceLive.Components do
<.icon_raw <.icon_raw
for="inspection-panel" for="inspection-panel"
color="auto" color="auto"
size="1.5rem" size="2rem"
class="ml-1 mr-5" class="ml-1 mr-5"
aria-hidden="true" aria-hidden="true"
/> />
@ -304,24 +298,4 @@ defmodule FreediveWeb.ServiceLive.Components do
<% end %> <% end %>
""" """
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 end

View file

@ -5,79 +5,71 @@ defmodule FreediveWeb.UserSettingsLive do
def render(assigns) do def render(assigns) do
~H""" ~H"""
<.section> <.header class="text-center">
<.title> Account Settings
Account Settings <:subtitle>Manage your account email address and password settings</:subtitle>
</.title> </.header>
<.subtitle>
Manage your account email address and password settings
</.subtitle>
</.section>
<.section>
<.title is-4>
Email
</.title>
<.simple_form <div class="space-y-12 divide-y">
for={@email_form} <div>
id="email_form" <.simple_form
phx-submit="update_email" for={@email_form}
phx-change="validate_email" id="email_form"
> phx-submit="update_email"
<.input field={@email_form[:email]} type="email" label="Email" required /> phx-change="validate_email"
<.input >
field={@email_form[:current_password]} <.input field={@email_form[:email]} type="email" label="Email" required />
name="current_password" <.input
id="current_password_for_email" field={@email_form[:current_password]}
type="password" name="current_password"
label="Current password" id="current_password_for_email"
value={@email_form_current_password} type="password"
required label="Current password"
/> value={@email_form_current_password}
<:actions> required
<.button phx-disable-with="Changing...">Change Email</.button> />
</:actions> <:actions>
</.simple_form> <.button phx-disable-with="Changing...">Change Email</.button>
</.section> </:actions>
<.section> </.simple_form>
<.title is-4> </div>
Password <div>
</.title> <.simple_form
<.simple_form for={@password_form}
for={@password_form} id="password_form"
id="password_form" action={~p"/users/log_in?_action=password_updated"}
action={~p"/users/log_in?_action=password_updated"} method="post"
method="post" phx-change="validate_password"
phx-change="validate_password" phx-submit="update_password"
phx-submit="update_password" phx-trigger-action={@trigger_submit}
phx-trigger-action={@trigger_submit} >
> <input
<input name={@password_form[:email].name}
name={@password_form[:email].name} type="hidden"
type="hidden" id="hidden_user_email"
id="hidden_user_email" value={@current_email}
value={@current_email} />
/> <.input field={@password_form[:password]} type="password" label="New password" required />
<.input field={@password_form[:password]} type="password" label="New password" required /> <.input
<.input field={@password_form[:password_confirmation]}
field={@password_form[:password_confirmation]} type="password"
type="password" label="Confirm new password"
label="Confirm new password" />
/> <.input
<.input field={@password_form[:current_password]}
field={@password_form[:current_password]} name="current_password"
name="current_password" type="password"
type="password" label="Current password"
label="Current password" id="current_password_for_password"
id="current_password_for_password" value={@current_password}
value={@current_password} required
required />
/> <:actions>
<:actions> <.button phx-disable-with="Changing...">Change Password</.button>
<.button phx-disable-with="Changing...">Change Password</.button> </:actions>
</:actions> </.simple_form>
</.simple_form> </div>
</.section> </div>
""" """
end end

View file

@ -88,7 +88,7 @@ defmodule Liliform.Flash do
<div id={@id}> <div id={@id}>
<.flash kind={:info} title={gettext("Info")} flash={@flash} is-info /> <.flash kind={:info} title={gettext("Info")} flash={@flash} is-info />
<.flash kind={:error} title={gettext("Error")} flash={@flash} is-danger /> <.flash kind={:error} title={gettext("Error")} flash={@flash} is-danger />
<%!-- <.flash <.flash
id="client-error" id="client-error"
kind={:error} kind={:error}
title={gettext("We can't find the internet")} title={gettext("We can't find the internet")}
@ -112,7 +112,7 @@ defmodule Liliform.Flash do
> >
<%= gettext("Hang in there while we get back on track") %> <%= gettext("Hang in there while we get back on track") %>
<Lucideicons.refresh_cw class="h-4 w-4 animate-spin is-inline-block" aria-hidden /> <Lucideicons.refresh_cw class="h-4 w-4 animate-spin is-inline-block" aria-hidden />
</.flash> --%> </.flash>
</div> </div>
""" """
end end

View file

@ -50,7 +50,6 @@ defmodule Liliform.Icon do
<.icon for="info" size="2rem" /> <.icon for="info" size="2rem" />
""" """
attr :for, :string, required: true, doc: "icon name" 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 :size, :string, default: nil, doc: "size of icon"
attr :color, :string, default: nil, doc: "color of icon" attr :color, :string, default: nil, doc: "color of icon"
attr :class, :string, default: "", doc: "additional classes" attr :class, :string, default: "", doc: "additional classes"
@ -73,14 +72,7 @@ defmodule Liliform.Icon do
~H""" ~H"""
<span class="icon-text"> <span class="icon-text">
<span class={["icon", @class]} {@rest}> <span class={["icon", @class]} {@rest}>
<.icon_svg <.icon_svg for={@for} height={@size} width={@size} {icon_color(assigns)} />
for={@for}
title={@title}
height={@size}
width={@size}
{icon_color(assigns)}
{@rest}
/>
</span> </span>
<%= if @inner_block != [] do %> <%= if @inner_block != [] do %>
<span> <span>
@ -95,7 +87,6 @@ defmodule Liliform.Icon do
Renders an icon without the icon-text wrapper. Renders an icon without the icon-text wrapper.
""" """
attr :for, :string, required: true, doc: "icon name" 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 :size, :string, default: nil, doc: "size of icon"
attr :color, :string, default: nil, doc: "color of icon" attr :color, :string, default: nil, doc: "color of icon"
attr :rest, :global attr :rest, :global
@ -106,7 +97,7 @@ defmodule Liliform.Icon do
|> set_bulma_classes() |> set_bulma_classes()
~H""" ~H"""
<.icon_svg for={@for} title={@title} height={@size} width={@size} {icon_color(assigns)} {@rest} /> <.icon_svg for={@for} height={@size} width={@size} {icon_color(assigns)} {@rest} />
""" """
end end
@ -121,35 +112,38 @@ defmodule Liliform.Icon do
defp icon_color(assigns) do defp icon_color(assigns) do
if Features.enabled?(:colorhash) do if Features.enabled?(:colorhash) do
case assigns.color do color =
nil -> 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
"" ->
[] []
clr -> _ ->
case String.downcase(clr) do [color: color]
"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 end
else else
[class: "has-text-dark"] [class: "has-text-dark"]
@ -176,9 +170,7 @@ defmodule Liliform.Icon do
"ntpdate" -> :clock "ntpdate" -> :clock
"httpd" -> :globe "httpd" -> :globe
"running" -> :circle_play "running" -> :circle_play
"stopped" -> :circle_stop
"enabled" -> :circle_check "enabled" -> :circle_check
"disabled" -> :circle_x
lucide_name -> String.to_atom(lucide_name) lucide_name -> String.to_atom(lucide_name)
end end
end end

View file

@ -37,7 +37,7 @@ defmodule Liliform.Page do
phx-click="filter" phx-click="filter"
phx-value-key={filter.key} phx-value-key={filter.key}
> >
<span class=""> <span class="is-size-5">
<%= filter.title %> <%= filter.title %>
</span> </span>
</a> </a>
@ -53,7 +53,7 @@ defmodule Liliform.Page do
phx-value-key={filter.key} phx-value-key={filter.key}
> >
<.icon_raw <.icon_raw
for={filter.title} for={filter.icon}
size="2rem" size="2rem"
color={if filter.active, do: "gray", else: "auto"} color={if filter.active, do: "gray", else: "auto"}
/> />

View file

@ -19,7 +19,7 @@ defmodule Freedive.MixProject do
defp freebsd_pkg do defp freebsd_pkg do
[ [
service_commands: ["init", "account_register", "password_reset"] service_commands: ["init", "account_register", "password_reset"],
] ]
end end
@ -73,8 +73,7 @@ defmodule Freedive.MixProject do
{:lucide_icons, "~> 1.1"}, {:lucide_icons, "~> 1.1"},
{:phx_component_helpers, "~> 1.4"}, {:phx_component_helpers, "~> 1.4"},
{:mix_freebsd_pkg, github: "hiway/mix_freebsd_pkg", runtime: Mix.env() == :dev}, {: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 end