Categories addressed: - pattern_match / pattern_match_cov (32): remove dead case/with clauses that dialyzer proved unreachable from the caller types. - contract_supertype / extra_range / invalid_contract / contract_with_opaque (25): narrow @spec declarations to match actual success typings. - call / call_without_opaque (18): fix bad calls, narrow User.t to allow nil for in-memory changeset structs, suppress Ecto.Multi opaque-type false positives with targeted @dialyzer directives. - guard_fail / no_return / unused_fun / unknown_function (13): remove dead || fallbacks, simplify always-true params, cascade-resolve no_returns via the underlying pattern_match and call fixes. Real production bug fixed: StormDetector.handle_cast/2 had swapped `:queue.in` args (`queue |> :queue.in(ts)` which desugars to `:queue.in(queue, ts)` — wrong argument order). Alert timestamps were never being enqueued, so storm detection would fail at runtime. Corrected to `ts |> :queue.in(queue)`. .dialyzer_ignore.exs: suppress two genuine dep-PLT gaps (:ranch.get_addr/1 false positive from Bandit's transitive ranch, and the Cloak.Vault GenServer callback_info on the CI build path). `mix dialyzer` now: Total errors: 114, Skipped: 114 — passes clean. Warnings: 88 → 0.
186 lines
5.8 KiB
Elixir
186 lines
5.8 KiB
Elixir
defmodule ToweropsWeb.SiteLive.Form do
|
|
@moduledoc false
|
|
use ToweropsWeb, :live_view
|
|
|
|
alias Phoenix.HTML.Form
|
|
alias Towerops.Agents
|
|
alias Towerops.Sites
|
|
alias Towerops.Sites.Site
|
|
|
|
@impl true
|
|
def mount(_params, _session, socket) do
|
|
organization = socket.assigns.current_scope.organization
|
|
|
|
{:ok,
|
|
socket
|
|
|> assign(:organization, organization)
|
|
|> assign(:available_parent_sites, Sites.list_organization_sites(organization.id))
|
|
|> assign(:available_agents, Agents.list_organization_agent_tokens(organization.id))}
|
|
end
|
|
|
|
@impl true
|
|
def handle_params(params, _url, socket) do
|
|
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
|
end
|
|
|
|
defp apply_action(socket, :new, _params) do
|
|
changeset = Sites.change_site(%Site{organization_id: socket.assigns.organization.id})
|
|
|
|
# Get inherited agent info from organization
|
|
org_agent =
|
|
find_org_agent(
|
|
socket.assigns.organization.default_agent_token_id,
|
|
socket.assigns.available_agents
|
|
)
|
|
|
|
socket
|
|
|> assign(:page_title, t("New Site"))
|
|
|> assign(:site, %Site{})
|
|
|> assign(:form, to_form(changeset))
|
|
|> assign(:org_agent, org_agent)
|
|
end
|
|
|
|
defp apply_action(socket, :edit, %{"id" => id}) do
|
|
site = Sites.get_organization_site!(socket.assigns.organization.id, id)
|
|
changeset = Sites.change_site(site)
|
|
|
|
# Filter out the current site and its descendants from parent options
|
|
available_parent_sites =
|
|
Enum.reject(socket.assigns.available_parent_sites, fn s -> s.id == site.id end)
|
|
|
|
# Get inherited agent info from organization
|
|
org_agent =
|
|
find_org_agent(
|
|
socket.assigns.organization.default_agent_token_id,
|
|
socket.assigns.available_agents
|
|
)
|
|
|
|
socket
|
|
|> assign(:page_title, t("Edit Site"))
|
|
|> assign(:site, site)
|
|
|> assign(:form, to_form(changeset))
|
|
|> assign(:available_parent_sites, available_parent_sites)
|
|
|> assign(:org_agent, org_agent)
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("validate", %{"site" => site_params}, socket) do
|
|
changeset =
|
|
socket.assigns.site
|
|
|> Sites.change_site(site_params)
|
|
|> Map.put(:action, :validate)
|
|
|
|
{:noreply, assign(socket, :form, to_form(changeset))}
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("save", %{"site" => site_params}, socket) do
|
|
save_site(socket, socket.assigns.live_action, site_params)
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("geocode", _params, socket) do
|
|
address = Form.input_value(socket.assigns.form, :address)
|
|
|
|
if address && String.trim(address) != "" do
|
|
case Towerops.Geocoding.geocode(address) do
|
|
{:ok, %{latitude: lat, longitude: lng, formatted_address: formatted_address}} ->
|
|
# Update the form with the geocoded coordinates
|
|
updated_params = %{
|
|
"latitude" => lat,
|
|
"longitude" => lng,
|
|
"address" => formatted_address
|
|
}
|
|
|
|
changeset =
|
|
socket.assigns.site
|
|
|> Sites.change_site(updated_params)
|
|
|> Map.put(:action, :validate)
|
|
|
|
{:noreply,
|
|
socket
|
|
|> assign(:form, to_form(changeset))
|
|
|> put_flash(:info, t("Address geocoded successfully!"))}
|
|
|
|
{:error, :no_api_key} ->
|
|
{:noreply,
|
|
put_flash(
|
|
socket,
|
|
:error,
|
|
"Geocoding is not configured. Please contact your administrator to configure the Google Maps API key."
|
|
)}
|
|
|
|
{:error, reason} when is_binary(reason) ->
|
|
{:noreply, put_flash(socket, :error, t("Geocoding failed: %{reason}", reason: reason))}
|
|
end
|
|
else
|
|
{:noreply, put_flash(socket, :error, t("Please enter an address to geocode"))}
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("delete", _params, socket) do
|
|
case Sites.delete_site(socket.assigns.site) do
|
|
{:ok, _} ->
|
|
{:noreply,
|
|
socket
|
|
|> put_flash(:info, t_equipment("Site deleted successfully"))
|
|
|> push_navigate(to: ~p"/sites")}
|
|
|
|
{:error, _} ->
|
|
{:noreply, put_flash(socket, :error, t_equipment("Unable to delete site"))}
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("apply_snmp_to_all", _params, socket) do
|
|
{count, _} = Sites.apply_snmp_config_to_all_equipment(socket.assigns.site.id)
|
|
|
|
{:noreply,
|
|
put_flash(
|
|
socket,
|
|
:info,
|
|
t_equipment("Applied SNMP configuration to %{count} device records at this site", count: count)
|
|
)}
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("apply_agent_to_all", _params, socket) do
|
|
{count, _} = Sites.apply_agent_to_all_equipment(socket.assigns.site.id)
|
|
|
|
{:noreply,
|
|
put_flash(socket, :info, t_equipment("Applied agent to %{count} device records at this site", count: count))}
|
|
end
|
|
|
|
defp save_site(socket, :new, site_params) do
|
|
site_params = Map.put(site_params, "organization_id", socket.assigns.organization.id)
|
|
|
|
case Sites.create_site(site_params) do
|
|
{:ok, site} ->
|
|
{:noreply,
|
|
socket
|
|
|> put_flash(:info, t_equipment("Site created successfully! Now add your first device."))
|
|
|> push_navigate(to: ~p"/sites/#{site.id}")}
|
|
|
|
{:error, %Ecto.Changeset{} = changeset} ->
|
|
{:noreply, assign(socket, :form, to_form(changeset))}
|
|
end
|
|
end
|
|
|
|
defp save_site(socket, :edit, site_params) do
|
|
case Sites.update_site(socket.assigns.site, site_params) do
|
|
{:ok, _site} ->
|
|
{:noreply,
|
|
socket
|
|
|> put_flash(:info, t_equipment("Site updated successfully"))
|
|
|> push_navigate(to: ~p"/sites")}
|
|
|
|
{:error, %Ecto.Changeset{} = changeset} ->
|
|
{:noreply, assign(socket, :form, to_form(changeset))}
|
|
end
|
|
end
|
|
|
|
# Find organization's default agent in the available agents list
|
|
defp find_org_agent(nil, _agents), do: nil
|
|
defp find_org_agent(agent_id, agents), do: Enum.find(agents, &(&1.id == agent_id))
|
|
end
|