+ <.header>
+ Backup
+
+
+ <%!-- Database status --%>
+
+
+
+ {Backup.format_size(@stats.total_size)} total ·
+ {length(@stats.tables)} tables ·
+ {@stats.key_counts["products"] || 0} products ·
+ {@stats.key_counts["orders"] || 0} orders ·
+ {@stats.key_counts["images"] || 0} images
+
+
+
+
+
+ <%= if @show_tables do %>
+
+
+
+
+ | Table |
+ Rows |
+ Size |
+
+
+
+
+ | {table.name} |
+ {table.rows} |
+ {Backup.format_size(table.size)} |
+
+
+
+
+ <% end %>
+
+
+ <%!-- Create backup --%>
+
+
+
+ Creates an encrypted snapshot of your database. Backups are stored locally and the last 5 are kept automatically.
+
+
+
+
+
+ <.inline_feedback status={@create_backup_status} />
+
+
+
+
+ <%!-- Backup history --%>
+ <%= if @backups != [] do %>
+
+ Saved backups
+
+ <%= if @restoring do %>
+
+ <.icon name="hero-arrow-path" class="size-5 animate-spin" />
+
+
Restoring database...
+
This may take a few seconds.
+
+
+ <% else %>
+
+ <%= for backup <- @backups do %>
+
+
+ {format_backup_date(backup.created_at)}
+
+ {Backup.format_size(backup.size)}
+ <%= if backup.type == :pre_restore do %>
+ · auto-saved before restore
+ <% end %>
+
+
+
+
+ <%= if @confirming_history_restore == backup.filename do %>
+ Replace current database?
+
+
+ <% else %>
+ <%= if @confirming_delete == backup.filename do %>
+ Delete this backup?
+
+
+ <% else %>
+
+
+
+ <% end %>
+ <% end %>
+
+
+ <% end %>
+
+ <% end %>
+
+ <% end %>
+
+ <%!-- Restore from file --%>
+
+ Restore from file
+
+ Upload a backup file to restore. Must be encrypted with the same key as this database.
+
+
+ <%= if @upload_error do %>
+ {@upload_error}
+ <% end %>
+
+ <%= if @uploaded_backup do %>
+
+
+
+
Current
+
+ - Size
- {Backup.format_size(@stats.total_size)}
+ - Products
- {@stats.key_counts["products"] || 0}
+ - Orders
- {@stats.key_counts["orders"] || 0}
+ - Images
- {@stats.key_counts["images"] || 0}
+
+
+
+ <.icon name="hero-arrow-right" class="size-5" />
+
+
+
Uploaded
+
+ - Size
- {Backup.format_size(@uploaded_backup.stats.file_size)}
+ - Products
- {@uploaded_backup.stats.key_counts["products"] || 0}
+ - Orders
- {@uploaded_backup.stats.key_counts["orders"] || 0}
+ - Images
- {@uploaded_backup.stats.key_counts["images"] || 0}
+
+
+
+
+ <%= if @uploaded_backup.stats.latest_migration == @stats.schema_version do %>
+
+ <.icon name="hero-check-circle-mini" class="size-4" />
+ Backup validated · Schema version {@uploaded_backup.stats.latest_migration}
+
+
+ <%= if @restoring do %>
+
+ <.icon name="hero-arrow-path" class="size-5 animate-spin" />
+
+
Restoring database...
+
This may take a few seconds.
+
+
+ <% else %>
+ <%= if @confirming_restore do %>
+
+
This will replace your current database. A backup will be saved automatically.
+
+
+
+
+
+ <% else %>
+
+
+
+
+ <% end %>
+ <% end %>
+ <% else %>
+
+ <.icon name="hero-x-circle-mini" class="size-4" />
+
+ Schema mismatch: backup is v{@uploaded_backup.stats.latest_migration},
+ current is v{@stats.schema_version}
+
+
+
+
+
+ <% end %>
+
+ <% else %>
+
+ <% end %>
+
+
+ """
+ end
+
+ defp format_backup_date(nil), do: "unknown date"
+
+ defp format_backup_date(datetime) do
+ Calendar.strftime(datetime, "%d %b %Y, %H:%M")
+ end
+
+ defp upload_error_to_string(:too_large), do: "File is too large (max 500 MB)"
+ defp upload_error_to_string(:too_many_files), do: "Only one file allowed"
+ defp upload_error_to_string(err), do: "Upload error: #{inspect(err)}"
+
+ attr :color, :string, default: "zinc"
+ slot :inner_block, required: true
+
+ defp status_pill(assigns) do
+ modifier =
+ case assigns.color do
+ "green" -> "admin-status-pill-green"
+ "amber" -> "admin-status-pill-amber"
+ _ -> "admin-status-pill-zinc"
+ end
+
+ assigns = assign(assigns, :modifier, modifier)
+
+ ~H"""
+