diff --git a/lib/towerops_web/live/device_live/show.ex b/lib/towerops_web/live/device_live/show.ex index 9dce91ba..5af29b61 100644 --- a/lib/towerops_web/live/device_live/show.ex +++ b/lib/towerops_web/live/device_live/show.ex @@ -873,11 +873,42 @@ defmodule ToweropsWeb.DeviceLive.Show do defp format_sensor_value(value, _divisor) when is_number(value) do # Value is already divided by divisor when stored in sensor_reading - Float.round(value, 1) + # Format as string to avoid scientific notation for large numbers + :erlang.float_to_binary(value * 1.0, decimals: 1) end defp format_sensor_value(_, _), do: "-" + # Format GPS coordinates to 6 decimal places (standard precision, ~0.1m accuracy) + defp format_location(location) when is_binary(location) do + if gps_coordinates?(location) do + format_gps_coordinates(location) + else + location + end + end + + defp format_location(nil), do: "N/A" + defp format_location(_), do: "N/A" + + defp gps_coordinates?(location) do + String.contains?(location, ",") && String.match?(location, ~r/[-\d.]+,\s*[-\d.]+/) + end + + defp format_gps_coordinates(location) do + location + |> String.split(",") + |> Enum.map(&String.trim/1) + |> Enum.map_join(", ", &format_coordinate/1) + end + + defp format_coordinate(coord) do + case Float.parse(coord) do + {num, _} -> :erlang.float_to_binary(num, decimals: 6) + :error -> coord + end + end + defp time_ago(nil), do: "Never" defp time_ago(datetime) do @@ -1232,7 +1263,7 @@ defmodule ToweropsWeb.DeviceLive.Show do # For backhaul devices, add capacity reference lines datasets = if device.device.device_role == "backhaul" do - add_capacity_datasets(datasets, all_stats, device.interfaces) + add_capacity_datasets(datasets, all_stats, device) else datasets end @@ -1242,14 +1273,21 @@ defmodule ToweropsWeb.DeviceLive.Show do # Add capacity reference lines for backhaul devices # Calculates capacity per timestamp based on which interfaces have data at that point - defp add_capacity_datasets(datasets, all_stats, interfaces) do - # Build a map of interface_id -> capacity for quick lookup + # For radios with Rx/Tx Capacity sensors, uses those values instead of if_speed + defp add_capacity_datasets(datasets, all_stats, device) do + # Get capacity sensors if available (for radios like AirFiber) + rx_capacity_sensor = Enum.find(device.sensors, &(&1.sensor_descr == "Rx Capacity")) + tx_capacity_sensor = Enum.find(device.sensors, &(&1.sensor_descr == "Tx Capacity")) + + # Build a map of interface_id -> {rx_capacity, tx_capacity} for quick lookup interface_capacity_map = - interfaces + device.interfaces |> Enum.filter(& &1.monitored) |> Map.new(fn interface -> - capacity = interface.configured_capacity_bps || interface.if_speed || 0 - {interface.id, capacity} + # Use sensor values if available (converted to bps), otherwise fall back to configured/if_speed + rx_cap = get_capacity_bps(rx_capacity_sensor) || interface.configured_capacity_bps || interface.if_speed || 0 + tx_cap = get_capacity_bps(tx_capacity_sensor) || interface.configured_capacity_bps || interface.if_speed || 0 + {interface.id, {rx_cap, tx_cap}} end) # Get the raw interface stats to determine which interfaces were active at each timestamp @@ -1257,7 +1295,7 @@ defmodule ToweropsWeb.DeviceLive.Show do # Track which interfaces have data at each timestamp interface_activity_by_timestamp = - interfaces + device.interfaces |> Enum.flat_map(fn interface -> interface.id |> Snmp.get_interface_stats(since: twenty_four_hours_ago, limit: 1000) @@ -1270,34 +1308,36 @@ defmodule ToweropsWeb.DeviceLive.Show do |> Enum.group_by(fn {ts, _} -> ts end, fn {_, iface_id} -> iface_id end) # Calculate capacity at each timestamp based on active interfaces + # For backhaul devices with Rx/Tx sensors, track separately capacity_data = Enum.map(all_stats, fn stat -> # Get interfaces that had data at this timestamp active_interfaces = Map.get(interface_activity_by_timestamp, stat.timestamp_ms, []) # Sum capacity of active interfaces - total_capacity = + {total_rx, total_tx} = active_interfaces |> Enum.uniq() - |> Enum.reduce(0, fn iface_id, acc -> - capacity = Map.get(interface_capacity_map, iface_id, 0) - acc + capacity + |> Enum.reduce({0, 0}, fn iface_id, {rx_acc, tx_acc} -> + {rx_cap, tx_cap} = Map.get(interface_capacity_map, iface_id, {0, 0}) + {rx_acc + rx_cap, tx_acc + tx_cap} end) - {stat.timestamp_ms, total_capacity} + {stat.timestamp_ms, total_rx, total_tx} end) # Only add capacity datasets if we have at least some capacity data - max_capacity = capacity_data |> Enum.map(fn {_, cap} -> cap end) |> Enum.max(fn -> 0 end) + max_rx = capacity_data |> Enum.map(fn {_, rx, _} -> rx end) |> Enum.max(fn -> 0 end) + max_tx = capacity_data |> Enum.map(fn {_, _, tx} -> tx end) |> Enum.max(fn -> 0 end) - if max_capacity > 0 do + if max_rx > 0 || max_tx > 0 do capacity_in_data = - Enum.map(capacity_data, fn {ts, cap} -> - %{x: ts, y: Float.round(cap * 1.0, 2)} + Enum.map(capacity_data, fn {ts, rx, _tx} -> + %{x: ts, y: Float.round(rx * 1.0, 2)} end) capacity_out_data = - Enum.map(capacity_data, fn {ts, cap} -> - %{x: ts, y: -Float.round(cap * 1.0, 2)} + Enum.map(capacity_data, fn {ts, _rx, tx} -> + %{x: ts, y: -Float.round(tx * 1.0, 2)} end) # Add capacity datasets with special styling @@ -1325,6 +1365,16 @@ defmodule ToweropsWeb.DeviceLive.Show do end end + # Get capacity in bps from a sensor (sensor values are in Mbps, convert to bps) + defp get_capacity_bps(nil), do: nil + + defp get_capacity_bps(sensor) do + if sensor.latest_reading && sensor.latest_reading.value do + # Sensor value is in Mbps, convert to bps + round(sensor.latest_reading.value * 1_000_000) + end + end + # Group interfaces by type for organized display defp group_interfaces_by_type(interfaces) do interfaces diff --git a/lib/towerops_web/live/device_live/show.html.heex b/lib/towerops_web/live/device_live/show.html.heex index d3d8acae..6cf72a3c 100644 --- a/lib/towerops_web/live/device_live/show.html.heex +++ b/lib/towerops_web/live/device_live/show.html.heex @@ -555,7 +555,7 @@
Location
- {@snmp_device.sys_location || "N/A"} + {format_location(@snmp_device.sys_location)}