cm-admin 0.7.8 → 0.8.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 +4 -4
- data/Gemfile.lock +22 -5
- data/app/assets/images/image_not_available.png +0 -0
- data/app/assets/stylesheets/cm_admin/cm_admin.css.scss +1 -0
- data/app/assets/stylesheets/cm_admin/pages/import_page.scss +125 -0
- data/app/controllers/cm_admin/resource_controller.rb +19 -0
- data/app/helpers/cm_admin/application_helper.rb +41 -0
- data/app/jobs/file_import_processor_job.rb +38 -0
- data/app/models/concerns/cm_admin/file_import.rb +40 -0
- data/app/models/file_import.rb +27 -0
- data/app/policies/cm_admin/file_import_policy.rb +11 -0
- data/app/views/cm_admin/main/_actions_dropdown.html.slim +3 -15
- data/app/views/cm_admin/main/_associated_table.html.slim +2 -0
- data/app/views/cm_admin/main/_member_custom_action_modal.html.slim +13 -0
- data/app/views/cm_admin/main/_table.html.slim +2 -0
- data/app/views/cm_admin/main/_top_navbar.html.slim +2 -0
- data/app/views/cm_admin/main/import_form.html.slim +35 -0
- data/cm_admin.gemspec +1 -0
- data/config/routes.rb +6 -0
- data/lib/cm_admin/model.rb +7 -1
- data/lib/cm_admin/models/column.rb +3 -1
- data/lib/cm_admin/models/field.rb +4 -2
- data/lib/cm_admin/models/form_field.rb +2 -1
- data/lib/cm_admin/models/importer.rb +17 -0
- data/lib/cm_admin/version.rb +1 -1
- data/lib/cm_admin/view_helpers/field_display_helper.rb +8 -0
- data/lib/cm_admin/view_helpers/form_field_helper.rb +2 -2
- data/lib/cm_admin.rb +2 -1
- data/lib/generators/cm_admin/install_generator.rb +2 -0
- data/lib/generators/cm_admin/templates/cm_admin_initializer.rb +1 -1
- data/tmp/cache/webpacker/last-compilation-digest-development +1 -1
- metadata +28 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5fa47a489bd6b44db00d91cb7631c5491646fd00148ba756b87208f1218ee705
|
|
4
|
+
data.tar.gz: 5a61fd4d0b01637fbf16bd0a443b912929d12993863a936bd329682433ce54f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 708568975092346a45370f46c3b7119feb5a4c4bc43a33ec61a30a3966da7ed8ec13d1a9d91e3a2120f2d5a462f18cb5827a9deaed6fc84bc451b0740f80187d
|
|
7
|
+
data.tar.gz: fc2fba169f614e1d48a11cec08135a12db4e20eb0dcfb6eada41a810bbd7bbeb40b046512c472232542670da2e2c301db58b3263c37fe05de791c3e2b15d46a8
|
data/Gemfile.lock
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
cm-admin (0.
|
|
4
|
+
cm-admin (0.8.0)
|
|
5
5
|
caxlsx_rails
|
|
6
6
|
cocoon (~> 1.2.15)
|
|
7
|
+
csv-importer (~> 0.8.2)
|
|
7
8
|
local_time (~> 2.1.0)
|
|
8
9
|
pagy (~> 4.11.0)
|
|
9
10
|
pundit (~> 2.2.0)
|
|
@@ -32,6 +33,10 @@ GEM
|
|
|
32
33
|
minitest (>= 5.1)
|
|
33
34
|
tzinfo (~> 2.0)
|
|
34
35
|
ast (2.4.2)
|
|
36
|
+
axiom-types (0.1.1)
|
|
37
|
+
descendants_tracker (~> 0.0.4)
|
|
38
|
+
ice_nine (~> 0.11.0)
|
|
39
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
|
35
40
|
builder (3.2.4)
|
|
36
41
|
caxlsx (3.2.0)
|
|
37
42
|
htmlentities (~> 4.3, >= 4.3.4)
|
|
@@ -42,22 +47,29 @@ GEM
|
|
|
42
47
|
actionpack (>= 3.1)
|
|
43
48
|
caxlsx (>= 3.0)
|
|
44
49
|
cocoon (1.2.15)
|
|
50
|
+
coercible (1.0.0)
|
|
51
|
+
descendants_tracker (~> 0.0.1)
|
|
45
52
|
concurrent-ruby (1.1.10)
|
|
46
53
|
crass (1.0.6)
|
|
54
|
+
csv-importer (0.8.2)
|
|
55
|
+
virtus
|
|
56
|
+
descendants_tracker (0.0.4)
|
|
57
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
|
47
58
|
diff-lcs (1.4.4)
|
|
48
|
-
erubi (1.
|
|
59
|
+
erubi (1.11.0)
|
|
49
60
|
htmlentities (4.3.4)
|
|
50
61
|
i18n (1.11.0)
|
|
51
62
|
concurrent-ruby (~> 1.0)
|
|
63
|
+
ice_nine (0.11.2)
|
|
52
64
|
local_time (2.1.0)
|
|
53
|
-
loofah (2.
|
|
65
|
+
loofah (2.19.0)
|
|
54
66
|
crass (~> 1.0.2)
|
|
55
67
|
nokogiri (>= 1.5.9)
|
|
56
68
|
marcel (1.0.2)
|
|
57
69
|
method_source (1.0.0)
|
|
58
70
|
mini_portile2 (2.8.0)
|
|
59
71
|
minitest (5.16.2)
|
|
60
|
-
nokogiri (1.13.
|
|
72
|
+
nokogiri (1.13.8)
|
|
61
73
|
mini_portile2 (~> 2.8.0)
|
|
62
74
|
racc (~> 1.4)
|
|
63
75
|
pagy (4.11.0)
|
|
@@ -68,7 +80,7 @@ GEM
|
|
|
68
80
|
activesupport (>= 3.0.0)
|
|
69
81
|
racc (1.6.0)
|
|
70
82
|
rack (2.2.4)
|
|
71
|
-
rack-proxy (0.7.
|
|
83
|
+
rack-proxy (0.7.4)
|
|
72
84
|
rack
|
|
73
85
|
rack-test (2.0.2)
|
|
74
86
|
rack (>= 1.3)
|
|
@@ -120,10 +132,15 @@ GEM
|
|
|
120
132
|
tilt (>= 2.0.6, < 2.1)
|
|
121
133
|
temple (0.8.2)
|
|
122
134
|
thor (1.2.1)
|
|
135
|
+
thread_safe (0.3.6)
|
|
123
136
|
tilt (2.0.10)
|
|
124
137
|
tzinfo (2.0.4)
|
|
125
138
|
concurrent-ruby (~> 1.0)
|
|
126
139
|
unicode-display_width (2.1.0)
|
|
140
|
+
virtus (2.0.0)
|
|
141
|
+
axiom-types (~> 0.1)
|
|
142
|
+
coercible (~> 1.0)
|
|
143
|
+
descendants_tracker (~> 0.0, >= 0.0.3)
|
|
127
144
|
webpacker (5.4.3)
|
|
128
145
|
activesupport (>= 5.2)
|
|
129
146
|
rack-proxy (>= 0.6.1)
|
|
Binary file
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* files in this directory. Styles in this file should be added after the last require_* statement.
|
|
11
11
|
* It is generally better to create a new file per style scope.
|
|
12
12
|
*
|
|
13
|
+
*= require 'cm_admin/pages/import_page'
|
|
13
14
|
*= require 'cm_admin/base/table'
|
|
14
15
|
*= require 'cm_admin/base/navbar'
|
|
15
16
|
*= require 'cm_admin/base/sidebar'
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
@import "../helpers/index.scss";
|
|
2
|
+
|
|
3
|
+
@function size($size) {
|
|
4
|
+
@return map-get($font-size, $size);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
@function weight($weight) {
|
|
8
|
+
@return map-get($font-weight, $weight);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.import-page {
|
|
12
|
+
&__header {
|
|
13
|
+
min-height: 110px;
|
|
14
|
+
padding: 24px;
|
|
15
|
+
background: $white;
|
|
16
|
+
.header-title {
|
|
17
|
+
@include font($size: size(24), $color: $primary-text-clr, $weight: bold);
|
|
18
|
+
font-family: $primary-font;
|
|
19
|
+
line-height: 32px;
|
|
20
|
+
margin-bottom: 8px;
|
|
21
|
+
}
|
|
22
|
+
.header-description {
|
|
23
|
+
@include font($size: size(14), $color: $primary-text-clr);
|
|
24
|
+
font-family: $primary-font;
|
|
25
|
+
line-height: 22px;
|
|
26
|
+
margin-bottom: 0;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
&__body {
|
|
30
|
+
padding: 16px 24px;
|
|
31
|
+
.body-title {
|
|
32
|
+
@include font($size: size(16), $color: $primary-text-clr, $weight: bold);
|
|
33
|
+
font-family: $primary-font;
|
|
34
|
+
line-height: 24px;
|
|
35
|
+
margin-bottom: 16px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.success-card {
|
|
39
|
+
max-width: 752px;
|
|
40
|
+
margin-top: 24px;
|
|
41
|
+
padding: 24px;
|
|
42
|
+
background: $green-lightest-clr;
|
|
43
|
+
border-radius: $radius-4;
|
|
44
|
+
.success-title {
|
|
45
|
+
@include font($size: size(12), $color: $green-regular-clr, $weight: bold);
|
|
46
|
+
font-family: $primary-font;
|
|
47
|
+
margin-bottom: 16px;
|
|
48
|
+
text-transform: uppercase;
|
|
49
|
+
}
|
|
50
|
+
.success-msg {
|
|
51
|
+
@include font($size: size(16), $weight: bold);
|
|
52
|
+
font-family: $primary-font;
|
|
53
|
+
line-height: 24px;
|
|
54
|
+
margin-bottom: 8px;
|
|
55
|
+
}
|
|
56
|
+
.success-msg-info {
|
|
57
|
+
@include font($size: size(14));
|
|
58
|
+
font-family: $primary-font;
|
|
59
|
+
line-height: 22px;
|
|
60
|
+
margin-bottom: 0;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.actions-wrapper {
|
|
65
|
+
margin-top: 32px;
|
|
66
|
+
button {
|
|
67
|
+
padding: 5px 10px;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//form UI
|
|
72
|
+
.csv-import-form {
|
|
73
|
+
.form-card {
|
|
74
|
+
max-width: 752px;
|
|
75
|
+
margin-bottom: 32px;
|
|
76
|
+
padding: 24px;
|
|
77
|
+
background: $white;
|
|
78
|
+
border: 1px solid $grey-light-clr;
|
|
79
|
+
border-radius: $radius-4;
|
|
80
|
+
}
|
|
81
|
+
.note-card {
|
|
82
|
+
width: 100%;
|
|
83
|
+
margin-top: 24px;
|
|
84
|
+
padding: 24px;
|
|
85
|
+
background: $blue-lightest-clr;
|
|
86
|
+
border-radius: $radius-4;
|
|
87
|
+
.note-title {
|
|
88
|
+
@include font($size: size(12), $color: $blue-regular-clr, $weight: bold);
|
|
89
|
+
font-family: $primary-font;
|
|
90
|
+
margin-bottom: 16px;
|
|
91
|
+
text-transform: uppercase;
|
|
92
|
+
}
|
|
93
|
+
.steps-wrapper {
|
|
94
|
+
.steps-title {
|
|
95
|
+
@include font($size: size(16), $weight: bold);
|
|
96
|
+
font-family: $primary-font;
|
|
97
|
+
line-height: 24px;
|
|
98
|
+
margin-bottom: 8px;
|
|
99
|
+
}
|
|
100
|
+
.steps-list {
|
|
101
|
+
margin-bottom: 0;
|
|
102
|
+
padding-inline-start: 16px;
|
|
103
|
+
li {
|
|
104
|
+
@include font($size: size(14));
|
|
105
|
+
font-family: $primary-font;
|
|
106
|
+
line-height: 22px;
|
|
107
|
+
margin-bottom: 8px;
|
|
108
|
+
&:nth-last-child(1) {
|
|
109
|
+
margin-bottom: 0;
|
|
110
|
+
}
|
|
111
|
+
a {
|
|
112
|
+
color: $brand-color;
|
|
113
|
+
text-decoration: underline !important;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
.import-btn {
|
|
120
|
+
padding: 5px 10px;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -71,6 +71,25 @@ module CmAdmin
|
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
def import
|
|
75
|
+
@model = Model.find_by({name: controller_name.titleize})
|
|
76
|
+
allowed_params = params.permit(file_import: [:associated_model_name, :import_file]).to_h
|
|
77
|
+
file_import = ::FileImport.new(allowed_params[:file_import])
|
|
78
|
+
file_import.added_by = Current.user
|
|
79
|
+
respond_to do |format|
|
|
80
|
+
if file_import.save!
|
|
81
|
+
format.html { redirect_back fallback_location: cm_admin.send("#{@model.name.underscore}_index_path"), notice: "Your import is successfully queued." }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def import_form
|
|
87
|
+
@model = Model.find_by({name: controller_name.titleize})
|
|
88
|
+
respond_to do |format|
|
|
89
|
+
format.html { render '/cm_admin/main/import_form' }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
74
93
|
def cm_custom_method(params)
|
|
75
94
|
scoped_model = "CmAdmin::#{@model.name}Policy::Scope".constantize.new(Current.user, @model.name.constantize).resolve
|
|
76
95
|
resource_identifier
|
|
@@ -24,5 +24,46 @@ module CmAdmin
|
|
|
24
24
|
return action_name.to_sym
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
|
+
|
|
28
|
+
def formatted_error_message(model_name, field_name)
|
|
29
|
+
invalid_rows = model_name.send(field_name)
|
|
30
|
+
if invalid_rows.present?
|
|
31
|
+
content_tag(:div) do
|
|
32
|
+
concat error_header
|
|
33
|
+
concat error_items(invalid_rows)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def error_header
|
|
39
|
+
content_tag :div, class: 'info-split' do
|
|
40
|
+
concat content_tag(:div, "Row number", class: 'info-split__lhs')
|
|
41
|
+
concat content_tag(:div, "Error")
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def error_items(invalid_rows)
|
|
46
|
+
content_tag :div do
|
|
47
|
+
invalid_rows.each do |row_item|
|
|
48
|
+
concat format_error_item(row_item)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def format_error_item(row_item)
|
|
54
|
+
content_tag :div, class: 'info-split' do
|
|
55
|
+
concat content_tag(:div, row_item[0], class: 'info-split__lhs')
|
|
56
|
+
concat format_error(row_item[2])
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def format_error(errors)
|
|
61
|
+
content_tag :div do
|
|
62
|
+
errors.each do |error|
|
|
63
|
+
message = error[1].instance_of?(Array) ? error[1].join(', ') : error[1]
|
|
64
|
+
concat content_tag(:div, error[0].titleize + '-' + message)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
27
68
|
end
|
|
28
69
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class FileImportProcessorJob < ApplicationJob
|
|
2
|
+
queue_as :default
|
|
3
|
+
|
|
4
|
+
def perform(file_import)
|
|
5
|
+
model = CmAdmin::Model.find_by({name: file_import.associated_model_name})
|
|
6
|
+
path = ActiveStorage::Blob.service.path_for(file_import.import_file.key)
|
|
7
|
+
importer = model.importer.class_name.classify.constantize.new(path: path)
|
|
8
|
+
case model.importer.importer_type.to_s
|
|
9
|
+
when 'csv_importer'
|
|
10
|
+
run_csv_importer(importer, file_import)
|
|
11
|
+
when 'custom_importer'
|
|
12
|
+
run_custom_importer(importer, file_import)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# All logic are from csv_importer gem
|
|
17
|
+
def run_csv_importer(importer, file_import)
|
|
18
|
+
if importer.valid_header?
|
|
19
|
+
importer.run!
|
|
20
|
+
if importer.report.success?
|
|
21
|
+
file_import.update(status: :success, completed_at: DateTime.now)
|
|
22
|
+
else
|
|
23
|
+
identifier = importer.config.identifiers.first
|
|
24
|
+
invalid_items_array = importer.report.invalid_rows.map { |row| [row.line_number, row.model.send(identifier), row.errors] }
|
|
25
|
+
file_import.update(status: :failed, completed_at: DateTime.now, invalid_row_items: invalid_items_array)
|
|
26
|
+
end
|
|
27
|
+
else
|
|
28
|
+
file_import.update(status: :failed, completed_at: DateTime.now, invalid_row_items: [[1, 'invalid_header', {invalid_header: importer.report.message}]])
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def run_custom_importer(importer, file_import)
|
|
33
|
+
importer.run!
|
|
34
|
+
invalid_items_array = importer.invalid_rows.map { |row| [row.line_number, row.identifier, row.errors] }
|
|
35
|
+
status = importer.invalid_rows.empty? ? :success : :failed
|
|
36
|
+
file_import.update(status: status, completed_at: DateTime.now, invalid_row_items: invalid_items_array)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module CmAdmin::FileImport
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
included do
|
|
4
|
+
cm_admin do
|
|
5
|
+
STATUS_TAG_COLOR = { in_progress: 'yellow-tag', success: 'success'}
|
|
6
|
+
actions only: [:index, :show]
|
|
7
|
+
set_icon 'fa fa-user'
|
|
8
|
+
cm_index do
|
|
9
|
+
page_title 'File Import'
|
|
10
|
+
page_description 'Manage all file import progress here'
|
|
11
|
+
|
|
12
|
+
column :id
|
|
13
|
+
column :imported_file_name, header: 'File'
|
|
14
|
+
column :created_at, header: 'Started At', field_type: :datetime, format: '%B %d, %Y, %H:%M %p'
|
|
15
|
+
column :completed_at, header: 'Completed At', field_type: :datetime, format: '%B %d, %Y, %H:%M %p'
|
|
16
|
+
column :associated_model_name, header: 'Model name'
|
|
17
|
+
column :added_by_name, header: 'Uploaded By'
|
|
18
|
+
column :status, field_type: :tag, tag_class: STATUS_TAG_COLOR
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
cm_show page_title: :id do
|
|
22
|
+
tab :profile, '' do
|
|
23
|
+
cm_show_section 'Import details' do
|
|
24
|
+
field :id, label: 'Import ID'
|
|
25
|
+
field :imported_file_name, label: 'File'
|
|
26
|
+
field :created_at, header: 'Started At', field_type: :datetime, format: '%B %d, %Y, %H:%M %p'
|
|
27
|
+
field :completed_at, header: 'Completed At', field_type: :datetime, format: '%B %d, %Y, %H:%M %p'
|
|
28
|
+
field :associated_model_name, header: 'Model name'
|
|
29
|
+
field :added_by_name, header: 'Uploaded By'
|
|
30
|
+
field :status, field_type: :tag, tag_class: STATUS_TAG_COLOR
|
|
31
|
+
end
|
|
32
|
+
cm_show_section 'Errors' do
|
|
33
|
+
field :invalid_row_items, field_type: :custom, helper_method: :formatted_error_message
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class FileImport < ApplicationRecord
|
|
2
|
+
include CmAdmin::FileImport
|
|
3
|
+
|
|
4
|
+
belongs_to :added_by, polymorphic: true
|
|
5
|
+
|
|
6
|
+
enum status: { in_progress: 0, success: 1, failed: 2 }
|
|
7
|
+
|
|
8
|
+
has_one_attached :import_file
|
|
9
|
+
|
|
10
|
+
after_create_commit :process_uploaded_file
|
|
11
|
+
|
|
12
|
+
store_accessor :error_report, :invalid_row_items
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def process_uploaded_file
|
|
16
|
+
FileImportProcessorJob.perform_later(self)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def imported_file_name
|
|
20
|
+
import_file.filename.to_s
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def added_by_name
|
|
24
|
+
added_by.first_name
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
- edit_action = available_actions(cm_model, 'edit')
|
|
2
2
|
- destroy_action = available_actions(cm_model, 'destroy')
|
|
3
3
|
- custom_actions = available_actions(cm_model, 'custom_actions')
|
|
4
|
-
-
|
|
5
|
-
|
|
4
|
+
- current_model = @associated_model || @model
|
|
6
5
|
- if custom_actions.any? || edit_action.present? || destroy_action.present?
|
|
7
6
|
td.row-action-cell
|
|
8
7
|
.row-action-tool
|
|
@@ -13,13 +12,13 @@
|
|
|
13
12
|
i.fa.fa-angle-down
|
|
14
13
|
.popup-card.table-export-popup.hidden
|
|
15
14
|
- if edit_action.present?
|
|
16
|
-
= link_to "#{
|
|
15
|
+
= link_to cm_admin.send("#{current_model.name.underscore}_edit_path", ar_object.id) do
|
|
17
16
|
.popup-option
|
|
18
17
|
span
|
|
19
18
|
i.fa.fa-edit
|
|
20
19
|
| Edit
|
|
21
20
|
- if destroy_action.present?
|
|
22
|
-
= link_to "#{
|
|
21
|
+
= link_to cm_admin.send("#{current_model.name.underscore}_destroy_path", ar_object.id), method: :delete do
|
|
23
22
|
.popup-option
|
|
24
23
|
span
|
|
25
24
|
i.fa.fa-trash
|
|
@@ -39,14 +38,3 @@
|
|
|
39
38
|
span
|
|
40
39
|
i class="#{custom_action.icon_name}"
|
|
41
40
|
= custom_action.name.humanize
|
|
42
|
-
|
|
43
|
-
- if custom_actions_modals.any?
|
|
44
|
-
- custom_actions_modals.each do |custom_action|
|
|
45
|
-
.modal.fade id="#{custom_action.name.classify}Modal-#{ar_object.id.to_s}" aria-hidden='true' aria-labelledby="#{custom_action.name.classify}ModalLabel" tabindex='1'
|
|
46
|
-
.modal-dialog
|
|
47
|
-
.modal-content
|
|
48
|
-
.modal-header
|
|
49
|
-
h5.modal-title id="#{custom_action.name.classify}ModalLabel" = custom_action.name.classify
|
|
50
|
-
button.btn-close aria-label='Close' data-bs-dismiss='modal'
|
|
51
|
-
.modal-body
|
|
52
|
-
= render partial: custom_action.partial, locals: { ar_object: ar_object }
|
|
@@ -46,3 +46,5 @@
|
|
|
46
46
|
.cm-pagination__lhs Showing #{@associated_ar_object.pagy.from} to #{@associated_ar_object.pagy.to} out of #{@associated_ar_object.pagy.count}
|
|
47
47
|
.cm-pagination__rhs
|
|
48
48
|
== render partial: 'cm_admin/main/cm_pagy_nav', locals: { pagy: @associated_ar_object.pagy }
|
|
49
|
+
|
|
50
|
+
= render partial: 'cm_admin/main/member_custom_action_modal', locals: { cm_model: @associated_model, ar_collection: @associated_ar_object }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
- custom_actions = available_actions(cm_model, 'custom_actions')
|
|
2
|
+
- custom_actions_modals = custom_actions.select{ |act| act if act.display_type.eql?(:modal) }
|
|
3
|
+
- ar_collection.data.each do |ar_object|
|
|
4
|
+
- if custom_actions_modals.any?
|
|
5
|
+
- custom_actions_modals.each do |custom_action|
|
|
6
|
+
.modal.fade id="#{custom_action.name.classify}Modal-#{ar_object.id.to_s}" aria-hidden='true' aria-labelledby="#{custom_action.name.classify}ModalLabel" tabindex='1'
|
|
7
|
+
.modal-dialog
|
|
8
|
+
.modal-content
|
|
9
|
+
.modal-header
|
|
10
|
+
h5.modal-title id="#{custom_action.name.classify}ModalLabel" = custom_action.name.classify
|
|
11
|
+
button.btn-close aria-label='Close' data-bs-dismiss='modal'
|
|
12
|
+
.modal-body
|
|
13
|
+
= render partial: custom_action.partial, locals: { ar_object: ar_object }
|
|
@@ -39,3 +39,5 @@
|
|
|
39
39
|
.cm-pagination__lhs Showing #{@ar_object.pagy.from} to #{@ar_object.pagy.to} out of #{@ar_object.pagy.count}
|
|
40
40
|
.cm-pagination__rhs
|
|
41
41
|
== render partial: 'cm_admin/main/cm_pagy_nav', locals: { pagy: @ar_object.pagy }
|
|
42
|
+
|
|
43
|
+
= render partial: 'cm_admin/main/member_custom_action_modal', locals: { cm_model: @model, ar_collection: @ar_object }
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
li
|
|
23
23
|
.popup-option.pointer data-bs-toggle='modal' data-bs-target='#exportmodal'
|
|
24
24
|
span Export
|
|
25
|
+
- if @model.importer
|
|
26
|
+
= link_to 'Import', cm_admin.send(:"#{@model.name.underscore}_import_path"), class: 'primary-btn ml-2'
|
|
25
27
|
- new_action = @model.available_actions.select{|act| act if act.action_type.eql?(:default) && act.name.eql?('new')}
|
|
26
28
|
- if new_action.any? && policy([:cm_admin, @model.name.classify.constantize]).new?
|
|
27
29
|
= link_to 'Add', cm_admin.send(:"#{@model.name.underscore}_new_path"), class: 'primary-btn ml-2'
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.import-page
|
|
2
|
+
.import-page__header
|
|
3
|
+
h1.header-title #{@model.name} Import
|
|
4
|
+
p.header-description To import #{@model.name.underscore} data using the form below.
|
|
5
|
+
.import-page__body
|
|
6
|
+
h6.body-title Import data
|
|
7
|
+
- if flash[:notice]
|
|
8
|
+
.success-card
|
|
9
|
+
h6.success-title
|
|
10
|
+
i.fa-solid.fa-circle-check
|
|
11
|
+
| SUCCESS
|
|
12
|
+
p.success-msg Your file has been uploaded and it will be processed soon.
|
|
13
|
+
/ p.success-msg-info An email will be sent once the process is completed. If there are any problems with the import, we'll let you know through an email.
|
|
14
|
+
.actions-wrapper
|
|
15
|
+
a.secondary-btn href="#{cm_admin.send(:"#{@model.name.underscore}_import_path")}" Import new data
|
|
16
|
+
/ button.cta-btn.ml-2 Back to Page_Name
|
|
17
|
+
- else
|
|
18
|
+
= simple_form_for(FileImport.new, url: "/admin/#{@model.ar_model.table_name}/import", method: :post, html: { class: "csv-import-form" }) do |f|
|
|
19
|
+
.form-card
|
|
20
|
+
= f.input :associated_model_name, as: :hidden, placeholder: 'Enter product title', label: false, input_html: { value: @model.name }
|
|
21
|
+
= f.input :import_file, as: :file, placeholder: 'Enter product title', label: false
|
|
22
|
+
.note-card
|
|
23
|
+
h6.note-title
|
|
24
|
+
i.fa.fa-info-circle
|
|
25
|
+
| Note
|
|
26
|
+
.steps-wrapper
|
|
27
|
+
h6.steps-title Follow these steps to import your data
|
|
28
|
+
ul.steps-list
|
|
29
|
+
li
|
|
30
|
+
| Download this
|
|
31
|
+
a href="#" template file
|
|
32
|
+
li Add your data on the file without changing the format
|
|
33
|
+
li Save the file as a csv, xls or xlsx
|
|
34
|
+
li Upload the file in the field above
|
|
35
|
+
= f.button :submit, class: "cta-btn import-btn", value: 'Import data'
|
data/cm_admin.gemspec
CHANGED
data/config/routes.rb
CHANGED
|
@@ -10,6 +10,12 @@ CmAdmin::Engine.routes.draw do
|
|
|
10
10
|
|
|
11
11
|
# Defining action routes for each model
|
|
12
12
|
CmAdmin.config.cm_admin_models.each do |model|
|
|
13
|
+
if model.importer
|
|
14
|
+
scope model.name.tableize do
|
|
15
|
+
send(:get, 'import', to: "#{model.name.underscore}#import_form", as: "#{model.name.underscore}_import_form")
|
|
16
|
+
send(:post, 'import', to: "#{model.name.underscore}#import", as: "#{model.name.underscore}_import")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
13
19
|
model.available_actions.sort_by {|act| act.name}.each do |act|
|
|
14
20
|
scope model.name.tableize do
|
|
15
21
|
send(act.verb, act.path.present? ? act.path : act.name, to: "#{model.name.underscore}##{act.name}", as: "#{model.name.underscore}_#{act.name}")
|
data/lib/cm_admin/model.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require_relative 'constants'
|
|
2
2
|
require_relative 'models/action'
|
|
3
|
+
require_relative 'models/importer'
|
|
3
4
|
require_relative 'models/custom_action'
|
|
4
5
|
require_relative 'models/field'
|
|
5
6
|
require_relative 'models/form_field'
|
|
@@ -15,6 +16,7 @@ require 'axlsx'
|
|
|
15
16
|
require 'cocoon'
|
|
16
17
|
require 'pundit'
|
|
17
18
|
require 'local_time'
|
|
19
|
+
require 'csv_importer'
|
|
18
20
|
|
|
19
21
|
module CmAdmin
|
|
20
22
|
class Model
|
|
@@ -23,7 +25,7 @@ module CmAdmin
|
|
|
23
25
|
include Models::DslMethod
|
|
24
26
|
attr_accessor :available_actions, :actions_set, :available_fields, :permitted_fields,
|
|
25
27
|
:current_action, :params, :filters, :available_tabs, :icon_name
|
|
26
|
-
attr_reader :name, :ar_model, :is_visible_on_sidebar
|
|
28
|
+
attr_reader :name, :ar_model, :is_visible_on_sidebar, :importer
|
|
27
29
|
|
|
28
30
|
def initialize(entity, &block)
|
|
29
31
|
@name = entity.name
|
|
@@ -80,6 +82,10 @@ module CmAdmin
|
|
|
80
82
|
@actions_set = true
|
|
81
83
|
end
|
|
82
84
|
|
|
85
|
+
def importable(class_name:, importer_type:)
|
|
86
|
+
@importer = CmAdmin::Models::Importer.new(class_name, importer_type)
|
|
87
|
+
end
|
|
88
|
+
|
|
83
89
|
def visible_on_sidebar(visible_option)
|
|
84
90
|
@is_visible_on_sidebar = visible_option
|
|
85
91
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module CmAdmin
|
|
2
2
|
module Models
|
|
3
3
|
class Column
|
|
4
|
-
attr_accessor :field_name, :field_type, :header, :format, :prefix, :suffix, :exportable, :round,
|
|
4
|
+
attr_accessor :field_name, :field_type, :header, :format, :prefix, :suffix, :exportable, :round, :height, :width,
|
|
5
5
|
:cm_css_class, :link, :url, :custom_method, :helper_method, :managable, :lockable, :drawer_partial, :tag_class
|
|
6
6
|
|
|
7
7
|
def initialize(field_name, attributes = {})
|
|
@@ -13,6 +13,8 @@ module CmAdmin
|
|
|
13
13
|
|
|
14
14
|
#formatting header (either field_name or value present in header attribute)
|
|
15
15
|
self.send("header=", format_header)
|
|
16
|
+
self.height = 50 if self.field_type == :image && self.height.nil?
|
|
17
|
+
self.width = 50 if self.field_type == :image && self.width.nil?
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
#returns a string value as a header (either field_name or value present in header attribute)
|
|
@@ -2,8 +2,8 @@ module CmAdmin
|
|
|
2
2
|
module Models
|
|
3
3
|
class Field
|
|
4
4
|
|
|
5
|
-
attr_accessor :field_name, :label, :header, :field_type, :format, :precision,
|
|
6
|
-
:helper_method, :preview, :custom_link, :precision, :prefix, :suffix, :tag_class
|
|
5
|
+
attr_accessor :field_name, :label, :header, :field_type, :format, :precision, :height,
|
|
6
|
+
:width, :helper_method, :preview, :custom_link, :precision, :prefix, :suffix, :tag_class
|
|
7
7
|
|
|
8
8
|
def initialize(field_name, attributes = {})
|
|
9
9
|
@field_name = field_name
|
|
@@ -11,6 +11,8 @@ module CmAdmin
|
|
|
11
11
|
attributes.each do |key, value|
|
|
12
12
|
self.send("#{key.to_s}=", value)
|
|
13
13
|
end
|
|
14
|
+
self.height = 50 if self.field_type == :image && self.height.nil?
|
|
15
|
+
self.width = 50 if self.field_type == :image && self.width.nil?
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
def set_default_values
|
|
@@ -5,17 +5,18 @@ module CmAdmin
|
|
|
5
5
|
VALID_INPUT_TYPES = [:integer, :decimal, :string, :single_select, :multi_select, :date, :date_time, :text, :single_file_upload, :multi_file_upload, :hidden, :rich_text].freeze
|
|
6
6
|
|
|
7
7
|
def initialize(field_name, input_type, attributes = {})
|
|
8
|
-
raise ArgumentError, "Kindly select a valid filter type like #{VALID_INPUT_TYPES.sort.to_sentence(last_word_connector: ', or ')} instead of #{input_type} for column #{field_name}" unless VALID_INPUT_TYPES.include?(input_type.to_sym)
|
|
9
8
|
@field_name = field_name
|
|
10
9
|
set_default_values
|
|
11
10
|
attributes.each do |key, value|
|
|
12
11
|
self.send("#{key.to_s}=", value)
|
|
13
12
|
end
|
|
13
|
+
raise ArgumentError, "Kindly select a valid input type like #{VALID_INPUT_TYPES.sort.to_sentence(last_word_connector: ', or ')} instead of #{self.input_type} for form field #{field_name}" unless VALID_INPUT_TYPES.include?(self.input_type.to_sym)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def set_default_values
|
|
17
17
|
self.disabled = false
|
|
18
18
|
self.label = self.field_name.to_s.titleize
|
|
19
|
+
self.input_type = :string
|
|
19
20
|
end
|
|
20
21
|
end
|
|
21
22
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module CmAdmin
|
|
2
|
+
module Models
|
|
3
|
+
class Importer
|
|
4
|
+
|
|
5
|
+
attr_accessor :class_name, :importer_type
|
|
6
|
+
|
|
7
|
+
VALID_IMPORTER_TYPES = [:csv_importer, :custom_importer]
|
|
8
|
+
|
|
9
|
+
def initialize(class_name, importer_type=:csv_importer)
|
|
10
|
+
raise ArgumentError, "Kindly select a valid importer type like #{VALID_IMPORTER_TYPES.sort.to_sentence(last_word_connector: ', or ')} instead of #{importer_type}" unless VALID_IMPORTER_TYPES.include?(importer_type.to_sym)
|
|
11
|
+
@class_name = class_name
|
|
12
|
+
@importer_type = importer_type
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/cm_admin/version.rb
CHANGED
|
@@ -61,6 +61,14 @@ module CmAdmin
|
|
|
61
61
|
concat content_tag(:div, ar_object.send(field.field_name).to_s, class: 'text-ellipsis')
|
|
62
62
|
concat content_tag(:div, 'View', class: 'drawer-btn')
|
|
63
63
|
end
|
|
64
|
+
when :image
|
|
65
|
+
content_tag(:div, class: 'd-flex') do
|
|
66
|
+
if ar_object.send(field.field_name).attached?
|
|
67
|
+
image_tag(ar_object.send(field.field_name).url, height: field.height, width: field.height)
|
|
68
|
+
else
|
|
69
|
+
image_tag('/assets/image_not_available', height: 50, width: 50)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
64
72
|
end
|
|
65
73
|
end
|
|
66
74
|
|
|
@@ -13,9 +13,9 @@ module CmAdmin
|
|
|
13
13
|
when :string
|
|
14
14
|
return f.text_field field.field_name, class: "normal-input #{required_class}", disabled: field.disabled, value: value, placeholder: "Enter #{field.field_name.to_s.downcase.gsub('_', ' ')}"
|
|
15
15
|
when :single_select
|
|
16
|
-
return f.select field.field_name, options_for_select(select_collection_value(field),
|
|
16
|
+
return f.select field.field_name, options_for_select(select_collection_value(field), f.object.send(field.field_name)), {include_blank: "Select #{field.field_name.to_s.downcase.gsub('_', ' ')}"}, class: "normal-input #{required_class} select-2", disabled: field.disabled
|
|
17
17
|
when :multi_select
|
|
18
|
-
return f.select field.field_name, options_for_select(select_collection_value(field),
|
|
18
|
+
return f.select field.field_name, options_for_select(select_collection_value(field), f.object.send(field.field_name)), {include_blank: "Select #{field.field_name.to_s.downcase.gsub('_', ' ')}"}, class: "normal-input #{required_class} select-2", disabled: field.disabled, multiple: true
|
|
19
19
|
when :date
|
|
20
20
|
return f.text_field field.field_name, class: "normal-input #{required_class}", disabled: field.disabled, value: value&.strftime('%d-%m-%Y'), placeholder: "Enter #{field.field_name.to_s.downcase.gsub('_', ' ')}", data: { behaviour: 'date-only' }
|
|
21
21
|
when :date_time
|
data/lib/cm_admin.rb
CHANGED
|
@@ -35,7 +35,8 @@ module CmAdmin
|
|
|
35
35
|
def initialize_model(entity, &block)
|
|
36
36
|
if entity.is_a?(Class)
|
|
37
37
|
return if CmAdmin::Model.find_by({name: entity.name})
|
|
38
|
-
|
|
38
|
+
cm_model = CmAdmin::Model.new(entity, &block)
|
|
39
|
+
config.cm_admin_models << cm_model
|
|
39
40
|
end
|
|
40
41
|
end
|
|
41
42
|
end
|
|
@@ -14,6 +14,8 @@ module CmAdmin
|
|
|
14
14
|
remove_file 'app/assets/stylesheets/actiontext.scss'
|
|
15
15
|
copy_file 'application_policy.rb', 'app/policies/application_policy.rb'
|
|
16
16
|
route 'mount CmAdmin::Engine => "/admin"'
|
|
17
|
+
generate 'migration', 'CreateFileImport associated_model_name:string added_by:references{polymorphic} error_report:jsonb completed_at:datetime status:integer'
|
|
18
|
+
rake 'db:migrate'
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
21
|
end
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
31533361827c4faa12bf892403c629479a81246a
|
metadata
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cm-admin
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- sajinmp
|
|
8
8
|
- anbublacky
|
|
9
9
|
- AdityaTiwari2102
|
|
10
|
-
autorequire:
|
|
10
|
+
autorequire:
|
|
11
11
|
bindir: exe
|
|
12
12
|
cert_chain: []
|
|
13
|
-
date: 2022-
|
|
13
|
+
date: 2022-10-12 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: caxlsx_rails
|
|
@@ -110,6 +110,20 @@ dependencies:
|
|
|
110
110
|
- - "~>"
|
|
111
111
|
- !ruby/object:Gem::Version
|
|
112
112
|
version: 5.4.3
|
|
113
|
+
- !ruby/object:Gem::Dependency
|
|
114
|
+
name: csv-importer
|
|
115
|
+
requirement: !ruby/object:Gem::Requirement
|
|
116
|
+
requirements:
|
|
117
|
+
- - "~>"
|
|
118
|
+
- !ruby/object:Gem::Version
|
|
119
|
+
version: 0.8.2
|
|
120
|
+
type: :runtime
|
|
121
|
+
prerelease: false
|
|
122
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
123
|
+
requirements:
|
|
124
|
+
- - "~>"
|
|
125
|
+
- !ruby/object:Gem::Version
|
|
126
|
+
version: 0.8.2
|
|
113
127
|
description: This is an admin panel gem
|
|
114
128
|
email:
|
|
115
129
|
- sajinprasadkm@gmail.com
|
|
@@ -128,6 +142,7 @@ files:
|
|
|
128
142
|
- LICENSE.txt
|
|
129
143
|
- README.md
|
|
130
144
|
- Rakefile
|
|
145
|
+
- app/assets/images/image_not_available.png
|
|
131
146
|
- app/assets/images/logo.png
|
|
132
147
|
- app/assets/stylesheets/cm_admin/base/auth.scss
|
|
133
148
|
- app/assets/stylesheets/cm_admin/base/common.scss
|
|
@@ -155,6 +170,7 @@ files:
|
|
|
155
170
|
- app/assets/stylesheets/cm_admin/helpers/_mixins.scss
|
|
156
171
|
- app/assets/stylesheets/cm_admin/helpers/_variable.scss
|
|
157
172
|
- app/assets/stylesheets/cm_admin/helpers/index.scss
|
|
173
|
+
- app/assets/stylesheets/cm_admin/pages/import_page.scss
|
|
158
174
|
- app/assets/stylesheets/cm_admin/scaffold.scss
|
|
159
175
|
- app/controllers/cm_admin/application_controller.rb
|
|
160
176
|
- app/controllers/cm_admin/exports_controller.rb
|
|
@@ -169,11 +185,16 @@ files:
|
|
|
169
185
|
- app/javascript/packs/cm_admin/quick_search.js
|
|
170
186
|
- app/javascript/packs/cm_admin/scaffolds.js
|
|
171
187
|
- app/javascript/stylesheets/cm_admin/application.scss
|
|
188
|
+
- app/jobs/file_import_processor_job.rb
|
|
189
|
+
- app/models/concerns/cm_admin/file_import.rb
|
|
190
|
+
- app/models/file_import.rb
|
|
191
|
+
- app/policies/cm_admin/file_import_policy.rb
|
|
172
192
|
- app/views/cm_admin/main/_actions_dropdown.html.slim
|
|
173
193
|
- app/views/cm_admin/main/_associated_table.html.slim
|
|
174
194
|
- app/views/cm_admin/main/_cm_pagy_nav.html.slim
|
|
175
195
|
- app/views/cm_admin/main/_drawer.html.slim
|
|
176
196
|
- app/views/cm_admin/main/_filters.html.slim
|
|
197
|
+
- app/views/cm_admin/main/_member_custom_action_modal.html.slim
|
|
177
198
|
- app/views/cm_admin/main/_nested_fields.html.slim
|
|
178
199
|
- app/views/cm_admin/main/_nested_table_form.html.slim
|
|
179
200
|
- app/views/cm_admin/main/_table.html.slim
|
|
@@ -183,6 +204,7 @@ files:
|
|
|
183
204
|
- app/views/cm_admin/main/associated_show.html.slim
|
|
184
205
|
- app/views/cm_admin/main/dashboard.html.slim
|
|
185
206
|
- app/views/cm_admin/main/edit.html.slim
|
|
207
|
+
- app/views/cm_admin/main/import_form.html.slim
|
|
186
208
|
- app/views/cm_admin/main/index.html.slim
|
|
187
209
|
- app/views/cm_admin/main/new.html.slim
|
|
188
210
|
- app/views/cm_admin/main/show.html.slim
|
|
@@ -225,6 +247,7 @@ files:
|
|
|
225
247
|
- lib/cm_admin/models/field.rb
|
|
226
248
|
- lib/cm_admin/models/filter.rb
|
|
227
249
|
- lib/cm_admin/models/form_field.rb
|
|
250
|
+
- lib/cm_admin/models/importer.rb
|
|
228
251
|
- lib/cm_admin/models/tab.rb
|
|
229
252
|
- lib/cm_admin/utils.rb
|
|
230
253
|
- lib/cm_admin/version.rb
|
|
@@ -278,7 +301,7 @@ licenses:
|
|
|
278
301
|
metadata:
|
|
279
302
|
homepage_uri: https://github.com/commutatus/cm-admin
|
|
280
303
|
source_code_uri: https://github.com/commutatus/cm-admin
|
|
281
|
-
post_install_message:
|
|
304
|
+
post_install_message:
|
|
282
305
|
rdoc_options: []
|
|
283
306
|
require_paths:
|
|
284
307
|
- lib
|
|
@@ -294,7 +317,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
294
317
|
version: '0'
|
|
295
318
|
requirements: []
|
|
296
319
|
rubygems_version: 3.2.3
|
|
297
|
-
signing_key:
|
|
320
|
+
signing_key:
|
|
298
321
|
specification_version: 4
|
|
299
322
|
summary: This is an admin panel gem
|
|
300
323
|
test_files: []
|