super_admin 0.2.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/README.md +216 -0
- data/Rakefile +30 -0
- data/app/assets/stylesheets/super_admin/application.css +15 -0
- data/app/assets/stylesheets/super_admin/tailwind.css +1 -0
- data/app/assets/stylesheets/super_admin/tailwind.source.css +25 -0
- data/app/controllers/super_admin/application_controller.rb +89 -0
- data/app/controllers/super_admin/associations_controller.rb +136 -0
- data/app/controllers/super_admin/audit_logs_controller.rb +39 -0
- data/app/controllers/super_admin/base_controller.rb +133 -0
- data/app/controllers/super_admin/dashboard_controller.rb +29 -0
- data/app/controllers/super_admin/exports_controller.rb +109 -0
- data/app/controllers/super_admin/resources_controller.rb +201 -0
- data/app/dashboards/super_admin/base_dashboard.rb +200 -0
- data/app/errors/super_admin/configuration_error.rb +6 -0
- data/app/helpers/super_admin/application_helper.rb +84 -0
- data/app/helpers/super_admin/exports_helper.rb +16 -0
- data/app/helpers/super_admin/resources_helper.rb +204 -0
- data/app/helpers/super_admin/route_helper.rb +7 -0
- data/app/javascript/super_admin/application.js +263 -0
- data/app/jobs/super_admin/application_job.rb +4 -0
- data/app/jobs/super_admin/generate_super_admin_csv_export_job.rb +100 -0
- data/app/mailers/super_admin/application_mailer.rb +6 -0
- data/app/models/super_admin/application_record.rb +5 -0
- data/app/models/super_admin/audit_log.rb +35 -0
- data/app/models/super_admin/csv_export.rb +67 -0
- data/app/services/super_admin/auditing.rb +74 -0
- data/app/services/super_admin/authorization.rb +113 -0
- data/app/services/super_admin/authorization_adapters/base_adapter.rb +100 -0
- data/app/services/super_admin/authorization_adapters/default_adapter.rb +77 -0
- data/app/services/super_admin/authorization_adapters/proc_adapter.rb +65 -0
- data/app/services/super_admin/authorization_adapters/pundit_adapter.rb +81 -0
- data/app/services/super_admin/csv_export_creator.rb +45 -0
- data/app/services/super_admin/dashboard_registry.rb +90 -0
- data/app/services/super_admin/dashboard_resolver.rb +100 -0
- data/app/services/super_admin/filter_builder.rb +185 -0
- data/app/services/super_admin/form_builder.rb +59 -0
- data/app/services/super_admin/form_fields/array_field.rb +35 -0
- data/app/services/super_admin/form_fields/association_field.rb +146 -0
- data/app/services/super_admin/form_fields/base_field.rb +53 -0
- data/app/services/super_admin/form_fields/boolean_field.rb +29 -0
- data/app/services/super_admin/form_fields/date_field.rb +15 -0
- data/app/services/super_admin/form_fields/date_time_field.rb +15 -0
- data/app/services/super_admin/form_fields/enum_field.rb +27 -0
- data/app/services/super_admin/form_fields/factory.rb +102 -0
- data/app/services/super_admin/form_fields/nested_field.rb +120 -0
- data/app/services/super_admin/form_fields/number_field.rb +29 -0
- data/app/services/super_admin/form_fields/text_area_field.rb +19 -0
- data/app/services/super_admin/model_inspector.rb +182 -0
- data/app/services/super_admin/queries/base_query.rb +45 -0
- data/app/services/super_admin/queries/filter_query.rb +188 -0
- data/app/services/super_admin/queries/resource_scope_query.rb +74 -0
- data/app/services/super_admin/queries/search_query.rb +146 -0
- data/app/services/super_admin/queries/sort_query.rb +41 -0
- data/app/services/super_admin/resource_configuration.rb +63 -0
- data/app/services/super_admin/resource_exporter.rb +78 -0
- data/app/services/super_admin/resource_query.rb +40 -0
- data/app/services/super_admin/resources/association_inspector.rb +112 -0
- data/app/services/super_admin/resources/collection_presenter.rb +63 -0
- data/app/services/super_admin/resources/context.rb +63 -0
- data/app/services/super_admin/resources/filter_params.rb +29 -0
- data/app/services/super_admin/resources/permitted_attributes.rb +104 -0
- data/app/services/super_admin/resources/value_normalizer.rb +121 -0
- data/app/services/super_admin/sensitive_attributes.rb +166 -0
- data/app/views/layouts/super_admin.html.erb +74 -0
- data/app/views/super_admin/audit_logs/index.html.erb +143 -0
- data/app/views/super_admin/dashboard/index.html.erb +79 -0
- data/app/views/super_admin/exports/index.html.erb +84 -0
- data/app/views/super_admin/exports/show.html.erb +57 -0
- data/app/views/super_admin/resources/_form.html.erb +42 -0
- data/app/views/super_admin/resources/destroy.turbo_stream.erb +17 -0
- data/app/views/super_admin/resources/edit.html.erb +37 -0
- data/app/views/super_admin/resources/index.html.erb +189 -0
- data/app/views/super_admin/resources/new.html.erb +31 -0
- data/app/views/super_admin/resources/show.html.erb +106 -0
- data/app/views/super_admin/shared/_breadcrumbs.html.erb +12 -0
- data/app/views/super_admin/shared/_custom_styles.html.erb +132 -0
- data/app/views/super_admin/shared/_flash.html.erb +55 -0
- data/app/views/super_admin/shared/_form_field.html.erb +35 -0
- data/app/views/super_admin/shared/_navigation.html.erb +92 -0
- data/app/views/super_admin/shared/_nested_fields.html.erb +59 -0
- data/app/views/super_admin/shared/_nested_record_fields.html.erb +45 -0
- data/config/importmap.rb +4 -0
- data/config/initializers/rack_attack.rb +134 -0
- data/config/initializers/super_admin.rb +117 -0
- data/config/locales/super_admin.en.yml +197 -0
- data/config/locales/super_admin.fr.yml +197 -0
- data/config/routes.rb +22 -0
- data/lib/generators/super_admin/dashboard_generator.rb +50 -0
- data/lib/generators/super_admin/install_generator.rb +58 -0
- data/lib/generators/super_admin/templates/20240101000001_create_super_admin_audit_logs.rb +24 -0
- data/lib/generators/super_admin/templates/20240101000002_create_super_admin_csv_exports.rb +33 -0
- data/lib/generators/super_admin/templates/super_admin.rb +58 -0
- data/lib/super_admin/dashboard_creator.rb +256 -0
- data/lib/super_admin/engine.rb +53 -0
- data/lib/super_admin/install_task.rb +96 -0
- data/lib/super_admin/version.rb +3 -0
- data/lib/super_admin.rb +7 -0
- data/lib/tasks/super_admin_tasks.rake +38 -0
- metadata +239 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="<%= I18n.locale %>">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<% page_title = content_for?(:title) ? content_for(:title) : t("super_admin.layout.default_page_title") %>
|
|
7
|
+
<title><%= t("super_admin.layout.window_title", page_title: page_title) %></title>
|
|
8
|
+
<%= csrf_meta_tags %>
|
|
9
|
+
<%= csp_meta_tag %>
|
|
10
|
+
|
|
11
|
+
<!-- SuperAdmin precompiled Tailwind CSS -->
|
|
12
|
+
<%= stylesheet_link_tag "super_admin/tailwind", "data-turbo-track": "reload" %>
|
|
13
|
+
<%= stylesheet_link_tag "super_admin/application", "data-turbo-track": "reload" %>
|
|
14
|
+
|
|
15
|
+
<%= javascript_importmap_tags if respond_to?(:javascript_importmap_tags) %>
|
|
16
|
+
<%= javascript_import_module_tag("super_admin/application") if respond_to?(:javascript_import_module_tag) %>
|
|
17
|
+
</head>
|
|
18
|
+
|
|
19
|
+
<body class="bg-gray-50" data-controller="super-admin--mobile-menu" data-action="keydown@window->super-admin--mobile-menu#close">
|
|
20
|
+
<div class="min-h-screen flex">
|
|
21
|
+
<!-- Mobile Menu Overlay -->
|
|
22
|
+
<div id="mobile-menu-overlay" data-super-admin--mobile-menu-target="overlay" data-action="click->super-admin--mobile-menu#toggle" class="fixed inset-0 bg-gray-900 bg-opacity-50 z-30 lg:hidden hidden"></div>
|
|
23
|
+
|
|
24
|
+
<!-- Sidebar -->
|
|
25
|
+
<%= render "super_admin/shared/navigation" %>
|
|
26
|
+
|
|
27
|
+
<!-- Main Content -->
|
|
28
|
+
<div class="flex-1 flex flex-col min-w-0">
|
|
29
|
+
<!-- Top Bar -->
|
|
30
|
+
<header class="bg-white border-b border-gray-200 sticky top-0 z-20">
|
|
31
|
+
<div class="px-4 sm:px-6 lg:px-8 py-4">
|
|
32
|
+
<div class="flex items-center justify-between lg:justify-start">
|
|
33
|
+
<!-- Mobile menu button -->
|
|
34
|
+
<button id="mobile-menu-button" data-action="click->super-admin--mobile-menu#toggle" class="lg:hidden inline-flex items-center justify-center p-2 rounded-md text-gray-700 hover:text-gray-900 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500">
|
|
35
|
+
<span class="sr-only">Open menu</span>
|
|
36
|
+
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
37
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
38
|
+
</svg>
|
|
39
|
+
</button>
|
|
40
|
+
|
|
41
|
+
<!-- Breadcrumbs -->
|
|
42
|
+
<div class="flex-1 min-w-0">
|
|
43
|
+
<%= render "super_admin/shared/breadcrumbs" if content_for?(:breadcrumbs) %>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</header>
|
|
48
|
+
|
|
49
|
+
<!-- Flash Messages -->
|
|
50
|
+
<% if flash.any? %>
|
|
51
|
+
<div class="px-4 sm:px-6 lg:px-8 py-4 space-y-2">
|
|
52
|
+
<% flash.each do |type, message| %>
|
|
53
|
+
<%= render "super_admin/shared/flash", type: type, message: message %>
|
|
54
|
+
<% end %>
|
|
55
|
+
</div>
|
|
56
|
+
<% end %>
|
|
57
|
+
|
|
58
|
+
<!-- Page Content -->
|
|
59
|
+
<main class="flex-1 px-4 sm:px-6 lg:px-8 py-6 sm:py-8">
|
|
60
|
+
<%= yield %>
|
|
61
|
+
</main>
|
|
62
|
+
|
|
63
|
+
<!-- Footer -->
|
|
64
|
+
<footer class="bg-white border-t border-gray-200 mt-auto">
|
|
65
|
+
<div class="px-4 sm:px-6 lg:px-8 py-4">
|
|
66
|
+
<p class="text-sm text-gray-500 text-center">
|
|
67
|
+
<%= t("super_admin.layout.footer", year: Date.current.year) %>
|
|
68
|
+
</p>
|
|
69
|
+
</div>
|
|
70
|
+
</footer>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</body>
|
|
74
|
+
</html>
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<% content_for :title, t("super_admin.audit_logs.title") %>
|
|
2
|
+
<% content_for :breadcrumbs do %>
|
|
3
|
+
<li class="flex items-center">
|
|
4
|
+
<span class="text-gray-900 font-medium"><%= t("super_admin.audit_logs.title") %></span>
|
|
5
|
+
</li>
|
|
6
|
+
<% end %>
|
|
7
|
+
|
|
8
|
+
<div class="max-w-7xl mx-auto">
|
|
9
|
+
<!-- Header -->
|
|
10
|
+
<div class="mb-8">
|
|
11
|
+
<h1 class="text-3xl font-bold text-gray-900"><%= t("super_admin.audit_logs.title") %></h1>
|
|
12
|
+
<p class="mt-2 text-sm text-gray-600">
|
|
13
|
+
<%= t("super_admin.audit_logs.subtitle") %>
|
|
14
|
+
</p>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<!-- Filters -->
|
|
18
|
+
<div class="mb-6 bg-white rounded-lg shadow border border-gray-200 p-4">
|
|
19
|
+
<%= form_with url: super_admin_audit_logs_path, method: :get, local: true, class: "flex flex-wrap gap-3 items-end" do |form| %>
|
|
20
|
+
<div class="flex-1 min-w-[200px]">
|
|
21
|
+
<%= form.text_field :query, value: params[:query], placeholder: t("super_admin.audit_logs.filters.search"), class: "sa-input w-full" %>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="min-w-[180px]">
|
|
24
|
+
<%= form.select :action_type, audit_action_options, include_blank: t("super_admin.audit_logs.filters.all_actions"), selected: params[:action_type], class: "sa-select w-full" %>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="min-w-[180px]">
|
|
27
|
+
<%= form.select :resource_type, audit_resource_options, include_blank: t("super_admin.audit_logs.filters.all_resources"), selected: params[:resource_type], class: "sa-select w-full" %>
|
|
28
|
+
</div>
|
|
29
|
+
<%= form.submit t("super_admin.audit_logs.filters.apply"), class: "sa-btn sa-btn-primary" %>
|
|
30
|
+
<% end %>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<!-- Audit Logs Table -->
|
|
34
|
+
<div class="bg-white rounded-lg shadow border border-gray-200 overflow-hidden">
|
|
35
|
+
<div class="px-6 py-4 border-b border-gray-200">
|
|
36
|
+
<h2 class="text-lg font-medium text-gray-900"><%= t("super_admin.audit_logs.table_title") %></h2>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="overflow-x-auto">
|
|
39
|
+
<% if @audit_logs.any? %>
|
|
40
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
41
|
+
<thead class="bg-gray-50">
|
|
42
|
+
<tr>
|
|
43
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
44
|
+
<%= t("super_admin.audit_logs.headers.performed_at") %>
|
|
45
|
+
</th>
|
|
46
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
47
|
+
<%= t("super_admin.audit_logs.headers.user") %>
|
|
48
|
+
</th>
|
|
49
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
50
|
+
<%= t("super_admin.audit_logs.headers.resource") %>
|
|
51
|
+
</th>
|
|
52
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
53
|
+
<%= t("super_admin.audit_logs.headers.action") %>
|
|
54
|
+
</th>
|
|
55
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
56
|
+
<%= t("super_admin.audit_logs.headers.changes") %>
|
|
57
|
+
</th>
|
|
58
|
+
</tr>
|
|
59
|
+
</thead>
|
|
60
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
61
|
+
<% @audit_logs.each do |log| %>
|
|
62
|
+
<tr class="hover:bg-gray-50">
|
|
63
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
|
64
|
+
<%= l log.performed_at, format: :long %>
|
|
65
|
+
</td>
|
|
66
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
|
67
|
+
<div class="flex items-center">
|
|
68
|
+
<div class="flex-shrink-0 h-8 w-8 bg-gray-200 rounded-full flex items-center justify-center">
|
|
69
|
+
<span class="text-xs font-medium text-gray-700">
|
|
70
|
+
<%= (log.user_email || t("super_admin.audit_logs.unknown_user"))[0..1].upcase %>
|
|
71
|
+
</span>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="ml-3">
|
|
74
|
+
<div class="text-sm font-medium text-gray-900">
|
|
75
|
+
<%= log.user_email || t("super_admin.audit_logs.unknown_user") %>
|
|
76
|
+
</div>
|
|
77
|
+
<% if log.user_id.present? %>
|
|
78
|
+
<div class="text-xs text-gray-500">
|
|
79
|
+
ID: <%= log.user_id %>
|
|
80
|
+
</div>
|
|
81
|
+
<% end %>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</td>
|
|
85
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
|
86
|
+
<div class="text-sm font-medium text-gray-900">
|
|
87
|
+
<%= log.resource_type %>
|
|
88
|
+
</div>
|
|
89
|
+
<% if log.resource_id.present? %>
|
|
90
|
+
<div class="text-xs text-gray-500">
|
|
91
|
+
ID: <%= log.resource_id %>
|
|
92
|
+
</div>
|
|
93
|
+
<% end %>
|
|
94
|
+
</td>
|
|
95
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
|
96
|
+
<span class="inline-flex px-2 py-1 text-xs font-semibold leading-5 rounded-full <%= action_badge_class(log.action) %>">
|
|
97
|
+
<%= log.action.humanize %>
|
|
98
|
+
</span>
|
|
99
|
+
</td>
|
|
100
|
+
<td class="px-6 py-4 text-sm text-gray-900">
|
|
101
|
+
<% if log.changes_snapshot.present? %>
|
|
102
|
+
<details class="cursor-pointer">
|
|
103
|
+
<summary class="text-sm font-medium text-blue-600 hover:text-blue-900">
|
|
104
|
+
<%= t("super_admin.audit_logs.view_changes") %>
|
|
105
|
+
</summary>
|
|
106
|
+
<pre class="mt-2 whitespace-pre-wrap text-xs bg-gray-50 rounded p-2 max-w-md overflow-auto"><%= JSON.pretty_generate(log.changes_snapshot) %></pre>
|
|
107
|
+
</details>
|
|
108
|
+
<% else %>
|
|
109
|
+
<span class="text-gray-400">—</span>
|
|
110
|
+
<% end %>
|
|
111
|
+
<% if log.context.present? %>
|
|
112
|
+
<details class="mt-2 cursor-pointer">
|
|
113
|
+
<summary class="text-xs text-gray-500 hover:text-gray-700">
|
|
114
|
+
<%= t("super_admin.audit_logs.headers.context") %>
|
|
115
|
+
</summary>
|
|
116
|
+
<pre class="mt-1 whitespace-pre-wrap text-xs bg-gray-50 rounded p-2 max-w-md overflow-auto"><%= JSON.pretty_generate(log.context) %></pre>
|
|
117
|
+
</details>
|
|
118
|
+
<% end %>
|
|
119
|
+
</td>
|
|
120
|
+
</tr>
|
|
121
|
+
<% end %>
|
|
122
|
+
</tbody>
|
|
123
|
+
</table>
|
|
124
|
+
<% else %>
|
|
125
|
+
<div class="p-12 text-center">
|
|
126
|
+
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
127
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
128
|
+
</svg>
|
|
129
|
+
<p class="mt-4 text-sm text-gray-500">
|
|
130
|
+
<%= t("super_admin.audit_logs.empty") %>
|
|
131
|
+
</p>
|
|
132
|
+
</div>
|
|
133
|
+
<% end %>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<!-- Pagination -->
|
|
138
|
+
<% if defined?(@pagy) && @pagy.pages > 1 %>
|
|
139
|
+
<div class="mt-6">
|
|
140
|
+
<%= pagy_nav(@pagy) %>
|
|
141
|
+
</div>
|
|
142
|
+
<% end %>
|
|
143
|
+
</div>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<% content_for :title, t("super_admin.dashboard.index.page_title") %>
|
|
2
|
+
<% content_for :breadcrumbs do %>
|
|
3
|
+
<li class="flex items-center">
|
|
4
|
+
<span class="text-gray-900 font-medium"><%= t("super_admin.dashboard.index.breadcrumb") %></span>
|
|
5
|
+
</li>
|
|
6
|
+
<% end %>
|
|
7
|
+
|
|
8
|
+
<div class="max-w-full mx-auto">
|
|
9
|
+
<!-- Header -->
|
|
10
|
+
<div class="mb-6 sm:mb-8">
|
|
11
|
+
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900"><%= t("super_admin.dashboard.index.heading") %></h1>
|
|
12
|
+
<p class="mt-2 text-xs sm:text-sm text-gray-600">
|
|
13
|
+
<%= t("super_admin.dashboard.index.intro") %>
|
|
14
|
+
</p>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<!-- Models Table -->
|
|
18
|
+
<div class="bg-white rounded-lg shadow border border-gray-200 overflow-hidden">
|
|
19
|
+
<div class="px-4 sm:px-6 py-3 sm:py-4 border-b border-gray-200">
|
|
20
|
+
<h2 class="text-base sm:text-lg font-medium text-gray-900"><%= t("super_admin.dashboard.index.table.title") %></h2>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="overflow-x-auto -mx-3 sm:mx-0">
|
|
23
|
+
<div class="inline-block min-w-full align-middle">
|
|
24
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
25
|
+
<thead class="bg-gray-50">
|
|
26
|
+
<tr>
|
|
27
|
+
<th scope="col" class="px-3 sm:px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider whitespace-nowrap">
|
|
28
|
+
<%= t("super_admin.dashboard.index.table.headers.model") %>
|
|
29
|
+
</th>
|
|
30
|
+
<th scope="col" class="hidden sm:table-cell px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
31
|
+
<%= t("super_admin.dashboard.index.table.headers.table") %>
|
|
32
|
+
</th>
|
|
33
|
+
<th scope="col" class="px-3 sm:px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider whitespace-nowrap">
|
|
34
|
+
<%= t("super_admin.dashboard.index.table.headers.records") %>
|
|
35
|
+
</th>
|
|
36
|
+
<th scope="col" class="px-3 sm:px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider whitespace-nowrap">
|
|
37
|
+
<%= t("super_admin.dashboard.index.table.headers.actions") %>
|
|
38
|
+
</th>
|
|
39
|
+
</tr>
|
|
40
|
+
</thead>
|
|
41
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
42
|
+
<% @models_info.each do |model_info| %>
|
|
43
|
+
<tr class="hover:bg-gray-50">
|
|
44
|
+
<td class="px-3 sm:px-6 py-3 sm:py-4 whitespace-nowrap">
|
|
45
|
+
<div class="flex items-center">
|
|
46
|
+
<div class="flex-shrink-0 h-8 w-8 sm:h-10 sm:w-10 bg-gray-200 rounded-full flex items-center justify-center">
|
|
47
|
+
<span class="text-xs sm:text-sm font-medium text-gray-700">
|
|
48
|
+
<%= model_info[:name][0..1].upcase %>
|
|
49
|
+
</span>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="ml-3 sm:ml-4 min-w-0">
|
|
52
|
+
<div class="text-xs sm:text-sm font-medium text-gray-900 truncate">
|
|
53
|
+
<%= model_info[:human_name] %>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="text-xs sm:text-sm text-gray-500 truncate">
|
|
56
|
+
<%= model_info[:name] %>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</td>
|
|
61
|
+
<td class="hidden sm:table-cell px-6 py-4 whitespace-nowrap">
|
|
62
|
+
<span class="inline-flex px-2 py-1 text-xs font-mono leading-5 rounded-md bg-gray-100 text-gray-800">
|
|
63
|
+
<%= model_info[:table_name] %>
|
|
64
|
+
</span>
|
|
65
|
+
</td>
|
|
66
|
+
<td class="px-3 sm:px-6 py-3 sm:py-4 whitespace-nowrap text-xs sm:text-sm text-gray-900">
|
|
67
|
+
<%= number_with_delimiter(model_info[:count]) %>
|
|
68
|
+
</td>
|
|
69
|
+
<td class="px-3 sm:px-6 py-3 sm:py-4 whitespace-nowrap text-right text-xs sm:text-sm font-medium">
|
|
70
|
+
<%= link_to t("super_admin.dashboard.index.table.manage"), model_info[:path], class: "text-blue-600 hover-text-blue-900" %>
|
|
71
|
+
</td>
|
|
72
|
+
</tr>
|
|
73
|
+
<% end %>
|
|
74
|
+
</tbody>
|
|
75
|
+
</table>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<% content_for :title, t("super_admin.exports.index.title") %>
|
|
2
|
+
<% content_for :breadcrumbs do %>
|
|
3
|
+
<li class="flex items-center">
|
|
4
|
+
<svg class="h-5 w-5 text-gray-400 mx-2" fill="currentColor" viewBox="0 0 20 20">
|
|
5
|
+
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
|
|
6
|
+
</svg>
|
|
7
|
+
<span class="text-gray-900 font-medium"><%= t("super_admin.exports.index.breadcrumb") %></span>
|
|
8
|
+
</li>
|
|
9
|
+
<% end %>
|
|
10
|
+
|
|
11
|
+
<div class="max-w-5xl mx-auto">
|
|
12
|
+
<div class="flex items-center justify-between mb-6">
|
|
13
|
+
<div>
|
|
14
|
+
<h1 class="text-3xl font-bold text-gray-900"><%= t("super_admin.exports.index.heading") %></h1>
|
|
15
|
+
<p class="mt-1 text-sm text-gray-600"><%= t("super_admin.exports.index.subtitle") %></p>
|
|
16
|
+
</div>
|
|
17
|
+
<%= link_to t("super_admin.exports.index.back_to_resources"), super_admin_root_path, class: "inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div class="bg-white rounded-lg shadow border border-gray-200 overflow-hidden">
|
|
21
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
22
|
+
<thead class="bg-gray-50">
|
|
23
|
+
<tr>
|
|
24
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"><%= t("super_admin.exports.index.table.headers.created_at") %></th>
|
|
25
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"><%= t("super_admin.exports.index.table.headers.resource") %></th>
|
|
26
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"><%= t("super_admin.exports.index.table.headers.status") %></th>
|
|
27
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"><%= t("super_admin.exports.index.table.headers.records") %></th>
|
|
28
|
+
<th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider"><%= t("super_admin.exports.index.table.headers.actions") %></th>
|
|
29
|
+
</tr>
|
|
30
|
+
</thead>
|
|
31
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
32
|
+
<% if @exports.any? %>
|
|
33
|
+
<% @exports.each do |export| %>
|
|
34
|
+
<tr class="hover:bg-gray-50">
|
|
35
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900"><%= l(export.created_at, format: :long) %></td>
|
|
36
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600"><%= export.resource_name.humanize %></td>
|
|
37
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm">
|
|
38
|
+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= export_badge_classes(export) %>">
|
|
39
|
+
<%= t("super_admin.exports.status.#{export.status}") %>
|
|
40
|
+
</span>
|
|
41
|
+
<% if export.failed_status? && export.error_message.present? %>
|
|
42
|
+
<p class="mt-1 text-xs text-red-600"><%= export.error_message %></p>
|
|
43
|
+
<% elsif export.processing_status? %>
|
|
44
|
+
<p class="mt-1 text-xs text-gray-500"><%= t("super_admin.exports.index.processing_hint") %></p>
|
|
45
|
+
<% elsif export.ready_status? && export.expires_at.present? %>
|
|
46
|
+
<p class="mt-1 text-xs text-gray-500"><%= t("super_admin.exports.index.expiration_hint", date: l(export.expires_at, format: :short)) %></p>
|
|
47
|
+
<% end %>
|
|
48
|
+
</td>
|
|
49
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
|
|
50
|
+
<% if export.records_count.present? %>
|
|
51
|
+
<%= number_with_delimiter(export.records_count) %>
|
|
52
|
+
<% else %>
|
|
53
|
+
<span class="text-gray-400">—</span>
|
|
54
|
+
<% end %>
|
|
55
|
+
</td>
|
|
56
|
+
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
57
|
+
<div class="flex items-center justify-end gap-3">
|
|
58
|
+
<% if export.ready_for_download? %>
|
|
59
|
+
<%= link_to t("super_admin.exports.index.download"), download_super_admin_export_path(export.token), class: "text-blue-600 hover-text-blue-900" %>
|
|
60
|
+
<% else %>
|
|
61
|
+
<span class="text-gray-400"><%= t("super_admin.exports.index.pending") %></span>
|
|
62
|
+
<% end %>
|
|
63
|
+
<%= button_to t("super_admin.exports.index.delete"), super_admin_export_path(export.token), method: :delete, form: { data: { turbo_confirm: t("super_admin.exports.index.delete_confirm") } }, class: "text-red-600 hover-text-red-900" %>
|
|
64
|
+
</div>
|
|
65
|
+
</td>
|
|
66
|
+
</tr>
|
|
67
|
+
<% end %>
|
|
68
|
+
<% else %>
|
|
69
|
+
<tr>
|
|
70
|
+
<td colspan="5" class="px-6 py-8 text-center text-sm text-gray-500">
|
|
71
|
+
<%= t("super_admin.exports.index.empty") %>
|
|
72
|
+
</td>
|
|
73
|
+
</tr>
|
|
74
|
+
<% end %>
|
|
75
|
+
</tbody>
|
|
76
|
+
</table>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<% if @pagy.pages > 1 %>
|
|
80
|
+
<div class="bg-white px-4 py-3 border-t border-gray-200 sm:px-6">
|
|
81
|
+
<%== pagy_nav(@pagy) %>
|
|
82
|
+
</div>
|
|
83
|
+
<% end %>
|
|
84
|
+
</div>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<% content_for :title, t("super_admin.exports.show.title", token: @export.token) %>
|
|
2
|
+
<% content_for :breadcrumbs do %>
|
|
3
|
+
<li class="flex items-center">
|
|
4
|
+
<svg class="h-5 w-5 text-gray-400 mx-2" fill="currentColor" viewBox="0 0 20 20">
|
|
5
|
+
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
|
|
6
|
+
</svg>
|
|
7
|
+
<%= link_to t("super_admin.exports.index.breadcrumb"), super_admin_exports_path, class: "text-blue-600 hover-text-blue-800" %>
|
|
8
|
+
</li>
|
|
9
|
+
<li class="flex items-center">
|
|
10
|
+
<svg class="h-5 w-5 text-gray-400 mx-2" fill="currentColor" viewBox="0 0 20 20">
|
|
11
|
+
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
|
|
12
|
+
</svg>
|
|
13
|
+
<span class="text-gray-900 font-medium">#<%= @export.token.first(8) %>…</span>
|
|
14
|
+
</li>
|
|
15
|
+
<% end %>
|
|
16
|
+
|
|
17
|
+
<div class="max-w-3xl mx-auto">
|
|
18
|
+
<div class="bg-white shadow border border-gray-200 rounded-lg overflow-hidden">
|
|
19
|
+
<div class="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
|
|
20
|
+
<div>
|
|
21
|
+
<h1 class="text-2xl font-semibold text-gray-900"><%= t("super_admin.exports.show.heading", resource: @export.resource_name.humanize) %></h1>
|
|
22
|
+
<p class="mt-1 text-sm text-gray-500"><%= t("super_admin.exports.status.#{@export.status}") %></p>
|
|
23
|
+
</div>
|
|
24
|
+
<% if @export.ready_for_download? %>
|
|
25
|
+
<%= link_to t("super_admin.exports.show.download"), download_super_admin_export_path(@export.token), class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover-bg-blue-700" %>
|
|
26
|
+
<% end %>
|
|
27
|
+
</div>
|
|
28
|
+
<dl class="divide-y divide-gray-200">
|
|
29
|
+
<div class="px-6 py-4 grid grid-cols-3 gap-4">
|
|
30
|
+
<dt class="text-sm font-medium text-gray-500"><%= t("super_admin.exports.show.created_at") %></dt>
|
|
31
|
+
<dd class="mt-1 text-sm text-gray-900 col-span-2"><%= l(@export.created_at, format: :long) %></dd>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="px-6 py-4 grid grid-cols-3 gap-4">
|
|
34
|
+
<dt class="text-sm font-medium text-gray-500"><%= t("super_admin.exports.show.status") %></dt>
|
|
35
|
+
<dd class="mt-1 text-sm text-gray-900 col-span-2"><%= t("super_admin.exports.status.#{@export.status}") %></dd>
|
|
36
|
+
</div>
|
|
37
|
+
<% if @export.records_count.present? %>
|
|
38
|
+
<div class="px-6 py-4 grid grid-cols-3 gap-4">
|
|
39
|
+
<dt class="text-sm font-medium text-gray-500"><%= t("super_admin.exports.show.records") %></dt>
|
|
40
|
+
<dd class="mt-1 text-sm text-gray-900 col-span-2"><%= number_with_delimiter(@export.records_count) %></dd>
|
|
41
|
+
</div>
|
|
42
|
+
<% end %>
|
|
43
|
+
<% if @export.expires_at.present? %>
|
|
44
|
+
<div class="px-6 py-4 grid grid-cols-3 gap-4">
|
|
45
|
+
<dt class="text-sm font-medium text-gray-500"><%= t("super_admin.exports.show.expires_at") %></dt>
|
|
46
|
+
<dd class="mt-1 text-sm text-gray-900 col-span-2"><%= l(@export.expires_at, format: :long) %></dd>
|
|
47
|
+
</div>
|
|
48
|
+
<% end %>
|
|
49
|
+
<% if @export.error_message.present? %>
|
|
50
|
+
<div class="px-6 py-4 grid grid-cols-3 gap-4">
|
|
51
|
+
<dt class="text-sm font-medium text-gray-500"><%= t("super_admin.exports.show.error") %></dt>
|
|
52
|
+
<dd class="mt-1 text-sm text-red-600 col-span-2"><%= @export.error_message %></dd>
|
|
53
|
+
</div>
|
|
54
|
+
<% end %>
|
|
55
|
+
</dl>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<%# Formulaire générique pour création/édition de ressources %>
|
|
2
|
+
<%# Locals: resource, model_class, editable_attributes %>
|
|
3
|
+
|
|
4
|
+
<%= form_with model: resource, url: resource.new_record? ? super_admin_resources_path(resource: params[:resource]) : super_admin_resource_path(resource: params[:resource], id: resource.id), method: resource.new_record? ? :post : :patch, local: true do |f| %>
|
|
5
|
+
|
|
6
|
+
<!-- Erreurs globales -->
|
|
7
|
+
<% if resource.errors.any? %>
|
|
8
|
+
<div class="mb-6 bg-red-50 border border-red-200 rounded-md p-4">
|
|
9
|
+
<div class="flex">
|
|
10
|
+
<div class="flex-shrink-0">
|
|
11
|
+
<svg class="h-5 w-5 text-red-400" fill="currentColor" viewBox="0 0 20 20">
|
|
12
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"></path>
|
|
13
|
+
</svg>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="ml-3">
|
|
16
|
+
<h3 class="text-sm font-medium text-red-800">
|
|
17
|
+
<%= t("super_admin.resources.form.error_heading", count: resource.errors.count) %>
|
|
18
|
+
</h3>
|
|
19
|
+
<ul class="mt-2 text-sm text-red-700 list-disc list-inside">
|
|
20
|
+
<% resource.errors.full_messages.each do |message| %>
|
|
21
|
+
<li><%= message %></li>
|
|
22
|
+
<% end %>
|
|
23
|
+
</ul>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
<% end %>
|
|
28
|
+
|
|
29
|
+
<!-- Champs du formulaire -->
|
|
30
|
+
<div class="space-y-6">
|
|
31
|
+
<% editable_attributes.each do |attribute| %>
|
|
32
|
+
<%= render "super_admin/shared/form_field", form: f, model_class: model_class, attribute_name: attribute %>
|
|
33
|
+
<% end %>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<!-- Actions -->
|
|
37
|
+
<div class="mt-8 flex items-center justify-between border-t border-gray-200 pt-6">
|
|
38
|
+
<%= link_to t("super_admin.resources.form.cancel"), resource.new_record? ? super_admin_resources_path(resource: params[:resource]) : super_admin_resource_path(resource: params[:resource], id: resource.id), class: "inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" %>
|
|
39
|
+
|
|
40
|
+
<%= f.submit resource.new_record? ? t("super_admin.resources.form.create") : t("super_admin.resources.form.update"), class: "inline-flex items-center px-6 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover-bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus-ring-blue-500" %>
|
|
41
|
+
</div>
|
|
42
|
+
<% end %>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<%= turbo_stream.remove "resource_row_#{@resource.id}" %>
|
|
2
|
+
<%= turbo_stream.update "flash_messages" do %>
|
|
3
|
+
<div class="rounded-md bg-green-50 p-4 mb-4" role="alert">
|
|
4
|
+
<div class="flex">
|
|
5
|
+
<div class="flex-shrink-0">
|
|
6
|
+
<svg class="h-5 w-5 text-green-400" fill="currentColor" viewBox="0 0 20 20">
|
|
7
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
|
8
|
+
</svg>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="ml-3">
|
|
11
|
+
<p class="text-sm font-medium text-green-800">
|
|
12
|
+
<%= t("super_admin.resources.flash.destroy.success", model: @model_class.model_name.human) %>
|
|
13
|
+
</p>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
<% end %>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<% content_for :title, t("super_admin.resources.edit.page_title", model: model_display_name(@model_class)) %>
|
|
2
|
+
<% content_for :breadcrumbs do %>
|
|
3
|
+
<li class="flex items-center">
|
|
4
|
+
<svg class="h-5 w-5 text-gray-400 mx-2" fill="currentColor" viewBox="0 0 20 20">
|
|
5
|
+
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
|
|
6
|
+
</svg>
|
|
7
|
+
<%= link_to model_display_name(@model_class), super_admin_resources_path(resource: params[:resource]), class: "text-blue-600 hover-text-blue-800" %>
|
|
8
|
+
</li>
|
|
9
|
+
<li class="flex items-center">
|
|
10
|
+
<svg class="h-5 w-5 text-gray-400 mx-2" fill="currentColor" viewBox="0 0 20 20">
|
|
11
|
+
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
|
|
12
|
+
</svg>
|
|
13
|
+
<%= link_to "##{@resource.id}", super_admin_resource_path(resource: params[:resource], id: @resource.id), class: "text-blue-600 hover-text-blue-800" %>
|
|
14
|
+
</li>
|
|
15
|
+
<li class="flex items-center">
|
|
16
|
+
<svg class="h-5 w-5 text-gray-400 mx-2" fill="currentColor" viewBox="0 0 20 20">
|
|
17
|
+
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path>
|
|
18
|
+
</svg>
|
|
19
|
+
<span class="text-gray-900 font-medium"><%= t("super_admin.resources.edit.breadcrumb") %></span>
|
|
20
|
+
</li>
|
|
21
|
+
<% end %>
|
|
22
|
+
|
|
23
|
+
<div class="max-w-3xl mx-auto">
|
|
24
|
+
<!-- Header -->
|
|
25
|
+
<div class="mb-6">
|
|
26
|
+
<h1 class="text-3xl font-bold text-gray-900">
|
|
27
|
+
<%= t("super_admin.resources.edit.heading", model: @model_class.model_name.human.downcase, id: @resource.id) %>
|
|
28
|
+
</h1>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<!-- Form Card -->
|
|
32
|
+
<div class="bg-white rounded-lg shadow border border-gray-200">
|
|
33
|
+
<div class="px-6 py-8">
|
|
34
|
+
<%= render "form", resource: @resource, model_class: @model_class, editable_attributes: @editable_attributes %>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|