+ <.link
+ navigate={
+ ~p"/orgs/#{@current_organization.slug}/equipment/#{@equipment.id}/graph/traffic"
+ }
+ class="block px-4 py-3 border-b border-zinc-200 dark:border-zinc-700 hover:bg-zinc-50 dark:hover:bg-zinc-750 transition-colors"
+ >
+
+ nil
+
+ device ->
+ if Enum.empty?(device.interfaces) do
+ nil
+ else
+ since = get_datetime_from_range(range)
+ limit = get_limit_for_range(range)
+
+ # Get all stats for all interfaces
+ all_stats =
+ device.interfaces
+ |> Enum.flat_map(fn interface ->
+ stats =
+ interface.id
+ |> Snmp.get_interface_stats(since: since, limit: limit)
+ |> 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