Implements real-time wireless client monitoring with historical tracking, LiveView UI, proactive alerting, and cross-browser e2e tests. Phase 1: Historical Tracking - Add TimescaleDB hypertable for wireless_client_readings - Batch insert client metrics every 60 seconds from DevicePollerWorker - 90-day retention with compression after 7 days - Continuous aggregates for hourly (1 year) and daily (5 years) rollups Phase 2: LiveView UI - Add wireless tab to device detail page - Real-time client list with PubSub updates - Signal strength and SNR badges with 5-level thresholds - Display MAC, IP, subscriber, TX/RX rates, distance, uptime - Subscriber matching via device_subscriber_links - Empty state handling Phase 3: Proactive Alerting - WirelessInsightWorker runs every 5 minutes via Oban cron - 4 insight types with auto-resolution: * wireless_signal_weak: < -75 dBm (warning), < -85 dBm (critical) * wireless_snr_low: < 15 dB (warning), < 10 dB (critical) * wireless_ap_overloaded: > 50 clients (warning), > 75 clients (critical) * wireless_client_missing: expected subscribers not connecting - Hysteresis thresholds prevent alert flapping - Multi-organization isolation with proper deduplication Code Quality: - Refactored reload_current_tab_data to reduce cyclomatic complexity - Combined double Enum.filter into single pass for efficiency - Fixed length/1 comparison to use empty list check - All Credo checks passing Testing: - 28 unit tests (ExUnit) - 100% passing - 15 e2e tests (Playwright) - 100% passing across chromium/firefox/webkit - Total: 73 tests, all passing Files changed: - lib/towerops/workers/wireless_insight_worker.ex (NEW) - lib/towerops_web/live/device_live/show.ex (wireless tab + refactoring) - lib/towerops_web/live/device_live/show.html.heex (wireless template) - lib/towerops/snmp.ex (5 new query functions) - lib/towerops/gaiia.ex (list_missing_subscribers) - lib/towerops/preseem/insight.ex (5 new insight types) - config/runtime.exs (Oban cron schedule) - test/support/fixtures/snmp_fixtures.ex (NEW) - test/towerops/workers/wireless_insight_worker_test.exs (NEW) - test/towerops_web/live/device_live/show_test.exs (9 new tests) - e2e/tests/wireless-clients.spec.ts (NEW - 15 cross-browser tests)
69 lines
2.5 KiB
Elixir
69 lines
2.5 KiB
Elixir
defmodule Towerops.Preseem.Insight do
|
|
@moduledoc """
|
|
Generated insights from Preseem data analysis - deviations, capacity warnings,
|
|
firmware recommendations, and performance observations.
|
|
"""
|
|
use Ecto.Schema
|
|
|
|
import Ecto.Changeset
|
|
|
|
@primary_key {:id, :binary_id, autogenerate: true}
|
|
@foreign_key_type :binary_id
|
|
|
|
@valid_types ~w(qoe_degradation capacity_saturation firmware_opportunity model_underperforming subscriber_growth config_drift snmp_cpu_high snmp_memory_high snmp_disk_high device_poll_gap agent_offline firmware_mismatch reconciliation_finding subscriber_growth_trend suspect_config_change backhaul_over_capacity backhaul_near_capacity wireless_signal_weak wireless_snr_low wireless_ap_overloaded wireless_client_missing wireless_coverage_gap)
|
|
@valid_urgencies ~w(critical warning info)
|
|
@valid_statuses ~w(active dismissed resolved)
|
|
@valid_channels ~w(proactive contextual passive)
|
|
@valid_sources ~w(preseem snmp gaiia system)
|
|
|
|
schema "preseem_insights" do
|
|
field :type, :string
|
|
field :urgency, :string
|
|
field :status, :string, default: "active"
|
|
field :channel, :string
|
|
field :title, :string
|
|
field :description, :string
|
|
field :metadata, :map
|
|
field :dismissed_at, :utc_datetime
|
|
field :source, :string, default: "preseem"
|
|
|
|
belongs_to :organization, Towerops.Organizations.Organization
|
|
belongs_to :preseem_access_point, Towerops.Preseem.AccessPoint
|
|
belongs_to :device, Towerops.Devices.Device
|
|
belongs_to :site, Towerops.Sites.Site
|
|
belongs_to :agent_token, Towerops.Agents.AgentToken
|
|
|
|
timestamps(type: :utc_datetime)
|
|
end
|
|
|
|
def changeset(insight, attrs) do
|
|
insight
|
|
|> cast(attrs, [
|
|
:organization_id,
|
|
:preseem_access_point_id,
|
|
:device_id,
|
|
:site_id,
|
|
:agent_token_id,
|
|
:type,
|
|
:urgency,
|
|
:status,
|
|
:channel,
|
|
:title,
|
|
:description,
|
|
:metadata,
|
|
:dismissed_at,
|
|
:source
|
|
])
|
|
|> validate_required([:organization_id, :type, :urgency, :channel, :title])
|
|
|> validate_inclusion(:type, @valid_types)
|
|
|> validate_inclusion(:urgency, @valid_urgencies)
|
|
|> validate_inclusion(:status, @valid_statuses)
|
|
|> validate_inclusion(:channel, @valid_channels)
|
|
|> validate_inclusion(:source, @valid_sources)
|
|
|> foreign_key_constraint(:organization_id)
|
|
|> foreign_key_constraint(:preseem_access_point_id)
|
|
|> foreign_key_constraint(:device_id)
|
|
|> foreign_key_constraint(:site_id)
|
|
|> foreign_key_constraint(:agent_token_id)
|
|
end
|
|
end
|