admin_suite 0.2.0 → 0.2.2
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/admin_suite.css +128 -0
- data/app/controllers/admin_suite/application_controller.rb +32 -2
- data/app/controllers/admin_suite/dashboard_controller.rb +59 -226
- data/app/helpers/admin_suite/base_helper.rb +108 -108
- data/app/helpers/admin_suite/panels_helper.rb +1 -1
- data/app/javascript/controllers/admin_suite/file_upload_controller.js +9 -9
- data/app/javascript/controllers/admin_suite/json_editor_controller.js +8 -8
- data/app/javascript/controllers/admin_suite/searchable_select_controller.js +2 -2
- data/app/javascript/controllers/admin_suite/tag_select_controller.js +1 -1
- data/app/javascript/controllers/admin_suite/toggle_switch_controller.js +1 -1
- data/app/views/admin_suite/dashboard/index.html.erb +6 -15
- data/app/views/admin_suite/panels/_cards.html.erb +6 -6
- data/app/views/admin_suite/panels/_chart.html.erb +12 -12
- data/app/views/admin_suite/panels/_health.html.erb +14 -14
- data/app/views/admin_suite/panels/_recent.html.erb +11 -11
- data/app/views/admin_suite/panels/_stat.html.erb +24 -24
- data/app/views/admin_suite/panels/_table.html.erb +10 -10
- data/app/views/admin_suite/portals/show.html.erb +1 -1
- data/app/views/admin_suite/resources/_form.html.erb +1 -1
- data/app/views/admin_suite/resources/edit.html.erb +4 -4
- data/app/views/admin_suite/resources/index.html.erb +23 -23
- data/app/views/admin_suite/resources/new.html.erb +4 -4
- data/app/views/admin_suite/resources/show.html.erb +17 -17
- data/app/views/admin_suite/shared/_form.html.erb +8 -8
- data/app/views/admin_suite/shared/_json_editor_field.html.erb +4 -4
- data/app/views/admin_suite/shared/_sidebar.html.erb +4 -4
- data/app/views/admin_suite/shared/_topbar.html.erb +1 -1
- data/app/views/layouts/admin_suite/application.html.erb +4 -4
- data/docs/configuration.md +56 -6
- data/docs/portals.md +42 -0
- data/lib/admin/base/action_executor.rb +69 -0
- data/lib/admin_suite/configuration.rb +12 -0
- data/lib/admin_suite/engine.rb +82 -31
- data/lib/admin_suite/ui/field_renderer_registry.rb +2 -2
- data/lib/admin_suite/ui/form_field_renderer.rb +2 -2
- data/lib/admin_suite/ui/show_formatter_registry.rb +5 -5
- data/lib/admin_suite/ui/show_value_formatter.rb +1 -1
- data/lib/admin_suite/version.rb +1 -1
- data/lib/admin_suite.rb +31 -0
- data/lib/generators/admin_suite/install/templates/admin_suite.rb +8 -0
- data/test/dummy/log/test.log +1512 -0
- data/test/dummy/tmp/local_secret.txt +1 -0
- data/test/integration/dashboard_test.rb +57 -1
- data/test/lib/action_executor_test.rb +172 -0
- data/test/lib/zeitwerk_integration_test.rb +69 -16
- metadata +4 -1
|
@@ -52,19 +52,19 @@ export default class extends Controller {
|
|
|
52
52
|
onDragOver(event) {
|
|
53
53
|
event.preventDefault()
|
|
54
54
|
event.stopPropagation()
|
|
55
|
-
this.dropZoneElement.classList.add("border-amber-500", "bg-amber-50"
|
|
55
|
+
this.dropZoneElement.classList.add("border-amber-500", "bg-amber-50")
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
onDragLeave(event) {
|
|
59
59
|
event.preventDefault()
|
|
60
60
|
event.stopPropagation()
|
|
61
|
-
this.dropZoneElement.classList.remove("border-amber-500", "bg-amber-50"
|
|
61
|
+
this.dropZoneElement.classList.remove("border-amber-500", "bg-amber-50")
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
onDrop(event) {
|
|
65
65
|
event.preventDefault()
|
|
66
66
|
event.stopPropagation()
|
|
67
|
-
this.dropZoneElement.classList.remove("border-amber-500", "bg-amber-50"
|
|
67
|
+
this.dropZoneElement.classList.remove("border-amber-500", "bg-amber-50")
|
|
68
68
|
|
|
69
69
|
const files = event.dataTransfer.files
|
|
70
70
|
if (files.length > 0) {
|
|
@@ -141,8 +141,8 @@ export default class extends Controller {
|
|
|
141
141
|
<svg class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
142
142
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
|
143
143
|
</svg>
|
|
144
|
-
<span class="font-medium text-slate-900
|
|
145
|
-
<span class="text-slate-500
|
|
144
|
+
<span class="font-medium text-slate-900">${fileName}</span>
|
|
145
|
+
<span class="text-slate-500">(${fileSize})</span>
|
|
146
146
|
</div>
|
|
147
147
|
`
|
|
148
148
|
}
|
|
@@ -173,7 +173,7 @@ export default class extends Controller {
|
|
|
173
173
|
if (!this.hasFilenameTarget) return
|
|
174
174
|
|
|
175
175
|
this.filenameTarget.innerHTML = `
|
|
176
|
-
<div class="flex items-center gap-2 text-red-600
|
|
176
|
+
<div class="flex items-center gap-2 text-red-600">
|
|
177
177
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
178
178
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
179
179
|
</svg>
|
|
@@ -191,10 +191,10 @@ export default class extends Controller {
|
|
|
191
191
|
|
|
192
192
|
this.progressTarget.classList.remove("hidden")
|
|
193
193
|
this.progressTarget.innerHTML = `
|
|
194
|
-
<div class="w-full bg-slate-200
|
|
194
|
+
<div class="w-full bg-slate-200 rounded-full h-2">
|
|
195
195
|
<div class="bg-amber-500 h-2 rounded-full transition-all duration-300" style="width: ${percent}%"></div>
|
|
196
196
|
</div>
|
|
197
|
-
<span class="text-xs text-slate-500
|
|
197
|
+
<span class="text-xs text-slate-500">${percent}%</span>
|
|
198
198
|
`
|
|
199
199
|
}
|
|
200
200
|
|
|
@@ -211,7 +211,7 @@ export default class extends Controller {
|
|
|
211
211
|
|
|
212
212
|
if (this.hasFilenameTarget) {
|
|
213
213
|
this.filenameTarget.innerHTML = `
|
|
214
|
-
<span class="text-slate-500
|
|
214
|
+
<span class="text-slate-500">No file selected</span>
|
|
215
215
|
`
|
|
216
216
|
}
|
|
217
217
|
|
|
@@ -17,12 +17,12 @@ export default class extends Controller {
|
|
|
17
17
|
try {
|
|
18
18
|
JSON.parse(value)
|
|
19
19
|
this.clearError()
|
|
20
|
-
this.inputTarget.classList.remove("border-red-500"
|
|
21
|
-
this.inputTarget.classList.add("border-slate-300"
|
|
20
|
+
this.inputTarget.classList.remove("border-red-500")
|
|
21
|
+
this.inputTarget.classList.add("border-slate-300")
|
|
22
22
|
} catch (e) {
|
|
23
23
|
this.showError(e.message)
|
|
24
|
-
this.inputTarget.classList.remove("border-slate-300"
|
|
25
|
-
this.inputTarget.classList.add("border-red-500"
|
|
24
|
+
this.inputTarget.classList.remove("border-slate-300")
|
|
25
|
+
this.inputTarget.classList.add("border-red-500")
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -38,12 +38,12 @@ export default class extends Controller {
|
|
|
38
38
|
const formatted = JSON.stringify(parsed, null, 2)
|
|
39
39
|
this.inputTarget.value = formatted
|
|
40
40
|
this.clearError()
|
|
41
|
-
this.inputTarget.classList.remove("border-red-500"
|
|
42
|
-
this.inputTarget.classList.add("border-slate-300"
|
|
41
|
+
this.inputTarget.classList.remove("border-red-500")
|
|
42
|
+
this.inputTarget.classList.add("border-slate-300")
|
|
43
43
|
} catch (e) {
|
|
44
44
|
this.showError(e.message)
|
|
45
|
-
this.inputTarget.classList.remove("border-slate-300"
|
|
46
|
-
this.inputTarget.classList.add("border-red-500"
|
|
45
|
+
this.inputTarget.classList.remove("border-slate-300")
|
|
46
|
+
this.inputTarget.classList.add("border-red-500")
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -105,7 +105,7 @@ export default class extends Controller {
|
|
|
105
105
|
renderDropdown() {
|
|
106
106
|
if (!this.filteredOptions.length) {
|
|
107
107
|
this.dropdownTarget.innerHTML = `
|
|
108
|
-
<div class="px-3 py-2 text-sm text-slate-400
|
|
108
|
+
<div class="px-3 py-2 text-sm text-slate-400">No results found</div>
|
|
109
109
|
`
|
|
110
110
|
return
|
|
111
111
|
}
|
|
@@ -114,7 +114,7 @@ export default class extends Controller {
|
|
|
114
114
|
.map(
|
|
115
115
|
(opt, index) => `
|
|
116
116
|
<button type="button"
|
|
117
|
-
class="block w-full text-left px-3 py-2 text-sm hover:bg-slate-100
|
|
117
|
+
class="block w-full text-left px-3 py-2 text-sm hover:bg-slate-100 ${index === this.selectedIndex ? "bg-slate-100" : ""} ${opt.isNew ? "text-indigo-600 font-medium" : "text-slate-700"}"
|
|
118
118
|
data-action="click->admin-suite--searchable-select#select"
|
|
119
119
|
data-value="${opt.value}"
|
|
120
120
|
data-label="${opt.label}">
|
|
@@ -118,7 +118,7 @@ export default class extends Controller {
|
|
|
118
118
|
|
|
119
119
|
const tagEl = document.createElement("span")
|
|
120
120
|
tagEl.className =
|
|
121
|
-
"inline-flex items-center gap-1 px-2 py-1 bg-indigo-100
|
|
121
|
+
"inline-flex items-center gap-1 px-2 py-1 bg-indigo-100 text-indigo-700 rounded text-sm"
|
|
122
122
|
tagEl.innerHTML = `
|
|
123
123
|
${this.escapeHtml(value)}
|
|
124
124
|
<input type="hidden" name="${this.getFieldName()}" value="${this.escapeHtml(value)}">
|
|
@@ -20,7 +20,7 @@ export default class extends Controller {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
get inactiveClasses() {
|
|
23
|
-
return this.hasInactiveClassesValue ? this.inactiveClassesValue : "bg-slate-200
|
|
23
|
+
return this.hasInactiveClassesValue ? this.inactiveClassesValue : "bg-slate-200"
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
toggle(event) {
|
|
@@ -1,21 +1,12 @@
|
|
|
1
|
-
<% content_for :title,
|
|
1
|
+
<% content_for :title, @page_title %>
|
|
2
2
|
|
|
3
3
|
<div class="px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto">
|
|
4
4
|
<div class="mb-6">
|
|
5
|
-
<h1 class="text-2xl font-bold text-slate-900
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
</p>
|
|
9
|
-
</div>
|
|
10
|
-
|
|
11
|
-
<% @dashboard_sections.each do |section| %>
|
|
12
|
-
<% if section[:title].present? %>
|
|
13
|
-
<div class="mt-8 mb-3 flex items-center gap-2 text-sm font-semibold text-slate-900 dark:text-white">
|
|
14
|
-
<%= admin_suite_icon("activity", class: "w-4 h-4 text-slate-400") %>
|
|
15
|
-
<span><%= section[:title] %></span>
|
|
16
|
-
</div>
|
|
5
|
+
<h1 class="text-2xl font-bold text-slate-900"><%= @page_title %></h1>
|
|
6
|
+
<% if @page_description.present? %>
|
|
7
|
+
<p class="text-sm text-slate-500 mt-1"><%= @page_description %></p>
|
|
17
8
|
<% end %>
|
|
9
|
+
</div>
|
|
18
10
|
|
|
19
|
-
|
|
20
|
-
<% end %>
|
|
11
|
+
<%= render_dashboard_rows(@dashboard_rows) %>
|
|
21
12
|
</div>
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<% resources = Array(panel_eval(panel.options[:resources])) %>
|
|
2
2
|
<% variant = (panel.options[:variant] || :default).to_sym %>
|
|
3
3
|
|
|
4
|
-
<div class="bg-white
|
|
5
|
-
<div class="px-4 py-3 border-b border-slate-200
|
|
6
|
-
<h3 class="font-semibold text-slate-900
|
|
4
|
+
<div class="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
|
5
|
+
<div class="px-4 py-3 border-b border-slate-200 flex items-center justify-between">
|
|
6
|
+
<h3 class="font-semibold text-slate-900"><%= panel.title %></h3>
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
9
|
<% if resources.any? %>
|
|
10
10
|
<% if variant == :portals %>
|
|
11
|
-
<div class="p-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
11
|
+
<div class="admin-suite-cards-grid admin-suite-cards-grid--portals p-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
12
12
|
<% resources.each do |portal_item| %>
|
|
13
13
|
<%
|
|
14
14
|
key = portal_item[:key] || portal_item["key"]
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
<% end %>
|
|
53
53
|
</div>
|
|
54
54
|
<% else %>
|
|
55
|
-
<div class="p-4 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
55
|
+
<div class="admin-suite-cards-grid admin-suite-cards-grid--resources p-4 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
56
56
|
<% resources.each do |resource_item| %>
|
|
57
57
|
<%
|
|
58
58
|
if resource_item.is_a?(Hash)
|
|
@@ -102,6 +102,6 @@
|
|
|
102
102
|
</div>
|
|
103
103
|
<% end %>
|
|
104
104
|
<% else %>
|
|
105
|
-
<div class="p-4 text-sm text-slate-500
|
|
105
|
+
<div class="p-4 text-sm text-slate-500">No cards configured.</div>
|
|
106
106
|
<% end %>
|
|
107
107
|
</div>
|
|
@@ -5,20 +5,20 @@
|
|
|
5
5
|
<% max_value = 1.0 if max_value.zero? %>
|
|
6
6
|
|
|
7
7
|
<% bar_color = case color
|
|
8
|
-
when :amber then "bg-amber-500
|
|
9
|
-
when :green then "bg-green-500
|
|
10
|
-
when :red then "bg-red-500
|
|
11
|
-
when :cyan then "bg-cyan-500
|
|
12
|
-
when :violet then "bg-violet-500
|
|
13
|
-
when :indigo then "bg-indigo-500
|
|
14
|
-
else "bg-indigo-500
|
|
8
|
+
when :amber then "bg-amber-500"
|
|
9
|
+
when :green then "bg-green-500"
|
|
10
|
+
when :red then "bg-red-500"
|
|
11
|
+
when :cyan then "bg-cyan-500"
|
|
12
|
+
when :violet then "bg-violet-500"
|
|
13
|
+
when :indigo then "bg-indigo-500"
|
|
14
|
+
else "bg-indigo-500"
|
|
15
15
|
end %>
|
|
16
16
|
|
|
17
|
-
<div class="bg-white
|
|
17
|
+
<div class="bg-white rounded-xl border border-slate-200 p-4">
|
|
18
18
|
<div class="flex items-center justify-between mb-4">
|
|
19
|
-
<h3 class="font-semibold text-slate-900
|
|
19
|
+
<h3 class="font-semibold text-slate-900"><%= panel.title %></h3>
|
|
20
20
|
<% if total.present? %>
|
|
21
|
-
<span class="text-2xl font-bold text-slate-900
|
|
21
|
+
<span class="text-2xl font-bold text-slate-900"><%= total %></span>
|
|
22
22
|
<% end %>
|
|
23
23
|
</div>
|
|
24
24
|
|
|
@@ -38,10 +38,10 @@ end %>
|
|
|
38
38
|
|
|
39
39
|
<div class="flex gap-1 mt-2">
|
|
40
40
|
<% data.each do |d| %>
|
|
41
|
-
<div class="flex-1 text-center text-xs text-slate-400
|
|
41
|
+
<div class="flex-1 text-center text-xs text-slate-400"><%= d[:label].to_s.first(3) %></div>
|
|
42
42
|
<% end %>
|
|
43
43
|
</div>
|
|
44
44
|
<% else %>
|
|
45
|
-
<div class="p-4 text-sm text-slate-500
|
|
45
|
+
<div class="p-4 text-sm text-slate-500">No chart data.</div>
|
|
46
46
|
<% end %>
|
|
47
47
|
</div>
|
|
@@ -3,26 +3,26 @@
|
|
|
3
3
|
|
|
4
4
|
<% status_config = case status
|
|
5
5
|
when :healthy
|
|
6
|
-
{ border: "border-green-200
|
|
7
|
-
badge: "bg-green-100
|
|
6
|
+
{ border: "border-green-200",
|
|
7
|
+
badge: "bg-green-100 text-green-700",
|
|
8
8
|
dot: "bg-green-500 animate-pulse" }
|
|
9
9
|
when :degraded
|
|
10
|
-
{ border: "border-amber-200
|
|
11
|
-
badge: "bg-amber-100
|
|
10
|
+
{ border: "border-amber-200",
|
|
11
|
+
badge: "bg-amber-100 text-amber-700",
|
|
12
12
|
dot: "bg-amber-500 animate-pulse" }
|
|
13
13
|
when :critical
|
|
14
|
-
{ border: "border-red-200
|
|
15
|
-
badge: "bg-red-100
|
|
14
|
+
{ border: "border-red-200",
|
|
15
|
+
badge: "bg-red-100 text-red-700",
|
|
16
16
|
dot: "bg-red-500 animate-pulse" }
|
|
17
17
|
else
|
|
18
|
-
{ border: "border-slate-200
|
|
19
|
-
badge: "bg-slate-100
|
|
18
|
+
{ border: "border-slate-200",
|
|
19
|
+
badge: "bg-slate-100 text-slate-600",
|
|
20
20
|
dot: "bg-slate-400" }
|
|
21
21
|
end %>
|
|
22
22
|
|
|
23
|
-
<div class="bg-white
|
|
24
|
-
<div class="px-4 py-3 border-b border-slate-200
|
|
25
|
-
<h3 class="font-semibold text-slate-900
|
|
23
|
+
<div class="bg-white rounded-xl border <%= status_config[:border] %> overflow-hidden">
|
|
24
|
+
<div class="px-4 py-3 border-b border-slate-200 flex items-center justify-between">
|
|
25
|
+
<h3 class="font-semibold text-slate-900"><%= panel.title %></h3>
|
|
26
26
|
<span class="flex items-center gap-1.5 px-2 py-1 rounded-full text-xs font-medium <%= status_config[:badge] %>">
|
|
27
27
|
<span class="w-2 h-2 rounded-full <%= status_config[:dot] %>"></span>
|
|
28
28
|
<%= status.to_s.humanize %>
|
|
@@ -33,12 +33,12 @@ end %>
|
|
|
33
33
|
<div class="p-4 grid grid-cols-2 gap-3">
|
|
34
34
|
<% metrics.each do |key, val| %>
|
|
35
35
|
<div>
|
|
36
|
-
<div class="text-lg font-semibold text-slate-900
|
|
37
|
-
<div class="text-xs text-slate-500
|
|
36
|
+
<div class="text-lg font-semibold text-slate-900"><%= val %></div>
|
|
37
|
+
<div class="text-xs text-slate-500"><%= key.to_s.humanize %></div>
|
|
38
38
|
</div>
|
|
39
39
|
<% end %>
|
|
40
40
|
</div>
|
|
41
41
|
<% else %>
|
|
42
|
-
<div class="p-4 text-sm text-slate-500
|
|
42
|
+
<div class="p-4 text-sm text-slate-500">No metrics.</div>
|
|
43
43
|
<% end %>
|
|
44
44
|
</div>
|
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
<% view_all_path = panel_eval(panel.options[:view_all_path]) %>
|
|
7
7
|
<% link_proc = panel.options[:link] %>
|
|
8
8
|
|
|
9
|
-
<div class="bg-white
|
|
10
|
-
<div class="px-4 py-3 border-b border-slate-200
|
|
11
|
-
<h3 class="font-semibold text-slate-900
|
|
9
|
+
<div class="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
|
10
|
+
<div class="px-4 py-3 border-b border-slate-200 flex items-center justify-between">
|
|
11
|
+
<h3 class="font-semibold text-slate-900"><%= panel.title %></h3>
|
|
12
12
|
<% if view_all_path.present? %>
|
|
13
13
|
<%= link_to "View all →", view_all_path, class: "text-sm #{theme_link_class}" %>
|
|
14
14
|
<% end %>
|
|
15
15
|
</div>
|
|
16
16
|
|
|
17
17
|
<% if items.any? %>
|
|
18
|
-
<ul class="divide-y divide-slate-100
|
|
18
|
+
<ul class="divide-y divide-slate-100">
|
|
19
19
|
<% items.each do |item| %>
|
|
20
20
|
<% path =
|
|
21
21
|
if link_proc.respond_to?(:call)
|
|
@@ -24,25 +24,25 @@
|
|
|
24
24
|
auto_admin_suite_path_for(item)
|
|
25
25
|
end
|
|
26
26
|
%>
|
|
27
|
-
<li class="px-4 py-3 hover:bg-slate-50
|
|
27
|
+
<li class="px-4 py-3 hover:bg-slate-50 transition-colors">
|
|
28
28
|
<% title = item_display_title(item) rescue item.to_s %>
|
|
29
29
|
<% subtitle = (item.respond_to?(:created_at) && item.created_at) ? "#{time_ago_in_words(item.created_at)} ago" : nil %>
|
|
30
30
|
<% if path.present? %>
|
|
31
31
|
<%= link_to path, class: "flex items-center justify-between gap-3" do %>
|
|
32
32
|
<div class="min-w-0">
|
|
33
|
-
<div class="text-sm font-medium text-slate-900
|
|
33
|
+
<div class="text-sm font-medium text-slate-900 truncate"><%= title.to_s.truncate(48) %></div>
|
|
34
34
|
<% if subtitle.present? %>
|
|
35
|
-
<div class="text-xs text-slate-500
|
|
35
|
+
<div class="text-xs text-slate-500"><%= subtitle %></div>
|
|
36
36
|
<% end %>
|
|
37
37
|
</div>
|
|
38
|
-
<%= admin_suite_icon("chevron-right", class: "w-4 h-4 text-slate-300
|
|
38
|
+
<%= admin_suite_icon("chevron-right", class: "w-4 h-4 text-slate-300 flex-shrink-0") %>
|
|
39
39
|
<% end %>
|
|
40
40
|
<% else %>
|
|
41
41
|
<div class="flex items-center justify-between gap-3">
|
|
42
42
|
<div class="min-w-0">
|
|
43
|
-
<div class="text-sm font-medium text-slate-900
|
|
43
|
+
<div class="text-sm font-medium text-slate-900 truncate"><%= title.to_s.truncate(48) %></div>
|
|
44
44
|
<% if subtitle.present? %>
|
|
45
|
-
<div class="text-xs text-slate-500
|
|
45
|
+
<div class="text-xs text-slate-500"><%= subtitle %></div>
|
|
46
46
|
<% end %>
|
|
47
47
|
</div>
|
|
48
48
|
</div>
|
|
@@ -51,6 +51,6 @@
|
|
|
51
51
|
<% end %>
|
|
52
52
|
</ul>
|
|
53
53
|
<% else %>
|
|
54
|
-
<div class="p-4 text-center text-sm text-slate-500
|
|
54
|
+
<div class="p-4 text-center text-sm text-slate-500"><%= empty_message %></div>
|
|
55
55
|
<% end %>
|
|
56
56
|
</div>
|
|
@@ -7,35 +7,35 @@
|
|
|
7
7
|
|
|
8
8
|
<% color_classes = case color
|
|
9
9
|
when :green
|
|
10
|
-
{ bg: "bg-gradient-to-br from-green-50 to-green-100
|
|
11
|
-
border: "border border-green-200
|
|
12
|
-
text: "text-green-700
|
|
13
|
-
label: "text-green-600
|
|
10
|
+
{ bg: "bg-gradient-to-br from-green-50 to-green-100",
|
|
11
|
+
border: "border border-green-200",
|
|
12
|
+
text: "text-green-700",
|
|
13
|
+
label: "text-green-600" }
|
|
14
14
|
when :red
|
|
15
|
-
{ bg: "bg-gradient-to-br from-red-50 to-red-100
|
|
16
|
-
border: "border border-red-200
|
|
17
|
-
text: "text-red-700
|
|
18
|
-
label: "text-red-600
|
|
15
|
+
{ bg: "bg-gradient-to-br from-red-50 to-red-100",
|
|
16
|
+
border: "border border-red-200",
|
|
17
|
+
text: "text-red-700",
|
|
18
|
+
label: "text-red-600" }
|
|
19
19
|
when :amber
|
|
20
|
-
{ bg: "bg-gradient-to-br from-amber-50 to-amber-100
|
|
21
|
-
border: "border border-amber-200
|
|
22
|
-
text: "text-amber-700
|
|
23
|
-
label: "text-amber-600
|
|
20
|
+
{ bg: "bg-gradient-to-br from-amber-50 to-amber-100",
|
|
21
|
+
border: "border border-amber-200",
|
|
22
|
+
text: "text-amber-700",
|
|
23
|
+
label: "text-amber-600" }
|
|
24
24
|
when :cyan
|
|
25
|
-
{ bg: "bg-gradient-to-br from-cyan-50 to-cyan-100
|
|
26
|
-
border: "border border-cyan-200
|
|
27
|
-
text: "text-cyan-700
|
|
28
|
-
label: "text-cyan-600
|
|
25
|
+
{ bg: "bg-gradient-to-br from-cyan-50 to-cyan-100",
|
|
26
|
+
border: "border border-cyan-200",
|
|
27
|
+
text: "text-cyan-700",
|
|
28
|
+
label: "text-cyan-600" }
|
|
29
29
|
when :violet
|
|
30
|
-
{ bg: "bg-gradient-to-br from-violet-50 to-violet-100
|
|
31
|
-
border: "border border-violet-200
|
|
32
|
-
text: "text-violet-700
|
|
33
|
-
label: "text-violet-600
|
|
30
|
+
{ bg: "bg-gradient-to-br from-violet-50 to-violet-100",
|
|
31
|
+
border: "border border-violet-200",
|
|
32
|
+
text: "text-violet-700",
|
|
33
|
+
label: "text-violet-600" }
|
|
34
34
|
else
|
|
35
|
-
{ bg: "bg-white
|
|
36
|
-
border: "border border-slate-200
|
|
37
|
-
text: "text-slate-900
|
|
38
|
-
label: "text-slate-500
|
|
35
|
+
{ bg: "bg-white",
|
|
36
|
+
border: "border border-slate-200",
|
|
37
|
+
text: "text-slate-900",
|
|
38
|
+
label: "text-slate-500" }
|
|
39
39
|
end %>
|
|
40
40
|
|
|
41
41
|
<% if variant == :mini %>
|
|
@@ -2,28 +2,28 @@
|
|
|
2
2
|
<% rows = Array(rows_value) %>
|
|
3
3
|
<% columns = Array(panel.options[:columns]).presence %>
|
|
4
4
|
|
|
5
|
-
<div class="bg-white
|
|
6
|
-
<div class="px-4 py-3 border-b border-slate-200
|
|
7
|
-
<h3 class="font-semibold text-slate-900
|
|
5
|
+
<div class="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
|
6
|
+
<div class="px-4 py-3 border-b border-slate-200 flex items-center justify-between">
|
|
7
|
+
<h3 class="font-semibold text-slate-900"><%= panel.title %></h3>
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
10
|
<% if rows.any? %>
|
|
11
11
|
<% columns ||= rows.first.respond_to?(:attributes) ? rows.first.attributes.keys.first(6).map(&:to_sym) : [] %>
|
|
12
12
|
<div class="overflow-x-auto">
|
|
13
|
-
<table class="min-w-full divide-y divide-slate-200
|
|
14
|
-
<thead class="bg-slate-50/50
|
|
13
|
+
<table class="min-w-full divide-y divide-slate-200">
|
|
14
|
+
<thead class="bg-slate-50/50">
|
|
15
15
|
<tr>
|
|
16
16
|
<% columns.each do |col| %>
|
|
17
|
-
<th class="px-4 py-2.5 text-left text-xs font-medium text-slate-500
|
|
17
|
+
<th class="px-4 py-2.5 text-left text-xs font-medium text-slate-500 uppercase tracking-wider"><%= col.to_s.humanize %></th>
|
|
18
18
|
<% end %>
|
|
19
19
|
</tr>
|
|
20
20
|
</thead>
|
|
21
|
-
<tbody class="divide-y divide-slate-200
|
|
21
|
+
<tbody class="divide-y divide-slate-200">
|
|
22
22
|
<% rows.each do |row| %>
|
|
23
|
-
<tr class="hover:bg-slate-50/50
|
|
23
|
+
<tr class="hover:bg-slate-50/50">
|
|
24
24
|
<% columns.each do |col| %>
|
|
25
25
|
<% val = row.respond_to?(col) ? (row.public_send(col) rescue nil) : (row[col] rescue nil) %>
|
|
26
|
-
<td class="px-4 py-3 text-sm text-slate-700
|
|
26
|
+
<td class="px-4 py-3 text-sm text-slate-700"><%= format_table_cell(val) rescue val.to_s %></td>
|
|
27
27
|
<% end %>
|
|
28
28
|
</tr>
|
|
29
29
|
<% end %>
|
|
@@ -31,6 +31,6 @@
|
|
|
31
31
|
</table>
|
|
32
32
|
</div>
|
|
33
33
|
<% else %>
|
|
34
|
-
<div class="p-4 text-sm text-slate-500
|
|
34
|
+
<div class="p-4 text-sm text-slate-500">No rows.</div>
|
|
35
35
|
<% end %>
|
|
36
36
|
</div>
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
<%= render_dashboard_rows(@dashboard_rows) %>
|
|
36
36
|
<% else %>
|
|
37
37
|
<!-- Sections (fallback) -->
|
|
38
|
-
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
38
|
+
<div class="admin-suite-portal-sections-grid grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
39
39
|
<% @sections.each do |_section_key, section| %>
|
|
40
40
|
<div class="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
|
41
41
|
<div class="px-6 py-4 border-b border-slate-200 bg-slate-50 flex items-center justify-between">
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<% columns = resource.class.column_names.reject { |c| %w[id created_at updated_at].include?(c) } %>
|
|
18
18
|
<% columns.each do |column| %>
|
|
19
19
|
<div>
|
|
20
|
-
<%= f.label column, class: "block text-sm font-medium text-slate-700
|
|
20
|
+
<%= f.label column, class: "block text-sm font-medium text-slate-700 mb-1" %>
|
|
21
21
|
<%= f.text_field column, class: "form-input w-full" %>
|
|
22
22
|
</div>
|
|
23
23
|
<% end %>
|
|
@@ -3,20 +3,20 @@
|
|
|
3
3
|
<div class="px-4 sm:px-6 lg:px-8 max-w-3xl mx-auto">
|
|
4
4
|
<!-- Header -->
|
|
5
5
|
<div class="mb-6">
|
|
6
|
-
<nav class="flex items-center gap-2 text-sm text-slate-500
|
|
6
|
+
<nav class="flex items-center gap-2 text-sm text-slate-500 mb-2">
|
|
7
7
|
<%= link_to "Dashboard", root_path, class: theme_link_hover_text_class %>
|
|
8
8
|
<%= admin_suite_icon("chevron-right", class: "w-4 h-4") %>
|
|
9
9
|
<%= link_to resource_config.human_name_plural, url_for(action: :index), class: theme_link_hover_text_class %>
|
|
10
10
|
<%= admin_suite_icon("chevron-right", class: "w-4 h-4") %>
|
|
11
11
|
<%= link_to @resource.respond_to?(:name) ? @resource.name.truncate(20) : "##{@resource.id}", url_for(action: :show, id: @resource.id), class: theme_link_hover_text_class %>
|
|
12
12
|
<%= admin_suite_icon("chevron-right", class: "w-4 h-4") %>
|
|
13
|
-
<span class="text-slate-900
|
|
13
|
+
<span class="text-slate-900">Edit</span>
|
|
14
14
|
</nav>
|
|
15
|
-
<h1 class="text-2xl font-bold text-slate-900
|
|
15
|
+
<h1 class="text-2xl font-bold text-slate-900">Edit <%= resource_config.human_name %></h1>
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
18
|
<!-- Form -->
|
|
19
|
-
<div class="bg-white
|
|
19
|
+
<div class="bg-white rounded-xl border border-slate-200 overflow-hidden">
|
|
20
20
|
<div class="p-6">
|
|
21
21
|
<%= render "admin_suite/shared/form", resource: @resource %>
|
|
22
22
|
</div>
|