towerops/lib/towerops_web/live/org/preseem_devices_live.ex

191 lines
5.1 KiB
Elixir

defmodule ToweropsWeb.Org.PreseemDevicesLive do
@moduledoc false
use ToweropsWeb, :live_view
alias Towerops.Devices
alias Towerops.Preseem
@impl true
def mount(_params, _session, socket) do
org = socket.assigns.current_scope.organization
{:ok,
socket
|> assign(:page_title, t("Preseem Devices"))
|> assign(:organization, org)
|> assign(:filter, "all")
|> assign(:linking_ap_id, nil)
|> assign(:device_search, "")
|> assign(:search_results, [])
|> assign(:suggestions, %{})
|> load_access_points()}
end
@impl true
def handle_params(params, _url, socket) do
filter =
case params["filter"] do
f when f in ~w(all unmatched matched) -> f
_ -> "all"
end
{:noreply, socket |> assign(:filter, filter) |> load_access_points()}
end
@impl true
def handle_event("filter", %{"filter" => filter}, socket) do
org = socket.assigns.organization
{:noreply,
push_patch(socket,
to: ~p"/orgs/#{org.slug}/settings/integrations/preseem/devices?filter=#{filter}"
)}
end
@impl true
def handle_event("start_link", %{"ap-id" => ap_id}, socket) do
{:noreply,
socket
|> assign(:linking_ap_id, ap_id)
|> assign(:device_search, "")
|> assign(:search_results, [])}
end
@impl true
def handle_event("cancel_link", _params, socket) do
{:noreply,
socket
|> assign(:linking_ap_id, nil)
|> assign(:device_search, "")
|> assign(:search_results, [])}
end
@impl true
def handle_event("search_devices", %{"query" => query}, socket) do
org = socket.assigns.organization
results =
if String.length(query) >= 2 do
Devices.search_devices(org.id, query)
else
[]
end
{:noreply, socket |> assign(:device_search, query) |> assign(:search_results, results)}
end
@impl true
def handle_event("link_device", %{"ap-id" => ap_id, "device-id" => device_id}, socket) do
case Preseem.link_access_point(ap_id, device_id) do
{:ok, _} ->
{:noreply,
socket
|> assign(:linking_ap_id, nil)
|> assign(:device_search, "")
|> assign(:search_results, [])
|> load_access_points()
|> put_flash(:info, t("Device linked successfully"))}
{:error, _} ->
{:noreply, put_flash(socket, :error, t("Failed to link device"))}
end
end
@impl true
def handle_event("unlink_device", %{"ap-id" => ap_id}, socket) do
case Preseem.unlink_access_point(ap_id) do
{:ok, _} ->
{:noreply,
socket
|> load_access_points()
|> put_flash(:info, t("Device unlinked"))}
{:error, _} ->
{:noreply, put_flash(socket, :error, t("Failed to unlink device"))}
end
end
defp match_confidence_classes(confidence) do
case confidence do
"unmatched" ->
"bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"
"ambiguous" ->
"bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400"
"manual" ->
"bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400"
_ ->
"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
end
end
defp humanize_confidence(confidence) do
case confidence do
"auto_mac" -> "Auto (MAC)"
"auto_ip" -> "Auto (IP)"
"auto_hostname" -> "Auto (Hostname)"
"manual" -> "Manual"
"ambiguous" -> "Ambiguous"
"unmatched" -> "Unmatched"
other -> String.capitalize(other || "unknown")
end
end
defp load_access_points(socket) do
org_id = socket.assigns.organization.id
filter = socket.assigns.filter
access_points =
case filter do
"unmatched" -> Preseem.list_unmatched_access_points(org_id)
"matched" -> Preseem.list_matched_access_points(org_id)
_ -> Preseem.list_access_points(org_id)
end
suggestions = compute_suggestions(access_points, org_id)
sorted =
Enum.sort_by(access_points, fn ap ->
cond do
Map.has_key?(suggestions, ap.id) -> 0
is_nil(ap.device_id) -> 1
true -> 2
end
end)
socket
|> assign(:access_points, sorted)
|> assign(:suggestions, suggestions)
end
defp compute_suggestions(access_points, org_id) do
unmatched_with_ip =
Enum.filter(access_points, &(is_nil(&1.device_id) and &1.ip_address not in [nil, ""]))
if unmatched_with_ip == [] do
%{}
else
linked_device_ids =
access_points
|> Enum.filter(&(not is_nil(&1.device_id)))
|> MapSet.new(& &1.device_id)
unlinked_by_ip =
org_id
|> Devices.list_organization_devices()
|> Enum.filter(&(not MapSet.member?(linked_device_ids, &1.id) and &1.ip_address not in [nil, ""]))
|> Enum.group_by(&to_string(&1.ip_address))
Enum.reduce(unmatched_with_ip, %{}, &add_ap_suggestion(&1, &2, unlinked_by_ip))
end
end
defp add_ap_suggestion(ap, acc, unlinked_by_ip) do
case Map.get(unlinked_by_ip, ap.ip_address, []) do
[] -> acc
matches -> Map.put(acc, ap.id, Enum.map(matches, &%{device: &1, match_type: "ip"}))
end
end
end