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.
@@ -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
- <body class="h-full <%= RailsNexus.configuration.body_class %>">
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-text"><%= RailsNexus.configuration.application_name || "RailsNexus" %></span>
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
- Dashboard
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
- <%# ─── Tools section ──────────────────────────────────────── %>
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
- v<%= RailsNexus::VERSION %> · <%= RailsNexus::LoggedException.count %> exceptions
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
- <span class="text-sm font-semibold"><%= RailsNexus.configuration.application_name || "RailsNexus" %></span>
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="flex items-center gap-1.5">
122
- <% if RailsNexus.configuration.keyboard_shortcuts %>
123
- <span class="text-xs flex items-center gap-1" style="color: var(--rn-text-muted)">
124
- <span class="rn-kbd">?</span>
125
- </span>
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
- <span class="rn-logo-text"><%= RailsNexus.configuration.application_name || "RailsNexus" %></span>
179
- <button type="button" class="rn-icon-btn" data-action="click->rails_nexus-sidebar#close">
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
- <nav class="rn-sidebar-nav">
184
- <%= link_to root_path, class: "rn-nav-item" do %>
185
- <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.25V6z" /></svg>
186
- Dashboard
187
- <% end %>
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) : backup_index_path), class: "space-y-6" do |f| %>
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>• <%= msg %></li>
8
+ <li>&bull; <%= 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>Enabled</label>
31
- <%= f.check_box :enabled, class: "rn-input" %>
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([["MySQL", "mysql"], ["PostgreSQL", "postgresql"], ["SQLite", "sqlite"]], @config.adapter), {}, class: "rn-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
- <%= text_field_tag "backup_config[skip_tables][]", @config.skip_tables_list.join(", "), class: "rn-input", placeholder: "e.g. sessions, versions" %>
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
- <%# ─── Compression & Encryption ────────────────────────────── %>
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)">Compression & Encryption</h2>
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>Compress (Gzip)</label>
98
- <%= f.check_box :compress, class: "rn-input" %>
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>Encrypt (OpenSSL AES-256)</label>
102
- <%= f.check_box :encrypt, class: "rn-input", data: { action: "change->rails_nexus#toggle" } %>
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" data-rails_nexus-target="encryptPassword" style="<%= 'display:none' unless @config.encrypt? %>">
105
- <label>Encryption Password</label>
106
- <%= f.password_field :encrypt_password, class: "rn-input", placeholder: "••••••••" %>
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
- <%# ─── Remote Sync ────────────────────────────────────────── %>
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)">Remote Sync (RSync)</h2>
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>Enable RSync</label>
119
- <%= f.check_box :rsync_enabled, class: "rn-input" %>
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>Host</label>
123
- <%= f.text_field :rsync_host, class: "rn-input", placeholder: "192.168.1.100" %>
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>Port</label>
127
- <%= f.number_field :rsync_port, class: "rn-input", value: @config.rsync_port || 22 %>
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>SSH User</label>
131
- <%= f.text_field :rsync_user, class: "rn-input", placeholder: "backup" %>
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>Remote Path</label>
135
- <%= f.text_field :rsync_path, class: "rn-input", placeholder: "/backups" %>
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>Mirror Mode</label>
139
- <%= f.check_box :rsync_mirror, class: "rn-input" %>
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>Notify on Success</label>
157
- <%= f.check_box :notify_on_success, class: "rn-input" %>
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>Notify on Failure</label>
161
- <%= f.check_box :notify_on_failure, class: "rn-input" %>
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 leave blank for manual only</p>
297
+ <p class="text-xs mt-1" style="color: var(--rn-text-muted)">min hour day month wday &mdash; 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>