rails_nexus 2.0.2 → 2.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/javascripts/rails_nexus/controllers.js +21 -1
- data/app/assets/stylesheets/rails_nexus/application.css +295 -29
- data/app/controllers/rails_nexus/backup_controller.rb +6 -2
- data/app/javascript/controllers/rails_nexus_controller.js +2 -0
- data/app/javascript/controllers/rails_nexus_sidebar_controller.js +13 -0
- data/app/models/rails_nexus/backup_config.rb +77 -14
- data/app/services/rails_nexus/backup_service.rb +416 -97
- data/app/views/layouts/rails_nexus/_sidebar_navigation.html.erb +83 -0
- data/app/views/layouts/rails_nexus/application.html.erb +70 -22
- data/app/views/rails_nexus/backup/_form.html.erb +183 -37
- data/app/views/rails_nexus/logged_exceptions/_exceptions.html.erb +16 -15
- data/app/views/rails_nexus/logged_exceptions/index.html.erb +3 -3
- data/db/seeds/backup_configs.rb +139 -0
- data/lib/generators/rails_nexus/templates/migration.rb +59 -0
- data/lib/rails_nexus/version.rb +1 -1
- data/lib/tasks/rails_nexus.rake +10 -0
- metadata +3 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<nav class="rn-sidebar-nav" aria-label="RailsNexus navigation">
|
|
2
|
+
<div class="rn-nav-section">
|
|
3
|
+
<span class="rn-nav-section-label">Monitor</span>
|
|
4
|
+
|
|
5
|
+
<%= link_to root_path,
|
|
6
|
+
class: "rn-nav-item #{request.path == rails_nexus.root_path ? 'active' : ''}" do %>
|
|
7
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-3.75v6.75A2.25 2.25 0 0118.75 18H5.25A2.25 2.25 0 013 15.75V9m18 0l-9-6-9 6m18 0l-9 6-9-6" /></svg>
|
|
8
|
+
Exceptions
|
|
9
|
+
<% if exception_count > 0 %>
|
|
10
|
+
<span class="rn-nav-badge"><%= number_with_delimiter(exception_count) %></span>
|
|
11
|
+
<% end %>
|
|
12
|
+
<% end %>
|
|
13
|
+
|
|
14
|
+
<%= link_to database_health_path,
|
|
15
|
+
class: "rn-nav-item #{request.path == rails_nexus.database_health_path ? 'active' : ''}" do %>
|
|
16
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125" /></svg>
|
|
17
|
+
Database Health
|
|
18
|
+
<% end %>
|
|
19
|
+
|
|
20
|
+
<%= link_to n1_patterns_path,
|
|
21
|
+
class: "rn-nav-item #{request.path == rails_nexus.n1_patterns_path ? 'active' : ''}" do %>
|
|
22
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" /></svg>
|
|
23
|
+
N+1 Queries
|
|
24
|
+
<% end %>
|
|
25
|
+
|
|
26
|
+
<%= link_to analytics_path,
|
|
27
|
+
class: "rn-nav-item #{request.path == rails_nexus.analytics_path ? 'active' : ''}" do %>
|
|
28
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 3v11.25A2.25 2.25 0 006 16.5h2.25M3.75 3h-1.5m1.5 0h16.5m0 0h1.5m-1.5 0v11.25A2.25 2.25 0 0118 16.5h-2.25m-7.5 0h7.5m-7.5 0l-1 3m8.5-3l1 3m0 0l.5 1.5m-.5-1.5h-9.5m0 0l-.5 1.5" /></svg>
|
|
29
|
+
Analytics
|
|
30
|
+
<% end %>
|
|
31
|
+
|
|
32
|
+
<%= link_to stats_path,
|
|
33
|
+
class: "rn-nav-item #{request.path == rails_nexus.stats_path ? 'active' : ''}" do %>
|
|
34
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" /></svg>
|
|
35
|
+
Statistics
|
|
36
|
+
<% end %>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="rn-nav-section">
|
|
40
|
+
<span class="rn-nav-section-label">Operations</span>
|
|
41
|
+
|
|
42
|
+
<%= link_to cron_jobs_path,
|
|
43
|
+
class: "rn-nav-item #{request.path.start_with?(rails_nexus.cron_jobs_path) ? 'active' : ''}" do %>
|
|
44
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
|
45
|
+
Cron Jobs
|
|
46
|
+
<% end %>
|
|
47
|
+
|
|
48
|
+
<%= link_to backup_path,
|
|
49
|
+
class: "rn-nav-item #{request.path.start_with?(rails_nexus.backup_path) ? 'active' : ''}" do %>
|
|
50
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125" /></svg>
|
|
51
|
+
Backups
|
|
52
|
+
<% end %>
|
|
53
|
+
|
|
54
|
+
<%= link_to settings_path,
|
|
55
|
+
class: "rn-nav-item #{request.path == rails_nexus.settings_path ? 'active' : ''}" do %>
|
|
56
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
|
|
57
|
+
Settings
|
|
58
|
+
<% end %>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="rn-nav-section">
|
|
62
|
+
<span class="rn-nav-section-label">Resources</span>
|
|
63
|
+
|
|
64
|
+
<% if main_app.respond_to?(:root_path) %>
|
|
65
|
+
<%= link_to main_app.root_path, class: "rn-nav-item rn-nav-item--muted" do %>
|
|
66
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" /></svg>
|
|
67
|
+
Back to App
|
|
68
|
+
<% end %>
|
|
69
|
+
<% end %>
|
|
70
|
+
|
|
71
|
+
<%= link_to feed_logged_exceptions_path(format: :rss), class: "rn-nav-item rn-nav-item--muted" do %>
|
|
72
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12.75 19.5v-.75a7.5 7.5 0 00-7.5-7.5H4.5m0-6.75h.75c7.87 0 14.25 6.38 14.25 14.25v.75M6 18.75a.375.375 0 11-.75 0 .375.375 0 01.75 0z" /></svg>
|
|
73
|
+
RSS Feed
|
|
74
|
+
<% end %>
|
|
75
|
+
|
|
76
|
+
<% RailsNexus.configuration.sidebar_links.each do |link| %>
|
|
77
|
+
<%= link_to link[:url], class: "rn-nav-item", target: link[:target] || "_self" do %>
|
|
78
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" /></svg>
|
|
79
|
+
<%= link[:label] %>
|
|
80
|
+
<% end %>
|
|
81
|
+
<% end %>
|
|
82
|
+
</div>
|
|
83
|
+
</nav>
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
<%= csrf_meta_tags %>
|
|
17
17
|
<%= csp_meta_tag %>
|
|
18
18
|
</head>
|
|
19
|
-
|
|
19
|
+
<% exception_count = RailsNexus::LoggedException.count %>
|
|
20
|
+
<body class="h-full <%= RailsNexus.configuration.body_class %>" data-controller="rails_nexus-sidebar">
|
|
20
21
|
<div class="min-h-full flex">
|
|
21
22
|
|
|
22
23
|
<%# Sidebar %>
|
|
@@ -25,16 +26,23 @@
|
|
|
25
26
|
<div class="rn-sidebar-logo">
|
|
26
27
|
<%= link_to root_path, class: "flex items-center gap-2.5" do %>
|
|
27
28
|
<div class="rn-logo-icon">⚡</div>
|
|
28
|
-
<span class="rn-logo-
|
|
29
|
+
<span class="rn-logo-copy">
|
|
30
|
+
<span class="rn-logo-text"><%= RailsNexus.configuration.application_name || "RailsNexus" %></span>
|
|
31
|
+
<span class="rn-logo-subtitle">Rails operations</span>
|
|
32
|
+
</span>
|
|
29
33
|
<% end %>
|
|
30
34
|
</div>
|
|
31
35
|
|
|
32
36
|
<%# Navigation %>
|
|
33
37
|
<nav class="rn-sidebar-nav">
|
|
38
|
+
<span class="rn-nav-section-label">Monitor</span>
|
|
34
39
|
<%= link_to root_path,
|
|
35
40
|
class: "rn-nav-item #{request.path == rails_nexus.root_path ? 'active' : ''}" do %>
|
|
36
41
|
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z" /></svg>
|
|
37
|
-
|
|
42
|
+
Exceptions
|
|
43
|
+
<% if exception_count > 0 %>
|
|
44
|
+
<span class="rn-nav-badge"><%= number_with_delimiter(exception_count) %></span>
|
|
45
|
+
<% end %>
|
|
38
46
|
<% end %>
|
|
39
47
|
|
|
40
48
|
<%= link_to database_health_path,
|
|
@@ -61,6 +69,8 @@
|
|
|
61
69
|
Statistics
|
|
62
70
|
<% end %>
|
|
63
71
|
|
|
72
|
+
<span class="rn-nav-section-label">Operations</span>
|
|
73
|
+
|
|
64
74
|
<%= link_to cron_jobs_path,
|
|
65
75
|
class: "rn-nav-item #{request.path.start_with?(rails_nexus.cron_jobs_path) ? 'active' : ''}" do %>
|
|
66
76
|
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
|
@@ -79,8 +89,7 @@
|
|
|
79
89
|
Settings
|
|
80
90
|
<% end %>
|
|
81
91
|
|
|
82
|
-
|
|
83
|
-
<div class="rn-sidebar-section-divider"></div>
|
|
92
|
+
<span class="rn-nav-section-label">Resources</span>
|
|
84
93
|
|
|
85
94
|
<% if main_app.respond_to?(:root_path) %>
|
|
86
95
|
<%= link_to main_app.root_path,
|
|
@@ -108,7 +117,8 @@
|
|
|
108
117
|
|
|
109
118
|
<%# Footer %>
|
|
110
119
|
<div class="rn-sidebar-footer">
|
|
111
|
-
|
|
120
|
+
<span class="rn-sidebar-status"><span class="rn-status-dot"></span>Operational</span>
|
|
121
|
+
<span>v<%= RailsNexus::VERSION %></span>
|
|
112
122
|
</div>
|
|
113
123
|
</aside>
|
|
114
124
|
|
|
@@ -116,19 +126,39 @@
|
|
|
116
126
|
<div class="flex-1 flex flex-col min-w-0">
|
|
117
127
|
<%# Top Bar %>
|
|
118
128
|
<header class="rn-topbar">
|
|
119
|
-
<
|
|
129
|
+
<div class="rn-topbar-title">
|
|
130
|
+
<button type="button"
|
|
131
|
+
class="rn-icon-btn rn-mobile-menu-button"
|
|
132
|
+
data-action="click->rails_nexus-sidebar#toggle"
|
|
133
|
+
title="Open navigation"
|
|
134
|
+
aria-label="Open navigation">
|
|
135
|
+
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" /></svg>
|
|
136
|
+
</button>
|
|
137
|
+
<div>
|
|
138
|
+
<span class="rn-topbar-heading"><%= RailsNexus.configuration.application_name || "RailsNexus" %></span>
|
|
139
|
+
<span class="rn-topbar-subtitle">Operations console</span>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
120
142
|
|
|
121
|
-
<div class="
|
|
122
|
-
<% if RailsNexus.configuration.keyboard_shortcuts %>
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
143
|
+
<div class="rn-topbar-actions">
|
|
144
|
+
<% if RailsNexus.configuration.keyboard_shortcuts && controller_path == "rails_nexus/logged_exceptions" && action_name == "index" %>
|
|
145
|
+
<button type="button"
|
|
146
|
+
id="rn-shortcuts-button"
|
|
147
|
+
class="rn-icon-btn"
|
|
148
|
+
data-action="click->rails_nexus-sidebar#toggleShortcuts"
|
|
149
|
+
title="Keyboard shortcuts"
|
|
150
|
+
aria-label="Open keyboard shortcuts"
|
|
151
|
+
aria-haspopup="dialog"
|
|
152
|
+
aria-expanded="false">
|
|
153
|
+
<span class="rn-kbd" aria-hidden="true">?</span>
|
|
154
|
+
</button>
|
|
126
155
|
<% end %>
|
|
127
156
|
|
|
128
157
|
<button type="button"
|
|
129
158
|
class="rn-icon-btn"
|
|
130
159
|
data-action="click->rails_nexus-theme#toggle"
|
|
131
|
-
title="Toggle theme (t)"
|
|
160
|
+
title="Toggle theme (t)"
|
|
161
|
+
aria-label="Toggle color theme">
|
|
132
162
|
<svg class="w-4 h-4 block dark:hidden" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" /></svg>
|
|
133
163
|
<svg class="w-4 h-4 hidden dark:block" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" /></svg>
|
|
134
164
|
</button>
|
|
@@ -163,9 +193,22 @@
|
|
|
163
193
|
<% end %>
|
|
164
194
|
|
|
165
195
|
<%# Main Content %>
|
|
166
|
-
<main class="flex-1 overflow-y-auto p-4 sm:p-6">
|
|
196
|
+
<main class="rn-main flex-1 overflow-y-auto p-4 sm:p-6">
|
|
167
197
|
<%= yield %>
|
|
168
198
|
</main>
|
|
199
|
+
|
|
200
|
+
<footer class="rn-footer">
|
|
201
|
+
<span>RailsNexus <span class="rn-footer-version">v<%= RailsNexus::VERSION %></span></span>
|
|
202
|
+
<span class="rn-footer-credit">
|
|
203
|
+
Built by
|
|
204
|
+
<a href="https://github.com/tamiru" target="_blank" rel="noopener noreferrer">Tamiru Hailu</a>
|
|
205
|
+
<span aria-hidden="true">·</span>
|
|
206
|
+
<a href="https://github.com/tamiru/rails_nexus" target="_blank" rel="noopener noreferrer" class="rn-footer-github">
|
|
207
|
+
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 .7A11.3 11.3 0 00.7 12c0 5 3.2 9.2 7.7 10.7.6.1.8-.3.8-.6v-2.2c-3.1.7-3.8-1.3-3.8-1.3-.5-1.3-1.3-1.7-1.3-1.7-1-.7.1-.7.1-.7 1.1.1 1.7 1.2 1.7 1.2 1 1.7 2.6 1.2 3.3.9.1-.7.4-1.2.7-1.5-2.5-.3-5.1-1.3-5.1-5.6 0-1.2.4-2.3 1.2-3.1-.1-.3-.5-1.5.1-3.1 0 0 1-.3 3.1 1.2A10.8 10.8 0 0112 6.5c1 0 1.9.1 2.8.4 2.2-1.5 3.1-1.2 3.1-1.2.6 1.6.2 2.8.1 3.1.7.8 1.2 1.9 1.2 3.1 0 4.3-2.6 5.3-5.1 5.6.4.4.8 1.1.8 2.1v2.5c0 .4.2.7.8.6A11.3 11.3 0 0023.3 12 11.3 11.3 0 0012 .7z" /></svg>
|
|
208
|
+
GitHub
|
|
209
|
+
</a>
|
|
210
|
+
</span>
|
|
211
|
+
</footer>
|
|
169
212
|
</div>
|
|
170
213
|
</div>
|
|
171
214
|
|
|
@@ -175,17 +218,22 @@
|
|
|
175
218
|
<%# Mobile sidebar drawer %>
|
|
176
219
|
<div class="rn-mobile-sidebar" data-rails_nexus-sidebar-target="drawer">
|
|
177
220
|
<div class="rn-sidebar-logo">
|
|
178
|
-
|
|
179
|
-
|
|
221
|
+
<%= link_to root_path, class: "flex items-center gap-2.5" do %>
|
|
222
|
+
<div class="rn-logo-icon">⚡</div>
|
|
223
|
+
<span class="rn-logo-copy">
|
|
224
|
+
<span class="rn-logo-text"><%= RailsNexus.configuration.application_name || "RailsNexus" %></span>
|
|
225
|
+
<span class="rn-logo-subtitle">Rails operations</span>
|
|
226
|
+
</span>
|
|
227
|
+
<% end %>
|
|
228
|
+
<button type="button" class="rn-icon-btn" data-action="click->rails_nexus-sidebar#close" aria-label="Close navigation">
|
|
180
229
|
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>
|
|
181
230
|
</button>
|
|
182
231
|
</div>
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
</nav>
|
|
232
|
+
<%= render "layouts/rails_nexus/sidebar_navigation", exception_count: exception_count %>
|
|
233
|
+
<div class="rn-sidebar-footer">
|
|
234
|
+
<span class="rn-sidebar-status"><span class="rn-status-dot"></span>Operational</span>
|
|
235
|
+
<span>v<%= RailsNexus::VERSION %></span>
|
|
236
|
+
</div>
|
|
189
237
|
</div>
|
|
190
238
|
|
|
191
239
|
<%= turbo_frame_tag :remote_modal, target: :_top %>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
<%= form_for @config, url: (@config.persisted? ? backup_path(@config) :
|
|
1
|
+
<%= form_for @config, url: (@config.persisted? ? backup_path(@config) : backup_path), class: "space-y-6" do |f| %>
|
|
2
2
|
<% if @config.errors.any? %>
|
|
3
3
|
<div class="rn-card" style="border-left: 3px solid var(--rn-danger)">
|
|
4
4
|
<div class="p-4">
|
|
5
5
|
<p class="text-sm font-semibold" style="color: var(--rn-danger)"><%= @config.errors.count %> error(s) prevented this config from being saved:</p>
|
|
6
6
|
<ul class="mt-2 text-xs" style="color: var(--rn-text-secondary)">
|
|
7
7
|
<% @config.errors.full_messages.each do |msg| %>
|
|
8
|
-
<li
|
|
8
|
+
<li>• <%= msg %></li>
|
|
9
9
|
<% end %>
|
|
10
10
|
</ul>
|
|
11
11
|
</div>
|
|
@@ -27,8 +27,10 @@
|
|
|
27
27
|
<%= f.text_field :description, class: "rn-input", placeholder: "e.g. TTI Daily Backup" %>
|
|
28
28
|
</div>
|
|
29
29
|
<div class="rn-filter-group">
|
|
30
|
-
<label>
|
|
31
|
-
|
|
30
|
+
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
31
|
+
<%= f.check_box :enabled %>
|
|
32
|
+
Enabled
|
|
33
|
+
</label>
|
|
32
34
|
</div>
|
|
33
35
|
</div>
|
|
34
36
|
</div>
|
|
@@ -41,7 +43,13 @@
|
|
|
41
43
|
<div class="p-4 grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
42
44
|
<div class="rn-filter-group">
|
|
43
45
|
<label>Adapter *</label>
|
|
44
|
-
<%= f.select :adapter, options_for_select([
|
|
46
|
+
<%= f.select :adapter, options_for_select([
|
|
47
|
+
["MySQL", "mysql"],
|
|
48
|
+
["PostgreSQL", "postgresql"],
|
|
49
|
+
["SQLite", "sqlite"],
|
|
50
|
+
["MongoDB", "mongodb"],
|
|
51
|
+
["Redis", "redis"]
|
|
52
|
+
], @config.adapter), {}, class: "rn-select" %>
|
|
45
53
|
</div>
|
|
46
54
|
<div class="rn-filter-group">
|
|
47
55
|
<label>Database Name *</label>
|
|
@@ -61,11 +69,11 @@
|
|
|
61
69
|
</div>
|
|
62
70
|
<div class="rn-filter-group">
|
|
63
71
|
<label>Password</label>
|
|
64
|
-
<%= f.password_field :password, class: "rn-input", placeholder: "
|
|
72
|
+
<%= f.password_field :password, class: "rn-input", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" %>
|
|
65
73
|
</div>
|
|
66
74
|
<div class="rn-filter-group md:col-span-3">
|
|
67
75
|
<label>Skip Tables (comma-separated)</label>
|
|
68
|
-
<%=
|
|
76
|
+
<%= f.text_field :skip_tables, value: @config.skip_tables_list.join(", "), class: "rn-input", placeholder: "e.g. sessions, versions, internal_logs" %>
|
|
69
77
|
</div>
|
|
70
78
|
</div>
|
|
71
79
|
</div>
|
|
@@ -87,56 +95,156 @@
|
|
|
87
95
|
</div>
|
|
88
96
|
</div>
|
|
89
97
|
|
|
90
|
-
<%# ───
|
|
98
|
+
<%# ─── Archives ────────────────────────────────────────────── %>
|
|
91
99
|
<div class="rn-card">
|
|
92
100
|
<div class="p-4" style="border-bottom: 1px solid var(--rn-border-light)">
|
|
93
|
-
<h2 class="text-sm font-semibold" style="color: var(--rn-text)">
|
|
101
|
+
<h2 class="text-sm font-semibold" style="color: var(--rn-text)">Archives (File/Directory Backup)</h2>
|
|
94
102
|
</div>
|
|
95
103
|
<div class="p-4 grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
96
104
|
<div class="rn-filter-group">
|
|
97
|
-
<label
|
|
98
|
-
|
|
105
|
+
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
106
|
+
<%= f.check_box :archive_enabled %>
|
|
107
|
+
Enable Archives
|
|
108
|
+
</label>
|
|
109
|
+
<p class="text-xs mt-1" style="color: var(--rn-text-muted)">Include additional files/directories in the backup tar</p>
|
|
99
110
|
</div>
|
|
100
|
-
<div class="rn-filter-group">
|
|
101
|
-
<label>
|
|
102
|
-
<%= f.
|
|
111
|
+
<div class="rn-filter-group md:col-span-2" id="archive-paths-group">
|
|
112
|
+
<label>Archive Paths (comma-separated)</label>
|
|
113
|
+
<%= f.text_field :archive_paths, value: @config.archive_paths_list.join(", "), class: "rn-input", placeholder: "e.g. /var/log, /etc/nginx, /home/app/uploads" %>
|
|
103
114
|
</div>
|
|
104
|
-
<div class="rn-filter-group
|
|
105
|
-
<label>
|
|
106
|
-
<%= f.
|
|
115
|
+
<div class="rn-filter-group md:col-span-2" id="archive-excludes-group">
|
|
116
|
+
<label>Exclude Patterns</label>
|
|
117
|
+
<%= f.text_field :archive_excludes, value: @config.archive_excludes_list.join(", "), class: "rn-input", placeholder: "e.g. *.log, tmp/*, node_modules" %>
|
|
107
118
|
</div>
|
|
108
119
|
</div>
|
|
109
120
|
</div>
|
|
110
121
|
|
|
111
|
-
<%# ───
|
|
122
|
+
<%# ─── Compression & Encryption ────────────────────────────── %>
|
|
112
123
|
<div class="rn-card">
|
|
113
124
|
<div class="p-4" style="border-bottom: 1px solid var(--rn-border-light)">
|
|
114
|
-
<h2 class="text-sm font-semibold" style="color: var(--rn-text)">
|
|
125
|
+
<h2 class="text-sm font-semibold" style="color: var(--rn-text)">Compression & Encryption</h2>
|
|
115
126
|
</div>
|
|
116
127
|
<div class="p-4 grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
117
128
|
<div class="rn-filter-group">
|
|
118
|
-
<label
|
|
119
|
-
|
|
129
|
+
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
130
|
+
<%= f.check_box :compress %>
|
|
131
|
+
Gzip Compression
|
|
132
|
+
</label>
|
|
120
133
|
</div>
|
|
121
134
|
<div class="rn-filter-group">
|
|
122
|
-
<label>
|
|
123
|
-
|
|
135
|
+
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
136
|
+
<%= f.check_box :bzip2_compress %>
|
|
137
|
+
Bzip2 Compression
|
|
138
|
+
</label>
|
|
139
|
+
<p class="text-xs mt-1" style="color: var(--rn-text-muted)">Better ratio but slower than Gzip</p>
|
|
124
140
|
</div>
|
|
125
141
|
<div class="rn-filter-group">
|
|
126
|
-
<label>
|
|
127
|
-
|
|
142
|
+
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
143
|
+
<%= f.check_box :encrypted %>
|
|
144
|
+
OpenSSL AES-256 Encryption
|
|
145
|
+
</label>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="rn-filter-group" id="encrypt-password-group" style="<%= 'display:none' unless @config.encrypted? %>">
|
|
148
|
+
<label>Encryption Password</label>
|
|
149
|
+
<%= f.password_field :encryption_password, class: "rn-input", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" %>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="rn-filter-group">
|
|
152
|
+
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
153
|
+
<%= f.check_box :gpg_enabled %>
|
|
154
|
+
GPG Encryption
|
|
155
|
+
</label>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="rn-filter-group" id="gpg-password-group" style="<%= 'display:none' unless @config.gpg_enabled? %>">
|
|
158
|
+
<label>GPG Password</label>
|
|
159
|
+
<%= f.password_field :gpg_password, class: "rn-input", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" %>
|
|
128
160
|
</div>
|
|
129
161
|
<div class="rn-filter-group">
|
|
130
|
-
<label
|
|
131
|
-
|
|
162
|
+
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
163
|
+
<%= f.check_box :split_chunks %>
|
|
164
|
+
Split into Chunks (50MB)
|
|
165
|
+
</label>
|
|
166
|
+
<p class="text-xs mt-1" style="color: var(--rn-text-muted)">Split large backups into 50MB files</p>
|
|
132
167
|
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<%# ─── Remote Sync (RSync) ────────────────────────────────── %>
|
|
172
|
+
<div class="rn-card">
|
|
173
|
+
<div class="p-4" style="border-bottom: 1px solid var(--rn-border-light)">
|
|
174
|
+
<h2 class="text-sm font-semibold" style="color: var(--rn-text)">Remote Sync (RSync)</h2>
|
|
175
|
+
</div>
|
|
176
|
+
<div class="p-4 grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
133
177
|
<div class="rn-filter-group">
|
|
134
|
-
<label
|
|
135
|
-
|
|
178
|
+
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
179
|
+
<%= f.check_box :rsync_enabled %>
|
|
180
|
+
Enable RSync
|
|
181
|
+
</label>
|
|
182
|
+
</div>
|
|
183
|
+
<div class="rn-filter-group" id="rsync-fields" style="<%= 'display:none' unless @config.rsync_enabled? %>">
|
|
184
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
185
|
+
<div>
|
|
186
|
+
<label>Host</label>
|
|
187
|
+
<%= f.text_field :rsync_host, class: "rn-input", placeholder: "192.168.1.100" %>
|
|
188
|
+
</div>
|
|
189
|
+
<div>
|
|
190
|
+
<label>Port</label>
|
|
191
|
+
<%= f.number_field :rsync_port, class: "rn-input", value: @config.rsync_port || 22 %>
|
|
192
|
+
</div>
|
|
193
|
+
<div>
|
|
194
|
+
<label>SSH User</label>
|
|
195
|
+
<%= f.text_field :rsync_user, class: "rn-input", placeholder: "backup" %>
|
|
196
|
+
</div>
|
|
197
|
+
<div>
|
|
198
|
+
<label>Remote Path</label>
|
|
199
|
+
<%= f.text_field :rsync_path, class: "rn-input", placeholder: "/backups" %>
|
|
200
|
+
</div>
|
|
201
|
+
<div>
|
|
202
|
+
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
203
|
+
<%= f.check_box :rsync_mirror %>
|
|
204
|
+
Mirror Mode
|
|
205
|
+
</label>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
136
208
|
</div>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<%# ─── S3 Storage ──────────────────────────────────────────── %>
|
|
213
|
+
<div class="rn-card">
|
|
214
|
+
<div class="p-4" style="border-bottom: 1px solid var(--rn-border-light)">
|
|
215
|
+
<h2 class="text-sm font-semibold" style="color: var(--rn-text)">S3 Storage</h2>
|
|
216
|
+
</div>
|
|
217
|
+
<div class="p-4 grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
137
218
|
<div class="rn-filter-group">
|
|
138
|
-
<label
|
|
139
|
-
|
|
219
|
+
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
220
|
+
<%= f.check_box :s3_enabled %>
|
|
221
|
+
Enable S3 Upload
|
|
222
|
+
</label>
|
|
223
|
+
<p class="text-xs mt-1" style="color: var(--rn-text-muted)">Requires aws-cli or s3cmd installed</p>
|
|
224
|
+
</div>
|
|
225
|
+
<div class="rn-filter-group md:col-span-2" id="s3-fields" style="<%= 'display:none' unless @config.s3_enabled? %>">
|
|
226
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
227
|
+
<div>
|
|
228
|
+
<label>Access Key *</label>
|
|
229
|
+
<%= f.text_field :s3_access_key, class: "rn-input", placeholder: "AKIA..." %>
|
|
230
|
+
</div>
|
|
231
|
+
<div>
|
|
232
|
+
<label>Secret Key *</label>
|
|
233
|
+
<%= f.password_field :s3_secret_key, class: "rn-input", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" %>
|
|
234
|
+
</div>
|
|
235
|
+
<div>
|
|
236
|
+
<label>Bucket *</label>
|
|
237
|
+
<%= f.text_field :s3_bucket, class: "rn-input", placeholder: "my-backups-bucket" %>
|
|
238
|
+
</div>
|
|
239
|
+
<div>
|
|
240
|
+
<label>Region</label>
|
|
241
|
+
<%= f.text_field :s3_region, class: "rn-input", placeholder: "us-east-1", value: @config.s3_region || "us-east-1" %>
|
|
242
|
+
</div>
|
|
243
|
+
<div>
|
|
244
|
+
<label>Key Prefix</label>
|
|
245
|
+
<%= f.text_field :s3_prefix, class: "rn-input", placeholder: "backups/production" %>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
140
248
|
</div>
|
|
141
249
|
</div>
|
|
142
250
|
</div>
|
|
@@ -148,17 +256,31 @@
|
|
|
148
256
|
</div>
|
|
149
257
|
<div class="p-4 grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
150
258
|
<div class="rn-filter-group md:col-span-2">
|
|
151
|
-
<label>Notify Command</label>
|
|
259
|
+
<label>Notify Command (Webhook / Script)</label>
|
|
152
260
|
<%= f.text_field :notify_command, class: "rn-input", placeholder: "e.g. curl -X POST https://hooks.slack.com/..." %>
|
|
153
261
|
<p class="text-xs mt-1" style="color: var(--rn-text-muted)">Variables: {STATUS}, {MODEL}, {ERROR}, {TIME}</p>
|
|
154
262
|
</div>
|
|
155
263
|
<div class="rn-filter-group">
|
|
156
|
-
<label
|
|
157
|
-
|
|
264
|
+
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
265
|
+
<%= f.check_box :notify_on_success %>
|
|
266
|
+
Notify on Success
|
|
267
|
+
</label>
|
|
158
268
|
</div>
|
|
159
269
|
<div class="rn-filter-group">
|
|
160
|
-
<label
|
|
161
|
-
|
|
270
|
+
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
271
|
+
<%= f.check_box :notify_on_failure %>
|
|
272
|
+
Notify on Failure
|
|
273
|
+
</label>
|
|
274
|
+
</div>
|
|
275
|
+
<div class="rn-filter-group">
|
|
276
|
+
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
|
277
|
+
<%= f.check_box :email_notify %>
|
|
278
|
+
Email Notifications
|
|
279
|
+
</label>
|
|
280
|
+
</div>
|
|
281
|
+
<div class="rn-filter-group" id="email-to-group" style="<%= 'display:none' unless @config.email_notify? %>">
|
|
282
|
+
<label>Email To</label>
|
|
283
|
+
<%= f.text_field :email_to, class: "rn-input", placeholder: "admin@example.com" %>
|
|
162
284
|
</div>
|
|
163
285
|
</div>
|
|
164
286
|
</div>
|
|
@@ -172,7 +294,7 @@
|
|
|
172
294
|
<div class="rn-filter-group" style="max-width: 300px">
|
|
173
295
|
<label>Cron Expression</label>
|
|
174
296
|
<%= f.text_field :schedule_cron, class: "rn-input", placeholder: "e.g. 0 2 * * * (daily at 2am)" %>
|
|
175
|
-
<p class="text-xs mt-1" style="color: var(--rn-text-muted)">min hour day month wday
|
|
297
|
+
<p class="text-xs mt-1" style="color: var(--rn-text-muted)">min hour day month wday — leave blank for manual only</p>
|
|
176
298
|
</div>
|
|
177
299
|
</div>
|
|
178
300
|
</div>
|
|
@@ -183,3 +305,27 @@
|
|
|
183
305
|
<%= link_to "Cancel", backup_path, class: "rn-btn rn-btn-ghost" %>
|
|
184
306
|
</div>
|
|
185
307
|
<% end %>
|
|
308
|
+
|
|
309
|
+
<script>
|
|
310
|
+
document.addEventListener("DOMContentLoaded", function() {
|
|
311
|
+
// Toggle visibility for dependent sections based on checkboxes
|
|
312
|
+
function setupToggle(checkboxId, targetId) {
|
|
313
|
+
var cb = document.getElementById(checkboxId);
|
|
314
|
+
var target = document.getElementById(targetId);
|
|
315
|
+
if (!cb || !target) return;
|
|
316
|
+
cb.addEventListener("change", function() {
|
|
317
|
+
target.style.display = cb.checked ? "" : "none";
|
|
318
|
+
});
|
|
319
|
+
// Set initial state
|
|
320
|
+
target.style.display = cb.checked ? "" : "none";
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
setupToggle("backup_config_encrypted", "encrypt-password-group");
|
|
324
|
+
setupToggle("backup_config_gpg_enabled", "gpg-password-group");
|
|
325
|
+
setupToggle("backup_config_rsync_enabled", "rsync-fields");
|
|
326
|
+
setupToggle("backup_config_s3_enabled", "s3-fields");
|
|
327
|
+
setupToggle("backup_config_email_notify", "email-to-group");
|
|
328
|
+
setupToggle("backup_config_archive_enabled", "archive-paths-group");
|
|
329
|
+
setupToggle("backup_config_archive_enabled", "archive-excludes-group");
|
|
330
|
+
});
|
|
331
|
+
</script>
|