From 1ba55ce451f81da21e5cd5d758db4ddddd9ac967 Mon Sep 17 00:00:00 2001
From: Graham McIntire
Date: Mon, 2 Feb 2026 10:35:44 -0600
Subject: [PATCH] mikrotik api update
---
lib/mix/tasks/populate_english.ex | 62 +++++++++++--------
lib/towerops/devices/device.ex | 31 +++++++---
lib/towerops/organizations.ex | 28 +++++++++
lib/towerops/sites.ex | 25 ++++++++
.../user_registration_html/new.html.heex | 29 +++++++--
.../user_session_html/new.html.heex | 9 ++-
6 files changed, 140 insertions(+), 44 deletions(-)
diff --git a/lib/mix/tasks/populate_english.ex b/lib/mix/tasks/populate_english.ex
index 78d87330..6d748576 100644
--- a/lib/mix/tasks/populate_english.ex
+++ b/lib/mix/tasks/populate_english.ex
@@ -65,38 +65,46 @@ defmodule Mix.Tasks.PopulateEnglish do
defp process_lines([], acc), do: acc
defp process_lines([line | rest], acc) do
- # Found msgid line
if String.starts_with?(line, "msgid ") do
- {msgid_lines, remaining} = collect_string_lines([line | rest], [])
-
- # Check if this is a plural form (msgid_plural follows)
- if is_plural_form?(remaining) do
- # Skip plural forms - they need special handling
- {plural_lines, remaining2} = skip_plural_translation(remaining, [])
- new_acc = Enum.reverse(plural_lines, Enum.reverse(msgid_lines, acc))
- process_lines(remaining2, new_acc)
- else
- {msgstr_lines, remaining2} = collect_msgstr_lines(remaining, [])
-
- msgid_value = extract_string_value(msgid_lines)
-
- # If msgstr is empty and msgid is not empty, populate it
- updated_msgstr =
- if should_populate?(msgid_value, msgstr_lines) do
- populate_msgstr(msgid_lines, msgid_value)
- else
- msgstr_lines
- end
-
- new_acc = Enum.reverse(updated_msgstr, Enum.reverse(msgid_lines, acc))
- process_lines(remaining2, new_acc)
- end
+ process_msgid_line([line | rest], acc)
else
# Regular line (comments, metadata, blank lines)
process_lines(rest, [line | acc])
end
end
+ defp process_msgid_line(lines, acc) do
+ {msgid_lines, remaining} = collect_string_lines(lines, [])
+
+ # Check if this is a plural form (msgid_plural follows)
+ if plural_form?(remaining) do
+ process_plural_form(msgid_lines, remaining, acc)
+ else
+ process_singular_form(msgid_lines, remaining, acc)
+ end
+ end
+
+ defp process_plural_form(msgid_lines, remaining, acc) do
+ {plural_lines, remaining2} = skip_plural_translation(remaining, [])
+ new_acc = Enum.reverse(plural_lines, Enum.reverse(msgid_lines, acc))
+ process_lines(remaining2, new_acc)
+ end
+
+ defp process_singular_form(msgid_lines, remaining, acc) do
+ {msgstr_lines, remaining2} = collect_msgstr_lines(remaining, [])
+ msgid_value = extract_string_value(msgid_lines)
+
+ updated_msgstr =
+ if should_populate?(msgid_value, msgstr_lines) do
+ populate_msgstr(msgid_lines, msgid_value)
+ else
+ msgstr_lines
+ end
+
+ new_acc = Enum.reverse(updated_msgstr, Enum.reverse(msgid_lines, acc))
+ process_lines(remaining2, new_acc)
+ end
+
# Collect lines that are part of a msgid string (including continuation lines)
defp collect_string_lines([], acc), do: {Enum.reverse(acc), []}
@@ -205,9 +213,9 @@ defmodule Mix.Tasks.PopulateEnglish do
end)
end
- defp is_plural_form?([]), do: false
+ defp plural_form?([]), do: false
- defp is_plural_form?([line | _rest]) do
+ defp plural_form?([line | _rest]) do
String.starts_with?(line, "msgid_plural ")
end
diff --git a/lib/towerops/devices/device.ex b/lib/towerops/devices/device.ex
index cb77d8d3..814c2980 100644
--- a/lib/towerops/devices/device.ex
+++ b/lib/towerops/devices/device.ex
@@ -222,19 +222,30 @@ defmodule Towerops.Devices.Device do
defp update_mikrotik_credential_source(changeset) do
# If user explicitly set a username, mark source as "device"
- # If it's blank/nil, mark source as "site" (will inherit from site or org)
- case get_change(changeset, :mikrotik_username) do
- nil ->
- # No change to username, keep existing source
- changeset
+ # If it's blank/nil/cleared, mark source as "site" (will inherit from site or org)
+ username_change = get_change(changeset, :mikrotik_username)
+ current_username = get_field(changeset, :mikrotik_username)
- "" ->
- # Explicitly cleared, set to inherit from site
+ cond do
+ # Explicitly set to empty string - clear and inherit
+ username_change == "" ->
+ changeset
+ |> put_change(:mikrotik_username, nil)
+ |> put_change(:mikrotik_credential_source, "site")
+
+ # Explicitly set to a value - mark as device-specific
+ username_change != nil && username_change != "" ->
+ put_change(changeset, :mikrotik_credential_source, "device")
+
+ # No change to username, but check if we should reset source
+ # (This handles cases where other MikroTik fields are cleared)
+ username_change == nil && (current_username == nil || current_username == "") &&
+ get_field(changeset, :mikrotik_credential_source) == "device" ->
put_change(changeset, :mikrotik_credential_source, "site")
- _value ->
- # Explicitly set a value, mark as device-specific
- put_change(changeset, :mikrotik_credential_source, "device")
+ # No change, keep existing
+ true ->
+ changeset
end
end
end
diff --git a/lib/towerops/organizations.ex b/lib/towerops/organizations.ex
index 391769f9..0fcf4c6a 100644
--- a/lib/towerops/organizations.ex
+++ b/lib/towerops/organizations.ex
@@ -115,6 +115,11 @@ defmodule Towerops.Organizations do
"""
def update_organization(%Organization{} = organization, attrs) do
old_community = organization.snmp_community
+ old_mikrotik_username = organization.mikrotik_username
+ old_mikrotik_password = organization.mikrotik_password
+ old_mikrotik_port = organization.mikrotik_port
+ old_mikrotik_use_ssl = organization.mikrotik_use_ssl
+ old_mikrotik_enabled = organization.mikrotik_enabled
case organization
|> Organization.changeset(attrs)
@@ -128,6 +133,29 @@ defmodule Towerops.Organizations do
)
end
+ # If any MikroTik settings changed, propagate to inheriting devices
+ mikrotik_changed =
+ updated_organization.mikrotik_username != old_mikrotik_username ||
+ updated_organization.mikrotik_password != old_mikrotik_password ||
+ updated_organization.mikrotik_port != old_mikrotik_port ||
+ updated_organization.mikrotik_use_ssl != old_mikrotik_use_ssl ||
+ updated_organization.mikrotik_enabled != old_mikrotik_enabled
+
+ if mikrotik_changed do
+ mikrotik_attrs = %{
+ mikrotik_username: updated_organization.mikrotik_username,
+ mikrotik_password: updated_organization.mikrotik_password,
+ mikrotik_port: updated_organization.mikrotik_port,
+ mikrotik_use_ssl: updated_organization.mikrotik_use_ssl,
+ mikrotik_enabled: updated_organization.mikrotik_enabled
+ }
+
+ Towerops.Devices.propagate_organization_mikrotik_change(
+ updated_organization.id,
+ mikrotik_attrs
+ )
+ end
+
result
error ->
diff --git a/lib/towerops/sites.ex b/lib/towerops/sites.ex
index 980451d0..e2bb7725 100644
--- a/lib/towerops/sites.ex
+++ b/lib/towerops/sites.ex
@@ -107,6 +107,11 @@ defmodule Towerops.Sites do
"""
def update_site(%Site{} = site, attrs) do
old_community = site.snmp_community
+ old_mikrotik_username = site.mikrotik_username
+ old_mikrotik_password = site.mikrotik_password
+ old_mikrotik_port = site.mikrotik_port
+ old_mikrotik_use_ssl = site.mikrotik_use_ssl
+ old_mikrotik_enabled = site.mikrotik_enabled
case site
|> Site.changeset(attrs)
@@ -117,6 +122,26 @@ defmodule Towerops.Sites do
Towerops.Devices.propagate_site_community_change(updated_site.id, updated_site.snmp_community)
end
+ # If any MikroTik settings changed, propagate to inheriting devices
+ mikrotik_changed =
+ updated_site.mikrotik_username != old_mikrotik_username ||
+ updated_site.mikrotik_password != old_mikrotik_password ||
+ updated_site.mikrotik_port != old_mikrotik_port ||
+ updated_site.mikrotik_use_ssl != old_mikrotik_use_ssl ||
+ updated_site.mikrotik_enabled != old_mikrotik_enabled
+
+ if mikrotik_changed do
+ mikrotik_attrs = %{
+ mikrotik_username: updated_site.mikrotik_username,
+ mikrotik_password: updated_site.mikrotik_password,
+ mikrotik_port: updated_site.mikrotik_port,
+ mikrotik_use_ssl: updated_site.mikrotik_use_ssl,
+ mikrotik_enabled: updated_site.mikrotik_enabled
+ }
+
+ Towerops.Devices.propagate_site_mikrotik_change(updated_site.id, mikrotik_attrs)
+ end
+
result
error ->
diff --git a/lib/towerops_web/controllers/user_registration_html/new.html.heex b/lib/towerops_web/controllers/user_registration_html/new.html.heex
index 5c74ece7..cfe3cd02 100644
--- a/lib/towerops_web/controllers/user_registration_html/new.html.heex
+++ b/lib/towerops_web/controllers/user_registration_html/new.html.heex
@@ -22,7 +22,10 @@
<%= if @invitation do %>
- {t_auth("You've been invited to join %{organization} as a %{role}.", organization: @invitation.organization.name, role: @invitation.role)}
+ {t_auth("You've been invited to join %{organization} as a %{role}.",
+ organization: @invitation.organization.name,
+ role: @invitation.role
+ )}
<% else %>
@@ -86,7 +89,12 @@
@@ -104,15 +112,26 @@
- <.button phx-disable-with={t_auth("Creating account...")} class="w-full mt-6" variant="primary">
- {if @invitation, do: t_auth("Accept invitation and create account"), else: t_auth("Create an account")}
+ <.button
+ phx-disable-with={t_auth("Creating account...")}
+ class="w-full mt-6"
+ variant="primary"
+ >
+ {if @invitation,
+ do: t_auth("Accept invitation and create account"),
+ else: t_auth("Create an account")}
diff --git a/lib/towerops_web/controllers/user_session_html/new.html.heex b/lib/towerops_web/controllers/user_session_html/new.html.heex
index c97a6469..46b4581a 100644
--- a/lib/towerops_web/controllers/user_session_html/new.html.heex
+++ b/lib/towerops_web/controllers/user_session_html/new.html.heex
@@ -34,7 +34,12 @@
{t_auth("You are running the local mail adapter.")}