easy-admin-rails 0.1.10 → 0.1.12
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/builds/easy_admin.base.js +3204 -1
- data/app/assets/builds/easy_admin.base.js.map +4 -4
- data/app/assets/builds/easy_admin.css +327 -0
- data/app/assets/stylesheets/easy_admin/application.tailwind.css +1 -0
- data/app/assets/stylesheets/easy_admin/swal_customizations.css +168 -0
- data/app/components/easy_admin/fields/base_component.rb +23 -0
- data/app/components/easy_admin/fields/form/boolean_component.rb +1 -0
- data/app/components/easy_admin/fields/form/date_component.rb +3 -2
- data/app/components/easy_admin/fields/form/datetime_component.rb +3 -2
- data/app/components/easy_admin/fields/form/email_component.rb +3 -2
- data/app/components/easy_admin/fields/form/file_component.rb +6 -7
- data/app/components/easy_admin/fields/form/number_component.rb +3 -2
- data/app/components/easy_admin/fields/form/select_component.rb +5 -3
- data/app/components/easy_admin/fields/form/text_component.rb +3 -2
- data/app/components/easy_admin/fields/form/textarea_component.rb +3 -2
- data/app/components/easy_admin/versions/diff_component.rb +184 -0
- data/app/components/easy_admin/versions/diff_modal_component.rb +314 -0
- data/app/components/easy_admin/versions/history_component.rb +141 -0
- data/app/components/easy_admin/versions/pagination_component.rb +206 -0
- data/app/components/easy_admin/versions/timeline_component.rb +412 -0
- data/app/concerns/easy_admin/resource_versions.rb +306 -0
- data/app/controllers/easy_admin/application_controller.rb +8 -0
- data/app/controllers/easy_admin/resources_controller.rb +5 -3
- data/app/javascript/controllers/modal_controller.js +38 -0
- data/app/javascript/easy_admin/controllers/version_revert_controller.js +108 -0
- data/app/javascript/easy_admin/controllers.js +3 -1
- data/config/routes.rb +5 -0
- data/lib/easy-admin-rails.rb +1 -0
- data/lib/easy_admin/resource.rb +15 -0
- data/lib/easy_admin/version.rb +1 -1
- data/lib/easy_admin/versioning.rb +43 -0
- metadata +12 -2
@@ -0,0 +1,306 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module ResourceVersions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
DEFAULT_VERSIONS_PER_PAGE = 10
|
6
|
+
MAX_VERSIONS_PER_PAGE = 50
|
7
|
+
|
8
|
+
included do
|
9
|
+
# Include in controllers that need version functionality
|
10
|
+
end
|
11
|
+
|
12
|
+
# Load versions for a specific record with pagination and turbo compatibility
|
13
|
+
def load_record_versions(page: 1, per_page: DEFAULT_VERSIONS_PER_PAGE)
|
14
|
+
return OpenStruct.new(items: [], total_count: 0, has_more: false, current_page: 1) unless record_has_versions?(@record)
|
15
|
+
|
16
|
+
per_page = [per_page.to_i, MAX_VERSIONS_PER_PAGE].min
|
17
|
+
per_page = DEFAULT_VERSIONS_PER_PAGE if per_page <= 0
|
18
|
+
page = [page.to_i, 1].max
|
19
|
+
|
20
|
+
# Use pagy for consistent pagination across the app
|
21
|
+
pagy, versions = pagy(@record.versions.reorder(id: :desc).includes(:item),
|
22
|
+
items: per_page,
|
23
|
+
limit: per_page,
|
24
|
+
page: page,
|
25
|
+
overflow: :last_page)
|
26
|
+
|
27
|
+
OpenStruct.new(
|
28
|
+
items: versions,
|
29
|
+
pagy: pagy,
|
30
|
+
total_count: pagy.count,
|
31
|
+
has_more: pagy.next.present?,
|
32
|
+
current_page: pagy.page,
|
33
|
+
per_page: pagy.vars[:items],
|
34
|
+
total_pages: pagy.last
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Check if a record has version support
|
39
|
+
def record_has_versions?(record)
|
40
|
+
return false unless record
|
41
|
+
return false unless defined?(PaperTrail)
|
42
|
+
|
43
|
+
record.respond_to?(:versions) && record.class.respond_to?(:paper_trail) rescue false
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get version statistics for a record
|
47
|
+
def version_statistics(record)
|
48
|
+
return {} unless record_has_versions?(record)
|
49
|
+
|
50
|
+
versions = record.versions
|
51
|
+
|
52
|
+
{
|
53
|
+
total_versions: versions.count,
|
54
|
+
create_count: versions.where(event: 'create').count,
|
55
|
+
update_count: versions.where(event: 'update').count,
|
56
|
+
destroy_count: versions.where(event: 'destroy').count,
|
57
|
+
first_version_at: versions.order(:created_at).first&.created_at,
|
58
|
+
last_version_at: versions.order(:created_at).last&.created_at,
|
59
|
+
unique_users: versions.where.not(whodunnit: nil).distinct.count(:whodunnit)
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
# Turbo-compatible version loading action
|
64
|
+
def versions
|
65
|
+
result = load_record_versions(
|
66
|
+
page: params[:page] || 1,
|
67
|
+
per_page: params[:per_page] || DEFAULT_VERSIONS_PER_PAGE
|
68
|
+
)
|
69
|
+
|
70
|
+
respond_to do |format|
|
71
|
+
format.html do
|
72
|
+
if turbo_frame_request?
|
73
|
+
# For lazy loading turbo frame (versions-timeline)
|
74
|
+
render plain: EasyAdmin::Versions::TimelineComponent.new(
|
75
|
+
versions: result.items,
|
76
|
+
turbo_frame_id: "versions-timeline",
|
77
|
+
resource_class: @resource_class,
|
78
|
+
record: @record
|
79
|
+
).call
|
80
|
+
else
|
81
|
+
# Regular page load - redirect or render full page
|
82
|
+
redirect_to easy_admin.resource_path(@resource_class.route_key, @record, anchor: "versions")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
format.turbo_stream do
|
87
|
+
if params[:append] == "true"
|
88
|
+
# Infinite scroll - append more versions
|
89
|
+
render turbo_stream: [
|
90
|
+
turbo_stream.append("versions-container",
|
91
|
+
EasyAdmin::Versions::TimelineComponent.new(
|
92
|
+
versions: result.items
|
93
|
+
).call
|
94
|
+
),
|
95
|
+
turbo_stream.replace("versions-pagination",
|
96
|
+
EasyAdmin::Versions::PaginationComponent.new(
|
97
|
+
pagy: result.pagy,
|
98
|
+
resource_class: @resource_class,
|
99
|
+
record: @record
|
100
|
+
).call
|
101
|
+
)
|
102
|
+
]
|
103
|
+
else
|
104
|
+
# Replace entire versions section
|
105
|
+
render turbo_stream: turbo_stream.replace("versions-section",
|
106
|
+
EasyAdmin::Versions::HistoryComponent.new(
|
107
|
+
record: @record,
|
108
|
+
resource_class: @resource_class,
|
109
|
+
versions_result: result
|
110
|
+
).call
|
111
|
+
)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Revert a record to a specific version with turbo response
|
118
|
+
def revert_version
|
119
|
+
version_id = params[:version_id]
|
120
|
+
version = @record.versions.find_by(id: version_id)
|
121
|
+
|
122
|
+
unless version
|
123
|
+
respond_to do |format|
|
124
|
+
format.turbo_stream do
|
125
|
+
render turbo_stream: turbo_stream.update("notifications",
|
126
|
+
EasyAdmin::NotificationComponent.new(
|
127
|
+
type: :error,
|
128
|
+
message: "Version not found",
|
129
|
+
title: "Error"
|
130
|
+
).call
|
131
|
+
)
|
132
|
+
end
|
133
|
+
format.html do
|
134
|
+
redirect_to easy_admin.resource_path(@resource_class.route_key, @record),
|
135
|
+
alert: "Version not found"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
return
|
139
|
+
end
|
140
|
+
|
141
|
+
result = revert_to_version(@record, version_id, current_user: current_admin_user)
|
142
|
+
|
143
|
+
respond_to do |format|
|
144
|
+
if result[:success]
|
145
|
+
format.turbo_stream do
|
146
|
+
render turbo_stream: [
|
147
|
+
turbo_stream.update("notifications",
|
148
|
+
EasyAdmin::NotificationComponent.new(
|
149
|
+
type: :success,
|
150
|
+
message: result[:message],
|
151
|
+
title: "Success"
|
152
|
+
).call
|
153
|
+
),
|
154
|
+
# Refresh the versions section
|
155
|
+
turbo_stream.replace("versions-section",
|
156
|
+
EasyAdmin::Versions::HistoryComponent.new(
|
157
|
+
record: @record.reload,
|
158
|
+
resource_class: @resource_class
|
159
|
+
).call
|
160
|
+
),
|
161
|
+
# Update the main record display if on show page
|
162
|
+
turbo_stream.replace("record-content",
|
163
|
+
render_record_content(@record.reload)
|
164
|
+
)
|
165
|
+
]
|
166
|
+
end
|
167
|
+
format.html do
|
168
|
+
redirect_to easy_admin.resource_path(@resource_class.route_key, @record),
|
169
|
+
notice: result[:message]
|
170
|
+
end
|
171
|
+
else
|
172
|
+
format.turbo_stream do
|
173
|
+
render turbo_stream: turbo_stream.update("notifications",
|
174
|
+
EasyAdmin::NotificationComponent.new(
|
175
|
+
type: :error,
|
176
|
+
message: result[:error],
|
177
|
+
title: "Error"
|
178
|
+
).call
|
179
|
+
)
|
180
|
+
end
|
181
|
+
format.html do
|
182
|
+
redirect_to easy_admin.resource_path(@resource_class.route_key, @record),
|
183
|
+
alert: result[:error]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Show detailed diff modal for a version
|
190
|
+
def version_diff
|
191
|
+
version = @record.versions.find_by(id: params[:version_id])
|
192
|
+
|
193
|
+
unless version
|
194
|
+
respond_to do |format|
|
195
|
+
format.html do
|
196
|
+
render plain: EasyAdmin::Versions::ErrorComponent.new(
|
197
|
+
message: "Version not found"
|
198
|
+
).call
|
199
|
+
end
|
200
|
+
end
|
201
|
+
return
|
202
|
+
end
|
203
|
+
|
204
|
+
respond_to do |format|
|
205
|
+
format.html do
|
206
|
+
render plain: EasyAdmin::Versions::DiffModalComponent.new(
|
207
|
+
version: version,
|
208
|
+
record: @record,
|
209
|
+
resource_class: @resource_class
|
210
|
+
).call
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
private
|
216
|
+
|
217
|
+
# Revert a record to a specific version
|
218
|
+
def revert_to_version(record, version_id, current_user: nil)
|
219
|
+
return { success: false, error: "Versioning not available" } unless record_has_versions?(record)
|
220
|
+
|
221
|
+
version = record.versions.find_by(id: version_id)
|
222
|
+
return { success: false, error: "Version not found" } unless version
|
223
|
+
|
224
|
+
begin
|
225
|
+
# Set whodunnit for the revert action
|
226
|
+
PaperTrail.request.whodunnit = current_user&.email if current_user
|
227
|
+
|
228
|
+
# Parse version object manually with safe YAML loading
|
229
|
+
old_attributes = parse_version_object_safely(version.object)
|
230
|
+
return { success: false, error: "Cannot reify this version" } unless old_attributes.is_a?(Hash)
|
231
|
+
|
232
|
+
# Filter out attributes that shouldn't be reverted
|
233
|
+
filtered_attributes = old_attributes.except('id', 'created_at', 'updated_at')
|
234
|
+
|
235
|
+
if record.update(filtered_attributes)
|
236
|
+
{
|
237
|
+
success: true,
|
238
|
+
message: "Successfully reverted to version ##{version_id}",
|
239
|
+
reverted_version: version,
|
240
|
+
new_version: record.versions.last
|
241
|
+
}
|
242
|
+
else
|
243
|
+
{
|
244
|
+
success: false,
|
245
|
+
error: "Failed to revert: #{record.errors.full_messages.join(', ')}",
|
246
|
+
validation_errors: record.errors
|
247
|
+
}
|
248
|
+
end
|
249
|
+
rescue => e
|
250
|
+
{
|
251
|
+
success: false,
|
252
|
+
error: "Error during revert: #{e.message}"
|
253
|
+
}
|
254
|
+
ensure
|
255
|
+
PaperTrail.request.whodunnit = nil
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Helper to render record content (override in controller if needed)
|
260
|
+
def render_record_content(record)
|
261
|
+
# This should be implemented in the controller that includes this concern
|
262
|
+
# to return the appropriate component for the record display
|
263
|
+
""
|
264
|
+
end
|
265
|
+
|
266
|
+
private
|
267
|
+
|
268
|
+
# Safe YAML parsing for version objects with proper class permissions
|
269
|
+
def parse_version_object_safely(object_yaml)
|
270
|
+
return {} if object_yaml.blank?
|
271
|
+
|
272
|
+
YAML.safe_load(
|
273
|
+
object_yaml,
|
274
|
+
permitted_classes: [Time, Date, DateTime, ActiveSupport::TimeWithZone, ActiveSupport::TimeZone, Symbol],
|
275
|
+
aliases: true
|
276
|
+
)
|
277
|
+
rescue Psych::DisallowedClass, Psych::BadAlias
|
278
|
+
begin
|
279
|
+
YAML.unsafe_load(object_yaml)
|
280
|
+
rescue
|
281
|
+
extract_basic_attributes_from_yaml(object_yaml)
|
282
|
+
end
|
283
|
+
rescue => e
|
284
|
+
Rails.logger.warn "Failed to parse version object for revert: #{e.message}"
|
285
|
+
{}
|
286
|
+
end
|
287
|
+
|
288
|
+
# Fallback method to extract basic attributes from YAML string
|
289
|
+
def extract_basic_attributes_from_yaml(yaml_string)
|
290
|
+
attributes = {}
|
291
|
+
|
292
|
+
yaml_string.scan(/^(\w+):\s*(.+)$/).each do |key, value|
|
293
|
+
# Skip complex objects
|
294
|
+
next if value.include?('!ruby/object')
|
295
|
+
|
296
|
+
# Clean up the value
|
297
|
+
cleaned_value = value.strip.gsub(/^["']|["']$/, '')
|
298
|
+
attributes[key] = cleaned_value unless cleaned_value.empty?
|
299
|
+
end
|
300
|
+
|
301
|
+
attributes
|
302
|
+
rescue
|
303
|
+
{}
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
@@ -8,6 +8,7 @@ module EasyAdmin
|
|
8
8
|
before_action :authenticate_easy_admin_admin_user!
|
9
9
|
before_action :set_active_storage_url_options
|
10
10
|
before_action :set_feature_toggles
|
11
|
+
before_action :set_paper_trail_whodunnit
|
11
12
|
|
12
13
|
protected
|
13
14
|
|
@@ -61,6 +62,13 @@ module EasyAdmin
|
|
61
62
|
descriptions[feature_name.to_s] || "Toggle #{feature_name.humanize.downcase}"
|
62
63
|
end
|
63
64
|
|
65
|
+
# Set Paper Trail whodunnit to track which admin user made changes
|
66
|
+
def set_paper_trail_whodunnit
|
67
|
+
if defined?(PaperTrail) && current_admin_user
|
68
|
+
PaperTrail.request.whodunnit = current_admin_user.email
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
64
72
|
helper_method :current_admin_user, :admin_user_signed_in?
|
65
73
|
end
|
66
74
|
end
|
@@ -1,7 +1,9 @@
|
|
1
1
|
module EasyAdmin
|
2
2
|
class ResourcesController < ApplicationController
|
3
|
+
include EasyAdmin::ResourceVersions
|
4
|
+
|
3
5
|
before_action :load_resource_class
|
4
|
-
before_action :load_record, only: [:show, :edit, :update, :destroy, :edit_field, :update_field, :belongs_to_reattach, :belongs_to_edit_attached, :update_belongs_to_attached]
|
6
|
+
before_action :load_record, only: [:show, :edit, :update, :destroy, :edit_field, :update_field, :belongs_to_reattach, :belongs_to_edit_attached, :update_belongs_to_attached, :versions, :revert_version, :version_diff]
|
5
7
|
|
6
8
|
def index
|
7
9
|
# Set up data needed for both HTML and turbo stream
|
@@ -151,7 +153,7 @@ module EasyAdmin
|
|
151
153
|
redirect_to easy_admin.resource_path(@resource_class.route_key, @record),
|
152
154
|
notice: "#{@resource_class.singular_title} was successfully created."
|
153
155
|
else
|
154
|
-
render :new
|
156
|
+
render :new, status: :unprocessable_entity
|
155
157
|
end
|
156
158
|
end
|
157
159
|
|
@@ -182,7 +184,7 @@ module EasyAdmin
|
|
182
184
|
}
|
183
185
|
end
|
184
186
|
else
|
185
|
-
render :edit
|
187
|
+
render :edit, status: :unprocessable_entity
|
186
188
|
end
|
187
189
|
end
|
188
190
|
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
connect() {
|
5
|
+
// Disable body scroll when modal opens
|
6
|
+
document.body.style.overflow = 'hidden'
|
7
|
+
|
8
|
+
// Focus trap - focus the modal
|
9
|
+
this.element.focus()
|
10
|
+
}
|
11
|
+
|
12
|
+
disconnect() {
|
13
|
+
// Re-enable body scroll when modal closes
|
14
|
+
document.body.style.overflow = ''
|
15
|
+
}
|
16
|
+
|
17
|
+
close() {
|
18
|
+
// Clear the modal turbo frame
|
19
|
+
const modalFrame = document.getElementById("modal")
|
20
|
+
if (modalFrame) {
|
21
|
+
modalFrame.innerHTML = ""
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
handleOutsideClick(event) {
|
26
|
+
// Close modal if clicking outside the modal container
|
27
|
+
if (event.target === this.element) {
|
28
|
+
this.close()
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
// Handle escape key
|
33
|
+
keydown(event) {
|
34
|
+
if (event.key === "Escape") {
|
35
|
+
this.close()
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
@@ -0,0 +1,108 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
import { FetchRequest } from "@rails/request.js"
|
3
|
+
import Swal from "sweetalert2"
|
4
|
+
|
5
|
+
export default class extends Controller {
|
6
|
+
static values = {
|
7
|
+
versionId: Number,
|
8
|
+
resourceId: String,
|
9
|
+
resourceName: String
|
10
|
+
}
|
11
|
+
|
12
|
+
confirm(event) {
|
13
|
+
event.preventDefault()
|
14
|
+
|
15
|
+
Swal.fire({
|
16
|
+
title: 'Revert to Version?',
|
17
|
+
html: `Are you sure you want to revert to version <strong>#${this.versionIdValue}</strong>?<br><br>This will restore the old data and create a new version entry.`,
|
18
|
+
icon: 'question',
|
19
|
+
showCancelButton: true,
|
20
|
+
confirmButtonText: 'Yes, Revert',
|
21
|
+
cancelButtonText: 'Cancel',
|
22
|
+
reverseButtons: true,
|
23
|
+
focusCancel: true,
|
24
|
+
...this.getAdminPanelStyles()
|
25
|
+
}).then((result) => {
|
26
|
+
if (result.isConfirmed) {
|
27
|
+
this.performRevert()
|
28
|
+
}
|
29
|
+
})
|
30
|
+
}
|
31
|
+
|
32
|
+
async performRevert() {
|
33
|
+
const url = `/admin/${this.resourceNameValue}/${this.resourceIdValue}/versions/${this.versionIdValue}/revert`
|
34
|
+
|
35
|
+
// Show loading indicator
|
36
|
+
Swal.fire({
|
37
|
+
title: 'Reverting...',
|
38
|
+
text: `Restoring version #${this.versionIdValue}`,
|
39
|
+
allowOutsideClick: false,
|
40
|
+
allowEscapeKey: false,
|
41
|
+
showConfirmButton: false,
|
42
|
+
didOpen: () => {
|
43
|
+
Swal.showLoading()
|
44
|
+
},
|
45
|
+
...this.getAdminPanelStyles()
|
46
|
+
})
|
47
|
+
|
48
|
+
try {
|
49
|
+
const request = new FetchRequest("post", url, {
|
50
|
+
headers: {
|
51
|
+
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content,
|
52
|
+
"Accept": "text/vnd.turbo-stream.html"
|
53
|
+
}
|
54
|
+
})
|
55
|
+
|
56
|
+
const response = await request.perform()
|
57
|
+
|
58
|
+
if (response.ok) {
|
59
|
+
// Show success message then reload
|
60
|
+
Swal.fire({
|
61
|
+
title: 'Success!',
|
62
|
+
text: `Reverted to version #${this.versionIdValue}`,
|
63
|
+
icon: 'success',
|
64
|
+
timer: 1500,
|
65
|
+
showConfirmButton: false,
|
66
|
+
...this.getAdminPanelStyles()
|
67
|
+
}).then(() => {
|
68
|
+
window.location.reload()
|
69
|
+
})
|
70
|
+
} else {
|
71
|
+
console.error("Revert failed:", response.statusText)
|
72
|
+
this.showError("Failed to revert. Please try again.")
|
73
|
+
}
|
74
|
+
} catch (error) {
|
75
|
+
console.error("Revert error:", error)
|
76
|
+
this.showError("An error occurred while reverting. Please try again.")
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
showError(message) {
|
81
|
+
Swal.fire({
|
82
|
+
title: 'Error',
|
83
|
+
text: message,
|
84
|
+
icon: 'error',
|
85
|
+
confirmButtonText: 'OK',
|
86
|
+
...this.getAdminPanelStyles()
|
87
|
+
})
|
88
|
+
}
|
89
|
+
|
90
|
+
getAdminPanelStyles() {
|
91
|
+
return {
|
92
|
+
buttonsStyling: false,
|
93
|
+
customClass: {
|
94
|
+
popup: 'easy-admin-swal-popup',
|
95
|
+
title: 'easy-admin-swal-title',
|
96
|
+
htmlContainer: 'easy-admin-swal-content',
|
97
|
+
confirmButton: 'easy-admin-swal-confirm-button',
|
98
|
+
cancelButton: 'easy-admin-swal-cancel-button',
|
99
|
+
actions: 'easy-admin-swal-actions'
|
100
|
+
},
|
101
|
+
background: '#ffffff',
|
102
|
+
backdrop: 'rgba(0, 0, 0, 0.4)',
|
103
|
+
width: '28rem',
|
104
|
+
padding: '0',
|
105
|
+
borderRadius: '0.75rem'
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}
|
@@ -25,6 +25,7 @@ import ContextMenuController from './controllers/context_menu_controller'
|
|
25
25
|
import RowModalController from './controllers/row_modal_controller'
|
26
26
|
import RowActionController from './controllers/row_action_controller'
|
27
27
|
import JsoneditorController from './controllers/jsoneditor_controller'
|
28
|
+
import VersionRevertController from './controllers/version_revert_controller'
|
28
29
|
|
29
30
|
// Register controllers
|
30
31
|
application.register('sidebar', SidebarController)
|
@@ -51,4 +52,5 @@ application.register('confirmation-modal', ConfirmationModalController)
|
|
51
52
|
application.register('context-menu', ContextMenuController)
|
52
53
|
application.register('row-modal', RowModalController)
|
53
54
|
application.register('row-action', RowActionController)
|
54
|
-
application.register('jsoneditor', JsoneditorController)
|
55
|
+
application.register('jsoneditor', JsoneditorController)
|
56
|
+
application.register('version-revert', VersionRevertController)
|
data/config/routes.rb
CHANGED
@@ -66,5 +66,10 @@ EasyAdmin::Engine.routes.draw do
|
|
66
66
|
get '/:id/metrics/:metric_id', to: 'resources#metric', as: 'resource_metric'
|
67
67
|
get '/:id/charts/:chart_id', to: 'resources#chart', as: 'resource_chart'
|
68
68
|
get '/:id/tabs/:tab_name', to: 'resources#tab_content', as: 'resource_tab_content'
|
69
|
+
|
70
|
+
# Version management routes
|
71
|
+
get '/:id/versions', to: 'resources#versions', as: 'resource_versions'
|
72
|
+
post '/:id/versions/:version_id/revert', to: 'resources#revert_version', as: 'revert_resource_version'
|
73
|
+
get '/:id/versions/:version_id/diff', to: 'resources#version_diff', as: 'resource_version_diff'
|
69
74
|
end
|
70
75
|
end
|
data/lib/easy-admin-rails.rb
CHANGED
data/lib/easy_admin/resource.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module EasyAdmin
|
2
2
|
class Resource
|
3
|
+
include EasyAdmin::Versioning
|
4
|
+
|
3
5
|
class_attribute :model_class_name, :resource_name, :fields_config, :form_tabs_config, :show_layout_config, :scopes_config, :pagination_config, :includes_config, :batch_actions_config, :batch_actions_enabled, :row_actions_config, :custom_title
|
4
6
|
|
5
7
|
def self.inherited(subclass)
|
@@ -822,5 +824,18 @@ module EasyAdmin
|
|
822
824
|
def self.param_key
|
823
825
|
resource_name.underscore
|
824
826
|
end
|
827
|
+
|
828
|
+
# Versioning DSL - Simple API for enabling version viewing
|
829
|
+
def self.versioning(enabled = true, **options)
|
830
|
+
if enabled
|
831
|
+
enable_versioning(options)
|
832
|
+
else
|
833
|
+
self.versioning_enabled = false
|
834
|
+
end
|
835
|
+
end
|
836
|
+
|
837
|
+
def self.has_versioning?
|
838
|
+
model_has_versions?
|
839
|
+
end
|
825
840
|
end
|
826
841
|
end
|
data/lib/easy_admin/version.rb
CHANGED
@@ -0,0 +1,43 @@
|
|
1
|
+
module EasyAdmin
|
2
|
+
module Versioning
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :versioning_enabled, default: false
|
7
|
+
class_attribute :versioning_options, default: {}
|
8
|
+
end
|
9
|
+
|
10
|
+
class_methods do
|
11
|
+
def enable_versioning(options = {})
|
12
|
+
self.versioning_enabled = true
|
13
|
+
self.versioning_options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def versioning_enabled?
|
17
|
+
versioning_enabled && defined?(PaperTrail)
|
18
|
+
end
|
19
|
+
|
20
|
+
def versioning_config
|
21
|
+
return nil unless versioning_enabled?
|
22
|
+
|
23
|
+
{
|
24
|
+
enabled: true,
|
25
|
+
diff_display: versioning_options[:diff_display] || :inline,
|
26
|
+
show_whodunnit: versioning_options[:show_whodunnit] != false,
|
27
|
+
show_metadata: versioning_options[:show_metadata] != false,
|
28
|
+
limit: versioning_options[:limit] || 50 # Default to showing last 50 versions
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
# Check if a model has versioning available
|
33
|
+
def model_has_versions?
|
34
|
+
return false unless versioning_enabled?
|
35
|
+
|
36
|
+
# Check if the model has paper_trail and instances respond to versions
|
37
|
+
model_class.respond_to?(:paper_trail) && model_class.new.respond_to?(:versions)
|
38
|
+
rescue
|
39
|
+
false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: easy-admin-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Slaurmagan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-09-
|
11
|
+
date: 2025-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -195,6 +195,7 @@ files:
|
|
195
195
|
- app/assets/config/easy_admin_manifest.js
|
196
196
|
- app/assets/images/jsoneditor-icons.svg
|
197
197
|
- app/assets/stylesheets/easy_admin/application.tailwind.css
|
198
|
+
- app/assets/stylesheets/easy_admin/swal_customizations.css
|
198
199
|
- app/components/easy_admin/base_component.rb
|
199
200
|
- app/components/easy_admin/batch_action_bar_component.rb
|
200
201
|
- app/components/easy_admin/batch_action_form_component.rb
|
@@ -283,6 +284,12 @@ files:
|
|
283
284
|
- app/components/easy_admin/sidebar_component.rb
|
284
285
|
- app/components/easy_admin/turbo/response_component.rb
|
285
286
|
- app/components/easy_admin/turbo/stream_component.rb
|
287
|
+
- app/components/easy_admin/versions/diff_component.rb
|
288
|
+
- app/components/easy_admin/versions/diff_modal_component.rb
|
289
|
+
- app/components/easy_admin/versions/history_component.rb
|
290
|
+
- app/components/easy_admin/versions/pagination_component.rb
|
291
|
+
- app/components/easy_admin/versions/timeline_component.rb
|
292
|
+
- app/concerns/easy_admin/resource_versions.rb
|
286
293
|
- app/controllers/easy_admin/application_controller.rb
|
287
294
|
- app/controllers/easy_admin/batch_actions_controller.rb
|
288
295
|
- app/controllers/easy_admin/confirmation_modal_controller.rb
|
@@ -299,6 +306,7 @@ files:
|
|
299
306
|
- app/helpers/easy_admin/fields_helper.rb
|
300
307
|
- app/helpers/easy_admin/pagy_helper.rb
|
301
308
|
- app/helpers/easy_admin/resources_helper.rb
|
309
|
+
- app/javascript/controllers/modal_controller.js
|
302
310
|
- app/javascript/easy_admin.base.js
|
303
311
|
- app/javascript/easy_admin/application.js
|
304
312
|
- app/javascript/easy_admin/controllers.js
|
@@ -332,6 +340,7 @@ files:
|
|
332
340
|
- app/javascript/easy_admin/controllers/table_row_controller.js
|
333
341
|
- app/javascript/easy_admin/controllers/toggle_switch_controller.js
|
334
342
|
- app/javascript/easy_admin/controllers/turbo_stream_redirect.js
|
343
|
+
- app/javascript/easy_admin/controllers/version_revert_controller.js
|
335
344
|
- app/models/easy_admin/admin_user.rb
|
336
345
|
- app/models/easy_admin/application_record.rb
|
337
346
|
- app/views/easy_admin/dashboard/index.html.erb
|
@@ -370,6 +379,7 @@ files:
|
|
370
379
|
- lib/easy_admin/resource_registry.rb
|
371
380
|
- lib/easy_admin/types/json_type.rb
|
372
381
|
- lib/easy_admin/version.rb
|
382
|
+
- lib/easy_admin/versioning.rb
|
373
383
|
- lib/generators/easy_admin/auth_generator.rb
|
374
384
|
- lib/generators/easy_admin/card/card_generator.rb
|
375
385
|
- lib/generators/easy_admin/card/templates/card_component.rb.erb
|