129 lines
4.3 KiB
Elixir
129 lines
4.3 KiB
Elixir
defmodule ToweropsWeb.MikrotikBackupLive.Compare do
|
|
@moduledoc false
|
|
use ToweropsWeb, :live_view
|
|
|
|
alias Towerops.Devices
|
|
alias Towerops.Devices.MikrotikBackups
|
|
alias Towerops.Devices.MikrotikBackups.Differ
|
|
alias Towerops.Repo
|
|
|
|
@impl true
|
|
def mount(_params, _session, socket) do
|
|
{:ok, socket}
|
|
end
|
|
|
|
@impl true
|
|
def handle_params(%{"device_id" => device_id, "backup_a" => backup_a_id, "backup_b" => backup_b_id}, _uri, socket) do
|
|
organization = socket.assigns.current_scope.organization
|
|
|
|
# Verify device access
|
|
case Devices.get_device(device_id) do
|
|
nil ->
|
|
{:noreply,
|
|
socket
|
|
|> put_flash(:error, t("Device not found"))
|
|
|> push_navigate(to: ~p"/devices")}
|
|
|
|
device ->
|
|
device_with_site = Repo.preload(device, site: :organization)
|
|
|
|
if device_with_site.site.organization_id == organization.id do
|
|
load_and_compare_backups(socket, device, device_id, backup_a_id, backup_b_id)
|
|
else
|
|
{:noreply,
|
|
socket
|
|
|> put_flash(:error, t("You don't have access to this device"))
|
|
|> push_navigate(to: ~p"/devices")}
|
|
end
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_params(_params, _uri, socket) do
|
|
{:noreply,
|
|
socket
|
|
|> put_flash(:error, t("Missing required parameters"))
|
|
|> push_navigate(to: ~p"/devices")}
|
|
end
|
|
|
|
defp load_and_compare_backups(socket, device, device_id, backup_a_id, backup_b_id) do
|
|
# Load backups
|
|
backup_a = MikrotikBackups.get_backup!(backup_a_id)
|
|
backup_b = MikrotikBackups.get_backup!(backup_b_id)
|
|
|
|
# Verify backups belong to this device
|
|
if backup_a.device_id != device_id || backup_b.device_id != device_id do
|
|
{:noreply,
|
|
socket
|
|
|> put_flash(:error, t("Backups do not belong to this device"))
|
|
|> push_navigate(to: ~p"/devices/#{device_id}?tab=backups")}
|
|
else
|
|
generate_and_display_diff(socket, device, backup_a, backup_b, device_id)
|
|
end
|
|
end
|
|
|
|
defp generate_and_display_diff(socket, device, backup_a, backup_b, device_id) do
|
|
# Decompress configs
|
|
config_a = MikrotikBackups.decompress_config(backup_a.config_compressed)
|
|
config_b = MikrotikBackups.decompress_config(backup_b.config_compressed)
|
|
|
|
# Mask sensitive data
|
|
config_a_masked = Differ.mask_sensitive_data(config_a)
|
|
config_b_masked = Differ.mask_sensitive_data(config_b)
|
|
|
|
# Generate diff
|
|
case Differ.generate_unified_diff(config_a_masked, config_b_masked) do
|
|
{:ok, diff_output} ->
|
|
stats = Differ.calculate_diff_stats(diff_output)
|
|
|
|
{:noreply,
|
|
socket
|
|
|> assign(:page_title, "Compare Backups - #{device.name}")
|
|
|> assign(:device, device)
|
|
|> assign(:backup_a, backup_a)
|
|
|> assign(:backup_b, backup_b)
|
|
|> assign(:diff_output, diff_output)
|
|
|> assign(:stats, stats)}
|
|
|
|
{:error, reason} ->
|
|
{:noreply,
|
|
socket
|
|
|> put_flash(:error, t("Failed to generate diff: %{reason}", reason: reason))
|
|
|> push_navigate(to: ~p"/devices/#{device_id}?tab=backups")}
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("download_config", %{"backup" => "a"}, socket) do
|
|
backup = socket.assigns.backup_a
|
|
config_text = MikrotikBackups.decompress_config(backup.config_compressed)
|
|
device = socket.assigns.device
|
|
|
|
filename = "#{device.name}_backup_#{Calendar.strftime(backup.backed_up_at, "%Y%m%d_%H%M%S")}.rsc"
|
|
|
|
{:noreply, push_event(socket, "download", %{content: config_text, filename: filename, mime_type: "text/plain"})}
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("download_config", %{"backup" => "b"}, socket) do
|
|
backup = socket.assigns.backup_b
|
|
config_text = MikrotikBackups.decompress_config(backup.config_compressed)
|
|
device = socket.assigns.device
|
|
|
|
filename = "#{device.name}_backup_#{Calendar.strftime(backup.backed_up_at, "%Y%m%d_%H%M%S")}.rsc"
|
|
|
|
{:noreply, push_event(socket, "download", %{content: config_text, filename: filename, mime_type: "text/plain"})}
|
|
end
|
|
|
|
# Helper function to format byte sizes
|
|
defp format_bytes(bytes) when is_integer(bytes) and bytes >= 0 do
|
|
cond do
|
|
bytes >= 1_073_741_824 -> "#{Float.round(bytes / 1_073_741_824, 1)} GB"
|
|
bytes >= 1_048_576 -> "#{Float.round(bytes / 1_048_576, 1)} MB"
|
|
bytes >= 1_024 -> "#{Float.round(bytes / 1_024, 1)} KB"
|
|
true -> "#{bytes} B"
|
|
end
|
|
end
|
|
|
|
defp format_bytes(_), do: "Invalid byte count"
|
|
end
|