doc update
This commit is contained in:
parent
e181c17f78
commit
5b84844e97
7 changed files with 461 additions and 10 deletions
|
|
@ -940,4 +940,5 @@ When writing new LiveView code or JavaScript hooks:
|
|||
- Compare - memory should not continuously grow
|
||||
- assets are rebuilt automatically on filesystem change
|
||||
- never try to run npm, it's not included in phoenix
|
||||
- assets build themselves on file change, you do not need to ever run mix assets.build
|
||||
- assets build themselves on file change, you do not need to ever run mix assets.build
|
||||
- never write custom one-off tests, if you're going to write a test, write a proper exunit test
|
||||
|
|
@ -14,6 +14,7 @@ defmodule Towerops.Devices.Device do
|
|||
|
||||
alias Ecto.Association.NotLoaded
|
||||
alias Towerops.Agents.AgentAssignment
|
||||
alias Towerops.Encrypted.Binary
|
||||
alias Towerops.Sites.Site
|
||||
alias Towerops.Snmp.Device, as: SnmpDevice
|
||||
|
||||
|
|
@ -40,8 +41,8 @@ defmodule Towerops.Devices.Device do
|
|||
field :last_snmp_poll_at, :utc_datetime
|
||||
|
||||
# MikroTik API credentials (device-level overrides)
|
||||
field :mikrotik_username, :string
|
||||
field :mikrotik_password, Towerops.Encrypted.Binary
|
||||
field :mikrotik_username, Binary
|
||||
field :mikrotik_password, Binary
|
||||
field :mikrotik_port, :integer
|
||||
field :mikrotik_ssh_port, :integer
|
||||
field :mikrotik_use_ssl, :boolean
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ defmodule Towerops.Organizations.Organization do
|
|||
|
||||
alias Ecto.Association.NotLoaded
|
||||
alias Towerops.Agents.AgentToken
|
||||
alias Towerops.Encrypted.Binary
|
||||
alias Towerops.Organizations.Invitation
|
||||
alias Towerops.Organizations.Membership
|
||||
|
||||
|
|
@ -30,8 +31,8 @@ defmodule Towerops.Organizations.Organization do
|
|||
field :snmp_community, :string
|
||||
|
||||
# MikroTik API credentials (organization-level defaults cascade to site → device)
|
||||
field :mikrotik_username, :string
|
||||
field :mikrotik_password, Towerops.Encrypted.Binary
|
||||
field :mikrotik_username, Binary
|
||||
field :mikrotik_password, Binary
|
||||
field :mikrotik_port, :integer, default: 8729
|
||||
field :mikrotik_ssh_port, :integer, default: 22
|
||||
field :mikrotik_use_ssl, :boolean, default: true
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ defmodule Towerops.Sites.Site do
|
|||
alias Ecto.Association.NotLoaded
|
||||
alias Towerops.Agents.AgentToken
|
||||
alias Towerops.Devices.Device
|
||||
alias Towerops.Encrypted.Binary
|
||||
alias Towerops.Organizations.Organization
|
||||
alias Towerops.Sites.Site
|
||||
|
||||
|
|
@ -27,8 +28,8 @@ defmodule Towerops.Sites.Site do
|
|||
field :snmp_community, :string
|
||||
|
||||
# MikroTik API credentials (overrides organization default)
|
||||
field :mikrotik_username, :string
|
||||
field :mikrotik_password, Towerops.Encrypted.Binary
|
||||
field :mikrotik_username, Binary
|
||||
field :mikrotik_password, Binary
|
||||
field :mikrotik_port, :integer
|
||||
field :mikrotik_ssh_port, :integer
|
||||
field :mikrotik_use_ssl, :boolean
|
||||
|
|
|
|||
|
|
@ -36,6 +36,13 @@ defmodule ToweropsWeb.HelpLive.Index do
|
|||
|> assign(:is_authenticated, is_authenticated)
|
||||
|> assign(:current_scope, current_scope)
|
||||
|> assign(:timezone, if(current_user, do: current_user.timezone, else: "UTC"))
|
||||
|> assign(:generated_password, nil)
|
||||
|> assign(:password_generating, false)
|
||||
|
||||
# Generate initial password
|
||||
if connected?(socket) do
|
||||
send(self(), :fetch_random_password)
|
||||
end
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
|
@ -43,7 +50,58 @@ defmodule ToweropsWeb.HelpLive.Index do
|
|||
@impl true
|
||||
def handle_params(params, _url, socket) do
|
||||
section = Map.get(params, "section", "about")
|
||||
{:noreply, assign(socket, active_section: section)}
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:active_section, section)
|
||||
|> assign(:generated_password, nil)
|
||||
|> assign(:password_generating, false)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("generate_password", _params, socket) do
|
||||
socket = assign(socket, :password_generating, true)
|
||||
send(self(), :fetch_random_password)
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(:fetch_random_password, socket) do
|
||||
case generate_random_password() do
|
||||
{:ok, password} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:generated_password, password)
|
||||
|> assign(:password_generating, false)
|
||||
|> put_flash(:info, "Password generated successfully from random.org")}
|
||||
|
||||
{:error, reason} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:password_generating, false)
|
||||
|> put_flash(:error, "Failed to generate password: #{reason}")}
|
||||
end
|
||||
end
|
||||
|
||||
defp generate_random_password do
|
||||
# Generate a 24-character truly random password from random.org
|
||||
# Format: uppercase, lowercase, and digits
|
||||
url =
|
||||
"https://www.random.org/strings/?num=1&len=24&digits=on&upperalpha=on&loweralpha=on&unique=on&format=plain&rnd=new"
|
||||
|
||||
case Req.get(url) do
|
||||
{:ok, %{status: 200, body: body}} ->
|
||||
password = String.trim(body)
|
||||
{:ok, password}
|
||||
|
||||
{:ok, %{status: status}} ->
|
||||
{:error, "random.org returned status #{status}"}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, Exception.message(error)}
|
||||
end
|
||||
end
|
||||
|
||||
defp get_current_user(socket, session) do
|
||||
|
|
@ -69,6 +127,8 @@ defmodule ToweropsWeb.HelpLive.Index do
|
|||
end
|
||||
|
||||
attr :active_section, :string, required: true
|
||||
attr :generated_password, :string, default: nil
|
||||
attr :password_generating, :boolean, default: false
|
||||
|
||||
defp help_content(assigns) do
|
||||
~H"""
|
||||
|
|
@ -148,6 +208,21 @@ defmodule ToweropsWeb.HelpLive.Index do
|
|||
Remote Pollers
|
||||
</.link>
|
||||
</li>
|
||||
<li>
|
||||
<.link
|
||||
patch={~p"/help?section=mikrotik"}
|
||||
class={[
|
||||
"block px-3 py-2 rounded-md text-sm font-medium transition-colors",
|
||||
if @active_section == "mikrotik" do
|
||||
"bg-blue-50 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300"
|
||||
else
|
||||
"text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
end
|
||||
]}
|
||||
>
|
||||
MikroTik
|
||||
</.link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
|
@ -626,6 +701,338 @@ defmodule ToweropsWeb.HelpLive.Index do
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% "mikrotik" -> %>
|
||||
<div class="p-6">
|
||||
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
MikroTik Configuration
|
||||
</h2>
|
||||
|
||||
<div class="prose prose-sm dark:prose-invert max-w-none">
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
Towerops supports read-only monitoring of MikroTik RouterOS devices via SSH.
|
||||
In addition to SNMP polling, SSH access allows Towerops to retrieve detailed system information
|
||||
and create configuration backups of your MikroTik devices for disaster recovery and audit purposes.
|
||||
</p>
|
||||
|
||||
<div class="mt-4 space-y-4">
|
||||
<div class="p-4 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
|
||||
<div class="flex gap-3">
|
||||
<.icon
|
||||
name="hero-information-circle"
|
||||
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5"
|
||||
/>
|
||||
<div>
|
||||
<h4 class="text-sm font-semibold text-blue-900 dark:text-blue-300 mb-1">
|
||||
Read-Only Access
|
||||
</h4>
|
||||
<p class="text-sm text-blue-800 dark:text-blue-300">
|
||||
Towerops currently operates in read-only mode for MikroTik devices. Configuration changes,
|
||||
reboots, and other administrative actions are not supported. This ensures your device
|
||||
configurations remain unchanged by monitoring operations.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
|
||||
<div class="flex gap-3">
|
||||
<.icon
|
||||
name="hero-server"
|
||||
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5"
|
||||
/>
|
||||
<div>
|
||||
<h4 class="text-sm font-semibold text-blue-900 dark:text-blue-300 mb-1">
|
||||
Cloud or Agent-Based Connections
|
||||
</h4>
|
||||
<p class="text-sm text-blue-800 dark:text-blue-300">
|
||||
Both SNMP polling and SSH connections to MikroTik devices can be performed from either
|
||||
Towerops cloud infrastructure or your remote poller (agent). For publicly accessible devices,
|
||||
cloud polling is the simplest option. For devices on private networks or when you prefer
|
||||
to keep SSH credentials on your local network, deploy a remote poller. The device assignment
|
||||
determines which poller handles both SNMP and SSH operations.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-6 mb-3">
|
||||
Security Best Practices
|
||||
</h3>
|
||||
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
For security purposes, it is strongly recommended to create a dedicated read-only user account
|
||||
for Towerops rather than using an administrator account. This follows the principle of least
|
||||
privilege and minimizes potential security risks. Even though Towerops only performs read-only
|
||||
operations, limiting the account permissions provides defense in depth.
|
||||
</p>
|
||||
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-6 mb-3">
|
||||
Creating a Read-Only User
|
||||
</h3>
|
||||
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
Connect to your MikroTik device via SSH or terminal and execute the following commands to
|
||||
create a read-only user named <.code>towerops</.code>:
|
||||
</p>
|
||||
|
||||
<div class="mt-4 space-y-3">
|
||||
<div>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300 mb-2 font-medium">
|
||||
Step 1: Create a new user group with read-only permissions
|
||||
</p>
|
||||
<div class="p-4 bg-black rounded-lg not-prose">
|
||||
<div class="flex items-start gap-2">
|
||||
<code class="text-sm text-white font-mono break-all flex-1">
|
||||
/user group add name=readonly policy=ssh,read,test,api
|
||||
</code>
|
||||
<button
|
||||
type="button"
|
||||
phx-click={JS.dispatch("phx:copy", to: "#step1-command")}
|
||||
class="flex-shrink-0 p-1 text-blue-400 hover:text-blue-300"
|
||||
title="Copy to clipboard"
|
||||
>
|
||||
<.icon name="hero-clipboard-document" class="h-4 w-4" />
|
||||
</button>
|
||||
<input
|
||||
type="hidden"
|
||||
id="step1-command"
|
||||
value="/user group add name=readonly policy=ssh,read,test,api"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300 mb-2 font-medium">
|
||||
Step 2: Create the monitoring user with a strong password
|
||||
</p>
|
||||
<div class="p-4 bg-black rounded-lg not-prose">
|
||||
<div class="flex items-start gap-2">
|
||||
<code class="text-sm text-white font-mono break-all flex-1">
|
||||
/user add name=towerops password={if @generated_password,
|
||||
do: @generated_password,
|
||||
else: "YOUR_STRONG_PASSWORD"} group=readonly
|
||||
</code>
|
||||
<%= if @generated_password do %>
|
||||
<button
|
||||
type="button"
|
||||
phx-click={JS.dispatch("phx:copy", to: "#generated-password-value")}
|
||||
class="flex-shrink-0 p-1 text-blue-400 hover:text-blue-300"
|
||||
title="Copy to clipboard"
|
||||
>
|
||||
<.icon name="hero-clipboard-document" class="h-4 w-4" />
|
||||
</button>
|
||||
<input
|
||||
type="hidden"
|
||||
id="generated-password-value"
|
||||
value={@generated_password}
|
||||
/>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 flex items-start gap-3">
|
||||
<.button
|
||||
type="button"
|
||||
phx-click="generate_password"
|
||||
disabled={@password_generating}
|
||||
>
|
||||
<%= if @password_generating do %>
|
||||
<.icon name="hero-arrow-path" class="h-4 w-4 mr-1 animate-spin" />
|
||||
Generating...
|
||||
<% else %>
|
||||
<.icon name="hero-sparkles" class="h-4 w-4 mr-1" />
|
||||
Regenerate Random Password
|
||||
<% end %>
|
||||
</.button>
|
||||
<%= if @generated_password do %>
|
||||
<p class="text-xs text-yellow-600 dark:text-yellow-400 font-medium mt-1.5">
|
||||
⚠️ This truly random password from random.org will only be shown once!
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300 mb-2 font-medium">
|
||||
Step 3: Verify the user was created successfully
|
||||
</p>
|
||||
<div class="p-4 bg-black rounded-lg not-prose">
|
||||
<div class="flex items-start gap-2">
|
||||
<code class="text-sm text-white font-mono break-all flex-1">
|
||||
/user print detail where name=towerops
|
||||
</code>
|
||||
<button
|
||||
type="button"
|
||||
phx-click={JS.dispatch("phx:copy", to: "#step3-command")}
|
||||
class="flex-shrink-0 p-1 text-blue-400 hover:text-blue-300"
|
||||
title="Copy to clipboard"
|
||||
>
|
||||
<.icon name="hero-clipboard-document" class="h-4 w-4" />
|
||||
</button>
|
||||
<input
|
||||
type="hidden"
|
||||
id="step3-command"
|
||||
value="/user print detail where name=towerops"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-8 mb-3">
|
||||
Permissions Explained
|
||||
</h3>
|
||||
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
The
|
||||
<.code>readonly</.code>
|
||||
group includes the following permissions. Note that while these
|
||||
permissions are granted to the user account, Towerops only performs read operations and does
|
||||
not make any configuration changes:
|
||||
</p>
|
||||
|
||||
<div class="mt-4 bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
|
||||
<thead class="bg-gray-50 dark:bg-gray-800/75">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
Permission
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
Description
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-white/10">
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-900 dark:text-white">
|
||||
ssh
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
Allow SSH access to the device
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-900 dark:text-white">
|
||||
read
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
Allow viewing configuration and status (read-only)
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-900 dark:text-white">
|
||||
test
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
Allow executing diagnostic commands (ping, traceroute, etc.)
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-900 dark:text-white">
|
||||
api
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
Allow API access for automated monitoring
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 p-4 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
|
||||
<div class="flex gap-3">
|
||||
<.icon
|
||||
name="hero-information-circle"
|
||||
class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5"
|
||||
/>
|
||||
<div>
|
||||
<h4 class="text-sm font-semibold text-blue-900 dark:text-blue-300 mb-1">
|
||||
Important Notes
|
||||
</h4>
|
||||
<ul class="text-sm text-blue-800 dark:text-blue-300 space-y-1 list-disc list-inside">
|
||||
<li>The read-only user cannot modify device configuration</li>
|
||||
<li>No write, reboot, or sensitive permissions are granted</li>
|
||||
<li>Use a strong, unique password for the monitoring account</li>
|
||||
<li>Consider restricting SSH access by source IP if possible</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mt-8 mb-3">
|
||||
Configuring in Towerops
|
||||
</h3>
|
||||
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
After creating the read-only user, configure SSH credentials in Towerops:
|
||||
</p>
|
||||
|
||||
<div class="mt-4 space-y-4">
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
|
||||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-2">
|
||||
Organization-Level Configuration
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Navigate to
|
||||
<.code>Settings</.code>
|
||||
→
|
||||
<.code>Organization</.code>
|
||||
to set default
|
||||
SSH credentials for all MikroTik devices in your organization.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
|
||||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-2">
|
||||
Site-Level Configuration
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Navigate to
|
||||
<.code>Sites</.code>
|
||||
→ select a site →
|
||||
<.code>Edit</.code>
|
||||
to override
|
||||
credentials for all devices at a specific location.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-white/10 rounded-lg">
|
||||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-2">
|
||||
Device-Level Configuration
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
When editing a MikroTik device, you can specify unique SSH credentials that override
|
||||
organization and site defaults.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg">
|
||||
<div class="flex gap-3">
|
||||
<.icon
|
||||
name="hero-shield-check"
|
||||
class="h-5 w-5 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-0.5"
|
||||
/>
|
||||
<div>
|
||||
<h4 class="text-sm font-semibold text-yellow-900 dark:text-yellow-300 mb-1">
|
||||
Security Recommendations
|
||||
</h4>
|
||||
<ul class="text-sm text-yellow-800 dark:text-yellow-300 space-y-1 list-disc list-inside">
|
||||
<li>Always use SSL/TLS for SSH connections (API-SSL on port 8729)</li>
|
||||
<li>Store credentials at the organization or site level when possible</li>
|
||||
<li>Rotate passwords periodically following your security policies</li>
|
||||
<li>Monitor access logs for unauthorized SSH connection attempts</li>
|
||||
<li>
|
||||
Consider using SSH keys instead of passwords (if supported by your setup)
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% _ -> %>
|
||||
<div class="p-6">
|
||||
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
|
|
|
|||
|
|
@ -4,12 +4,20 @@
|
|||
current_scope={@current_scope}
|
||||
active_page="help"
|
||||
>
|
||||
<.help_content active_section={@active_section} />
|
||||
<.help_content
|
||||
active_section={@active_section}
|
||||
generated_password={@generated_password}
|
||||
password_generating={@password_generating}
|
||||
/>
|
||||
</Layouts.authenticated>
|
||||
<% else %>
|
||||
<Layouts.app flash={@flash}>
|
||||
<div class="py-8">
|
||||
<.help_content active_section={@active_section} />
|
||||
<.help_content
|
||||
active_section={@active_section}
|
||||
generated_password={@generated_password}
|
||||
password_generating={@password_generating}
|
||||
/>
|
||||
</div>
|
||||
</Layouts.app>
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
defmodule Towerops.Repo.Migrations.EncryptMikrotikUsernames do
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
# Change mikrotik_username from string to binary for encryption
|
||||
# Note: This will clear any existing usernames since we're moving from
|
||||
# plaintext string to encrypted binary. Users will need to re-enter credentials.
|
||||
|
||||
# Organizations
|
||||
execute "ALTER TABLE organizations ALTER COLUMN mikrotik_username TYPE bytea USING NULL"
|
||||
|
||||
# Sites
|
||||
execute "ALTER TABLE sites ALTER COLUMN mikrotik_username TYPE bytea USING NULL"
|
||||
|
||||
# Devices
|
||||
execute "ALTER TABLE devices ALTER COLUMN mikrotik_username TYPE bytea USING NULL"
|
||||
end
|
||||
|
||||
def down do
|
||||
# Revert back to string type
|
||||
# Note: Encrypted data will be cleared when reverting
|
||||
|
||||
# Organizations
|
||||
execute "ALTER TABLE organizations ALTER COLUMN mikrotik_username TYPE varchar USING NULL"
|
||||
|
||||
# Sites
|
||||
execute "ALTER TABLE sites ALTER COLUMN mikrotik_username TYPE varchar USING NULL"
|
||||
|
||||
# Devices
|
||||
execute "ALTER TABLE devices ALTER COLUMN mikrotik_username TYPE varchar USING NULL"
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue