ops_backups 0.1.12 → 0.1.14

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d1cc538be0d6d4d093b408d7479a71bdb5b127d0d7ee4d34638c204ec1bff0d
4
- data.tar.gz: eaf0ee80ac34ad175fce340d8e58c4291facbdfe95fc349b3332b2a3a9144b53
3
+ metadata.gz: 676b08b7df0898e0bc8fbc2572b8aa7801f09c74cba43f5544836d6111a27cfe
4
+ data.tar.gz: 6377a44547eb4a34e97d13ccdf16db48a3739ca2c61ed43c5f8cf371be6fbfe1
5
5
  SHA512:
6
- metadata.gz: 715640d119469926a5ec0babe36f82909c164d00964259be61c1df2912af8b7f23c61f53ad23718e96b17dcf4787fae0b47bced9fd0e0536f1c93e4d686cc717
7
- data.tar.gz: bc0924697d406c27160cada6abcab128ef172c652f10f87f99cd3fbbfc67091eeef44e30e7a53e55a4ef0041c546b1d14d0713026577aea6a46de1cf556ed731
6
+ metadata.gz: 0d03c15b798b0fe5cef8ced86cbb27ff14cf657ceb9052883e88e6e24d132cf28e712797ac87c3f436a2e04da5fc4c1dfc65540877648615c9c9fade12658da8
7
+ data.tar.gz: f3cbf7c12f6f54feaeca3a05fed9c667fbcc559936b36eb2b81408f02bd802c1e6354a9b7574e42c236427de6700acb47965180d57b26809e9262cd5fa38e33f
@@ -1,4 +1,17 @@
1
1
  module OpsBackups
2
2
  class ApplicationController < ActionController::Base
3
+ before_action :set_locale
4
+
5
+ private
6
+
7
+ def set_locale
8
+ # set the locale only if the locale is available
9
+ locale = extract_locale
10
+ I18n.locale = locale if I18n.available_locales.map(&:to_s).include?(locale)
11
+ end
12
+
13
+ def extract_locale
14
+ request.env["HTTP_ACCEPT_LANGUAGE"].split(",")&.first&.split(";")&.first.to_s
15
+ end
3
16
  end
4
17
  end
@@ -1,12 +1,40 @@
1
1
  module OpsBackups
2
2
  class BackupsController < ApplicationController
3
3
  def index
4
- @backups = OpsBackups::Backup.limit(20)
4
+ @backups = OpsBackups::Backup.all.order(updated_at: :desc)
5
+ @jobs = jobs
5
6
  end
6
7
 
7
8
  def download
8
9
  backup = OpsBackups::Backup.find(params[:id])
9
10
  redirect_to backup.backup_file.url(disposition: :attachment), allow_other_host: true
10
11
  end
12
+
13
+ def destroy
14
+ backup = OpsBackups::Backup.find(params[:id])
15
+ backup.destroy
16
+ redirect_to backups_url, notice: "#{backup} destroyed."
17
+ end
18
+
19
+ def trigger_job
20
+ job_name = params[:job_name]
21
+ job_config = jobs[job_name]
22
+
23
+ job_class = job_config["class"].constantize
24
+ args = job_config["args"] || []
25
+
26
+ Rails.logger.info "Triggering job: #{job_name} with args: #{args.reduce({}, :merge).symbolize_keys}"
27
+
28
+ job_class.perform_later(args.reduce({}, :merge).symbolize_keys)
29
+
30
+ redirect_to backups_url, notice: "#{job_name} triggered successfully."
31
+ end
32
+
33
+ private
34
+
35
+ def jobs
36
+ jobs = YAML.load_file(Rails.root.join("config", "recurring.yml"))[Rails.env]
37
+ jobs&.select { |k, v| v["class"].start_with?("OpsBackups") }
38
+ end
11
39
  end
12
40
  end
@@ -12,6 +12,7 @@ class OpsBackups::BackupDbJob < ApplicationJob
12
12
  exclude_tables = options[:exclude_tables] || []
13
13
  tag = options[:tag] || (exclude_tables.empty? ? "db_pg_full" : "db_pg_partial")
14
14
  cleanup = options[:cleanup]
15
+ Rails.logger.info "Performing backup with tag: #{tag} and exclude_tables: #{exclude_tables}"
15
16
  OpsBackups::Backup.new.db_pg_backup(exclude_tables:, tag:)
16
17
  OpsBackups::Backup.send("#{cleanup}_cleanup_policy", tag: tag) if cleanup.present?
17
18
  end
@@ -8,10 +8,6 @@ module OpsBackups
8
8
 
9
9
  default_scope { order(updated_at: :desc) }
10
10
 
11
- def self.ransackable_attributes(auth_object = nil)
12
- [ "created_at", "id", "name", "new_id", "tag", "updated_at" ]
13
- end
14
-
15
11
  def size
16
12
  backup_file.attached? ? number_to_human_size(backup_file.byte_size) : "N/A"
17
13
  end
@@ -21,6 +17,11 @@ module OpsBackups
21
17
  end
22
18
 
23
19
  def db_pg_backup(tag: nil, exclude_tables: [])
20
+ if ENV["DATABASE_URL"].blank?
21
+ Rails.logger.error("Failed to backup database: DATABASE_URL is not set")
22
+ return
23
+ end
24
+
24
25
  db_url = ENV["DATABASE_URL"]
25
26
  tag ||= exclude_tables.empty? ? "db_pg_full" : "db_pg_partial" # if tag.empty?
26
27
  self.tag = tag
@@ -1,46 +1,119 @@
1
1
  <style>
2
+ @keyframes fadeOut {
3
+ to {
4
+ opacity: 0;
5
+ }
6
+ }
7
+ /* all elements have a border */
8
+ ops-backups * {
9
+ /* border: 1px solid red; */
10
+ }
2
11
  ops-backups {
3
12
  display: flex;
4
- padding: 1em;
5
- /* flex column */
13
+ padding: 0.5em;
6
14
  flex-direction: column;
15
+ & h1 {
16
+ margin: 0;
17
+ margin-bottom: 0.25em;
18
+ }
7
19
  & ops-backup {
8
20
  display: grid;
9
- grid-template-columns: 1fr 1fr 10em 10em 18em 3em;
21
+ grid-template-columns: 3em 3fr 2fr 6em 6em 12em 6em;
22
+ gap: 0.5em;
10
23
  padding: 0.25em;
11
- & ops-backup-size,
24
+ & ops-backup-index,
25
+ ops-backup-size,
12
26
  ops-backup-duration,
13
27
  ops-backup-created_at,
14
- ops-backup-download {
28
+ ops-backup-actions {
15
29
  text-align: right;
16
30
  }
31
+ & ops-backup-actions {
32
+ display: flex;
33
+ flex-direction: row;
34
+ justify-content: flex-end;
35
+ gap: 0.5em;
36
+ }
17
37
  }
18
38
  & ops-backup.header {
19
39
  font-weight: bold;
40
+ border-bottom: 1px solid black;
41
+ margin-bottom: 0.25em;
42
+ }
43
+ & ops-backup-jobs {
44
+ display: flex;
45
+ gap: 0.5em;
46
+ margin-bottom: 0.5em;
47
+ /* padding: 0.5em; */
48
+ }
49
+ }
50
+ .alerts {
51
+ /* position these alerts at the bottom right */
52
+ position: fixed;
53
+ bottom: 1em;
54
+ right: 1em;
55
+ animation: fadeOut 3s forwards 3s;
56
+ display: flex;
57
+ flex-direction: column;
58
+ align-items: flex-end;
59
+ gap: 0.5em;
60
+ & .alert {
61
+ padding: 0.5em;
62
+ border-radius: 0.25em;
63
+ }
64
+ & .alert-notice {
65
+ background-color: lightgreen;
66
+ }
67
+ & .alert-danger {
68
+ background-color: lightcoral;
20
69
  }
21
70
  }
22
71
  </style>
23
- <h1>Backups</h1>
24
72
  <ops-backups>
73
+ <h1>Backups</h1>
74
+ <% if @jobs.present? %>
75
+ <ops-backup-jobs>
76
+ <% @jobs.each do |name, config| %>
77
+ <%= button_to name, trigger_job_path(job_name: name), method: :post %>
78
+ <% end %>
79
+ </ops-backup-jobs>
80
+ <% end %>
25
81
  <ops-backup class="header">
82
+ <ops-backup-index>Index</ops-backup-index>
26
83
  <ops-backup-name>Name</ops-backup-name>
27
84
  <ops-backup-tag>Tag</ops-backup-tag>
28
85
  <ops-backup-size>Size</ops-backup-size>
29
86
  <ops-backup-duration>Duration</ops-backup-duration>
30
87
  <ops-backup-created_at>Created At</ops-backup-created_at>
88
+ <ops-backup-actions>Actions</ops-backup-actions>
31
89
  </ops-backup>
32
- <% @backups.each do |backup| %>
33
- <ops-backup>
90
+ <% @backups.each.with_index do |backup, i| %>
91
+ <ops-backup id="ops-backup-<%= i %>">
92
+ <ops-backup-index><%= i %></ops-backup-index>
34
93
  <ops-backup-name><%= backup.name %></ops-backup-name>
35
- <ops-backup-tag><%= backup.name %></ops-backup-tag>
94
+ <ops-backup-tag><%= backup.tag %></ops-backup-tag>
36
95
  <ops-backup-size><%= backup.size %></ops-backup-size>
37
96
  <ops-backup-duration><%= backup.duration %></ops-backup-duration>
38
- <ops-backup-created_at><%= backup.created_at %></ops-backup-created_at>
39
- <ops-backup-download>
97
+ <ops-backup-created_at data-time="<%= backup.created_at.iso8601 %>"><%= l backup.created_at, format: :short %></ops-backup-created_at>
98
+ <ops-backup-actions>
40
99
  <% if backup.backup_file.attached? %>
41
- <a href="<%= download_backup_path(backup) %>" download>📎</a>
100
+ <%= button_to "📎", download_backup_path(backup), method: :get %>
42
101
  <% end %>
43
- </ops-backup-download>
102
+ <%= button_to "🗑️", backup_path(backup), method: :delete %>
103
+ </ops-backup-actions>
44
104
  </ops-backup>
45
105
  <% end %>
46
106
  </ops-backups>
107
+ <div class="alerts">
108
+ <% flash.each do |key, value| %>
109
+ <div class="alert alert-<%= key %>"><%= value %></div>
110
+ <% end %>
111
+ </div>
112
+ <script>
113
+ // document.addEventListener("DOMContentLoaded", function() {
114
+ // document.querySelectorAll("ops-backup-created_at").forEach(function(element) {
115
+ // const time = new Date(element.getAttribute("data-time"));
116
+ // element.textContent = time.toLocaleString();
117
+ // });
118
+ // });
119
+ </script>
data/config/routes.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  OpsBackups::Engine.routes.draw do
2
- get 'backups', to: 'backups#index'
3
- get 'backups/:id/download', to: 'backups#download', as: 'download_backup'
2
+ get "backups", to: "backups#index"
3
+ get "backups/:id/download", to: "backups#download", as: "download_backup"
4
+ delete "backups/:id", to: "backups#destroy", as: "backup"
5
+ post "backups/trigger_job", to: "backups#trigger_job", as: "trigger_job"
4
6
  end
@@ -1,3 +1,13 @@
1
+ # monkey patch the Backup model to allow for ransackable_attributes
2
+ module OpsBackups
3
+ class Backup < ActiveRecord::Base
4
+ def self.ransackable_attributes(auth_object = nil)
5
+ [ "created_at", "id", "name", "new_id", "tag", "updated_at" ]
6
+ end
7
+ end
8
+ end
9
+
10
+ # ActiveAdmin resource for OpsBackups::Backup
1
11
  ActiveAdmin.register OpsBackups::Backup do
2
12
  menu parent: "Ops", label: I18n.t("admin.ops.backup")
3
13
 
@@ -39,7 +39,7 @@ class OpsBackups::InstallGenerator < Rails::Generators::Base
39
39
  recurring_config = <<~YAML.lines.map { |line| " #{line}" }.join
40
40
  backup_db:
41
41
  class: OpsBackups::BackupDbJob
42
- args: [tag: "db_pg_backup", cleanup: "retain_tiered_cleanup_policy"]
42
+ args: [tag: "db_pg_backup", cleanup: "retain_tiered"]
43
43
  schedule: every hour
44
44
  YAML
45
45
 
@@ -1,3 +1,3 @@
1
1
  module OpsBackups
2
- VERSION = "0.1.12"
2
+ VERSION = "0.1.14"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ops_backups
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.12
4
+ version: 0.1.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koen Handekyn
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-12-03 00:00:00.000000000 Z
11
+ date: 2024-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails