Fixed 5 critical issues from code review: 1. **GlobalSearchTrigger hook listener leak** - Added destroyed() callback to remove click listener - Prevents memory leak on LiveView unmount 2. **NetworkMap/WeathermapViewer hook listener leaks** - Store zoom/fit button handlers as properties - Remove DOM event listeners in destroyed() callback - Fixes leak on every LiveView remount 3. **SitesMap uses undocumented window.liveSocket.execJS** - Changed to proper LiveView pattern using pushEvent - Attach listener via Leaflet's popupopen event 4. **handle_info(:refresh_data) double DB fetch** - Removed redundant Devices.get_device call - Reuse device from assign_base_data 5. **Redundant DB queries in DeviceLive.Show overview** - Changed load_sensor_chart_data to accept snmp_device - Changed load_overall_traffic_chart_data to accept snmp_device - Eliminated 4 redundant Snmp.get_device_with_associations calls Also removed unused functions: - load_equipment_data/2 - reload_active_tab_data/1 - assign_subscriber_impact/1 Note: DeviceLive.Show module size (2252 lines) is acknowledged but deferred as it requires larger architectural refactoring. Reviewed-on: graham/towerops-web#192
331 lines
14 KiB
Elixir
331 lines
14 KiB
Elixir
defmodule ToweropsWeb.DeviceLive.Components.GaiiaTab do
|
|
@moduledoc """
|
|
LiveComponent for the device Gaiia integration tab.
|
|
|
|
Displays Gaiia inventory item data, assigned account, subscriptions,
|
|
and network site information.
|
|
"""
|
|
use ToweropsWeb, :live_component
|
|
|
|
alias ToweropsWeb.DeviceLive.Helpers.DataLoaders
|
|
|
|
@impl true
|
|
def update(assigns, socket) do
|
|
{:ok,
|
|
socket
|
|
|> assign(assigns)
|
|
|> load_gaiia_data()}
|
|
end
|
|
|
|
defp load_gaiia_data(socket) do
|
|
device = socket.assigns.device
|
|
gaiia_data = DataLoaders.load_gaiia_data(device)
|
|
|
|
socket
|
|
|> assign(:gaiia_item, gaiia_data.gaiia_item)
|
|
|> assign(:gaiia_account, gaiia_data.gaiia_account)
|
|
|> assign(:gaiia_subscriptions, gaiia_data.gaiia_subscriptions)
|
|
|> assign(:gaiia_network_site, gaiia_data.gaiia_network_site)
|
|
end
|
|
|
|
@impl true
|
|
def render(assigns) do
|
|
~H"""
|
|
<div id="gaiia-tab">
|
|
<%= if @gaiia_item do %>
|
|
<div class="space-y-6">
|
|
<%!-- Inventory Item Details --%>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 overflow-hidden">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-white/5">
|
|
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{t("Gaiia Inventory Item")}
|
|
</h3>
|
|
</div>
|
|
<div class="px-4 py-4">
|
|
<dl class="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
<div>
|
|
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">Name</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
|
{@gaiia_item.name || "---"}
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">Gaiia ID</dt>
|
|
<dd class="mt-1 text-sm font-mono text-gray-900 dark:text-white">
|
|
{@gaiia_item.gaiia_id}
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">Status</dt>
|
|
<dd class="mt-1">
|
|
<span class={[
|
|
"inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium",
|
|
case @gaiia_item.status do
|
|
"active" ->
|
|
"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
|
|
|
|
"decommissioned" ->
|
|
"bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400"
|
|
|
|
_ ->
|
|
"bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400"
|
|
end
|
|
]}>
|
|
{@gaiia_item.status || "unknown"}
|
|
</span>
|
|
</dd>
|
|
</div>
|
|
<%= if @gaiia_item.model_name do %>
|
|
<div>
|
|
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">Model</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
|
{@gaiia_item.model_name}
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
<%= if @gaiia_item.manufacturer_name do %>
|
|
<div>
|
|
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
|
{t("Manufacturer")}
|
|
</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
|
{@gaiia_item.manufacturer_name}
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
<%= if @gaiia_item.category do %>
|
|
<div>
|
|
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
|
{t("Category")}
|
|
</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
|
{@gaiia_item.category}
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
<%= if @gaiia_item.serial_number do %>
|
|
<div>
|
|
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
|
{t("Serial Number")}
|
|
</dt>
|
|
<dd class="mt-1 text-sm font-mono text-gray-900 dark:text-white">
|
|
{@gaiia_item.serial_number}
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
<%= if @gaiia_item.mac_address do %>
|
|
<div>
|
|
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
|
{t("MAC Address")}
|
|
</dt>
|
|
<dd class="mt-1 text-sm font-mono text-gray-900 dark:text-white">
|
|
{@gaiia_item.mac_address}
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
<%= if @gaiia_item.ip_address do %>
|
|
<div>
|
|
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
|
{t("IP Address (Gaiia)")}
|
|
</dt>
|
|
<dd class="mt-1 text-sm font-mono text-gray-900 dark:text-white">
|
|
{@gaiia_item.ip_address}
|
|
<%= if @device.ip_address && @gaiia_item.ip_address != @device.ip_address do %>
|
|
<span class="ml-2 inline-flex items-center rounded-full bg-amber-100 px-2 py-0.5 text-xs font-medium text-amber-800 dark:bg-amber-900/30 dark:text-amber-400">
|
|
<.icon name="hero-exclamation-triangle-mini" class="mr-0.5 h-3 w-3" />
|
|
{t("Mismatch")}
|
|
</span>
|
|
<% end %>
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
|
|
<%!-- Network Site --%>
|
|
<%= if @gaiia_network_site do %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 overflow-hidden">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-white/5">
|
|
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{t("Gaiia Network Site")}
|
|
</h3>
|
|
</div>
|
|
<div class="px-4 py-4">
|
|
<dl class="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
<div>
|
|
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
|
{t("Site Name")}
|
|
</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
|
{@gaiia_network_site.name}
|
|
</dd>
|
|
</div>
|
|
<%= if @gaiia_network_site.account_count do %>
|
|
<div>
|
|
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
|
{t("Subscribers")}
|
|
</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
|
{@gaiia_network_site.account_count}
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
<%= if @gaiia_network_site.total_mrr do %>
|
|
<div>
|
|
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
|
{t("Monthly Revenue")}
|
|
</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
|
${Decimal.round(@gaiia_network_site.total_mrr, 2)}
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
|
|
<%!-- Linked Subscriber --%>
|
|
<%= if @gaiia_account do %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 overflow-hidden">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-white/5">
|
|
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{t("Linked Subscriber")}
|
|
</h3>
|
|
</div>
|
|
<div class="px-4 py-4">
|
|
<dl class="grid grid-cols-1 gap-x-4 gap-y-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
<div>
|
|
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">Name</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
|
{@gaiia_account.name}
|
|
</dd>
|
|
</div>
|
|
<%= if @gaiia_account.readable_id do %>
|
|
<div>
|
|
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
|
{t("Account ID")}
|
|
</dt>
|
|
<dd class="mt-1 text-sm font-mono text-gray-900 dark:text-white">
|
|
{@gaiia_account.readable_id}
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
<div>
|
|
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">Status</dt>
|
|
<dd class="mt-1">
|
|
<span class={[
|
|
"inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium",
|
|
case @gaiia_account.status do
|
|
"active" ->
|
|
"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
|
|
|
|
"suspended" ->
|
|
"bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400"
|
|
|
|
_ ->
|
|
"bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400"
|
|
end
|
|
]}>
|
|
{@gaiia_account.status || "unknown"}
|
|
</span>
|
|
</dd>
|
|
</div>
|
|
<%= if @gaiia_account.mrr do %>
|
|
<div>
|
|
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">MRR</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-white">
|
|
${Decimal.round(@gaiia_account.mrr, 2)}
|
|
</dd>
|
|
</div>
|
|
<% end %>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
|
|
<%!-- Billing Subscriptions --%>
|
|
<%= if @gaiia_subscriptions != [] do %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 overflow-hidden">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-white/5">
|
|
<h3 class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{t("Billing Subscriptions")}
|
|
</h3>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
|
<thead class="bg-gray-50 dark:bg-white/5">
|
|
<tr>
|
|
<th
|
|
scope="col"
|
|
class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400"
|
|
>
|
|
{t("Plan")}
|
|
</th>
|
|
<th
|
|
scope="col"
|
|
class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400"
|
|
>
|
|
{t("Status")}
|
|
</th>
|
|
<th
|
|
scope="col"
|
|
class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400"
|
|
>
|
|
{t("MRR")}
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200 bg-white dark:divide-white/5 dark:bg-transparent">
|
|
<%= for sub <- @gaiia_subscriptions do %>
|
|
<tr class="hover:bg-gray-50 dark:hover:bg-white/5">
|
|
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-900 dark:text-white">
|
|
{sub.product_name || "---"}
|
|
</td>
|
|
<td class="whitespace-nowrap px-4 py-3 text-sm">
|
|
<span class={[
|
|
"inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium",
|
|
case sub.status do
|
|
"active" ->
|
|
"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
|
|
|
|
"suspended" ->
|
|
"bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400"
|
|
|
|
_ ->
|
|
"bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400"
|
|
end
|
|
]}>
|
|
{sub.status || "unknown"}
|
|
</span>
|
|
</td>
|
|
<td class="whitespace-nowrap px-4 py-3 text-right text-sm text-gray-900 dark:text-white">
|
|
<%= if sub.mrr_amount do %>
|
|
${Decimal.round(sub.mrr_amount, 2)}
|
|
<span class="text-gray-400">
|
|
{sub.currency || ""}
|
|
</span>
|
|
<% else %>
|
|
---
|
|
<% end %>
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<% else %>
|
|
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-8 text-center">
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
{t("No Gaiia inventory item linked to this device.")}
|
|
</p>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
"""
|
|
end
|
|
end
|