diff --git a/lib/towerops/organizations.ex b/lib/towerops/organizations.ex index aca55206..02fb7e60 100644 --- a/lib/towerops/organizations.ex +++ b/lib/towerops/organizations.ex @@ -5,6 +5,8 @@ defmodule Towerops.Organizations do import Ecto.Query + alias Towerops.Agents.AgentAssignment + alias Towerops.Equipment.Equipment alias Towerops.Organizations.Invitation alias Towerops.Organizations.Membership alias Towerops.Organizations.Organization @@ -248,6 +250,75 @@ defmodule Towerops.Organizations do Repo.delete(invitation) end + ## Bulk Configuration Updates + + @doc """ + Applies organization's SNMP configuration to all equipment in the organization. + + This updates all equipment records to use the organization's SNMP version and community string, + overwriting any existing equipment-level or site-level configurations. + + Returns the number of equipment records updated. + """ + def apply_snmp_config_to_all_equipment(organization_id) do + organization = get_organization!(organization_id) + + Repo.update_all(from(e in Equipment, join: s in assoc(e, :site), where: s.organization_id == ^organization_id), + set: [ + snmp_version: organization.snmp_version, + snmp_community: organization.snmp_community, + updated_at: DateTime.truncate(DateTime.utc_now(), :second) + ] + ) + end + + @doc """ + Applies organization's default agent to all equipment in the organization. + + This creates/updates agent assignments for all equipment to use the organization's + default agent, overwriting any existing equipment-level or site-level assignments. + + Returns the number of equipment records assigned. + """ + def apply_agent_to_all_equipment(organization_id) do + organization = Repo.get!(Organization, organization_id) + + if organization.default_agent_token_id do + # Get all equipment IDs for this organization + equipment_ids = + Repo.all( + from(e in Equipment, join: s in assoc(e, :site), where: s.organization_id == ^organization_id, select: e.id) + ) + + # Delete existing assignments + Repo.delete_all(from(a in AgentAssignment, where: a.equipment_id in ^equipment_ids)) + + # Create new assignments + now = DateTime.truncate(DateTime.utc_now(), :second) + + assignments = + Enum.map(equipment_ids, fn equipment_id -> + %{ + equipment_id: equipment_id, + agent_token_id: organization.default_agent_token_id, + inserted_at: now, + updated_at: now + } + end) + + {count, _} = Repo.insert_all(AgentAssignment, assignments) + {count, nil} + else + # No default agent set, delete all assignments + equipment_ids = + Repo.all( + from(e in Equipment, join: s in assoc(e, :site), where: s.organization_id == ^organization_id, select: e.id) + ) + + Repo.delete_all(from(a in AgentAssignment, where: a.equipment_id in ^equipment_ids)) + end + end + ## Authorization @doc """ diff --git a/lib/towerops/sites.ex b/lib/towerops/sites.ex index 81311850..0cc66739 100644 --- a/lib/towerops/sites.ex +++ b/lib/towerops/sites.ex @@ -5,6 +5,8 @@ defmodule Towerops.Sites do import Ecto.Query + alias Towerops.Agents.AgentAssignment + alias Towerops.Equipment.Equipment alias Towerops.Repo alias Towerops.Sites.Site @@ -121,4 +123,65 @@ defmodule Towerops.Sites do %{site: site, children: children} end + + ## Bulk Configuration Updates + + @doc """ + Applies site's SNMP configuration to all equipment at the site. + + This updates all equipment records to use the site's SNMP version and community string, + overwriting any existing equipment-level configurations. + + Returns the number of equipment records updated. + """ + def apply_snmp_config_to_all_equipment(site_id) do + site = get_site!(site_id) + + Repo.update_all(from(e in Equipment, where: e.site_id == ^site_id), + set: [ + snmp_version: site.snmp_version, + snmp_community: site.snmp_community, + updated_at: DateTime.truncate(DateTime.utc_now(), :second) + ] + ) + end + + @doc """ + Applies site's agent to all equipment at the site. + + This creates/updates agent assignments for all equipment at the site to use the site's + agent, overwriting any existing equipment-level assignments. + + Returns the number of equipment records assigned. + """ + def apply_agent_to_all_equipment(site_id) do + site = Repo.get!(Site, site_id) + + # Get all equipment IDs for this site + equipment_ids = Repo.all(from(e in Equipment, where: e.site_id == ^site_id, select: e.id)) + + if site.agent_token_id do + # Delete existing assignments + Repo.delete_all(from(a in AgentAssignment, where: a.equipment_id in ^equipment_ids)) + + # Create new assignments + now = DateTime.truncate(DateTime.utc_now(), :second) + + assignments = + Enum.map(equipment_ids, fn equipment_id -> + %{ + equipment_id: equipment_id, + agent_token_id: site.agent_token_id, + inserted_at: now, + updated_at: now + } + end) + + {count, _} = Repo.insert_all(AgentAssignment, assignments) + {count, nil} + else + # No agent set, delete all assignments (they will inherit from org or use cloud) + Repo.delete_all(from(a in AgentAssignment, where: a.equipment_id in ^equipment_ids)) + end + end end diff --git a/lib/towerops_web/live/org/settings_live.ex b/lib/towerops_web/live/org/settings_live.ex index 88ac4d79..ab8416df 100644 --- a/lib/towerops_web/live/org/settings_live.ex +++ b/lib/towerops_web/live/org/settings_live.ex @@ -47,4 +47,18 @@ defmodule ToweropsWeb.Org.SettingsLive do {:noreply, assign(socket, :form, to_form(changeset))} end end + + @impl true + def handle_event("apply_snmp_to_all", _params, socket) do + {count, _} = Organizations.apply_snmp_config_to_all_equipment(socket.assigns.organization.id) + + {:noreply, put_flash(socket, :info, "Applied SNMP configuration to #{count} equipment records across all sites")} + end + + @impl true + def handle_event("apply_agent_to_all", _params, socket) do + {count, _} = Organizations.apply_agent_to_all_equipment(socket.assigns.organization.id) + + {:noreply, put_flash(socket, :info, "Applied default agent to #{count} equipment records across all sites")} + end end diff --git a/lib/towerops_web/live/org/settings_live.html.heex b/lib/towerops_web/live/org/settings_live.html.heex index aebd635b..b9dd0f60 100644 --- a/lib/towerops_web/live/org/settings_live.html.heex +++ b/lib/towerops_web/live/org/settings_live.html.heex @@ -48,6 +48,25 @@ <.icon name="hero-information-circle" class="h-4 w-4 inline" /> SNMP configuration hierarchy: Equipment > Site > Organization
+ + <%= if @organization.snmp_community do %> ++ Force Apply to All Equipment +
++ This will override SNMP settings for ALL equipment across all sites in this organization. +
+ <.button + type="button" + phx-click="apply_snmp_to_all" + data-confirm="This will replace SNMP settings for ALL equipment in this organization. Are you sure?" + variant="danger" + > + <.icon name="hero-arrow-path" class="h-4 w-4" /> Apply SNMP Config to All Equipment + ++ Force Apply to All Equipment +
++ This will assign the default agent to ALL equipment across all sites in this organization. +
+ <.button + type="button" + phx-click="apply_agent_to_all" + data-confirm="This will replace agent assignments for ALL equipment in this organization. Are you sure?" + variant="danger" + > + <.icon name="hero-arrow-path" class="h-4 w-4" /> + Apply Default Agent to All Equipment + +- <.icon name="hero-exclamation-triangle" class="h-4 w-4" /> - Overriding organization default - - all equipment at this site will use this agent +
+ Set a default agent for SNMP polling at this site. This will override the organization default for all equipment at this site. Leave blank to inherit from organization.
- <% else %> - <%= if Map.get(assigns, :org_agent) do %> -- <.icon name="hero-building-office-2" class="h-4 w-4" /> - Currently inheriting organization default: {@org_agent.name} + + <.input + field={@form[:agent_token_id]} + type="select" + label="Default Agent for Site" + prompt="Inherit from organization" + options={Enum.map(@available_agents, &{&1.name, &1.id})} + /> + + <%= if @live_action == :edit and @site.agent_token_id do %> +
+ <.icon name="hero-exclamation-triangle" class="h-4 w-4" /> + Overriding organization default - + all equipment at this site will use this agent
+ ++ Force Apply to Site Equipment +
++ This will assign this agent to ALL equipment at this site. +
+ <.button + type="button" + phx-click="apply_agent_to_all" + data-confirm="This will replace agent assignments for ALL equipment at this site. Are you sure?" + variant="danger" + > + <.icon name="hero-arrow-path" class="h-4 w-4" /> Apply Agent to All Equipment + +- Set a default agent for all equipment at this site. Equipment can override this setting individually. -
+ <%= if Map.get(assigns, :org_agent) do %> ++ <.icon name="hero-building-office-2" class="h-4 w-4" /> + Currently inheriting organization default: {@org_agent.name} +
+ <% else %> ++ Set a default agent for all equipment at this site. Equipment can override this setting individually. +
+ <% end %> <% end %> - <% end %> ++ Force Apply to Site Equipment +
++ This will override SNMP settings for ALL equipment at this site. +
+ <.button + type="button" + phx-click="apply_snmp_to_all" + data-confirm="This will replace SNMP settings for ALL equipment at this site. Are you sure?" + variant="danger" + > + <.icon name="hero-arrow-path" class="h-4 w-4" /> Apply SNMP Config to All Equipment + +