Add overall traffic graph combining all interfaces

This commit is contained in:
Graham McIntire 2026-01-05 13:32:07 -06:00
parent 223434dd99
commit 3643700d73
No known key found for this signature in database
2 changed files with 99 additions and 0 deletions

View file

@ -55,6 +55,7 @@ defmodule ToweropsWeb.EquipmentLive.Show do
cpu_chart_data = load_sensor_chart_data(equipment_id, ["cpu_load"])
memory_chart_data = load_sensor_chart_data(equipment_id, ["memory_usage"])
storage_chart_data = load_sensor_chart_data(equipment_id, ["disk_usage"])
traffic_chart_data = load_overall_traffic_chart_data(equipment_id)
# Filter sensors by type for temperature and voltage
temperature_sensors =
@ -77,6 +78,7 @@ defmodule ToweropsWeb.EquipmentLive.Show do
|> assign(:cpu_chart_data, cpu_chart_data)
|> assign(:memory_chart_data, memory_chart_data)
|> assign(:storage_chart_data, storage_chart_data)
|> assign(:traffic_chart_data, traffic_chart_data)
|> assign(:temperature_sensors, temperature_sensors)
|> assign(:voltage_sensors, voltage_sensors)
end
@ -260,4 +262,78 @@ defmodule ToweropsWeb.EquipmentLive.Show do
end
end
end
defp load_overall_traffic_chart_data(equipment_id) do
case Snmp.get_device_with_associations(equipment_id) do
nil ->
nil
device ->
if Enum.empty?(device.interfaces) do
nil
else
# Get stats for past 24 hours for all interfaces
twenty_four_hours_ago = DateTime.add(DateTime.utc_now(), -24, :hour)
# Get all stats for all interfaces
all_stats =
device.interfaces
|> Enum.flat_map(fn interface ->
stats =
interface.id
|> Snmp.get_interface_stats(since: twenty_four_hours_ago, limit: 1000)
|> Enum.reverse()
Enum.map(stats, fn stat ->
{stat.checked_at, stat.if_in_octets, stat.if_out_octets}
end)
end)
|> Enum.group_by(&elem(&1, 0))
|> Enum.map(fn {timestamp, stats} ->
total_in = Enum.reduce(stats, 0, fn {_, in_octets, _}, acc -> acc + (in_octets || 0) end)
total_out = Enum.reduce(stats, 0, fn {_, _, out_octets}, acc -> acc + (out_octets || 0) end)
{timestamp, total_in, total_out}
end)
|> Enum.sort_by(&elem(&1, 0), DateTime)
if Enum.empty?(all_stats) do
nil
else
# Calculate bits per second from counter differences
in_data =
all_stats
|> Enum.chunk_every(2, 1, :discard)
|> Enum.map(fn [{t1, in1, _out1}, {t2, in2, _out2}] ->
time_diff = t2 |> DateTime.diff(t1, :second) |> max(1)
bps = ((in2 - in1) * 8 / time_diff) |> max(0) |> Float.round(2)
%{
x: DateTime.to_unix(t2, :millisecond),
y: bps
}
end)
out_data =
all_stats
|> Enum.chunk_every(2, 1, :discard)
|> Enum.map(fn [{t1, _in1, out1}, {t2, _in2, out2}] ->
time_diff = t2 |> DateTime.diff(t1, :second) |> max(1)
bps = ((out2 - out1) * 8 / time_diff) |> max(0) |> Float.round(2)
%{
x: DateTime.to_unix(t2, :millisecond),
y: bps
}
end)
datasets = [
%{label: "Inbound", data: in_data},
%{label: "Outbound", data: out_data}
]
Jason.encode!(%{datasets: datasets})
end
end
end
end
end

View file

@ -70,6 +70,29 @@
<div class="mt-6">
<%= case @active_tab do %>
<% "overview" -> %>
<!-- Overall Traffic Chart -->
<%= if @traffic_chart_data do %>
<div class="mb-6 bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700">
<div class="px-4 py-3 border-b border-zinc-200 dark:border-zinc-700">
<h3 class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">
Overall Traffic (24 Hours)
</h3>
<p class="text-xs text-zinc-600 dark:text-zinc-400 mt-1">
Combined traffic across all interfaces
</p>
</div>
<div class="p-4">
<div
id="traffic-chart"
phx-hook="SensorChart"
data-chart={@traffic_chart_data}
style="height: 300px;"
>
<canvas></canvas>
</div>
</div>
</div>
<% end %>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
<!-- Device Information -->
<div class="bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700">