allow nonroutable for me
This commit is contained in:
parent
a0ba9285dd
commit
a4335a047a
4 changed files with 113 additions and 80 deletions
11
lib/towerops/snmp/profiles/vendors/routeros.ex
vendored
11
lib/towerops/snmp/profiles/vendors/routeros.ex
vendored
|
|
@ -606,10 +606,11 @@ defmodule Towerops.Snmp.Profiles.Vendors.Routeros do
|
|||
end
|
||||
|
||||
# Map mtxrGaugeUnit value to sensor type, unit string, and divisor
|
||||
defp gauge_unit_to_sensor_type(@gauge_unit_celsius), do: {"temperature", "°C", 10}
|
||||
# Based on LibreNMS routeros.yaml - temperature is in whole degrees, voltage/current/power in deci-units
|
||||
defp gauge_unit_to_sensor_type(@gauge_unit_celsius), do: {"temperature", "°C", 1}
|
||||
defp gauge_unit_to_sensor_type(@gauge_unit_rpm), do: {"fanspeed", "RPM", 1}
|
||||
defp gauge_unit_to_sensor_type(@gauge_unit_dv), do: {"voltage", "V", 10}
|
||||
defp gauge_unit_to_sensor_type(@gauge_unit_da), do: {"current", "A", 1000}
|
||||
defp gauge_unit_to_sensor_type(@gauge_unit_da), do: {"current", "mA", 1}
|
||||
defp gauge_unit_to_sensor_type(@gauge_unit_dw), do: {"power", "W", 10}
|
||||
defp gauge_unit_to_sensor_type(@gauge_unit_status), do: {"state", "", 1}
|
||||
defp gauge_unit_to_sensor_type(_), do: {"unknown", "", 1}
|
||||
|
|
@ -643,10 +644,10 @@ defmodule Towerops.Snmp.Profiles.Vendors.Routeros do
|
|||
end
|
||||
end
|
||||
|
||||
# Sensor type configurations for overrides
|
||||
# Sensor type configurations for overrides (must match gauge_unit_to_sensor_type divisors)
|
||||
defp sensor_type_config("voltage"), do: {"voltage", "V", 10}
|
||||
defp sensor_type_config("temperature"), do: {"temperature", "°C", 10}
|
||||
defp sensor_type_config("current"), do: {"current", "A", 1000}
|
||||
defp sensor_type_config("temperature"), do: {"temperature", "°C", 1}
|
||||
defp sensor_type_config("current"), do: {"current", "mA", 1}
|
||||
defp sensor_type_config("power"), do: {"power", "W", 10}
|
||||
defp sensor_type_config("fanspeed"), do: {"fanspeed", "RPM", 1}
|
||||
|
||||
|
|
|
|||
|
|
@ -516,17 +516,34 @@ defmodule ToweropsWeb.DeviceLive.Form do
|
|||
end
|
||||
|
||||
# Check if a non-routable IP is being used with cloud poller
|
||||
# Skip this check in dev mode or for specific users
|
||||
defp check_non_routable_ip_cloud_error(device_params, assigns) do
|
||||
ip_address = device_params["ip_address"]
|
||||
# Skip check in dev mode or for specific users
|
||||
if skip_non_routable_check?(assigns) do
|
||||
false
|
||||
else
|
||||
ip_address = device_params["ip_address"]
|
||||
|
||||
# Only check if we have a valid IP address
|
||||
with true <- is_binary(ip_address) and String.trim(ip_address) != "",
|
||||
trimmed_ip = String.trim(ip_address),
|
||||
true <- non_routable_ip?(trimmed_ip),
|
||||
true <- using_cloud_poller?(device_params, assigns) do
|
||||
# Only check if we have a valid IP address
|
||||
with true <- is_binary(ip_address) and String.trim(ip_address) != "",
|
||||
trimmed_ip = String.trim(ip_address),
|
||||
true <- non_routable_ip?(trimmed_ip),
|
||||
true <- using_cloud_poller?(device_params, assigns) do
|
||||
true
|
||||
else
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp skip_non_routable_check?(assigns) do
|
||||
# Skip in dev mode
|
||||
if Mix.env() == :dev do
|
||||
true
|
||||
else
|
||||
_ -> false
|
||||
# Skip for specific users
|
||||
user = assigns[:current_scope] && assigns.current_scope.user
|
||||
user && user.email == "graham@mcintire.me"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -179,9 +179,9 @@ defmodule ToweropsWeb.DeviceLive.Show do
|
|||
end)
|
||||
|
||||
temperature_sensors =
|
||||
Enum.filter(non_transceiver_sensors, &(&1.sensor_type in ["temperature", "cpu_temperature", "celsius"]))
|
||||
Enum.filter(non_transceiver_sensors, &temperature_sensor?/1)
|
||||
|
||||
voltage_sensors = Enum.filter(non_transceiver_sensors, &(&1.sensor_type in ["voltage", "volts"]))
|
||||
voltage_sensors = Enum.filter(non_transceiver_sensors, &voltage_sensor?/1)
|
||||
|
||||
storage_sensors = Enum.filter(non_transceiver_sensors, &(&1.sensor_type in ["disk_usage"]))
|
||||
|
||||
|
|
@ -626,34 +626,57 @@ defmodule ToweropsWeb.DeviceLive.Show do
|
|||
end
|
||||
end
|
||||
|
||||
# Calculate BPS per interface first, then aggregate by timestamp
|
||||
# This avoids issues with misaligned timestamps between interfaces
|
||||
defp aggregate_interface_traffic_stats(interfaces, since) do
|
||||
interfaces
|
||||
|> Enum.flat_map(fn interface ->
|
||||
stats =
|
||||
interface.id
|
||||
|> Snmp.get_interface_stats(since: since, limit: 1000)
|
||||
|> Enum.reverse()
|
||||
|
||||
Enum.map(stats, &interface_stat_to_tuple/1)
|
||||
interface.id
|
||||
|> Snmp.get_interface_stats(since: since, limit: 1000)
|
||||
|> Enum.reverse()
|
||||
|> calculate_interface_bps()
|
||||
end)
|
||||
|> Enum.group_by(&elem(&1, 0))
|
||||
|> Enum.map(&sum_traffic_stats_by_timestamp/1)
|
||||
|> Enum.sort_by(&elem(&1, 0), DateTime)
|
||||
|> Enum.group_by(& &1.timestamp_ms)
|
||||
|> Enum.map(fn {ts, bps_list} ->
|
||||
total_in = Enum.reduce(bps_list, 0.0, fn x, acc -> acc + x.in_bps end)
|
||||
total_out = Enum.reduce(bps_list, 0.0, fn x, acc -> acc + x.out_bps end)
|
||||
%{timestamp_ms: ts, in_bps: total_in, out_bps: total_out}
|
||||
end)
|
||||
|> Enum.sort_by(& &1.timestamp_ms)
|
||||
end
|
||||
|
||||
defp interface_stat_to_tuple(stat) do
|
||||
{stat.checked_at, stat.if_in_octets, stat.if_out_octets}
|
||||
end
|
||||
# Calculate BPS for a single interface's stats
|
||||
defp calculate_interface_bps(stats) do
|
||||
stats
|
||||
|> Enum.chunk_every(2, 1, :discard)
|
||||
|> Enum.map(fn [s1, s2] ->
|
||||
time_diff = s2.checked_at |> DateTime.diff(s1.checked_at, :second) |> max(1)
|
||||
|
||||
defp sum_traffic_stats_by_timestamp({timestamp, stats}) do
|
||||
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}
|
||||
in_bps =
|
||||
if s2.if_in_octets && s1.if_in_octets do
|
||||
max((s2.if_in_octets - s1.if_in_octets) * 8 / time_diff, 0)
|
||||
else
|
||||
0.0
|
||||
end
|
||||
|
||||
out_bps =
|
||||
if s2.if_out_octets && s1.if_out_octets do
|
||||
max((s2.if_out_octets - s1.if_out_octets) * 8 / time_diff, 0)
|
||||
else
|
||||
0.0
|
||||
end
|
||||
|
||||
# Round timestamp to nearest minute for aggregation (avoids microsecond misalignment)
|
||||
ts_seconds = DateTime.to_unix(s2.checked_at)
|
||||
rounded_ts_ms = div(ts_seconds, 60) * 60 * 1000
|
||||
|
||||
%{timestamp_ms: rounded_ts_ms, in_bps: in_bps, out_bps: out_bps}
|
||||
end)
|
||||
end
|
||||
|
||||
defp build_traffic_json_datasets(all_stats) do
|
||||
in_data = calculate_bps_data(all_stats, :inbound)
|
||||
out_data = calculate_bps_data(all_stats, :outbound)
|
||||
in_data = Enum.map(all_stats, fn s -> %{x: s.timestamp_ms, y: Float.round(s.in_bps, 2)} end)
|
||||
out_data = Enum.map(all_stats, fn s -> %{x: s.timestamp_ms, y: -Float.round(s.out_bps, 2)} end)
|
||||
|
||||
datasets = [
|
||||
%{label: "Outbound", data: out_data},
|
||||
|
|
@ -663,34 +686,6 @@ defmodule ToweropsWeb.DeviceLive.Show do
|
|||
Jason.encode!(%{datasets: datasets})
|
||||
end
|
||||
|
||||
defp calculate_bps_data(all_stats, :inbound) do
|
||||
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) |> :erlang.float() |> Float.round(2)
|
||||
|
||||
%{
|
||||
x: DateTime.to_unix(t2, :millisecond),
|
||||
y: bps
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
defp calculate_bps_data(all_stats, :outbound) do
|
||||
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) |> :erlang.float() |> Float.round(2)
|
||||
|
||||
%{
|
||||
x: DateTime.to_unix(t2, :millisecond),
|
||||
y: -bps
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
# Group interfaces by type for organized display
|
||||
defp group_interfaces_by_type(interfaces) do
|
||||
interfaces
|
||||
|
|
@ -744,4 +739,33 @@ defmodule ToweropsWeb.DeviceLive.Show do
|
|||
defp group_ip_addresses_by_interface(ip_addresses) do
|
||||
Enum.group_by(ip_addresses, & &1.snmp_interface_id)
|
||||
end
|
||||
|
||||
# Sensor type classification helpers
|
||||
# These check multiple signals (type, unit, description) to correctly categorize sensors
|
||||
# since some devices (especially Mikrotik) report incorrect unit types
|
||||
defp temperature_sensor?(sensor) do
|
||||
type_match = sensor.sensor_type in ["temperature", "cpu_temperature", "celsius"]
|
||||
unit_match = sensor.sensor_unit in ["°C", "C", "celsius"]
|
||||
|
||||
descr_match =
|
||||
sensor.sensor_descr &&
|
||||
String.contains?(String.downcase(sensor.sensor_descr), ["temperature", "temp"]) &&
|
||||
!String.contains?(String.downcase(sensor.sensor_descr), "voltage")
|
||||
|
||||
# Match by type OR unit, but exclude if description says "voltage"
|
||||
(type_match || unit_match || descr_match) && !voltage_by_description?(sensor)
|
||||
end
|
||||
|
||||
defp voltage_sensor?(sensor) do
|
||||
type_match = sensor.sensor_type in ["voltage", "volts"]
|
||||
unit_match = sensor.sensor_unit in ["V", "mV", "volts"]
|
||||
descr_match = voltage_by_description?(sensor)
|
||||
|
||||
type_match || unit_match || descr_match
|
||||
end
|
||||
|
||||
defp voltage_by_description?(sensor) do
|
||||
sensor.sensor_descr &&
|
||||
String.contains?(String.downcase(sensor.sensor_descr), "voltage")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
active_page="devices"
|
||||
timezone={@timezone}
|
||||
>
|
||||
<div class="mb-6">
|
||||
<div class="mb-6 -mt-2">
|
||||
<!-- Breadcrumbs -->
|
||||
<nav class="flex mb-4" aria-label="Breadcrumb">
|
||||
<ol class="inline-flex items-center space-x-1 text-sm text-gray-600 dark:text-gray-400">
|
||||
|
|
@ -29,32 +29,23 @@
|
|||
<div class="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{@device.name}</h1>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 font-mono">{@device.ip_address}</p>
|
||||
<!-- Poller Indicator -->
|
||||
<div class="mt-2 flex items-center gap-2">
|
||||
<div class="flex items-center gap-3 mt-0.5">
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400 font-mono">
|
||||
{@device.ip_address}
|
||||
</span>
|
||||
<span class="text-gray-300 dark:text-gray-600">•</span>
|
||||
<%= if @agent_info.type == :cloud do %>
|
||||
<span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300">
|
||||
<.icon name="hero-cloud" class="h-3.5 w-3.5" /> Cloud Polling
|
||||
<span class="inline-flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400">
|
||||
<.icon name="hero-cloud" class="h-3.5 w-3.5" /> Cloud
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300">
|
||||
<span class="inline-flex items-center gap-1 text-xs text-green-600 dark:text-green-400">
|
||||
<.icon name="hero-server" class="h-3.5 w-3.5" />
|
||||
{@agent_info.name}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||
<%= case @agent_info.source do %>
|
||||
<% :device -> %>
|
||||
(assigned directly)
|
||||
<% :site -> %>
|
||||
(via site default)
|
||||
<% :organization -> %>
|
||||
(via org default)
|
||||
<% _ -> %>
|
||||
<% end %>
|
||||
</span>
|
||||
<%= if @agent_info.last_seen_at do %>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||
• Last seen: {format_relative_time(@agent_info.last_seen_at)}
|
||||
<span class="text-xs text-gray-400 dark:text-gray-500">
|
||||
({format_relative_time(@agent_info.last_seen_at)})
|
||||
</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue