defmodule ToweropsWeb.DeviceLive.Components.BackupsTab do @moduledoc """ LiveComponent for the device MikroTik backups tab. Displays MikroTik configuration backups with download, compare, and backup-now functionality. """ use ToweropsWeb, :live_component alias ToweropsWeb.DeviceLive.Helpers.DataLoaders alias ToweropsWeb.DeviceLive.Helpers.Formatters @impl true def update(assigns, socket) do {:ok, socket |> assign(assigns) |> load_backups_data()} end defp load_backups_data(socket) do device = socket.assigns.device mikrotik_backups = DataLoaders.load_mikrotik_backups(device) socket |> assign(:mikrotik_backups, mikrotik_backups) |> assign(:selected_backup_ids, MapSet.new()) end @impl true def render(assigns) do ~H"""
<%= if false and @device.mikrotik_enabled do %>

{t("Configuration Backups")} <%= if Enum.any?(@mikrotik_backups) do %> ({length(@mikrotik_backups)}) <% end %>

<.icon name="hero-beaker" class="h-3 w-3" /> Experimental
<%= if @agent_info.agent_token_id do %> <% else %>
{t("Assign an agent to enable backups")}
<% end %>
<%= if Enum.any?(@mikrotik_backups) do %> <%!-- Instructions for comparing backups --%> <%= if length(@mikrotik_backups) > 1 do %>
<.icon name="hero-information-circle" class="h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
Compare configurations: {t("Select any 2 backups using the checkboxes to compare their differences")}
<% end %>
<%= for backup <- @mikrotik_backups do %> <% compression_ratio = if backup.config_size_bytes && backup.config_size_bytes > 0 do Float.round( (1 - backup.compressed_size_bytes / backup.config_size_bytes) * 100, 1 ) else 0.0 end %> <% end %>
<.icon name="hero-check-circle" class="h-4 w-4 mx-auto" /> Date Original Size Compressed Size Ratio Source Actions
= 2 } title={ if MapSet.member?(@selected_backup_ids, backup.id), do: "Selected for comparison", else: if(MapSet.size(@selected_backup_ids) >= 2, do: "Maximum 2 backups can be selected", else: "Select to compare" ) } class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-600 disabled:opacity-50 disabled:cursor-not-allowed dark:border-gray-600 dark:bg-gray-700" /> {ToweropsWeb.TimeHelpers.format_iso8601(backup.backed_up_at, @timezone)} {format_bytes(backup.config_size_bytes)} {format_bytes(backup.compressed_size_bytes)} {compression_ratio}% {String.replace(backup.trigger_source, "_", " ") |> String.capitalize()}
<%= if ToweropsWeb.Permissions.owner?(@current_scope) do %> <.button phx-click="delete_backup" phx-value-id={backup.id} data-confirm={ t( "Are you sure you want to delete this backup? This action cannot be undone." ) } variant="danger" > <.icon name="hero-trash" class="h-4 w-4" /> Delete <% end %>
<%!-- Pagination --%> <%= if assigns[:pagination] do %>
<.pagination meta={@pagination} path={~p"/devices/#{@device.id}"} params={%{"tab" => "backups"}} />
<% end %> <%!-- Floating Compare Button --%> <%= if MapSet.size(@selected_backup_ids) > 0 do %>
<%!-- Selection status --%>
<.icon name="hero-check-circle" class="h-5 w-5 text-blue-600 dark:text-blue-400" />
{MapSet.size(@selected_backup_ids)} of 2 backups selected
<%!-- Instructions when only 1 selected --%> <%= if MapSet.size(@selected_backup_ids) == 1 do %>
{t("Select one more backup to compare")}
<% end %> <%!-- Action buttons --%>
<% end %> <% else %>
<.icon name="hero-document-text" class="mx-auto h-12 w-12 text-gray-400" />

{t("No backups yet")}

<%= if @agent_info.agent_token_id do %>

Automatic backups run daily at {ToweropsWeb.TimeHelpers.format_utc_hour( 7, @timezone )}.

<% else %>

{t("Assign an agent to enable automatic backups.")}

<% end %>
<% end %>
<% else %>
<.icon name="hero-server" class="mx-auto h-12 w-12 text-gray-400" />

{t("MikroTik API not enabled")}

{t("Enable MikroTik API in device settings to enable configuration backups.")}

<.button navigate={~p"/devices/#{@device.id}/edit"}> {t("Edit Device Settings")}
<% end %>
""" end @impl true def handle_event("toggle_backup_selection", %{"id" => backup_id}, socket) do selected = socket.assigns.selected_backup_ids updated_selected = if MapSet.member?(selected, backup_id) do MapSet.delete(selected, backup_id) else # Limit to 2 selections max if MapSet.size(selected) >= 2 do selected else MapSet.put(selected, backup_id) end end {:noreply, assign(socket, :selected_backup_ids, updated_selected)} end @impl true def handle_event("clear_selection", _params, socket) do {:noreply, assign(socket, :selected_backup_ids, MapSet.new())} end # Helper functions defp format_bytes(bytes), do: Formatters.format_bytes(bytes) end