tiny_admin 0.2.1 → 0.4.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/README.md +35 -29
- data/lib/tiny_admin/actions/basic_action.rb +15 -6
- data/lib/tiny_admin/actions/index.rb +33 -14
- data/lib/tiny_admin/actions/show.rb +12 -8
- data/lib/tiny_admin/context.rb +1 -1
- data/lib/tiny_admin/field.rb +9 -3
- data/lib/tiny_admin/plugins/active_record_repository.rb +16 -28
- data/lib/tiny_admin/plugins/base_repository.rb +14 -0
- data/lib/tiny_admin/router.rb +21 -15
- data/lib/tiny_admin/settings.rb +51 -24
- data/lib/tiny_admin/support.rb +35 -0
- data/lib/tiny_admin/utils.rb +12 -3
- data/lib/tiny_admin/version.rb +1 -1
- data/lib/tiny_admin/views/actions/index.rb +23 -31
- data/lib/tiny_admin/views/actions/show.rb +22 -21
- data/lib/tiny_admin/views/basic_layout.rb +0 -4
- data/lib/tiny_admin/views/components/flash.rb +8 -8
- data/lib/tiny_admin/views/components/head.rb +6 -6
- data/lib/tiny_admin/views/components/pagination.rb +33 -12
- data/lib/tiny_admin/views/default_layout.rb +8 -14
- data/lib/tiny_admin.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6eb4a218cc1a4ff6acc940c402d74202582d2092b39f920e6551e5b395da752e
|
4
|
+
data.tar.gz: fb137625f9f5faa5b33dd5c7633a68eec7a4a2d2bb31db6f70758d6ed9ec3750
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd7800ab65bd0e92ce5646210f08823d031fdd217f5230bc99207a0e00375b068ad2337805ddce196ca317fe6f0df60ad3e209fc885e31c765c68c125f83d900
|
7
|
+
data.tar.gz: f1df2bd938781a04c4af36c9653a7efffcc075cf72accd8f9fbaedbcc74286d860a1953bab159ec833c6a2cf5851a00633f555966b915fedec625af9ef2f85fa
|
data/README.md
CHANGED
@@ -16,7 +16,7 @@ Please ⭐ if you like it.
|
|
16
16
|
|
17
17
|
## Install
|
18
18
|
|
19
|
-
- Add to your Gemfile: `gem 'tiny_admin', '~> 0.
|
19
|
+
- Add to your Gemfile: `gem 'tiny_admin', '~> 0.4'`
|
20
20
|
- Mount the app in a route (check some examples with: Hanami, Rails, Roda and standalone in [extra](extra))
|
21
21
|
+ in Rails, update _config/routes.rb_: `mount TinyAdmin::Router => '/admin'`
|
22
22
|
- Configure the dashboard using `TinyAdmin.configure` and/or `TinyAdmin.configure_from_file` (see [configuration](#configuration) below):
|
@@ -82,6 +82,12 @@ root:
|
|
82
82
|
redirect: posts
|
83
83
|
```
|
84
84
|
|
85
|
+
`helper_class` (String): class or module with helper methods, used for attributes' formatters.
|
86
|
+
|
87
|
+
`page_not_found` (String): a view object to render when a missing page is requested.
|
88
|
+
|
89
|
+
`record_not_found` (String): a view object to render when a missing record is requested.
|
90
|
+
|
85
91
|
`authentication` (Hash): define the authentication method, properties:
|
86
92
|
- `plugin` (String): a plugin class to use (ex. `TinyAdmin::Plugins::SimpleAuth`);
|
87
93
|
- `password` (String): a password hash used by _SimpleAuth_ plugin (generated with `Digest::SHA512.hexdigest("some password")`).
|
@@ -175,65 +181,64 @@ end
|
|
175
181
|
---
|
176
182
|
authentication:
|
177
183
|
plugin: TinyAdmin::Plugins::SimpleAuth
|
178
|
-
# password: '
|
179
|
-
page_not_found: Admin::PageNotFound
|
180
|
-
record_not_found: Admin::RecordNotFound
|
184
|
+
# password: 'f1891cea80fc05e433c943254c6bdabc159577a02a7395dfebbfbc4f7661d4af56f2d372131a45936de40160007368a56ef216a30cb202c66d3145fd24380906'
|
181
185
|
root:
|
182
|
-
title:
|
183
|
-
page:
|
184
|
-
|
186
|
+
title: Test Admin
|
187
|
+
# page: RootPage
|
188
|
+
helper_class: AdminHelper
|
189
|
+
page_not_found: PageNotFound
|
190
|
+
record_not_found: RecordNotFound
|
185
191
|
sections:
|
186
192
|
- slug: google
|
187
193
|
name: Google.it
|
188
194
|
type: url
|
189
195
|
url: https://www.google.it
|
190
196
|
options:
|
191
|
-
target:
|
192
|
-
- slug:
|
193
|
-
name:
|
197
|
+
target: _blank
|
198
|
+
- slug: sample
|
199
|
+
name: Sample page
|
194
200
|
type: page
|
195
|
-
page:
|
201
|
+
page: SamplePage
|
196
202
|
- slug: authors
|
197
203
|
name: Authors
|
198
204
|
type: resource
|
199
205
|
model: Author
|
200
|
-
repository: Admin::AuthorsRepo
|
201
206
|
collection_actions:
|
202
|
-
-
|
207
|
+
- sample_col: SampleCollectionAction
|
203
208
|
member_actions:
|
204
|
-
-
|
205
|
-
# only:
|
206
|
-
# - index
|
207
|
-
# options:
|
208
|
-
# - hidden
|
209
|
+
- sample_mem: SampleMemberAction
|
209
210
|
- slug: posts
|
210
211
|
name: Posts
|
211
212
|
type: resource
|
212
213
|
model: Post
|
213
214
|
index:
|
214
|
-
|
215
|
-
- author_id DESC
|
216
|
-
pagination: 15
|
215
|
+
pagination: 5
|
217
216
|
attributes:
|
218
217
|
- id
|
219
218
|
- title
|
220
219
|
- field: author_id
|
221
220
|
link_to: authors
|
222
|
-
-
|
221
|
+
- category: upcase
|
222
|
+
- state: downcase
|
223
223
|
- published
|
224
|
-
-
|
224
|
+
- position: round, 1
|
225
|
+
- dt: to_date
|
225
226
|
- field: created_at
|
226
|
-
converter:
|
227
|
+
converter: AdminUtils
|
227
228
|
method: datetime_formatter
|
229
|
+
- updated_at: strftime, %Y%m%d %H:%M
|
228
230
|
filters:
|
229
231
|
- title
|
230
|
-
-
|
232
|
+
- author_id
|
233
|
+
- field: category
|
231
234
|
type: select
|
232
235
|
values:
|
233
|
-
-
|
234
|
-
-
|
235
|
-
-
|
236
|
+
- news
|
237
|
+
- sport
|
238
|
+
- tech
|
236
239
|
- published
|
240
|
+
- dt
|
241
|
+
- created_at
|
237
242
|
show:
|
238
243
|
attributes:
|
239
244
|
- id
|
@@ -243,7 +248,8 @@ sections:
|
|
243
248
|
link_to: authors
|
244
249
|
- category
|
245
250
|
- published
|
246
|
-
-
|
251
|
+
- position: format, %f
|
252
|
+
- dt
|
247
253
|
- created_at
|
248
254
|
style_links:
|
249
255
|
- href: /bootstrap.min.css
|
@@ -5,12 +5,21 @@ module TinyAdmin
|
|
5
5
|
class BasicAction
|
6
6
|
include Utils
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
def attribute_options(options)
|
9
|
+
options&.each_with_object({}) do |field, result|
|
10
|
+
field_data =
|
11
|
+
if field.is_a?(Hash)
|
12
|
+
if field.one?
|
13
|
+
field, method = field.first
|
14
|
+
{ field.to_s => { field: field.to_s, method: method } }
|
15
|
+
else
|
16
|
+
{ field[:field] => field }
|
17
|
+
end
|
18
|
+
else
|
19
|
+
{ field => { field: field } }
|
20
|
+
end
|
21
|
+
result.merge!(field_data)
|
22
|
+
end
|
14
23
|
end
|
15
24
|
end
|
16
25
|
end
|
@@ -3,30 +3,41 @@
|
|
3
3
|
module TinyAdmin
|
4
4
|
module Actions
|
5
5
|
class Index < BasicAction
|
6
|
-
attr_reader :current_page,
|
6
|
+
attr_reader :current_page,
|
7
|
+
:fields_options,
|
8
|
+
:filters_list,
|
9
|
+
:pagination,
|
10
|
+
:pages,
|
11
|
+
:params,
|
12
|
+
:query_string,
|
13
|
+
:repository,
|
14
|
+
:sort
|
7
15
|
|
8
16
|
def call(app:, context:, options:, actions:)
|
9
17
|
evaluate_options(options)
|
10
18
|
fields = repository.fields(options: fields_options)
|
11
19
|
filters = prepare_filters(fields, filters_list)
|
12
20
|
records, total_count = repository.list(page: current_page, limit: pagination, filters: filters, sort: sort)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
|
22
|
+
prepare_page(Views::Actions::Index) do |page|
|
23
|
+
setup_pagination(page, settings.components[:pagination], total_count: total_count)
|
24
|
+
page.update_attributes(
|
25
|
+
actions: actions,
|
26
|
+
fields: fields,
|
27
|
+
filters: filters,
|
28
|
+
prepare_record: ->(record) { repository.index_record_attrs(record, fields: fields_options) },
|
29
|
+
records: records,
|
30
|
+
title: repository.index_title
|
31
|
+
)
|
21
32
|
end
|
22
33
|
end
|
23
34
|
|
24
35
|
private
|
25
36
|
|
26
37
|
def evaluate_options(options)
|
27
|
-
@fields_options = options[:attributes]
|
28
|
-
|
29
|
-
|
38
|
+
@fields_options = attribute_options(options[:attributes])
|
39
|
+
@params = context.request.params
|
40
|
+
@repository = context.repository
|
30
41
|
@filters_list = options[:filters]
|
31
42
|
@pagination = options[:pagination] || 10
|
32
43
|
@sort = options[:sort] || ['id']
|
@@ -39,10 +50,18 @@ module TinyAdmin
|
|
39
50
|
filters = (filters_list || []).map { _1.is_a?(Hash) ? _1 : { field: _1 } }
|
40
51
|
filters = filters.each_with_object({}) { |filter, result| result[filter[:field]] = filter }
|
41
52
|
values = (params['q'] || {})
|
42
|
-
fields.each_with_object({}) do |field, result|
|
43
|
-
result[field] = { value: values[
|
53
|
+
fields.each_with_object({}) do |(name, field), result|
|
54
|
+
result[field] = { value: values[name], filter: filters[name] } if filters.key?(name)
|
44
55
|
end
|
45
56
|
end
|
57
|
+
|
58
|
+
def setup_pagination(page, pagination_component_class, total_count:)
|
59
|
+
@pages = (total_count / pagination.to_f).ceil
|
60
|
+
return if pages <= 1 || !pagination_component_class
|
61
|
+
|
62
|
+
page.pagination_component = pagination_component_class.new
|
63
|
+
page.pagination_component.update(current: current_page, pages: pages, query_string: query_string)
|
64
|
+
end
|
46
65
|
end
|
47
66
|
end
|
48
67
|
end
|
@@ -3,17 +3,21 @@
|
|
3
3
|
module TinyAdmin
|
4
4
|
module Actions
|
5
5
|
class Show < BasicAction
|
6
|
+
attr_reader :repository
|
7
|
+
|
6
8
|
def call(app:, context:, options:, actions:)
|
7
|
-
|
8
|
-
|
9
|
-
end
|
9
|
+
@repository = context.repository
|
10
|
+
fields_options = attribute_options(options[:attributes])
|
10
11
|
record = repository.find(context.reference)
|
11
|
-
prepare_record = ->(record_data) { repository.show_record_attrs(record_data, fields: fields_options) }
|
12
|
-
fields = repository.fields(options: fields_options)
|
13
12
|
|
14
|
-
prepare_page(Views::Actions::Show
|
15
|
-
page.
|
16
|
-
|
13
|
+
prepare_page(Views::Actions::Show) do |page|
|
14
|
+
page.update_attributes(
|
15
|
+
actions: actions,
|
16
|
+
fields: repository.fields(options: fields_options),
|
17
|
+
prepare_record: ->(record_data) { repository.show_record_attrs(record_data, fields: fields_options) },
|
18
|
+
record: record,
|
19
|
+
title: repository.show_title(record)
|
20
|
+
)
|
17
21
|
end
|
18
22
|
rescue Plugins::BaseRepository::RecordNotFound => _e
|
19
23
|
prepare_page(options[:record_not_found_page] || Views::Pages::RecordNotFound)
|
data/lib/tiny_admin/context.rb
CHANGED
data/lib/tiny_admin/field.rb
CHANGED
@@ -4,7 +4,7 @@ module TinyAdmin
|
|
4
4
|
class Field
|
5
5
|
attr_reader :name, :options, :title, :type
|
6
6
|
|
7
|
-
def initialize(
|
7
|
+
def initialize(name:, title:, type:, options: {})
|
8
8
|
@type = type
|
9
9
|
@name = name
|
10
10
|
@title = title || name
|
@@ -12,8 +12,14 @@ module TinyAdmin
|
|
12
12
|
end
|
13
13
|
|
14
14
|
class << self
|
15
|
-
def create_field(name:, title
|
16
|
-
|
15
|
+
def create_field(name:, title: nil, type: nil, options: {})
|
16
|
+
field_name = name.to_s
|
17
|
+
new(
|
18
|
+
name: field_name,
|
19
|
+
title: title || field_name.respond_to?(:humanize) ? field_name.humanize : field_name.tr('_', ' ').capitalize,
|
20
|
+
type: type || :string,
|
21
|
+
options: options
|
22
|
+
)
|
17
23
|
end
|
18
24
|
end
|
19
25
|
end
|
@@ -4,17 +4,11 @@ module TinyAdmin
|
|
4
4
|
module Plugins
|
5
5
|
class ActiveRecordRepository < BaseRepository
|
6
6
|
def index_record_attrs(record, fields: nil)
|
7
|
-
return record.attributes.transform_values(&:to_s)
|
7
|
+
return record.attributes.transform_values(&:to_s) unless fields
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
if field_data[:converter] && field_data[:method]
|
13
|
-
converter = Object.const_get(field_data[:converter])
|
14
|
-
converter.send(field_data[:method], value)
|
15
|
-
else
|
16
|
-
value&.to_s
|
17
|
-
end
|
9
|
+
fields.to_h do |name, field|
|
10
|
+
value = record.send(name)
|
11
|
+
[name, translate_value(value, field)]
|
18
12
|
end
|
19
13
|
end
|
20
14
|
|
@@ -24,25 +18,19 @@ module TinyAdmin
|
|
24
18
|
end
|
25
19
|
|
26
20
|
def fields(options: nil)
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
name = column.name
|
37
|
-
type = opts.dig(column.name, :type) || column.type
|
38
|
-
TinyAdmin::Field.create_field(name: name, title: name.humanize, type: type, options: opts[name])
|
21
|
+
if options
|
22
|
+
types = model.columns.to_h { [_1.name, _1.type] }
|
23
|
+
options.each_with_object({}) do |(name, field_options), result|
|
24
|
+
result[name] = TinyAdmin::Field.create_field(name: name, type: types[name], options: field_options)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
model.columns.each_with_object({}) do |column, result|
|
28
|
+
result[column.name] = TinyAdmin::Field.create_field(name: column.name, type: column.type)
|
29
|
+
end
|
39
30
|
end
|
40
31
|
end
|
41
32
|
|
42
|
-
|
43
|
-
attrs = !fields || fields.empty? ? record.attributes : record.attributes.slice(*fields.keys)
|
44
|
-
attrs.transform_values(&:to_s)
|
45
|
-
end
|
33
|
+
alias show_record_attrs index_record_attrs
|
46
34
|
|
47
35
|
def show_title(record)
|
48
36
|
"#{model} ##{record.id}"
|
@@ -54,10 +42,10 @@ module TinyAdmin
|
|
54
42
|
raise BaseRepository::RecordNotFound, e.message
|
55
43
|
end
|
56
44
|
|
57
|
-
def list(page: 1, limit: 10,
|
58
|
-
page_offset = page.positive? ? (page - 1) * limit : 0
|
45
|
+
def list(page: 1, limit: 10, sort: ['id'], filters: nil)
|
59
46
|
query = model.all.order(sort)
|
60
47
|
query = apply_filters(query, filters) if filters
|
48
|
+
page_offset = page.positive? ? (page - 1) * limit : 0
|
61
49
|
records = query.offset(page_offset).limit(limit).to_a
|
62
50
|
[records, query.count]
|
63
51
|
end
|
@@ -10,6 +10,20 @@ module TinyAdmin
|
|
10
10
|
def initialize(model)
|
11
11
|
@model = model
|
12
12
|
end
|
13
|
+
|
14
|
+
def translate_value(value, field)
|
15
|
+
if field[:method]
|
16
|
+
method, *options = field[:method].split(',').map(&:strip)
|
17
|
+
if field[:converter]
|
18
|
+
converter = Object.const_get(field[:converter])
|
19
|
+
converter.send(method, value, options: options || [])
|
20
|
+
else
|
21
|
+
Settings.instance.helper_class.send(method, value, options: options || [])
|
22
|
+
end
|
23
|
+
else
|
24
|
+
value&.to_s
|
25
|
+
end
|
26
|
+
end
|
13
27
|
end
|
14
28
|
end
|
15
29
|
end
|
data/lib/tiny_admin/router.rb
CHANGED
@@ -2,9 +2,11 @@
|
|
2
2
|
|
3
3
|
module TinyAdmin
|
4
4
|
class Router < BasicApp
|
5
|
-
TinyAdmin::Settings.instance.load_settings
|
6
|
-
|
7
5
|
route do |r|
|
6
|
+
context.settings = TinyAdmin::Settings.instance
|
7
|
+
context.settings.load_settings
|
8
|
+
context.router = r
|
9
|
+
|
8
10
|
r.on 'auth' do
|
9
11
|
context.slug = nil
|
10
12
|
r.run Authentication
|
@@ -27,11 +29,11 @@ module TinyAdmin
|
|
27
29
|
r.redirect settings.root_path
|
28
30
|
end
|
29
31
|
|
30
|
-
|
32
|
+
context.settings.pages.each do |slug, data|
|
31
33
|
setup_page_route(r, slug, data)
|
32
34
|
end
|
33
35
|
|
34
|
-
|
36
|
+
context.settings.resources.each do |slug, options|
|
35
37
|
setup_resource_routes(r, slug, options: options || {})
|
36
38
|
end
|
37
39
|
|
@@ -54,14 +56,14 @@ module TinyAdmin
|
|
54
56
|
else
|
55
57
|
page = settings.root[:page]
|
56
58
|
page_class = page.is_a?(String) ? Object.const_get(page) : page
|
57
|
-
render_page prepare_page(page_class
|
59
|
+
render_page prepare_page(page_class)
|
58
60
|
end
|
59
61
|
end
|
60
62
|
|
61
63
|
def setup_page_route(router, slug, page_class)
|
62
64
|
router.get slug do
|
63
65
|
context.slug = slug
|
64
|
-
render_page prepare_page(page_class
|
66
|
+
render_page prepare_page(page_class)
|
65
67
|
end
|
66
68
|
end
|
67
69
|
|
@@ -74,14 +76,14 @@ module TinyAdmin
|
|
74
76
|
end
|
75
77
|
|
76
78
|
def setup_collection_routes(router, options:)
|
77
|
-
repository = options[:repository].new(options[:model])
|
79
|
+
context.repository = options[:repository].new(options[:model])
|
78
80
|
action_options = options[:index] || {}
|
79
81
|
|
80
82
|
# Custom actions
|
81
83
|
custom_actions = setup_custom_actions(
|
82
84
|
router,
|
83
85
|
options[:collection_actions],
|
84
|
-
repository: repository,
|
86
|
+
repository: context.repository,
|
85
87
|
options: action_options
|
86
88
|
)
|
87
89
|
|
@@ -89,14 +91,15 @@ module TinyAdmin
|
|
89
91
|
actions = options[:only]
|
90
92
|
if !actions || actions.include?(:index) || actions.include?('index')
|
91
93
|
router.is do
|
92
|
-
|
94
|
+
context.request = request
|
95
|
+
index_action = TinyAdmin::Actions::Index.new
|
93
96
|
render_page index_action.call(app: self, context: context, options: action_options, actions: custom_actions)
|
94
97
|
end
|
95
98
|
end
|
96
99
|
end
|
97
100
|
|
98
101
|
def setup_member_routes(router, options:)
|
99
|
-
repository = options[:repository].new(options[:model])
|
102
|
+
context.repository = options[:repository].new(options[:model])
|
100
103
|
action_options = (options[:show] || {}).merge(record_not_found_page: settings.record_not_found)
|
101
104
|
|
102
105
|
router.on String do |reference|
|
@@ -106,7 +109,7 @@ module TinyAdmin
|
|
106
109
|
custom_actions = setup_custom_actions(
|
107
110
|
router,
|
108
111
|
options[:member_actions],
|
109
|
-
repository: repository,
|
112
|
+
repository: context.repository,
|
110
113
|
options: action_options
|
111
114
|
)
|
112
115
|
|
@@ -114,7 +117,8 @@ module TinyAdmin
|
|
114
117
|
actions = options[:only]
|
115
118
|
if !actions || actions.include?(:show) || actions.include?('show')
|
116
119
|
router.is do
|
117
|
-
|
120
|
+
context.request = request
|
121
|
+
show_action = TinyAdmin::Actions::Show.new
|
118
122
|
render_page show_action.call(app: self, context: context, options: action_options, actions: custom_actions)
|
119
123
|
end
|
120
124
|
end
|
@@ -122,16 +126,18 @@ module TinyAdmin
|
|
122
126
|
end
|
123
127
|
|
124
128
|
def setup_custom_actions(router, custom_actions, repository:, options:)
|
125
|
-
|
129
|
+
context.repository = repository
|
130
|
+
(custom_actions || []).each_with_object({}) do |custom_action, result|
|
126
131
|
action_slug, action = custom_action.first
|
127
132
|
action_class = action.is_a?(String) ? Object.const_get(action) : action
|
128
133
|
|
129
134
|
router.get action_slug.to_s do
|
130
|
-
|
135
|
+
context.request = request
|
136
|
+
custom_action = action_class.new
|
131
137
|
render_page custom_action.call(app: self, context: context, options: options)
|
132
138
|
end
|
133
139
|
|
134
|
-
action_slug.to_s
|
140
|
+
result[action_slug.to_s] = action_class
|
135
141
|
end
|
136
142
|
end
|
137
143
|
end
|
data/lib/tiny_admin/settings.rb
CHANGED
@@ -5,9 +5,26 @@ module TinyAdmin
|
|
5
5
|
include Singleton
|
6
6
|
include Utils
|
7
7
|
|
8
|
+
DEFAULTS = {
|
9
|
+
%i[authentication plugin] => Plugins::NoAuth,
|
10
|
+
%i[authentication login] => Views::Pages::SimpleAuthLogin,
|
11
|
+
%i[components flash] => Views::Components::Flash,
|
12
|
+
%i[components head] => Views::Components::Head,
|
13
|
+
%i[components navbar] => Views::Components::Navbar,
|
14
|
+
%i[components pagination] => Views::Components::Pagination,
|
15
|
+
%i[helper_class] => Support,
|
16
|
+
%i[page_not_found] => Views::Pages::PageNotFound,
|
17
|
+
%i[record_not_found] => Views::Pages::RecordNotFound,
|
18
|
+
%i[repository] => Plugins::ActiveRecordRepository,
|
19
|
+
%i[root_path] => '/admin',
|
20
|
+
%i[root page] => Views::Pages::Root,
|
21
|
+
%i[root title] => 'TinyAdmin'
|
22
|
+
}.freeze
|
23
|
+
|
8
24
|
attr_accessor :authentication,
|
9
25
|
:components,
|
10
26
|
:extra_styles,
|
27
|
+
:helper_class,
|
11
28
|
:navbar,
|
12
29
|
:page_not_found,
|
13
30
|
:record_not_found,
|
@@ -20,38 +37,33 @@ module TinyAdmin
|
|
20
37
|
|
21
38
|
attr_reader :pages, :resources
|
22
39
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
40
|
+
def [](key)
|
41
|
+
send(key)
|
42
|
+
end
|
43
|
+
|
44
|
+
def []=(key, value)
|
45
|
+
send("#{key}=", value)
|
46
|
+
convert_value(key, value)
|
47
|
+
end
|
28
48
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
49
|
+
def load_settings
|
50
|
+
# default values
|
51
|
+
DEFAULTS.each do |(option, param), default|
|
52
|
+
if param
|
53
|
+
self[option] ||= {}
|
54
|
+
self[option][param] ||= default
|
55
|
+
else
|
56
|
+
self[option] ||= default
|
57
|
+
end
|
58
|
+
end
|
33
59
|
|
34
60
|
@pages ||= {}
|
35
|
-
@repository ||= Plugins::ActiveRecordRepository
|
36
61
|
@resources ||= {}
|
37
|
-
@root_path ||= '/admin'
|
38
|
-
@root_path = '/' if @root_path == ''
|
39
62
|
@sections ||= []
|
40
|
-
|
41
|
-
@root ||= {}
|
42
|
-
@root[:title] ||= 'TinyAdmin'
|
43
|
-
@root[:page] ||= Views::Pages::Root
|
44
|
-
|
63
|
+
@root_path = '/' if @root_path == ''
|
45
64
|
if @authentication[:plugin] == Plugins::SimpleAuth
|
46
65
|
@authentication[:logout] ||= ['logout', "#{root_path}/auth/logout"]
|
47
66
|
end
|
48
|
-
|
49
|
-
@components ||= {}
|
50
|
-
@components[:flash] ||= Views::Components::Flash
|
51
|
-
@components[:head] ||= Views::Components::Head
|
52
|
-
@components[:navbar] ||= Views::Components::Navbar
|
53
|
-
@components[:pagination] ||= Views::Components::Pagination
|
54
|
-
|
55
67
|
@navbar = prepare_navbar(sections, logout: authentication[:logout])
|
56
68
|
end
|
57
69
|
|
@@ -79,5 +91,20 @@ module TinyAdmin
|
|
79
91
|
items['auth/logout'] = logout if logout
|
80
92
|
items
|
81
93
|
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def convert_value(key, value)
|
98
|
+
if value.is_a?(Hash)
|
99
|
+
value.each_key do |key2|
|
100
|
+
path = [key, key2]
|
101
|
+
if DEFAULTS[path].is_a?(Class) || DEFAULTS[path].is_a?(Module)
|
102
|
+
self[key][key2] = Object.const_get(self[key][key2])
|
103
|
+
end
|
104
|
+
end
|
105
|
+
elsif value.is_a?(String) && (DEFAULTS[[key]].is_a?(Class) || DEFAULTS[[key]].is_a?(Module))
|
106
|
+
self[key] = Object.const_get(self[key])
|
107
|
+
end
|
108
|
+
end
|
82
109
|
end
|
83
110
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinyAdmin
|
4
|
+
class Support
|
5
|
+
class << self
|
6
|
+
def call(value, options: [])
|
7
|
+
value && options&.any? ? options.inject(value) { |result, message| result&.send(message) } : value
|
8
|
+
end
|
9
|
+
|
10
|
+
def downcase(value, options: [])
|
11
|
+
value&.downcase
|
12
|
+
end
|
13
|
+
|
14
|
+
def format(value, options: [])
|
15
|
+
value && options&.any? ? Kernel.format(options.first, value) : value
|
16
|
+
end
|
17
|
+
|
18
|
+
def round(value, options: [])
|
19
|
+
value&.round(options&.first&.to_i || 2)
|
20
|
+
end
|
21
|
+
|
22
|
+
def strftime(value, options: [])
|
23
|
+
value ? value.strftime(options&.first || '%Y-%m-%d %H:%M') : ''
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_date(value, options: [])
|
27
|
+
value ? value.to_date.to_s : value
|
28
|
+
end
|
29
|
+
|
30
|
+
def upcase(value, options: [])
|
31
|
+
value&.upcase
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/tiny_admin/utils.rb
CHANGED
@@ -13,17 +13,26 @@ module TinyAdmin
|
|
13
13
|
list.join('&')
|
14
14
|
end
|
15
15
|
|
16
|
-
def prepare_page(page_class,
|
16
|
+
def prepare_page(page_class, options: nil)
|
17
17
|
page_class.new.tap do |page|
|
18
|
-
page.context = context
|
19
18
|
page.options = options
|
19
|
+
page.head_component = settings.components[:head]&.new
|
20
|
+
page.flash_component = settings.components[:flash]&.new
|
21
|
+
page.navbar_component = settings.components[:navbar]&.new(
|
22
|
+
current_slug: context&.slug,
|
23
|
+
root_path: settings.root_path,
|
24
|
+
root_title: settings.root[:title],
|
25
|
+
items: options&.include?(:no_menu) ? [] : settings.navbar
|
26
|
+
)
|
27
|
+
|
20
28
|
yield(page) if block_given?
|
21
29
|
end
|
22
30
|
end
|
23
31
|
|
24
|
-
def route_for(section, reference: nil, action: nil)
|
32
|
+
def route_for(section, reference: nil, action: nil, query: nil)
|
25
33
|
root_path = settings.root_path == '/' ? nil : settings.root_path
|
26
34
|
route = [root_path, section, reference, action].compact.join("/")
|
35
|
+
route << "?#{query}" if query
|
27
36
|
route[0] == '/' ? route : route.prepend('/')
|
28
37
|
end
|
29
38
|
|
data/lib/tiny_admin/version.rb
CHANGED
@@ -4,23 +4,9 @@ module TinyAdmin
|
|
4
4
|
module Views
|
5
5
|
module Actions
|
6
6
|
class Index < DefaultLayout
|
7
|
-
|
8
|
-
attr_accessor :actions, :filters, :query_string
|
9
|
-
|
10
|
-
def setup_pagination(current_page:, pages:)
|
11
|
-
@current_page = current_page
|
12
|
-
@pages = pages
|
13
|
-
end
|
14
|
-
|
15
|
-
def setup_records(records:, fields:, prepare_record:)
|
16
|
-
@records = records
|
17
|
-
@fields = fields.each_with_object({}) { |field, result| result[field.name] = field }
|
18
|
-
@prepare_record = prepare_record
|
19
|
-
end
|
7
|
+
attr_accessor :actions, :fields, :filters, :pagination_component, :prepare_record, :records
|
20
8
|
|
21
9
|
def template
|
22
|
-
@filters ||= {}
|
23
|
-
|
24
10
|
super do
|
25
11
|
div(class: 'index') {
|
26
12
|
div(class: 'row') {
|
@@ -28,19 +14,12 @@ module TinyAdmin
|
|
28
14
|
h1(class: 'title') { title }
|
29
15
|
}
|
30
16
|
div(class: 'col-8') {
|
31
|
-
|
32
|
-
(actions || []).each do |action|
|
33
|
-
li(class: 'nav-item') {
|
34
|
-
href = route_for(context.slug, action: action)
|
35
|
-
a(href: href, class: 'nav-link btn btn-outline-secondary') { action }
|
36
|
-
}
|
37
|
-
end
|
38
|
-
}
|
17
|
+
actions_buttons
|
39
18
|
}
|
40
19
|
}
|
41
20
|
|
42
21
|
div(class: 'row') {
|
43
|
-
div_class = filters
|
22
|
+
div_class = filters&.any? ? 'col-9' : 'col-12'
|
44
23
|
div(class: div_class) {
|
45
24
|
table(class: 'table') {
|
46
25
|
table_header if fields.any?
|
@@ -49,7 +28,7 @@ module TinyAdmin
|
|
49
28
|
}
|
50
29
|
}
|
51
30
|
|
52
|
-
if filters
|
31
|
+
if filters&.any?
|
53
32
|
div(class: 'col-3') {
|
54
33
|
filters_form_attrs = { section_path: route_for(context.slug), filters: filters }
|
55
34
|
render TinyAdmin::Views::Components::FiltersForm.new(**filters_form_attrs)
|
@@ -57,9 +36,7 @@ module TinyAdmin
|
|
57
36
|
end
|
58
37
|
}
|
59
38
|
|
60
|
-
if
|
61
|
-
render components[:pagination].new(current: current_page, pages: pages, query_string: query_string)
|
62
|
-
end
|
39
|
+
render pagination_component if pagination_component
|
63
40
|
}
|
64
41
|
end
|
65
42
|
end
|
@@ -70,7 +47,9 @@ module TinyAdmin
|
|
70
47
|
thead {
|
71
48
|
tr {
|
72
49
|
fields.each_value do |field|
|
73
|
-
td(class: "field-header-#{field.name} field-header-type-#{field.type}") {
|
50
|
+
td(class: "field-header-#{field.name} field-header-type-#{field.type}") {
|
51
|
+
field.options[:header] || field.title
|
52
|
+
}
|
74
53
|
end
|
75
54
|
td { whitespace }
|
76
55
|
}
|
@@ -86,8 +65,9 @@ module TinyAdmin
|
|
86
65
|
field = fields[key]
|
87
66
|
td(class: "field-value-#{field.name} field-value-type-#{field.type}") {
|
88
67
|
if field.options && field.options[:link_to]
|
89
|
-
|
90
|
-
|
68
|
+
messages = (field.options[:call] || '').split(',').map(&:strip)
|
69
|
+
label = messages.any? ? messages.inject(record) { |result, msg| result&.send(msg) } : value
|
70
|
+
a(href: route_for(field.options[:link_to], reference: value)) { label }
|
91
71
|
else
|
92
72
|
value
|
93
73
|
end
|
@@ -100,6 +80,18 @@ module TinyAdmin
|
|
100
80
|
end
|
101
81
|
}
|
102
82
|
end
|
83
|
+
|
84
|
+
def actions_buttons
|
85
|
+
ul(class: 'nav justify-content-end') {
|
86
|
+
(actions || {}).each do |action, action_class|
|
87
|
+
li(class: 'nav-item mx-1') {
|
88
|
+
href = route_for(context.slug, action: action)
|
89
|
+
title = action_class.respond_to?(:title) ? action_class.title : action
|
90
|
+
a(href: href, class: 'nav-link btn btn-outline-secondary') { title }
|
91
|
+
}
|
92
|
+
end
|
93
|
+
}
|
94
|
+
end
|
103
95
|
end
|
104
96
|
end
|
105
97
|
end
|
@@ -4,14 +4,7 @@ module TinyAdmin
|
|
4
4
|
module Views
|
5
5
|
module Actions
|
6
6
|
class Show < DefaultLayout
|
7
|
-
|
8
|
-
attr_accessor :actions
|
9
|
-
|
10
|
-
def setup_record(record:, fields:, prepare_record:)
|
11
|
-
@record = record
|
12
|
-
@fields = fields
|
13
|
-
@prepare_record = prepare_record
|
14
|
-
end
|
7
|
+
attr_accessor :actions, :fields, :prepare_record, :record
|
15
8
|
|
16
9
|
def template
|
17
10
|
super do
|
@@ -21,27 +14,21 @@ module TinyAdmin
|
|
21
14
|
h1(class: 'title') { title }
|
22
15
|
}
|
23
16
|
div(class: 'col-8') {
|
24
|
-
|
25
|
-
(actions || []).each do |action|
|
26
|
-
li(class: 'nav-item') {
|
27
|
-
href = route_for(context.slug, reference: context.reference, action: action)
|
28
|
-
a(href: href, class: 'nav-link btn btn-outline-secondary') { action }
|
29
|
-
}
|
30
|
-
end
|
31
|
-
}
|
17
|
+
actions_buttons
|
32
18
|
}
|
33
19
|
}
|
34
20
|
|
35
|
-
prepare_record.call(record).
|
36
|
-
field = fields[
|
21
|
+
prepare_record.call(record).each do |key, value|
|
22
|
+
field = fields[key]
|
37
23
|
div(class: "field-#{field.name} row lh-lg") {
|
38
24
|
if field
|
39
|
-
div(class: 'field-header col-2') { field.title }
|
25
|
+
div(class: 'field-header col-2') { field.options[:header] || field.title }
|
40
26
|
end
|
41
27
|
div(class: 'field-value col-10') {
|
42
28
|
if field.options && field.options[:link_to]
|
43
|
-
|
44
|
-
|
29
|
+
messages = (field.options[:call] || '').split(',').map(&:strip)
|
30
|
+
label = messages.any? ? messages.inject(record) { |result, msg| result&.send(msg) } : value
|
31
|
+
a(href: route_for(field.options[:link_to], reference: value)) { label }
|
45
32
|
else
|
46
33
|
value
|
47
34
|
end
|
@@ -51,6 +38,20 @@ module TinyAdmin
|
|
51
38
|
}
|
52
39
|
end
|
53
40
|
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def actions_buttons
|
45
|
+
ul(class: 'nav justify-content-end') {
|
46
|
+
(actions || {}).each do |action, action_class|
|
47
|
+
li(class: 'nav-item mx-1') {
|
48
|
+
href = route_for(context.slug, reference: context.reference, action: action)
|
49
|
+
title = action_class.respond_to?(:title) ? action_class.title : action
|
50
|
+
a(href: href, class: 'nav-link btn btn-outline-secondary') { title }
|
51
|
+
}
|
52
|
+
end
|
53
|
+
}
|
54
|
+
end
|
54
55
|
end
|
55
56
|
end
|
56
57
|
end
|
@@ -6,14 +6,6 @@ module TinyAdmin
|
|
6
6
|
class Flash < Phlex::HTML
|
7
7
|
attr_reader :errors, :notices, :warnings
|
8
8
|
|
9
|
-
def initialize(messages:)
|
10
|
-
return unless messages
|
11
|
-
|
12
|
-
@notices = messages[:notices]
|
13
|
-
@warnings = messages[:warnings]
|
14
|
-
@errors = messages[:errors]
|
15
|
-
end
|
16
|
-
|
17
9
|
def template
|
18
10
|
div(class: 'flash') {
|
19
11
|
div(class: 'notices alert alert-success', role: 'alert') { notices.join(', ') } if notices&.any?
|
@@ -21,6 +13,14 @@ module TinyAdmin
|
|
21
13
|
div(class: 'notices alert alert-danger', role: 'alert') { errors.join(', ') } if errors&.any?
|
22
14
|
}
|
23
15
|
end
|
16
|
+
|
17
|
+
def update(messages:)
|
18
|
+
return unless messages
|
19
|
+
|
20
|
+
@notices = messages[:notices]
|
21
|
+
@warnings = messages[:warnings]
|
22
|
+
@errors = messages[:errors]
|
23
|
+
end
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -6,12 +6,6 @@ module TinyAdmin
|
|
6
6
|
class Head < Phlex::HTML
|
7
7
|
attr_reader :extra_styles, :page_title, :style_links
|
8
8
|
|
9
|
-
def initialize(page_title, style_links: [], extra_styles: nil)
|
10
|
-
@page_title = page_title
|
11
|
-
@style_links = style_links
|
12
|
-
@extra_styles = extra_styles
|
13
|
-
end
|
14
|
-
|
15
9
|
def template
|
16
10
|
head {
|
17
11
|
meta charset: 'utf-8'
|
@@ -25,6 +19,12 @@ module TinyAdmin
|
|
25
19
|
style { extra_styles } if extra_styles
|
26
20
|
}
|
27
21
|
end
|
22
|
+
|
23
|
+
def update(page_title, style_links: [], extra_styles: nil)
|
24
|
+
@page_title = page_title
|
25
|
+
@style_links = style_links
|
26
|
+
@extra_styles = extra_styles
|
27
|
+
end
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
@@ -6,27 +6,48 @@ module TinyAdmin
|
|
6
6
|
class Pagination < Phlex::HTML
|
7
7
|
attr_reader :current, :pages, :query_string
|
8
8
|
|
9
|
-
def initialize(current:, pages:, query_string:)
|
10
|
-
@current = current
|
11
|
-
@pages = pages
|
12
|
-
@query_string = query_string
|
13
|
-
end
|
14
|
-
|
15
9
|
def template
|
16
10
|
div(class: 'pagination-div') {
|
17
11
|
nav('aria-label' => 'Pagination') {
|
18
12
|
ul(class: 'pagination justify-content-center') {
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
13
|
+
if pages <= 10
|
14
|
+
pages_range(1..pages)
|
15
|
+
elsif current <= 4 || current >= pages - 3
|
16
|
+
pages_range(1..(current <= 4 ? current + 2 : 4), with_dots: true)
|
17
|
+
pages_range((current > pages - 4 ? current - 2 : pages - 2)..pages)
|
18
|
+
else
|
19
|
+
pages_range(1..1, with_dots: true)
|
20
|
+
pages_range(current - 2..current + 2, with_dots: true)
|
21
|
+
pages_range(pages..pages)
|
25
22
|
end
|
26
23
|
}
|
27
24
|
}
|
28
25
|
}
|
29
26
|
end
|
27
|
+
|
28
|
+
def update(current:, pages:, query_string:)
|
29
|
+
@current = current
|
30
|
+
@pages = pages
|
31
|
+
@query_string = query_string
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def pages_range(range, with_dots: false)
|
37
|
+
range.each do |page|
|
38
|
+
li(class: page == current ? 'page-item active' : 'page-item') {
|
39
|
+
href = query_string.empty? ? "?p=#{page}" : "?#{query_string}&p=#{page}"
|
40
|
+
a(class: 'page-link', href: href) { page }
|
41
|
+
}
|
42
|
+
end
|
43
|
+
dots if with_dots
|
44
|
+
end
|
45
|
+
|
46
|
+
def dots
|
47
|
+
li(class: 'page-item disabled') {
|
48
|
+
a(class: 'page-link') { '...' }
|
49
|
+
}
|
50
|
+
end
|
30
51
|
end
|
31
52
|
end
|
32
53
|
end
|
@@ -3,24 +3,22 @@
|
|
3
3
|
module TinyAdmin
|
4
4
|
module Views
|
5
5
|
class DefaultLayout < BasicLayout
|
6
|
-
attr_accessor :
|
6
|
+
attr_accessor :flash_component, :head_component, :messages, :navbar_component, :options, :title
|
7
7
|
|
8
8
|
def template(&block)
|
9
|
+
flash_component&.update(messages: messages)
|
10
|
+
head_component&.update(title, style_links: style_links, extra_styles: settings.extra_styles)
|
11
|
+
|
9
12
|
doctype
|
10
13
|
html {
|
11
|
-
render
|
14
|
+
render head_component if head_component
|
12
15
|
|
13
16
|
body(class: body_class) {
|
14
|
-
|
15
|
-
current_slug: context&.slug,
|
16
|
-
root_path: settings.root_path,
|
17
|
-
root_title: settings.root[:title],
|
18
|
-
items: navbar_items
|
19
|
-
}
|
20
|
-
render components[:navbar].new(**navbar_attrs)
|
17
|
+
render navbar_component if navbar_component
|
21
18
|
|
22
19
|
main_content {
|
23
|
-
render
|
20
|
+
render flash_component if flash_component
|
21
|
+
|
24
22
|
yield_content(&block)
|
25
23
|
}
|
26
24
|
|
@@ -35,10 +33,6 @@ module TinyAdmin
|
|
35
33
|
"module-#{self.class.to_s.split('::').last.downcase}"
|
36
34
|
end
|
37
35
|
|
38
|
-
def navbar_items
|
39
|
-
options&.include?(:no_menu) ? [] : settings.navbar
|
40
|
-
end
|
41
|
-
|
42
36
|
def main_content
|
43
37
|
div(class: 'container main-content py-4') do
|
44
38
|
if options&.include?(:compact_layout)
|
data/lib/tiny_admin.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tiny_admin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mattia Roccoberton
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-04-
|
11
|
+
date: 2023-04-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: phlex
|
@@ -88,6 +88,7 @@ files:
|
|
88
88
|
- lib/tiny_admin/plugins/simple_auth.rb
|
89
89
|
- lib/tiny_admin/router.rb
|
90
90
|
- lib/tiny_admin/settings.rb
|
91
|
+
- lib/tiny_admin/support.rb
|
91
92
|
- lib/tiny_admin/utils.rb
|
92
93
|
- lib/tiny_admin/version.rb
|
93
94
|
- lib/tiny_admin/views/actions/index.rb
|