fix device identification
This commit is contained in:
parent
8e1703cf28
commit
2324b75e47
7 changed files with 113 additions and 21 deletions
|
|
@ -164,22 +164,50 @@ defmodule Towerops.DeviceProfiles.Importer do
|
|||
|
||||
defp create_sys_object_id_rules(profile_id, oids) when is_list(oids) do
|
||||
Enum.each(oids, fn oid ->
|
||||
DeviceProfiles.create_detection_rule(%{
|
||||
device_profile_id: profile_id,
|
||||
rule_type: "sysObjectID",
|
||||
pattern: oid
|
||||
})
|
||||
# Skip overly broad patterns that would match any enterprise OID
|
||||
if is_overly_broad_oid?(oid) do
|
||||
Logger.warning("Skipping overly broad sysObjectID pattern: #{oid}")
|
||||
else
|
||||
DeviceProfiles.create_detection_rule(%{
|
||||
device_profile_id: profile_id,
|
||||
rule_type: "sysObjectID",
|
||||
pattern: oid
|
||||
})
|
||||
end
|
||||
end)
|
||||
|
||||
{:ok, :created}
|
||||
end
|
||||
|
||||
defp create_sys_object_id_rules(profile_id, oid) when is_binary(oid) do
|
||||
DeviceProfiles.create_detection_rule(%{
|
||||
device_profile_id: profile_id,
|
||||
rule_type: "sysObjectID",
|
||||
pattern: oid
|
||||
})
|
||||
# Skip overly broad patterns that would match any enterprise OID
|
||||
if is_overly_broad_oid?(oid) do
|
||||
Logger.warning("Skipping overly broad sysObjectID pattern: #{oid}")
|
||||
else
|
||||
DeviceProfiles.create_detection_rule(%{
|
||||
device_profile_id: profile_id,
|
||||
rule_type: "sysObjectID",
|
||||
pattern: oid
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
# Check if an OID pattern is too broad and would match too many devices
|
||||
# Patterns like ".1.3.6.1.4.1" would match ANY enterprise OID
|
||||
defp is_overly_broad_oid?(oid) do
|
||||
# Normalize to dotted format
|
||||
normalized = if String.starts_with?(oid, "."), do: oid, else: "." <> oid
|
||||
|
||||
# These are the only acceptable root OIDs that are specific enough
|
||||
# .1.3.6.1.4.1 is the enterprise prefix - anything shorter is too broad
|
||||
# Must have at least one more component after .1.3.6.1.4.1
|
||||
case normalized do
|
||||
".1.3.6.1.4.1" -> true
|
||||
# Also reject other common roots that are too broad
|
||||
".1.3.6.1.4" -> true
|
||||
".1.3.6.1" -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp create_sys_descr_regex_rules(profile_id, patterns) when is_list(patterns) do
|
||||
|
|
@ -418,8 +446,22 @@ defmodule Towerops.DeviceProfiles.Importer do
|
|||
defp present?(_), do: true
|
||||
|
||||
# Convert OID values, preserving all data types since the field is now JSONB
|
||||
# Only convert empty strings to nil
|
||||
# Handles:
|
||||
# - Empty strings -> nil
|
||||
# - Arrays (data corruption) -> extract first element if it's a string
|
||||
# - Everything else -> pass through
|
||||
defp nilify_blank(nil), do: nil
|
||||
defp nilify_blank(""), do: nil
|
||||
|
||||
defp nilify_blank([single_oid | _rest]) when is_binary(single_oid) do
|
||||
Logger.warning("OID stored as array (data corruption), extracting: #{single_oid}")
|
||||
single_oid
|
||||
end
|
||||
|
||||
defp nilify_blank([_non_string | _rest] = value) do
|
||||
Logger.warning("OID array contains non-string values, skipping: #{inspect(value)}")
|
||||
nil
|
||||
end
|
||||
|
||||
defp nilify_blank(value), do: value
|
||||
end
|
||||
|
|
|
|||
|
|
@ -50,6 +50,9 @@ defmodule Towerops.Snmp.Client do
|
|||
:no_such_instance ->
|
||||
Logger.debug("SNMP instance not found for #{target}: #{inspect(oid)}")
|
||||
|
||||
:no_such_name ->
|
||||
Logger.debug("SNMP name not found for #{target}: #{inspect(oid)}")
|
||||
|
||||
:end_of_mib_view ->
|
||||
Logger.debug("End of MIB view reached for #{target}: #{inspect(oid)}")
|
||||
|
||||
|
|
@ -133,6 +136,9 @@ defmodule Towerops.Snmp.Client do
|
|||
:no_such_instance ->
|
||||
Logger.debug("SNMP WALK: instance not found for #{target} at #{inspect(start_oid)}")
|
||||
|
||||
:no_such_name ->
|
||||
Logger.debug("SNMP WALK: name not found for #{target} at #{inspect(start_oid)}")
|
||||
|
||||
:end_of_mib_view ->
|
||||
Logger.debug("SNMP WALK: end of MIB view for #{target} at #{inspect(start_oid)}")
|
||||
|
||||
|
|
@ -185,6 +191,9 @@ defmodule Towerops.Snmp.Client do
|
|||
:no_such_instance ->
|
||||
Logger.debug("SNMP GET-BULK: instance not found for #{target} at #{inspect(start_oid)}")
|
||||
|
||||
:no_such_name ->
|
||||
Logger.debug("SNMP GET-BULK: name not found for #{target} at #{inspect(start_oid)}")
|
||||
|
||||
:end_of_mib_view ->
|
||||
Logger.debug("SNMP GET-BULK: end of MIB view for #{target} at #{inspect(start_oid)}")
|
||||
|
||||
|
|
|
|||
|
|
@ -148,10 +148,23 @@ defmodule Towerops.Snmp.Profiles.Dynamic do
|
|||
oid = sensor_def.num_oid || sensor_def.oid
|
||||
|
||||
if oid do
|
||||
# Remove template variables like ".{{ $index }}" from OID
|
||||
# For walks, we need the base OID without the index placeholder
|
||||
walk_oid = clean_oid_template(oid)
|
||||
walk_sensor_oid(client_opts, walk_oid, sensor_def)
|
||||
# Handle case where OID is mistakenly stored as array (data corruption)
|
||||
oid_string =
|
||||
case oid do
|
||||
[single_oid | _] when is_binary(single_oid) -> single_oid
|
||||
oid when is_binary(oid) -> oid
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
if oid_string do
|
||||
# Remove template variables like ".{{ $index }}" from OID
|
||||
# For walks, we need the base OID without the index placeholder
|
||||
walk_oid = clean_oid_template(oid_string)
|
||||
walk_sensor_oid(client_opts, walk_oid, sensor_def)
|
||||
else
|
||||
Logger.warning("Sensor definition has invalid OID format: #{inspect(oid)}")
|
||||
[]
|
||||
end
|
||||
else
|
||||
Logger.warning("Sensor definition missing OID: #{inspect(sensor_def)}")
|
||||
[]
|
||||
|
|
|
|||
|
|
@ -576,6 +576,31 @@ defmodule ToweropsWeb.CoreComponents do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Renders a relative timestamp with full timestamp on hover.
|
||||
|
||||
## Examples
|
||||
|
||||
<.timestamp datetime={@device.inserted_at} timezone={@timezone} />
|
||||
|
||||
"""
|
||||
attr :datetime, :any, required: true, doc: "DateTime to display"
|
||||
attr :timezone, :string, default: "UTC", doc: "User's timezone for full timestamp"
|
||||
attr :class, :string, default: "", doc: "Additional CSS classes"
|
||||
|
||||
def timestamp(assigns) do
|
||||
assigns =
|
||||
assigns
|
||||
|> assign(:relative_time, ToweropsWeb.TimeHelpers.format_time_ago(assigns.datetime))
|
||||
|> assign(:full_time, ToweropsWeb.TimeHelpers.format_datetime(assigns.datetime, assigns.timezone))
|
||||
|
||||
~H"""
|
||||
<span class={@class} title={@full_time}>
|
||||
{@relative_time}
|
||||
</span>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Translates the errors for a field from a keyword list of errors.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -459,7 +459,7 @@ defmodule ToweropsWeb.Layouts do
|
|||
<div class="flex flex-col items-center gap-2 text-sm text-zinc-600 dark:text-zinc-400">
|
||||
<p>Copyright © {Date.utc_today().year} Towerops</p>
|
||||
<p class="text-xs">
|
||||
Last deploy {@time_ago} · {@formatted_time}
|
||||
Last deploy <span title={@formatted_time}>{@time_ago}</span> · {@formatted_time}
|
||||
</p>
|
||||
<div class="flex items-center gap-1 text-xs opacity-60">
|
||||
<span class="leading-none">made in</span>
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@
|
|||
|
||||
<:col :let={agent} label="Last Seen">
|
||||
<div class="text-sm text-zinc-900 dark:text-zinc-100">
|
||||
{format_last_seen(agent.last_seen_at)}
|
||||
<.timestamp datetime={agent.last_seen_at} timezone={@timezone} />
|
||||
</div>
|
||||
<%= if agent.last_seen_at do %>
|
||||
<div class="text-xs text-zinc-500 dark:text-zinc-400 mt-0.5">
|
||||
|
|
|
|||
|
|
@ -65,7 +65,10 @@
|
|||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm font-medium text-zinc-600 dark:text-zinc-400">Last Seen</p>
|
||||
<p class="mt-2 text-lg font-semibold text-zinc-900 dark:text-zinc-100">
|
||||
{format_last_seen(@agent_token.last_seen_at)}
|
||||
<.timestamp
|
||||
datetime={@agent_token.last_seen_at}
|
||||
timezone={@timezone}
|
||||
/>
|
||||
</p>
|
||||
<%= if @agent_token.last_seen_at do %>
|
||||
<p class="mt-1 text-xs text-zinc-500 dark:text-zinc-400">
|
||||
|
|
@ -154,7 +157,7 @@
|
|||
{format_datetime(@agent_token.inserted_at, @timezone)}
|
||||
</dd>
|
||||
<dd class="mt-0.5 text-xs text-zinc-500 dark:text-zinc-400">
|
||||
{format_last_seen(@agent_token.inserted_at)} (created)
|
||||
<.timestamp datetime={@agent_token.inserted_at} timezone={@timezone} /> (created)
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
|
|
@ -164,7 +167,7 @@
|
|||
{format_datetime(@agent_token.updated_at, @timezone)}
|
||||
</dd>
|
||||
<dd class="mt-0.5 text-xs text-zinc-500 dark:text-zinc-400">
|
||||
{format_last_seen(@agent_token.updated_at)}
|
||||
<.timestamp datetime={@agent_token.updated_at} timezone={@timezone} />
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
|
|
@ -177,7 +180,7 @@
|
|||
</dd>
|
||||
<%= if @agent_token.last_seen_at do %>
|
||||
<dd class="mt-0.5 text-xs text-zinc-500 dark:text-zinc-400">
|
||||
{format_last_seen(@agent_token.last_seen_at)}
|
||||
<.timestamp datetime={@agent_token.last_seen_at} timezone={@timezone} />
|
||||
</dd>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue