rails-settings-cached-rails-admin 0.1.0
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 +7 -0
- data/.cursor/rules +12 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +21 -0
- data/MANUAL_SETUP.md +68 -0
- data/README.md +180 -0
- data/Rakefile +6 -0
- data/app/views/rails_admin_settings_ui/settings/index.html.erb +435 -0
- data/example_app_setup.md +156 -0
- data/lib/locales/en.yml +8 -0
- data/lib/rails_admin_settings_ui/engine.rb +27 -0
- data/lib/rails_admin_settings_ui/helper.rb +20 -0
- data/lib/rails_admin_settings_ui/railtie.rb +14 -0
- data/lib/rails_admin_settings_ui/settings_ui.rb +375 -0
- data/lib/rails_admin_settings_ui/version.rb +3 -0
- data/lib/rails_admin_settings_ui.rb +12 -0
- data/screenshot.png +0 -0
- metadata +147 -0
@@ -0,0 +1,435 @@
|
|
1
|
+
<%# Set page title with fallback %>
|
2
|
+
<% content_for :title do %>
|
3
|
+
<%= t('admin.actions.settings_ui.title', default: 'Settings') %>
|
4
|
+
<% end %>
|
5
|
+
|
6
|
+
<%# Set breadcrumb with fallback %>
|
7
|
+
<% content_for :breadcrumbs do %>
|
8
|
+
<li class="breadcrumb-item">
|
9
|
+
<%= link_to t('admin.home.name', default: 'Home'), rails_admin.dashboard_path %>
|
10
|
+
</li>
|
11
|
+
<% end %>
|
12
|
+
|
13
|
+
<!-- Search Bar -->
|
14
|
+
<div class="row mb-3">
|
15
|
+
<div class="col-12">
|
16
|
+
<div class="card">
|
17
|
+
<div class="card-body py-2">
|
18
|
+
<div class="row">
|
19
|
+
<div class="col-md-6">
|
20
|
+
<div class="input-group">
|
21
|
+
<div class="input-group-prepend">
|
22
|
+
<span class="input-group-text"><i class="fa fa-search"></i></span>
|
23
|
+
</div>
|
24
|
+
<input type="text" class="form-control" id="settings-search" placeholder="Search settings..." autocomplete="off">
|
25
|
+
<div class="input-group-append">
|
26
|
+
<button class="btn btn-outline-secondary" type="button" id="clear-search">
|
27
|
+
<i class="fa fa-times"></i>
|
28
|
+
</button>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
<div class="col-md-6">
|
33
|
+
<div class="float-right">
|
34
|
+
<span class="badge badge-info" id="settings-count">
|
35
|
+
<i class="fa fa-cog"></i> <span id="visible-count"><%= @settings_data.values.flatten.size %></span> settings
|
36
|
+
</span>
|
37
|
+
</div>
|
38
|
+
</div>
|
39
|
+
</div>
|
40
|
+
</div>
|
41
|
+
</div>
|
42
|
+
</div>
|
43
|
+
</div>
|
44
|
+
|
45
|
+
<!-- Settings Table -->
|
46
|
+
<div class="row">
|
47
|
+
<div class="col-md-12">
|
48
|
+
<% if @settings_data.any? %>
|
49
|
+
<% @settings_data.each_with_index do |(category, settings), category_index| %>
|
50
|
+
<div class="card settings-category" data-category="<%= category.parameterize %>">
|
51
|
+
<div class="card-header">
|
52
|
+
<h3 class="card-title">
|
53
|
+
<i class="fa fa-folder-o"></i>
|
54
|
+
<%= category %>
|
55
|
+
<span class="badge badge-primary ml-2"><%= settings.size %></span>
|
56
|
+
</h3>
|
57
|
+
<div class="card-tools">
|
58
|
+
<button type="button" class="btn btn-tool" data-card-widget="collapse">
|
59
|
+
<i class="fa fa-minus"></i>
|
60
|
+
</button>
|
61
|
+
</div>
|
62
|
+
</div>
|
63
|
+
|
64
|
+
<div class="card-body table-responsive p-0">
|
65
|
+
<table class="table table-striped table-hover">
|
66
|
+
<thead>
|
67
|
+
<tr>
|
68
|
+
<th style="width: 250px;">Setting</th>
|
69
|
+
<th>Current Value</th>
|
70
|
+
<th style="width: 120px;">Type</th>
|
71
|
+
<th style="width: 100px;">Status</th>
|
72
|
+
<th style="width: 120px;">Actions</th>
|
73
|
+
</tr>
|
74
|
+
</thead>
|
75
|
+
<tbody>
|
76
|
+
<% settings.each do |setting| %>
|
77
|
+
<tr class="setting-row" data-key="<%= setting[:key] %>" data-searchable="<%= [setting[:label], setting[:key]].compact.join(' ').downcase %>">
|
78
|
+
<td>
|
79
|
+
<div class="setting-info">
|
80
|
+
<strong><%= setting[:label] %></strong>
|
81
|
+
<br>
|
82
|
+
<small class="text-muted"><code><%= setting[:key] %></code></small>
|
83
|
+
</div>
|
84
|
+
</td>
|
85
|
+
|
86
|
+
<td>
|
87
|
+
<div class="setting-value-container">
|
88
|
+
<%= form_tag request.path, method: :post, class: 'setting-form d-inline', local: false, data: { key: setting[:key] } do %>
|
89
|
+
<% field_name = "setting_value" %>
|
90
|
+
<% field_id = "setting_#{setting[:key]}" %>
|
91
|
+
<% current_value = setting[:current_value] %>
|
92
|
+
<%= hidden_field_tag "setting_key", setting[:key] %>
|
93
|
+
|
94
|
+
<div class="setting-input-group">
|
95
|
+
<% case setting[:field_type] %>
|
96
|
+
<% when :boolean %>
|
97
|
+
<div class="custom-control custom-switch">
|
98
|
+
<%= check_box_tag(field_name, '1', current_value, id: field_id, class: 'custom-control-input') %>
|
99
|
+
<%= hidden_field_tag(field_name, '0') %>
|
100
|
+
<%= label_tag(field_id, current_value ? 'Enabled' : 'Disabled', class: 'custom-control-label') %>
|
101
|
+
</div>
|
102
|
+
<% when :integer %>
|
103
|
+
<%= number_field_tag(field_name, current_value, id: field_id, class: 'form-control form-control-sm', step: 1, style: 'max-width: 200px;') %>
|
104
|
+
<% when :float %>
|
105
|
+
<%= number_field_tag(field_name, current_value, id: field_id, class: 'form-control form-control-sm', step: 0.01, style: 'max-width: 200px;') %>
|
106
|
+
<% when :text %>
|
107
|
+
<%= text_area_tag(field_name, current_value, id: field_id, class: 'form-control form-control-sm', rows: 2, style: 'min-width: 300px;') %>
|
108
|
+
<% when :email %>
|
109
|
+
<%= email_field_tag(field_name, current_value, id: field_id, class: 'form-control form-control-sm', style: 'min-width: 250px;') %>
|
110
|
+
<% when :url %>
|
111
|
+
<%= url_field_tag(field_name, current_value, id: field_id, class: 'form-control form-control-sm', style: 'min-width: 300px;') %>
|
112
|
+
<% when :array %>
|
113
|
+
<% array_value = current_value.is_a?(Array) ? current_value.join(', ') : current_value.to_s %>
|
114
|
+
<%= text_field_tag(field_name, array_value, id: field_id, class: 'form-control form-control-sm', placeholder: 'comma, separated, values', style: 'min-width: 300px;') %>
|
115
|
+
<% when :json %>
|
116
|
+
<% json_value = current_value.is_a?(String) ? current_value : current_value.to_json %>
|
117
|
+
<%= text_area_tag(field_name, json_value, id: field_id, class: 'form-control form-control-sm font-monospace', rows: 3, placeholder: '{"key": "value"}', style: 'min-width: 350px;') %>
|
118
|
+
<% else # :string %>
|
119
|
+
<%= text_field_tag(field_name, current_value, id: field_id, class: 'form-control form-control-sm', style: 'min-width: 250px;') %>
|
120
|
+
<% end %>
|
121
|
+
|
122
|
+
<% if [:array, :json].include?(setting[:field_type]) %>
|
123
|
+
<small class="form-text text-muted">
|
124
|
+
<% if setting[:field_type] == :array %>
|
125
|
+
Separate values with commas
|
126
|
+
<% else %>
|
127
|
+
Valid JSON format required
|
128
|
+
<% end %>
|
129
|
+
</small>
|
130
|
+
<% end %>
|
131
|
+
</div>
|
132
|
+
<% end %>
|
133
|
+
|
134
|
+
<% if setting[:default_value] != setting[:current_value] %>
|
135
|
+
<small class="text-muted mt-1 d-block">
|
136
|
+
<i class="fa fa-info-circle"></i>
|
137
|
+
Default: <code><%= setting[:default_value].inspect %></code>
|
138
|
+
</small>
|
139
|
+
<% end %>
|
140
|
+
</div>
|
141
|
+
</td>
|
142
|
+
|
143
|
+
<td>
|
144
|
+
<span>
|
145
|
+
<%= setting[:field_type].to_s.humanize %>
|
146
|
+
</span>
|
147
|
+
</td>
|
148
|
+
|
149
|
+
<td>
|
150
|
+
<div class="setting-status">
|
151
|
+
<% if setting[:current_value] != setting[:default_value] %>
|
152
|
+
<span>
|
153
|
+
<i class="fa fa-edit"></i> Modified
|
154
|
+
</span>
|
155
|
+
<% else %>
|
156
|
+
<span>
|
157
|
+
<i class="fa fa-check"></i> Default
|
158
|
+
</span>
|
159
|
+
<% end %>
|
160
|
+
</div>
|
161
|
+
<div class="setting-feedback mt-1">
|
162
|
+
<!-- AJAX feedback messages -->
|
163
|
+
</div>
|
164
|
+
</td>
|
165
|
+
|
166
|
+
<td>
|
167
|
+
<button type="button" class="btn btn-primary btn-sm btn-save" data-key="<%= setting[:key] %>" title="Save changes">
|
168
|
+
<i class="fa fa-save"></i>
|
169
|
+
</button>
|
170
|
+
</td>
|
171
|
+
</tr>
|
172
|
+
<% end %>
|
173
|
+
</tbody>
|
174
|
+
</table>
|
175
|
+
</div>
|
176
|
+
</div>
|
177
|
+
<% end %>
|
178
|
+
|
179
|
+
<!-- Empty state when no settings match search -->
|
180
|
+
<div class="card no-results" style="display: none;">
|
181
|
+
<div class="card-body text-center py-5">
|
182
|
+
<i class="fa fa-search fa-3x text-muted mb-3"></i>
|
183
|
+
<h4 class="text-muted">No settings found</h4>
|
184
|
+
<p class="text-muted">Try adjusting your search criteria.</p>
|
185
|
+
<button type="button" class="btn btn-primary" id="clear-search-results">
|
186
|
+
<i class="fa fa-refresh"></i> Clear Search
|
187
|
+
</button>
|
188
|
+
</div>
|
189
|
+
</div>
|
190
|
+
|
191
|
+
<% else %>
|
192
|
+
<div class="card">
|
193
|
+
<div class="card-body text-center py-5">
|
194
|
+
<i class="fa fa-cog fa-3x text-muted mb-3"></i>
|
195
|
+
<h4>No settings configured</h4>
|
196
|
+
<p class="text-muted">Make sure your Setting class has default values defined.</p>
|
197
|
+
<a href="<%= rails_admin.dashboard_path %>" class="btn btn-primary">
|
198
|
+
<i class="fa fa-arrow-left"></i> Back to Dashboard
|
199
|
+
</a>
|
200
|
+
</div>
|
201
|
+
</div>
|
202
|
+
<% end %>
|
203
|
+
</div>
|
204
|
+
</div>
|
205
|
+
|
206
|
+
<!-- Back to Dashboard Footer -->
|
207
|
+
<div class="row mt-4">
|
208
|
+
<div class="col-12">
|
209
|
+
<div class="text-center">
|
210
|
+
<a href="<%= rails_admin.dashboard_path %>" class="btn btn-secondary">
|
211
|
+
<i class="fa fa-arrow-left"></i> Back to Dashboard
|
212
|
+
</a>
|
213
|
+
</div>
|
214
|
+
</div>
|
215
|
+
</div>
|
216
|
+
|
217
|
+
<% content_for :before_body_end do %>
|
218
|
+
<style>
|
219
|
+
.settings-category .card-header {
|
220
|
+
background-color: #f8f9fa;
|
221
|
+
border-bottom: 1px solid #dee2e6;
|
222
|
+
}
|
223
|
+
|
224
|
+
.setting-input-group {
|
225
|
+
display: inline-block;
|
226
|
+
vertical-align: top;
|
227
|
+
}
|
228
|
+
|
229
|
+
.setting-value-container {
|
230
|
+
min-height: 60px;
|
231
|
+
}
|
232
|
+
|
233
|
+
.setting-info strong {
|
234
|
+
font-size: 0.95em;
|
235
|
+
}
|
236
|
+
|
237
|
+
.badge {
|
238
|
+
font-size: 0.75em;
|
239
|
+
font-weight: 500;
|
240
|
+
}
|
241
|
+
|
242
|
+
/* Ensure good contrast for all badges */
|
243
|
+
.badge-dark {
|
244
|
+
background-color: #343a40 !important;
|
245
|
+
color: #fff !important;
|
246
|
+
}
|
247
|
+
|
248
|
+
.badge-success {
|
249
|
+
background-color: #28a745 !important;
|
250
|
+
color: #fff !important;
|
251
|
+
}
|
252
|
+
|
253
|
+
.badge-primary {
|
254
|
+
background-color: #007bff !important;
|
255
|
+
color: #fff !important;
|
256
|
+
}
|
257
|
+
|
258
|
+
/* Fix table header text visibility */
|
259
|
+
.table thead th {
|
260
|
+
color: #495057 !important;
|
261
|
+
background-color: #f8f9fa !important;
|
262
|
+
border-bottom: 2px solid #dee2e6 !important;
|
263
|
+
font-weight: 600 !important;
|
264
|
+
}
|
265
|
+
|
266
|
+
/* Fix table cell text visibility */
|
267
|
+
.table td, .table tbody th {
|
268
|
+
color: #495057 !important;
|
269
|
+
}
|
270
|
+
|
271
|
+
.setting-feedback {
|
272
|
+
min-height: 20px;
|
273
|
+
}
|
274
|
+
|
275
|
+
.table td {
|
276
|
+
vertical-align: middle;
|
277
|
+
}
|
278
|
+
|
279
|
+
.custom-switch {
|
280
|
+
padding-left: 2.25rem;
|
281
|
+
}
|
282
|
+
|
283
|
+
.content-header h1 {
|
284
|
+
font-size: 1.8rem;
|
285
|
+
font-weight: 500;
|
286
|
+
}
|
287
|
+
|
288
|
+
@media (max-width: 768px) {
|
289
|
+
.table-responsive table {
|
290
|
+
font-size: 0.85em;
|
291
|
+
}
|
292
|
+
|
293
|
+
.setting-input-group input,
|
294
|
+
.setting-input-group textarea {
|
295
|
+
min-width: 200px !important;
|
296
|
+
}
|
297
|
+
}
|
298
|
+
</style>
|
299
|
+
<% end %>
|
300
|
+
|
301
|
+
<script>
|
302
|
+
document.addEventListener('DOMContentLoaded', function() {
|
303
|
+
// Search functionality
|
304
|
+
const searchInput = document.getElementById('settings-search');
|
305
|
+
const clearSearchButton = document.getElementById('clear-search');
|
306
|
+
const settingRows = document.querySelectorAll('.setting-row');
|
307
|
+
const visibleCountSpan = document.getElementById('visible-count');
|
308
|
+
const noResultsCard = document.querySelector('.no-results');
|
309
|
+
const settingsCategories = document.querySelectorAll('.settings-category');
|
310
|
+
|
311
|
+
// Search functionality
|
312
|
+
if (searchInput) {
|
313
|
+
searchInput.addEventListener('input', function() {
|
314
|
+
const searchTerm = this.value.toLowerCase();
|
315
|
+
let visibleCount = 0;
|
316
|
+
let hasVisibleCategories = false;
|
317
|
+
|
318
|
+
settingsCategories.forEach(function(category) {
|
319
|
+
let categoryHasVisible = false;
|
320
|
+
const rows = category.querySelectorAll('.setting-row');
|
321
|
+
|
322
|
+
rows.forEach(function(row) {
|
323
|
+
const searchable = row.dataset.searchable || '';
|
324
|
+
const isVisible = searchTerm === '' || searchable.includes(searchTerm);
|
325
|
+
|
326
|
+
row.style.display = isVisible ? '' : 'none';
|
327
|
+
if (isVisible) {
|
328
|
+
visibleCount++;
|
329
|
+
categoryHasVisible = true;
|
330
|
+
}
|
331
|
+
});
|
332
|
+
|
333
|
+
category.style.display = categoryHasVisible ? '' : 'none';
|
334
|
+
if (categoryHasVisible) hasVisibleCategories = true;
|
335
|
+
});
|
336
|
+
|
337
|
+
visibleCountSpan.textContent = visibleCount;
|
338
|
+
noResultsCard.style.display = hasVisibleCategories ? 'none' : 'block';
|
339
|
+
|
340
|
+
// Update clear button
|
341
|
+
clearSearchButton.style.display = searchTerm ? 'block' : 'none';
|
342
|
+
});
|
343
|
+
}
|
344
|
+
|
345
|
+
// Clear search
|
346
|
+
if (clearSearchButton) {
|
347
|
+
clearSearchButton.addEventListener('click', function() {
|
348
|
+
searchInput.value = '';
|
349
|
+
searchInput.dispatchEvent(new Event('input'));
|
350
|
+
});
|
351
|
+
}
|
352
|
+
|
353
|
+
// Clear search from no results page
|
354
|
+
document.getElementById('clear-search-results')?.addEventListener('click', function() {
|
355
|
+
searchInput.value = '';
|
356
|
+
searchInput.dispatchEvent(new Event('input'));
|
357
|
+
});
|
358
|
+
|
359
|
+
// Handle save buttons
|
360
|
+
document.querySelectorAll('.btn-save').forEach(function(button) {
|
361
|
+
button.addEventListener('click', function() {
|
362
|
+
const key = this.dataset.key;
|
363
|
+
const settingRow = document.querySelector(`.setting-row[data-key="${key}"]`);
|
364
|
+
const form = settingRow.querySelector('.setting-form');
|
365
|
+
|
366
|
+
if (form) {
|
367
|
+
// Create and dispatch form submit event
|
368
|
+
const submitEvent = new Event('submit', { bubbles: true, cancelable: true });
|
369
|
+
form.dispatchEvent(submitEvent);
|
370
|
+
}
|
371
|
+
});
|
372
|
+
});
|
373
|
+
|
374
|
+
// Handle individual setting form submissions
|
375
|
+
document.querySelectorAll('.setting-form').forEach(function(form) {
|
376
|
+
form.addEventListener('submit', function(e) {
|
377
|
+
e.preventDefault();
|
378
|
+
|
379
|
+
var formData = new FormData(form);
|
380
|
+
var key = form.dataset.key;
|
381
|
+
var settingRow = form.closest('.setting-row');
|
382
|
+
var saveButton = settingRow.querySelector('.btn-save');
|
383
|
+
var feedbackDiv = settingRow.querySelector('.setting-feedback');
|
384
|
+
|
385
|
+
// Show loading state
|
386
|
+
saveButton.disabled = true;
|
387
|
+
saveButton.innerHTML = '<i class="fa fa-spinner fa-spin"></i>';
|
388
|
+
feedbackDiv.innerHTML = '';
|
389
|
+
|
390
|
+
fetch(form.action, {
|
391
|
+
method: 'POST',
|
392
|
+
body: formData,
|
393
|
+
headers: {
|
394
|
+
'X-Requested-With': 'XMLHttpRequest',
|
395
|
+
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
396
|
+
}
|
397
|
+
})
|
398
|
+
.then(response => response.json())
|
399
|
+
.then(data => {
|
400
|
+
if (data.success) {
|
401
|
+
// Show success message
|
402
|
+
feedbackDiv.innerHTML = '<small class="text-success"><i class="fa fa-check"></i> Saved successfully!</small>';
|
403
|
+
|
404
|
+
// Clear success message after 3 seconds
|
405
|
+
setTimeout(function() {
|
406
|
+
feedbackDiv.innerHTML = '';
|
407
|
+
}, 3000);
|
408
|
+
} else {
|
409
|
+
// Show error message
|
410
|
+
feedbackDiv.innerHTML = '<small class="text-danger"><i class="fa fa-exclamation-triangle"></i> ' + (data.error || 'Failed to save') + '</small>';
|
411
|
+
}
|
412
|
+
})
|
413
|
+
.catch(error => {
|
414
|
+
console.error('Error:', error);
|
415
|
+
feedbackDiv.innerHTML = '<small class="text-danger"><i class="fa fa-exclamation-triangle"></i> Network error</small>';
|
416
|
+
})
|
417
|
+
.finally(function() {
|
418
|
+
// Restore button state
|
419
|
+
saveButton.disabled = false;
|
420
|
+
saveButton.innerHTML = '<i class="fa fa-save"></i>';
|
421
|
+
});
|
422
|
+
});
|
423
|
+
});
|
424
|
+
|
425
|
+
// Handle switch changes for boolean fields
|
426
|
+
document.querySelectorAll('input[type="checkbox"]').forEach(function(checkbox) {
|
427
|
+
checkbox.addEventListener('change', function() {
|
428
|
+
const label = document.querySelector(`label[for="${this.id}"]`);
|
429
|
+
if (label && label.classList.contains('custom-control-label')) {
|
430
|
+
label.textContent = this.checked ? 'Enabled' : 'Disabled';
|
431
|
+
}
|
432
|
+
});
|
433
|
+
});
|
434
|
+
});
|
435
|
+
</script>
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# Testing the gem locally
|
2
|
+
|
3
|
+
## 1. Create a test Rails app
|
4
|
+
|
5
|
+
```bash
|
6
|
+
rails new test_app
|
7
|
+
cd test_app
|
8
|
+
```
|
9
|
+
|
10
|
+
## 2. Add gems to Gemfile
|
11
|
+
|
12
|
+
Add these gems to your `Gemfile`:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
# Add these to your Gemfile
|
16
|
+
gem 'rails_admin'
|
17
|
+
gem 'rails-settings-cached'
|
18
|
+
gem 'rails_admin_settings_ui', path: '/Users/r3cha/rails_admin_settings_ui'
|
19
|
+
|
20
|
+
# For authentication (required by rails_admin)
|
21
|
+
gem 'devise'
|
22
|
+
```
|
23
|
+
|
24
|
+
## 3. Bundle install
|
25
|
+
|
26
|
+
```bash
|
27
|
+
bundle install
|
28
|
+
```
|
29
|
+
|
30
|
+
## 4. Set up Devise (required for Rails Admin)
|
31
|
+
|
32
|
+
```bash
|
33
|
+
rails generate devise:install
|
34
|
+
rails generate devise User
|
35
|
+
```
|
36
|
+
|
37
|
+
## 5. Set up Rails Admin
|
38
|
+
|
39
|
+
```bash
|
40
|
+
rails generate rails_admin:install
|
41
|
+
```
|
42
|
+
|
43
|
+
## 6. Create Setting model
|
44
|
+
|
45
|
+
Create `app/models/setting.rb`:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
class Setting < RailsSettings::Base
|
49
|
+
cache_prefix { "v1" }
|
50
|
+
|
51
|
+
# General settings
|
52
|
+
field :app_name, default: "My Test Application"
|
53
|
+
field :maintenance_mode, default: false
|
54
|
+
field :max_users, default: 1000
|
55
|
+
field :welcome_message, default: "Welcome to our application!"
|
56
|
+
|
57
|
+
# Email settings
|
58
|
+
field :mail_from, default: "noreply@example.com"
|
59
|
+
field :mail_host, default: "smtp.example.com"
|
60
|
+
field :mail_port, default: 587
|
61
|
+
field :mail_ssl, default: true
|
62
|
+
|
63
|
+
# API settings
|
64
|
+
field :api_key, default: ""
|
65
|
+
field :api_timeout, default: 30.0
|
66
|
+
field :api_endpoints, default: ["https://api.example.com", "https://backup-api.example.com"]
|
67
|
+
|
68
|
+
# Advanced settings
|
69
|
+
field :feature_flags, default: { "new_ui" => false, "beta_features" => false, "analytics" => true }
|
70
|
+
field :cache_ttl, default: 3600
|
71
|
+
field :debug_mode, default: false
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
## 7. Configure Rails Admin
|
76
|
+
|
77
|
+
Edit `config/initializers/rails_admin.rb`:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
RailsAdmin.config do |config|
|
81
|
+
config.authenticate_with do
|
82
|
+
# Add your authentication logic here
|
83
|
+
# For testing, you can comment this out or use a simple check
|
84
|
+
end
|
85
|
+
|
86
|
+
config.current_user_method(&:current_user)
|
87
|
+
|
88
|
+
config.actions do
|
89
|
+
dashboard # mandatory
|
90
|
+
index # mandatory
|
91
|
+
new
|
92
|
+
export
|
93
|
+
bulk_delete
|
94
|
+
show
|
95
|
+
edit
|
96
|
+
delete
|
97
|
+
show_in_app
|
98
|
+
|
99
|
+
# Add the settings UI action
|
100
|
+
settings_ui
|
101
|
+
end
|
102
|
+
|
103
|
+
# Hide the default Setting model from navigation
|
104
|
+
config.model 'Setting' do
|
105
|
+
visible false
|
106
|
+
end
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
## 8. Run migrations
|
111
|
+
|
112
|
+
```bash
|
113
|
+
rails db:create
|
114
|
+
rails db:migrate
|
115
|
+
```
|
116
|
+
|
117
|
+
## 9. Create a user (if using Devise)
|
118
|
+
|
119
|
+
```bash
|
120
|
+
rails console
|
121
|
+
```
|
122
|
+
|
123
|
+
In the console:
|
124
|
+
```ruby
|
125
|
+
User.create!(email: 'admin@example.com', password: 'password', password_confirmation: 'password')
|
126
|
+
```
|
127
|
+
|
128
|
+
## 10. Start the server
|
129
|
+
|
130
|
+
```bash
|
131
|
+
rails server
|
132
|
+
```
|
133
|
+
|
134
|
+
## 11. Test the Settings UI
|
135
|
+
|
136
|
+
1. Go to `http://localhost:3000/admin`
|
137
|
+
2. Sign in with your test user
|
138
|
+
3. Click on "Settings" in the navigation
|
139
|
+
4. You should see your settings organized in tabs with a user-friendly interface!
|
140
|
+
|
141
|
+
## Testing different setting types
|
142
|
+
|
143
|
+
Try changing various settings to see the different field types in action:
|
144
|
+
|
145
|
+
- **Boolean**: `maintenance_mode`, `mail_ssl`, `debug_mode`
|
146
|
+
- **Integer**: `max_users`, `mail_port`, `cache_ttl`
|
147
|
+
- **Float**: `api_timeout`
|
148
|
+
- **String**: `app_name`, `api_key`, `mail_from`, `mail_host`
|
149
|
+
- **Text**: `welcome_message` (if you make it longer)
|
150
|
+
- **Array**: `api_endpoints`
|
151
|
+
- **JSON**: `feature_flags`
|
152
|
+
|
153
|
+
The interface will automatically categorize them into:
|
154
|
+
- **General**: `app_name`, `maintenance_mode`, `max_users`, etc.
|
155
|
+
- **Mail**: `mail_from`, `mail_host`, `mail_port`, `mail_ssl`
|
156
|
+
- **Api**: `api_key`, `api_timeout`, `api_endpoints`
|
data/lib/locales/en.yml
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require "rails"
|
2
|
+
require_relative "settings_ui"
|
3
|
+
require_relative "helper"
|
4
|
+
|
5
|
+
module RailsAdminSettingsUi
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
isolate_namespace RailsAdminSettingsUi
|
8
|
+
|
9
|
+
# Action registration is handled by the Railtie
|
10
|
+
|
11
|
+
# Load locale files
|
12
|
+
config.before_configuration do
|
13
|
+
I18n.load_path += Dir[File.join(File.dirname(__FILE__), '..', 'locales', '*.yml')]
|
14
|
+
end
|
15
|
+
|
16
|
+
# Include helpers in Rails Admin
|
17
|
+
initializer "rails_admin_settings_ui.helpers" do
|
18
|
+
ActiveSupport.on_load(:action_view) do
|
19
|
+
ActionView::Base.include RailsAdminSettingsUi::Helper
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
config.generators do |g|
|
24
|
+
g.test_framework :rspec
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module RailsAdminSettingsUi
|
2
|
+
module Helper
|
3
|
+
def badge_color_for_type(field_type)
|
4
|
+
case field_type
|
5
|
+
when :boolean
|
6
|
+
'success'
|
7
|
+
when :integer, :float
|
8
|
+
'info'
|
9
|
+
when :text, :json
|
10
|
+
'warning'
|
11
|
+
when :email, :url
|
12
|
+
'primary'
|
13
|
+
when :array
|
14
|
+
'dark'
|
15
|
+
else # :string
|
16
|
+
'dark'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module RailsAdminSettingsUi
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
railtie_name :rails_admin_settings_ui
|
4
|
+
|
5
|
+
initializer "rails_admin_settings_ui.register_action", before: :load_config_initializers do
|
6
|
+
# This will run before the Rails Admin config initializer
|
7
|
+
Rails.application.config.to_prepare do
|
8
|
+
if defined?(RailsAdmin) && defined?(RailsAdmin::Config::Actions)
|
9
|
+
RailsAdmin::Config::Actions.register(:settings_ui, RailsAdminSettingsUi::SettingsUi)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|