Add comprehensive Dialyzer type specifications
- Add @type definitions to all schema modules: - User, UserCredential, Membership, Invitation - Organization, Site, AgentAssignment - InterfaceStat, SensorReading, Alert - Fix all compilation warnings with stronger Elixir types: - Remove unused require Logger in log_filter.ex - Remove unused parse_float(nil) clause - Add pin operators (^) for variables in binary pattern matches - Fix Dialyzer errors (25 → 0): - Remove unreachable pattern match clauses - Fix unmatched return values with _ = prefix - Update @spec for deliver_alert_notification/1 - Properly handle all Phoenix.PubSub.subscribe and Task.start return values - Explicitly ignore if statement return values where needed All files now pass mix compile --warnings-as-errors and mix dialyzer.
This commit is contained in:
parent
02c163986e
commit
b53a53b199
25 changed files with 256 additions and 74 deletions
|
|
@ -1,2 +1,2 @@
|
|||
erlang 28.3
|
||||
elixir 1.19.5-otp-28
|
||||
elixir 1.20.0-rc.1-otp-28
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ defmodule Towerops.Accounts.User do
|
|||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Ecto.Association.NotLoaded
|
||||
alias Towerops.Accounts.UserCredential
|
||||
alias Towerops.Organizations.Membership
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
@foreign_key_type :binary_id
|
||||
schema "users" do
|
||||
|
|
@ -14,13 +18,28 @@ defmodule Towerops.Accounts.User do
|
|||
field :authenticated_at, :utc_datetime, virtual: true
|
||||
field :is_superuser, :boolean, default: false
|
||||
|
||||
has_many :memberships, Towerops.Organizations.Membership
|
||||
has_many :memberships, Membership
|
||||
has_many :organizations, through: [:memberships, :organization]
|
||||
has_many :credentials, Towerops.Accounts.UserCredential
|
||||
has_many :credentials, UserCredential
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
id: Ecto.UUID.t(),
|
||||
email: String.t(),
|
||||
password: String.t() | nil,
|
||||
hashed_password: String.t(),
|
||||
confirmed_at: DateTime.t() | nil,
|
||||
authenticated_at: DateTime.t() | nil,
|
||||
is_superuser: boolean(),
|
||||
memberships: NotLoaded.t() | [Membership.t()],
|
||||
organizations: NotLoaded.t() | [Towerops.Organizations.Organization.t()],
|
||||
credentials: NotLoaded.t() | [UserCredential.t()],
|
||||
inserted_at: DateTime.t(),
|
||||
updated_at: DateTime.t()
|
||||
}
|
||||
|
||||
@doc """
|
||||
A user changeset for registering or changing the email.
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ defmodule Towerops.Accounts.UserCredential do
|
|||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Towerops.Accounts.User
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
@foreign_key_type :binary_id
|
||||
|
||||
|
|
@ -24,11 +26,29 @@ defmodule Towerops.Accounts.UserCredential do
|
|||
field :backup_state, :boolean, default: false
|
||||
field :attestation_format, :string
|
||||
|
||||
belongs_to :user, Towerops.Accounts.User
|
||||
belongs_to :user, User
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
id: Ecto.UUID.t(),
|
||||
credential_id: binary(),
|
||||
public_key: binary(),
|
||||
sign_count: integer(),
|
||||
name: String.t(),
|
||||
last_used_at: DateTime.t() | nil,
|
||||
aaguid: binary(),
|
||||
transports: [String.t()],
|
||||
backup_eligible: boolean(),
|
||||
backup_state: boolean(),
|
||||
attestation_format: String.t() | nil,
|
||||
user_id: Ecto.UUID.t(),
|
||||
user: Ecto.Association.NotLoaded.t() | User.t(),
|
||||
inserted_at: DateTime.t(),
|
||||
updated_at: DateTime.t()
|
||||
}
|
||||
|
||||
@doc """
|
||||
Changeset for creating or updating a credential.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -79,10 +79,6 @@ defmodule Towerops.Accounts.WebAuthn do
|
|||
{:error, reason} = error ->
|
||||
Logger.error("Failed to decode client data: #{inspect(reason)}")
|
||||
error
|
||||
|
||||
error ->
|
||||
Logger.error("Failed to decode client data JSON: #{inspect(error)}")
|
||||
{:error, :invalid_client_data}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -241,11 +237,11 @@ defmodule Towerops.Accounts.WebAuthn do
|
|||
|
||||
with <<aaguid::binary-size(16), cred_id_length::16, rest::binary>> <- data,
|
||||
true <- byte_size(rest) >= cred_id_length,
|
||||
<<credential_id::binary-size(cred_id_length), public_key_bytes::binary>> <- rest,
|
||||
<<credential_id::binary-size(^cred_id_length), public_key_bytes::binary>> <- rest,
|
||||
{:ok, cose_key, remaining} <- CBOR.decode(public_key_bytes),
|
||||
# Calculate the actual length of the COSE key CBOR data
|
||||
public_key_cbor_length = byte_size(public_key_bytes) - byte_size(remaining),
|
||||
<<public_key_cbor::binary-size(public_key_cbor_length), _::binary>> <-
|
||||
<<public_key_cbor::binary-size(^public_key_cbor_length), _::binary>> <-
|
||||
public_key_bytes do
|
||||
{:ok,
|
||||
%{
|
||||
|
|
|
|||
|
|
@ -9,17 +9,32 @@ defmodule Towerops.Agents.AgentAssignment do
|
|||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Ecto.Association.NotLoaded
|
||||
alias Towerops.Agents.AgentToken
|
||||
alias Towerops.Equipment.Equipment
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
@foreign_key_type :binary_id
|
||||
schema "agent_assignments" do
|
||||
field :enabled, :boolean, default: true
|
||||
|
||||
belongs_to :agent_token, Towerops.Agents.AgentToken
|
||||
belongs_to :equipment, Towerops.Equipment.Equipment
|
||||
belongs_to :agent_token, AgentToken
|
||||
belongs_to :equipment, Equipment
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
id: Ecto.UUID.t(),
|
||||
enabled: boolean(),
|
||||
agent_token_id: Ecto.UUID.t(),
|
||||
agent_token: NotLoaded.t() | AgentToken.t(),
|
||||
equipment_id: Ecto.UUID.t(),
|
||||
equipment: NotLoaded.t() | Equipment.t(),
|
||||
inserted_at: DateTime.t(),
|
||||
updated_at: DateTime.t()
|
||||
}
|
||||
|
||||
@doc """
|
||||
Changeset for creating and updating agent assignments.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ defmodule Towerops.Alerts.Alert do
|
|||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Ecto.Association.NotLoaded
|
||||
alias Towerops.Accounts.User
|
||||
alias Towerops.Equipment.Equipment
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
@foreign_key_type :binary_id
|
||||
schema "alerts" do
|
||||
|
|
@ -14,12 +18,28 @@ defmodule Towerops.Alerts.Alert do
|
|||
field :email_sent_at, :utc_datetime
|
||||
field :message, :string
|
||||
|
||||
belongs_to :equipment, Towerops.Equipment.Equipment
|
||||
belongs_to :acknowledged_by, Towerops.Accounts.User
|
||||
belongs_to :equipment, Equipment
|
||||
belongs_to :acknowledged_by, User
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
id: Ecto.UUID.t(),
|
||||
alert_type: :equipment_down | :equipment_up,
|
||||
triggered_at: DateTime.t(),
|
||||
acknowledged_at: DateTime.t() | nil,
|
||||
resolved_at: DateTime.t() | nil,
|
||||
email_sent_at: DateTime.t() | nil,
|
||||
message: String.t() | nil,
|
||||
equipment_id: Ecto.UUID.t(),
|
||||
equipment: NotLoaded.t() | Equipment.t(),
|
||||
acknowledged_by_id: Ecto.UUID.t() | nil,
|
||||
acknowledged_by: NotLoaded.t() | User.t() | nil,
|
||||
inserted_at: DateTime.t(),
|
||||
updated_at: DateTime.t()
|
||||
}
|
||||
|
||||
@doc false
|
||||
def changeset(alert, attrs) do
|
||||
alert
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ defmodule Towerops.Alerts.AlertNotifier do
|
|||
|
||||
Returns list of sent emails for tracking.
|
||||
"""
|
||||
@spec deliver_alert_notification(Alert) :: {:ok, [{:ok, Swoosh.Email.t()} | {:error, term()}]}
|
||||
@spec deliver_alert_notification(Alert.t()) ::
|
||||
{:ok, [{:ok, Swoosh.Email.t()} | {:error, term()}]}
|
||||
def deliver_alert_notification(%Alert{} = alert) do
|
||||
equipment =
|
||||
alert.equipment_id
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@ defmodule Towerops.Application do
|
|||
@impl true
|
||||
def start(_type, _args) do
|
||||
# Run migrations on startup (Ecto handles locking for concurrent runs)
|
||||
if Application.get_env(:towerops, Towerops.Repo)[:database] != "towerops_test" do
|
||||
Towerops.Release.migrate()
|
||||
end
|
||||
_ =
|
||||
if Application.get_env(:towerops, Towerops.Repo)[:database] != "towerops_test" do
|
||||
Towerops.Release.migrate()
|
||||
end
|
||||
|
||||
topologies = Application.get_env(:libcluster, :topologies, [])
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ defmodule Towerops.Equipment.EventLogger do
|
|||
@impl true
|
||||
def init(state) do
|
||||
# Subscribe to equipment events topic
|
||||
Phoenix.PubSub.subscribe(Towerops.PubSub, "equipment:events")
|
||||
_ = Phoenix.PubSub.subscribe(Towerops.PubSub, "equipment:events")
|
||||
Logger.info("EventLogger started and subscribed to equipment events")
|
||||
{:ok, state}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ defmodule Towerops.LogFilter do
|
|||
Filters out noisy log messages from external sources like port scanners and bots.
|
||||
"""
|
||||
|
||||
require Logger
|
||||
|
||||
@doc """
|
||||
Filters out Bandit HTTP errors from invalid requests.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ defmodule Towerops.Organizations.Invitation do
|
|||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Ecto.Association.NotLoaded
|
||||
alias Towerops.Accounts.User
|
||||
alias Towerops.Organizations.Organization
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
@foreign_key_type :binary_id
|
||||
|
|
@ -15,13 +17,30 @@ defmodule Towerops.Organizations.Invitation do
|
|||
field :accepted_at, :utc_datetime
|
||||
field :expires_at, :utc_datetime
|
||||
|
||||
belongs_to :organization, Towerops.Organizations.Organization
|
||||
belongs_to :organization, Organization
|
||||
belongs_to :invited_by, User
|
||||
belongs_to :accepted_by, User
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
id: Ecto.UUID.t(),
|
||||
email: String.t(),
|
||||
role: :admin | :member | :viewer,
|
||||
token: String.t(),
|
||||
accepted_at: DateTime.t() | nil,
|
||||
expires_at: DateTime.t(),
|
||||
organization_id: Ecto.UUID.t(),
|
||||
organization: NotLoaded.t() | Organization.t(),
|
||||
invited_by_id: Ecto.UUID.t(),
|
||||
invited_by: NotLoaded.t() | User.t(),
|
||||
accepted_by_id: Ecto.UUID.t() | nil,
|
||||
accepted_by: NotLoaded.t() | User.t() | nil,
|
||||
inserted_at: DateTime.t(),
|
||||
updated_at: DateTime.t()
|
||||
}
|
||||
|
||||
@doc false
|
||||
def changeset(invitation, attrs) do
|
||||
invitation
|
||||
|
|
|
|||
|
|
@ -4,17 +4,32 @@ defmodule Towerops.Organizations.Membership do
|
|||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Ecto.Association.NotLoaded
|
||||
alias Towerops.Accounts.User
|
||||
alias Towerops.Organizations.Organization
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
@foreign_key_type :binary_id
|
||||
schema "organization_memberships" do
|
||||
field :role, Ecto.Enum, values: [:owner, :admin, :member, :viewer]
|
||||
|
||||
belongs_to :organization, Towerops.Organizations.Organization
|
||||
belongs_to :user, Towerops.Accounts.User
|
||||
belongs_to :organization, Organization
|
||||
belongs_to :user, User
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
id: Ecto.UUID.t(),
|
||||
role: :owner | :admin | :member | :viewer,
|
||||
organization_id: Ecto.UUID.t(),
|
||||
organization: NotLoaded.t() | Organization.t(),
|
||||
user_id: Ecto.UUID.t(),
|
||||
user: NotLoaded.t() | User.t(),
|
||||
inserted_at: DateTime.t(),
|
||||
updated_at: DateTime.t()
|
||||
}
|
||||
|
||||
@doc false
|
||||
def changeset(membership, attrs) do
|
||||
membership
|
||||
|
|
|
|||
|
|
@ -4,21 +4,39 @@ defmodule Towerops.Organizations.Organization do
|
|||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Ecto.Association.NotLoaded
|
||||
alias Towerops.Agents.AgentToken
|
||||
alias Towerops.Organizations.Invitation
|
||||
alias Towerops.Organizations.Membership
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
@foreign_key_type :binary_id
|
||||
schema "organizations" do
|
||||
field :name, :string
|
||||
field :slug, :string
|
||||
|
||||
belongs_to :default_agent_token, Towerops.Agents.AgentToken
|
||||
belongs_to :default_agent_token, AgentToken
|
||||
|
||||
has_many :memberships, Towerops.Organizations.Membership
|
||||
has_many :memberships, Membership
|
||||
has_many :users, through: [:memberships, :user]
|
||||
has_many :invitations, Towerops.Organizations.Invitation
|
||||
has_many :invitations, Invitation
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
id: Ecto.UUID.t(),
|
||||
name: String.t(),
|
||||
slug: String.t(),
|
||||
default_agent_token_id: Ecto.UUID.t() | nil,
|
||||
default_agent_token: NotLoaded.t() | AgentToken.t() | nil,
|
||||
memberships: NotLoaded.t() | [Membership.t()],
|
||||
users: NotLoaded.t() | [Towerops.Accounts.User.t()],
|
||||
invitations: NotLoaded.t() | [Invitation.t()],
|
||||
inserted_at: DateTime.t(),
|
||||
updated_at: DateTime.t()
|
||||
}
|
||||
|
||||
@doc false
|
||||
def changeset(organization, attrs) do
|
||||
organization
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ defmodule Towerops.Sites.Site do
|
|||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Ecto.Association.NotLoaded
|
||||
alias Towerops.Agents.AgentToken
|
||||
alias Towerops.Equipment.Equipment
|
||||
alias Towerops.Organizations.Organization
|
||||
alias Towerops.Sites.Site
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
|
|
@ -13,15 +17,32 @@ defmodule Towerops.Sites.Site do
|
|||
field :description, :string
|
||||
field :location, :string
|
||||
|
||||
belongs_to :organization, Towerops.Organizations.Organization
|
||||
belongs_to :agent_token, Towerops.Agents.AgentToken
|
||||
belongs_to :organization, Organization
|
||||
belongs_to :agent_token, AgentToken
|
||||
belongs_to :parent_site, Site
|
||||
has_many :child_sites, Site, foreign_key: :parent_site_id
|
||||
has_many :equipment, Towerops.Equipment.Equipment
|
||||
has_many :equipment, Equipment
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
id: Ecto.UUID.t(),
|
||||
name: String.t(),
|
||||
description: String.t() | nil,
|
||||
location: String.t() | nil,
|
||||
organization_id: Ecto.UUID.t(),
|
||||
organization: NotLoaded.t() | Organization.t(),
|
||||
agent_token_id: Ecto.UUID.t() | nil,
|
||||
agent_token: NotLoaded.t() | AgentToken.t() | nil,
|
||||
parent_site_id: Ecto.UUID.t() | nil,
|
||||
parent_site: NotLoaded.t() | t() | nil,
|
||||
child_sites: NotLoaded.t() | [t()],
|
||||
equipment: NotLoaded.t() | [Equipment.t()],
|
||||
inserted_at: DateTime.t(),
|
||||
updated_at: DateTime.t()
|
||||
}
|
||||
|
||||
@doc false
|
||||
def changeset(site, attrs) do
|
||||
site
|
||||
|
|
|
|||
|
|
@ -109,15 +109,16 @@ defmodule Towerops.Snmp.Discovery do
|
|||
{:ok, device} <- save_discovery_results(equipment, device_info, interfaces, sensors),
|
||||
{:ok, neighbors} <- NeighborDiscovery.discover_neighbors(client_opts, device.interfaces),
|
||||
:ok <- save_neighbors(equipment.id, neighbors) do
|
||||
update_equipment_discovery_time(equipment)
|
||||
_ = update_equipment_discovery_time(equipment)
|
||||
Logger.info("SNMP discovery completed successfully for: #{equipment.name}")
|
||||
|
||||
# Broadcast discovery completion for real-time updates
|
||||
Phoenix.PubSub.broadcast(
|
||||
Towerops.PubSub,
|
||||
"equipment:#{equipment.id}",
|
||||
{:discovery_completed, equipment.id}
|
||||
)
|
||||
_ =
|
||||
Phoenix.PubSub.broadcast(
|
||||
Towerops.PubSub,
|
||||
"equipment:#{equipment.id}",
|
||||
{:discovery_completed, equipment.id}
|
||||
)
|
||||
|
||||
{:ok, device}
|
||||
else
|
||||
|
|
@ -278,9 +279,9 @@ defmodule Towerops.Snmp.Discovery do
|
|||
device = upsert_device(equipment, device_info)
|
||||
|
||||
# Delete old interfaces/sensors and insert new ones
|
||||
delete_old_data(device)
|
||||
_ = delete_old_data(device)
|
||||
new_interfaces = insert_interfaces(device, interfaces)
|
||||
insert_sensors(device, sensors)
|
||||
_ = insert_sensors(device, sensors)
|
||||
|
||||
# Return device with interfaces loaded and equipment_id added
|
||||
%{device | interfaces: Enum.map(new_interfaces, &Map.put(&1, :equipment_id, equipment.id))}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ defmodule Towerops.Snmp.InterfaceStat do
|
|||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Towerops.Snmp.Interface
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
@foreign_key_type :binary_id
|
||||
schema "snmp_interface_stats" do
|
||||
|
|
@ -15,11 +17,25 @@ defmodule Towerops.Snmp.InterfaceStat do
|
|||
field :if_out_discards, :integer
|
||||
field :checked_at, :utc_datetime
|
||||
|
||||
belongs_to :interface, Towerops.Snmp.Interface
|
||||
belongs_to :interface, Interface
|
||||
|
||||
timestamps(type: :utc_datetime, updated_at: false)
|
||||
end
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
id: Ecto.UUID.t(),
|
||||
if_in_octets: integer() | nil,
|
||||
if_out_octets: integer() | nil,
|
||||
if_in_errors: integer() | nil,
|
||||
if_out_errors: integer() | nil,
|
||||
if_in_discards: integer() | nil,
|
||||
if_out_discards: integer() | nil,
|
||||
checked_at: DateTime.t(),
|
||||
interface_id: Ecto.UUID.t(),
|
||||
interface: Ecto.Association.NotLoaded.t() | Interface.t(),
|
||||
inserted_at: DateTime.t()
|
||||
}
|
||||
|
||||
@doc false
|
||||
def changeset(stat, attrs) do
|
||||
stat
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ defmodule Towerops.Snmp.PollerWorker do
|
|||
equipment = Equipment.get_equipment!(equipment_id)
|
||||
|
||||
# Subscribe to discovery completion events for this equipment
|
||||
Phoenix.PubSub.subscribe(Towerops.PubSub, "equipment:#{equipment_id}")
|
||||
_ = Phoenix.PubSub.subscribe(Towerops.PubSub, "equipment:#{equipment_id}")
|
||||
|
||||
if equipment.snmp_enabled do
|
||||
# Get the device to check if it has sensors/interfaces
|
||||
|
|
@ -500,7 +500,13 @@ defmodule Towerops.Snmp.PollerWorker do
|
|||
|
||||
defp broadcast_interface_events(events) do
|
||||
Enum.each(events, fn {:event, event_attrs} ->
|
||||
Phoenix.PubSub.broadcast(Towerops.PubSub, "equipment:events", {:equipment_event, event_attrs})
|
||||
_ =
|
||||
Phoenix.PubSub.broadcast(
|
||||
Towerops.PubSub,
|
||||
"equipment:events",
|
||||
{:equipment_event, event_attrs}
|
||||
)
|
||||
|
||||
Logger.debug("Broadcast event: #{event_attrs.message}")
|
||||
end)
|
||||
end
|
||||
|
|
@ -590,7 +596,7 @@ defmodule Towerops.Snmp.PollerWorker do
|
|||
s when s > 8 ->
|
||||
# Large binary, try reading last 8 bytes
|
||||
offset = s - 8
|
||||
<<_prefix::binary-size(offset), counter::unsigned-big-integer-size(64)>> = value
|
||||
<<_prefix::binary-size(^offset), counter::unsigned-big-integer-size(64)>> = value
|
||||
counter
|
||||
|
||||
_ ->
|
||||
|
|
@ -896,7 +902,7 @@ defmodule Towerops.Snmp.PollerWorker do
|
|||
|
||||
defp broadcast_sensor_events(events) do
|
||||
Enum.each(events, fn event ->
|
||||
Phoenix.PubSub.broadcast(Towerops.PubSub, "equipment:events", {:equipment_event, event})
|
||||
_ = Phoenix.PubSub.broadcast(Towerops.PubSub, "equipment:events", {:equipment_event, event})
|
||||
Logger.debug("Sensor event: #{event.message}")
|
||||
end)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ defmodule Towerops.Snmp.SensorReading do
|
|||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Towerops.Snmp.Sensor
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
@foreign_key_type :binary_id
|
||||
schema "snmp_sensor_readings" do
|
||||
|
|
@ -11,11 +13,21 @@ defmodule Towerops.Snmp.SensorReading do
|
|||
field :status, :string
|
||||
field :checked_at, :utc_datetime
|
||||
|
||||
belongs_to :sensor, Towerops.Snmp.Sensor
|
||||
belongs_to :sensor, Sensor
|
||||
|
||||
timestamps(type: :utc_datetime, updated_at: false)
|
||||
end
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
id: Ecto.UUID.t(),
|
||||
value: float() | nil,
|
||||
status: String.t(),
|
||||
checked_at: DateTime.t(),
|
||||
sensor_id: Ecto.UUID.t(),
|
||||
sensor: Ecto.Association.NotLoaded.t() | Sensor.t(),
|
||||
inserted_at: DateTime.t()
|
||||
}
|
||||
|
||||
@doc false
|
||||
def changeset(reading, attrs) do
|
||||
reading
|
||||
|
|
|
|||
|
|
@ -356,7 +356,6 @@ defmodule ToweropsWeb.AgentChannel do
|
|||
|
||||
defp parse_integer(_), do: nil
|
||||
|
||||
defp parse_float(nil), do: nil
|
||||
defp parse_float(value) when is_float(value), do: value
|
||||
defp parse_float(value) when is_integer(value), do: value / 1.0
|
||||
|
||||
|
|
|
|||
|
|
@ -149,9 +149,10 @@ defmodule ToweropsWeb.EquipmentLive.Form do
|
|||
|
||||
if equipment.snmp_enabled do
|
||||
# Run discovery in background
|
||||
Task.start(fn ->
|
||||
Snmp.discover_equipment(equipment)
|
||||
end)
|
||||
_ =
|
||||
Task.start(fn ->
|
||||
Snmp.discover_equipment(equipment)
|
||||
end)
|
||||
|
||||
{:noreply, put_flash(socket, :info, "Discovery started...")}
|
||||
else
|
||||
|
|
@ -207,7 +208,7 @@ defmodule ToweropsWeb.EquipmentLive.Form do
|
|||
|
||||
defp handle_equipment_creation(equipment) do
|
||||
if equipment.snmp_enabled do
|
||||
Task.start(fn -> Snmp.discover_equipment(equipment) end)
|
||||
_ = Task.start(fn -> Snmp.discover_equipment(equipment) end)
|
||||
"Equipment created successfully. SNMP discovery started in background."
|
||||
else
|
||||
"Equipment created successfully"
|
||||
|
|
@ -216,7 +217,7 @@ defmodule ToweropsWeb.EquipmentLive.Form do
|
|||
|
||||
defp handle_equipment_update(old_equipment, equipment) do
|
||||
if should_trigger_snmp_discovery?(old_equipment, equipment) do
|
||||
Task.start(fn -> Snmp.discover_equipment(equipment) end)
|
||||
_ = Task.start(fn -> Snmp.discover_equipment(equipment) end)
|
||||
"Equipment updated successfully. SNMP discovery started in background."
|
||||
else
|
||||
"Equipment updated successfully"
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@ defmodule ToweropsWeb.EquipmentLive.Show do
|
|||
|
||||
@impl true
|
||||
def handle_params(%{"id" => id} = params, _, socket) do
|
||||
if connected?(socket) do
|
||||
Phoenix.PubSub.subscribe(Towerops.PubSub, "equipment:#{id}")
|
||||
Process.send_after(self(), :refresh_data, 10_000)
|
||||
end
|
||||
_ =
|
||||
if connected?(socket) do
|
||||
_ = Phoenix.PubSub.subscribe(Towerops.PubSub, "equipment:#{id}")
|
||||
Process.send_after(self(), :refresh_data, 10_000)
|
||||
end
|
||||
|
||||
tab = Map.get(params, "tab", "overview")
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@ defmodule ToweropsWeb.GraphLive.Show do
|
|||
|
||||
@impl true
|
||||
def handle_params(%{"id" => equipment_id, "sensor_type" => sensor_type} = params, _, socket) do
|
||||
if connected?(socket) do
|
||||
Phoenix.PubSub.subscribe(Towerops.PubSub, "equipment:#{equipment_id}")
|
||||
end
|
||||
_ =
|
||||
if connected?(socket) do
|
||||
Phoenix.PubSub.subscribe(Towerops.PubSub, "equipment:#{equipment_id}")
|
||||
end
|
||||
|
||||
range = Map.get(params, "range", "24h")
|
||||
interface_id = Map.get(params, "interface_id")
|
||||
|
|
|
|||
|
|
@ -17,9 +17,10 @@ defmodule ToweropsWeb.MobileQRLive do
|
|||
{:ok, qr_token} = MobileSessions.create_qr_login_token(user.id)
|
||||
|
||||
# Start polling to check if token has been completed
|
||||
if connected?(socket) do
|
||||
schedule_check()
|
||||
end
|
||||
_ =
|
||||
if connected?(socket) do
|
||||
schedule_check()
|
||||
end
|
||||
|
||||
socket =
|
||||
socket
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ defmodule ToweropsWeb.Plugs.MobileAuth do
|
|||
{:ok, session} <- validate_session(token),
|
||||
{:ok, user} <- get_user(session.user_id) do
|
||||
# Touch the session to update last_used_at
|
||||
Task.start(fn -> MobileSessions.touch_session(session) end)
|
||||
_ = Task.start(fn -> MobileSessions.touch_session(session) end)
|
||||
|
||||
conn
|
||||
|> assign(:current_user, user)
|
||||
|
|
@ -59,5 +59,4 @@ defmodule ToweropsWeb.Plugs.MobileAuth do
|
|||
defp error_message(:missing_token), do: "Authorization header is missing or invalid"
|
||||
defp error_message(:invalid_token), do: "Invalid or expired authentication token"
|
||||
defp error_message(:user_not_found), do: "User not found"
|
||||
defp error_message(_), do: "Authentication failed"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,19 +11,21 @@ defmodule ToweropsWeb.Telemetry do
|
|||
@impl true
|
||||
def init(_arg) do
|
||||
# Attach telemetry handlers for logging request failures
|
||||
:telemetry.attach(
|
||||
"towerops-router-exception",
|
||||
[:phoenix, :router_dispatch, :exception],
|
||||
&__MODULE__.handle_router_exception/4,
|
||||
nil
|
||||
)
|
||||
_ =
|
||||
:telemetry.attach(
|
||||
"towerops-router-exception",
|
||||
[:phoenix, :router_dispatch, :exception],
|
||||
&__MODULE__.handle_router_exception/4,
|
||||
nil
|
||||
)
|
||||
|
||||
:telemetry.attach(
|
||||
"towerops-endpoint-stop",
|
||||
[:phoenix, :endpoint, :stop],
|
||||
&__MODULE__.handle_endpoint_stop/4,
|
||||
nil
|
||||
)
|
||||
_ =
|
||||
:telemetry.attach(
|
||||
"towerops-endpoint-stop",
|
||||
[:phoenix, :endpoint, :stop],
|
||||
&__MODULE__.handle_endpoint_stop/4,
|
||||
nil
|
||||
)
|
||||
|
||||
children = [
|
||||
# Telemetry poller will execute the given period measurements
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue