193 lines
6.1 KiB
Elixir
193 lines
6.1 KiB
Elixir
defmodule ToweropsWeb.DeviceLive.Index do
|
|
@moduledoc false
|
|
use ToweropsWeb, :live_view
|
|
|
|
alias Towerops.Devices
|
|
alias Towerops.Sites
|
|
alias Towerops.Snmp
|
|
alias Towerops.Workers.DiscoveryWorker
|
|
|
|
@impl true
|
|
def mount(_params, _session, socket) do
|
|
organization = socket.assigns.current_organization
|
|
device = Devices.list_organization_devices(organization.id)
|
|
sites = Sites.list_organization_sites(organization.id)
|
|
grouped_devices = group_devices_by_site(device)
|
|
|
|
{:ok,
|
|
socket
|
|
|> assign(:page_title, "Devices")
|
|
|> assign(:device, device)
|
|
|> assign(:grouped_devices, grouped_devices)
|
|
|> assign(:has_sites, sites != [])
|
|
|> assign(:reorder_mode, false)}
|
|
end
|
|
|
|
@impl true
|
|
def handle_params(params, _url, socket) do
|
|
tab = Map.get(params, "tab", "existing")
|
|
{:noreply, apply_action(socket, socket.assigns.live_action, params, tab)}
|
|
end
|
|
|
|
defp apply_action(socket, :index, _params, tab) do
|
|
organization = socket.assigns.current_organization
|
|
|
|
case tab do
|
|
"discovered" ->
|
|
discovered = Snmp.list_discovered_devices_for_organization(organization.id)
|
|
|
|
socket
|
|
|> assign(:active_tab, "discovered")
|
|
|> assign(:discovered_devices, discovered)
|
|
|
|
_ ->
|
|
socket
|
|
|> assign(:active_tab, "existing")
|
|
|> assign(:discovered_devices, [])
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("force_rediscover_all", _params, socket) do
|
|
devices = socket.assigns.device
|
|
snmp_devices = Enum.filter(devices, & &1.snmp_enabled)
|
|
|
|
if Enum.empty?(snmp_devices) do
|
|
{:noreply, put_flash(socket, :error, "No SNMP-enabled devices found")}
|
|
else
|
|
Enum.each(snmp_devices, fn device ->
|
|
enqueue_discovery(device.id)
|
|
end)
|
|
|
|
count = length(snmp_devices)
|
|
|
|
{:noreply, put_flash(socket, :info, "Discovery started for #{count} device#{if count == 1, do: "", else: "s"}")}
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("reorder_site", %{"site_id" => site_id, "new_position" => new_position}, socket) do
|
|
# Handle both integer and string input
|
|
position = if is_integer(new_position), do: new_position, else: String.to_integer(new_position)
|
|
|
|
case Sites.reorder_site(site_id, position) do
|
|
{:ok, _site} ->
|
|
# Reload devices with updated order
|
|
devices = Devices.list_organization_devices(socket.assigns.current_organization.id)
|
|
grouped_devices = group_devices_by_site(devices)
|
|
|
|
{:noreply,
|
|
socket
|
|
|> assign(:device, devices)
|
|
|> assign(:grouped_devices, grouped_devices)
|
|
|> put_flash(:info, "Site order updated")}
|
|
|
|
{:error, _changeset} ->
|
|
{:noreply, put_flash(socket, :error, "Failed to reorder site")}
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("reorder_device", %{"device_id" => device_id, "new_position" => new_position}, socket) do
|
|
# Handle both integer and string input
|
|
position = if is_integer(new_position), do: new_position, else: String.to_integer(new_position)
|
|
|
|
case Devices.reorder_device(device_id, position) do
|
|
{:ok, _device} ->
|
|
# Reload devices with updated order
|
|
devices = Devices.list_organization_devices(socket.assigns.current_organization.id)
|
|
grouped_devices = group_devices_by_site(devices)
|
|
|
|
{:noreply,
|
|
socket
|
|
|> assign(:device, devices)
|
|
|> assign(:grouped_devices, grouped_devices)
|
|
|> put_flash(:info, "Device order updated")}
|
|
|
|
{:error, _changeset} ->
|
|
{:noreply, put_flash(socket, :error, "Failed to reorder device")}
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("toggle_reorder_mode", _params, socket) do
|
|
{:noreply, assign(socket, :reorder_mode, !socket.assigns.reorder_mode)}
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("reset_order", _params, socket) do
|
|
organization_id = socket.assigns.current_organization.id
|
|
|
|
Sites.reset_site_order(organization_id)
|
|
Devices.reset_organization_device_order(organization_id)
|
|
|
|
# Reload devices with alphabetical order
|
|
devices = Devices.list_organization_devices(organization_id)
|
|
grouped_devices = group_devices_by_site(devices)
|
|
|
|
{:noreply,
|
|
socket
|
|
|> assign(:device, devices)
|
|
|> assign(:grouped_devices, grouped_devices)
|
|
|> put_flash(:info, "Order reset to alphabetical")}
|
|
end
|
|
|
|
def handle_event("add_discovered_device", params, socket) do
|
|
identifier = Jason.decode!(params["identifier"])
|
|
discovered = Enum.find(socket.assigns.discovered_devices, &(&1.identifier == identifier))
|
|
|
|
prefill_params = %{
|
|
"name" => discovered.hostname || "",
|
|
"ip_address" => List.first(discovered.ip_addresses) || "",
|
|
"snmp_enabled" => "true"
|
|
}
|
|
|
|
{:noreply, push_navigate(socket, to: ~p"/devices/new?#{prefill_params}")}
|
|
end
|
|
|
|
defp enqueue_discovery(device_id) do
|
|
if Application.get_env(:towerops, :env) == :test do
|
|
_ = Task.start(fn -> Snmp.discover_device(Devices.get_device!(device_id)) end)
|
|
else
|
|
DiscoveryWorker.enqueue(device_id)
|
|
end
|
|
end
|
|
|
|
# Group devices by site with statistics
|
|
# Sites and devices are sorted by display_order (with alphabetical fallback)
|
|
defp group_devices_by_site(devices) do
|
|
devices
|
|
|> Enum.group_by(& &1.site)
|
|
|> Enum.map(fn {site, site_devices} ->
|
|
{site, site_devices, calculate_site_stats(site_devices)}
|
|
end)
|
|
|> Enum.sort_by(
|
|
fn {site, _devices, _stats} ->
|
|
{site.display_order || 999_999, site.name}
|
|
end,
|
|
:asc
|
|
)
|
|
end
|
|
|
|
defp calculate_site_stats(devices) do
|
|
status_counts = Enum.frequencies_by(devices, & &1.status)
|
|
|
|
%{
|
|
total: length(devices),
|
|
up: Map.get(status_counts, :up, 0),
|
|
down: Map.get(status_counts, :down, 0),
|
|
unknown: Map.get(status_counts, :unknown, 0)
|
|
}
|
|
end
|
|
|
|
defp device_type_icon(:router), do: "hero-signal"
|
|
defp device_type_icon(:switch), do: "hero-squares-2x2"
|
|
defp device_type_icon(:wireless), do: "hero-wifi"
|
|
defp device_type_icon(:server), do: "hero-server"
|
|
defp device_type_icon(:workstation), do: "hero-computer-desktop"
|
|
defp device_type_icon(_), do: "hero-question-mark-circle"
|
|
|
|
defp device_type_label(type) do
|
|
type |> to_string() |> String.capitalize()
|
|
end
|
|
end
|