tiny_admin 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +125 -19
- data/lib/tiny_admin/actions/index.rb +6 -6
- data/lib/tiny_admin/actions/show.rb +2 -2
- data/lib/tiny_admin/authentication.rb +2 -2
- data/lib/tiny_admin/basic_app.rb +1 -7
- data/lib/tiny_admin/plugins/simple_auth.rb +5 -4
- data/lib/tiny_admin/router.rb +44 -31
- data/lib/tiny_admin/settings.rb +5 -5
- data/lib/tiny_admin/utils.rb +6 -8
- data/lib/tiny_admin/version.rb +1 -1
- data/lib/tiny_admin/views/actions/index.rb +2 -2
- data/lib/tiny_admin/views/basic_layout.rb +19 -0
- data/lib/tiny_admin/views/components/flash.rb +6 -4
- data/lib/tiny_admin/views/components/navbar.rb +5 -4
- data/lib/tiny_admin/views/default_layout.rb +28 -54
- metadata +26 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa8eb3987f51f28c4f9a55c62fddfd521accf075aac9be4d0b4fab7e88145770
|
4
|
+
data.tar.gz: 98e2f4af557280ca167816fa325f824d5eda99cde96d35407d472ab1e37b370d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 501e766d535a08b80597c7e5e9c5c40c075a74d1ddf347ad6d0858600c37fb945e39616a7d3b62d91a9486484a6a7d5edbd59fdfaca7e373fa099ecb8092edef
|
7
|
+
data.tar.gz: cfd95059d66a9f5d83b60086b5b59d01840fc654e929546778d02457605d6bca1b22cf3f587a92db93575464281ef0f1cd7993a2082bd234ef91c124e84b5ad7
|
data/README.md
CHANGED
@@ -1,40 +1,51 @@
|
|
1
1
|
# Tiny Admin
|
2
2
|
|
3
|
-
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/tiny_admin.svg)](https://badge.fury.io/rb/tiny_admin) [![Linters](https://github.com/blocknotes/tiny_admin/actions/workflows/linters.yml/badge.svg)](https://github.com/blocknotes/tiny_admin/actions/workflows/linters.yml) [![Specs Rails 7.0](https://github.com/blocknotes/tiny_admin/actions/workflows/specs_rails_70.yml/badge.svg)](https://github.com/blocknotes/tiny_admin/actions/workflows/specs_rails_70.yml)
|
4
4
|
|
5
|
-
|
6
|
-
- a Rack app that can be mounted in any Rack-enabled framework;
|
7
|
-
- some features are handled as plugins, so they can be replaced with little effort;
|
8
|
-
- routing is provided by Roda (which is small and performant);
|
9
|
-
- views are Phlex components.
|
5
|
+
A compact and composable dashboard component for Ruby.
|
10
6
|
|
11
|
-
|
7
|
+
Main features:
|
8
|
+
- a Rack app that can be mounted in any Rack-enabled framework or used standalone;
|
9
|
+
- structured with plugins also for main components that can be replaced with little effort;
|
10
|
+
- routing is provided by Roda which is small and performant;
|
11
|
+
- views are Phlex components, so plain Ruby objects for views, no assets are needed.
|
12
12
|
|
13
13
|
Please ⭐ if you like it.
|
14
14
|
|
15
|
+
![screenshot](extra/screenshot.png)
|
16
|
+
|
15
17
|
## Install
|
16
18
|
|
17
|
-
- Add to your Gemfile: `gem 'tiny_admin'`
|
18
|
-
-
|
19
|
+
- Add to your Gemfile: `gem 'tiny_admin', '~> 0.2'`
|
20
|
+
- Mount the app in a route (check some examples with: Hanami, Rails, Roda and standalone in [extra](extra))
|
21
|
+
+ in Rails, update _config/routes.rb_: `mount TinyAdmin::Router => '/admin'`
|
22
|
+
- Configure the dashboard using `TinyAdmin.configure` and/or `TinyAdmin.configure_from_file` (see [configuration](#configuration) below):
|
19
23
|
|
20
|
-
|
24
|
+
```rb
|
25
|
+
TinyAdmin.configure do |settings|
|
26
|
+
settings.root = {
|
27
|
+
title: 'Home',
|
28
|
+
page: Admin::PageRoot
|
29
|
+
}
|
30
|
+
end
|
31
|
+
```
|
21
32
|
|
22
|
-
|
33
|
+
## Plugins and components
|
23
34
|
|
24
35
|
### Authentication
|
25
36
|
|
26
|
-
|
27
|
-
- _SimpleAuth_:
|
37
|
+
Plugins available:
|
38
|
+
- _SimpleAuth_: session authentication based on Warden (`warden` gem must be included in the Gemfile) using a password hash provided via config or via environment variable (`ADMIN_PASSWORD_HASH`);
|
28
39
|
- _NoAuth_: no authentication.
|
29
40
|
|
30
41
|
### Repository
|
31
42
|
|
32
|
-
|
43
|
+
Plugin available:
|
33
44
|
- _ActiveRecordRepository_: isolates the query layer to expose the resources in the admin interface.
|
34
45
|
|
35
46
|
### View pages
|
36
47
|
|
37
|
-
|
48
|
+
Pages available:
|
38
49
|
- _Root_: define how to present the content in the main page of the interface;
|
39
50
|
- _PageNotFound_: define how to present pages not found;
|
40
51
|
- _RecordNotFound_: define how to present record not found page;
|
@@ -44,7 +55,7 @@ There are 5 view pages included:
|
|
44
55
|
|
45
56
|
### View components
|
46
57
|
|
47
|
-
|
58
|
+
Components available:
|
48
59
|
- _FiltersForm_: define how to present the filters form in the resource collection pages;
|
49
60
|
- _Flash_: define how to present the flash messages;
|
50
61
|
- _Head_: define how to present the Head tag;
|
@@ -53,23 +64,118 @@ There are 5 view components included:
|
|
53
64
|
|
54
65
|
## Configuration
|
55
66
|
|
56
|
-
TinyAdmin can be configured
|
67
|
+
TinyAdmin can be configured using a YAML file and/or programmatically.
|
68
|
+
See [extra](extra) folder for some usage examples.
|
69
|
+
|
70
|
+
The following options are supported:
|
71
|
+
|
72
|
+
`root` (Hash): define the root section of the admin, properties:
|
73
|
+
- `title` (String): root section's title;
|
74
|
+
- `page` (String): a view object to render;
|
75
|
+
- `redirect` (String): alternative to _page_ option - redirects to a specific slug;
|
76
|
+
|
77
|
+
Example:
|
78
|
+
|
79
|
+
```yml
|
80
|
+
root:
|
81
|
+
title: MyAdmin
|
82
|
+
redirect: posts
|
83
|
+
```
|
84
|
+
|
85
|
+
`authentication` (Hash): define the authentication method, properties:
|
86
|
+
- `plugin` (String): a plugin class to use (ex. `TinyAdmin::Plugins::SimpleAuth`);
|
87
|
+
- `password` (String): a password hash used by _SimpleAuth_ plugin (generated with `Digest::SHA512.hexdigest("some password")`).
|
88
|
+
|
89
|
+
Example:
|
90
|
+
|
91
|
+
```yml
|
92
|
+
authentication:
|
93
|
+
plugin: TinyAdmin::Plugins::SimpleAuth
|
94
|
+
password: 'f1891cea80fc05e433c943254c6bdabc159577a02a7395dfebbfbc4f7661d4af56f2d372131a45936de40160007368a56ef216a30cb202c66d3145fd24380906'
|
95
|
+
```
|
96
|
+
|
97
|
+
`sections` (Array of hashes): define the admin sections, properties:
|
98
|
+
- `slug` (String): section reference identifier;
|
99
|
+
- `name` (String): section's title;
|
100
|
+
- `type` (String): the type of section: `url`, `page` or `resource`;
|
101
|
+
- other properties depends on the section's type.
|
102
|
+
|
103
|
+
For _url_ sections:
|
104
|
+
- `url` (String): the URL to load when clicking on the section's menu item;
|
105
|
+
- `options` (Hash): properties:
|
106
|
+
+ `target` (String): link _target_ attributes (ex. `_blank`).
|
107
|
+
|
108
|
+
Example:
|
109
|
+
|
110
|
+
```yml
|
111
|
+
slug: google
|
112
|
+
name: Google.it
|
113
|
+
type: url
|
114
|
+
url: https://www.google.it
|
115
|
+
options:
|
116
|
+
target: '_blank'
|
117
|
+
```
|
118
|
+
|
119
|
+
For _page_ sections:
|
120
|
+
- `page` (String): a view object to render.
|
121
|
+
|
122
|
+
Example:
|
123
|
+
|
124
|
+
```yml
|
125
|
+
slug: stats
|
126
|
+
name: Stats
|
127
|
+
type: page
|
128
|
+
page: Admin::Stats
|
129
|
+
```
|
130
|
+
|
131
|
+
For _resource_ sections:
|
132
|
+
- `model` (String): the class to use to fetch the data on an item of a collection;
|
133
|
+
- `repository` (String): the class to get the properties related to the model;
|
134
|
+
- `index` (Hash): collection's action options;
|
135
|
+
- `show` (Hash): detail's action options;
|
136
|
+
- `collection_actions` (Array of hashes): custom collection's actions;
|
137
|
+
- `member_actions` (Array of hashes): custom details's actions;
|
138
|
+
- `only` (Array of strings): list of supported actions (ex. `index`);
|
139
|
+
- `options` (Array of strings): resource options (ex. `hidden`).
|
57
140
|
|
58
141
|
Example:
|
59
142
|
|
143
|
+
```yml
|
144
|
+
slug: posts
|
145
|
+
name: Posts
|
146
|
+
type: resource
|
147
|
+
model: Post
|
148
|
+
```
|
149
|
+
|
150
|
+
`style_links` (Array of hashes): list of styles files to include, properties:
|
151
|
+
- `href` (String): URL for the style file;
|
152
|
+
- `rel` (String): type of style file.
|
153
|
+
|
154
|
+
`scripts` (Array of hashes): list of scripts to include, properties:
|
155
|
+
- `src` (String): source URL for the script.
|
156
|
+
|
157
|
+
`extra_styles` (String): inline CSS styles.
|
158
|
+
|
159
|
+
### Sample
|
160
|
+
|
60
161
|
```rb
|
61
162
|
# config/initializers/tiny_admin.rb
|
62
163
|
|
63
|
-
# hash generated using: Digest::SHA512.hexdigest("changeme")
|
64
|
-
ENV['ADMIN_PASSWORD_HASH'] = 'f1891cea80fc05e433c943254c6bdabc159577a02a7395dfebbfbc4f7661d4af56f2d372131a45936de40160007368a56ef216a30cb202c66d3145fd24380906'
|
65
164
|
config = Rails.root.join('config/tiny_admin.yml').to_s
|
66
165
|
TinyAdmin.configure_from_file(config)
|
166
|
+
|
167
|
+
# Change some settings programmatically
|
168
|
+
TinyAdmin.configure do |settings|
|
169
|
+
settings.authentication[:password] = Digest::SHA512.hexdigest('changeme')
|
170
|
+
end
|
67
171
|
```
|
68
172
|
|
69
173
|
```yml
|
174
|
+
# config/tiny_admin.yml
|
70
175
|
---
|
71
176
|
authentication:
|
72
177
|
plugin: TinyAdmin::Plugins::SimpleAuth
|
178
|
+
# password: 'f1891cea80fc05e433c943254c6bdabc159577a02a7395df...' <= SHA512
|
73
179
|
page_not_found: Admin::PageNotFound
|
74
180
|
record_not_found: Admin::RecordNotFound
|
75
181
|
root:
|
@@ -12,13 +12,12 @@ module TinyAdmin
|
|
12
12
|
records, total_count = repository.list(page: current_page, limit: pagination, filters: filters, sort: sort)
|
13
13
|
prepare_record = ->(record) { repository.index_record_attrs(record, fields: fields_options) }
|
14
14
|
title = repository.index_title
|
15
|
-
pages = (total_count / pagination)
|
15
|
+
pages = (total_count / pagination.to_f).ceil
|
16
16
|
|
17
|
-
prepare_page(Views::Actions::Index,
|
17
|
+
prepare_page(Views::Actions::Index, context: context) do |page|
|
18
18
|
page.setup_pagination(current_page: current_page, pages: pages > 1 ? pages : false)
|
19
19
|
page.setup_records(records: records, fields: fields, prepare_record: prepare_record)
|
20
|
-
page.actions
|
21
|
-
page.filters = filters
|
20
|
+
page.update_attributes(actions: actions, filters: filters, query_string: query_string, title: title)
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
@@ -33,11 +32,12 @@ module TinyAdmin
|
|
33
32
|
@sort = options[:sort] || ['id']
|
34
33
|
|
35
34
|
@current_page = (params['p'] || 1).to_i
|
36
|
-
@query_string = params_to_s(params.
|
35
|
+
@query_string = params_to_s(params.except('p'))
|
37
36
|
end
|
38
37
|
|
39
38
|
def prepare_filters(fields, filters_list)
|
40
|
-
filters = (filters_list || []).map { _1.is_a?(Hash) ? _1 : { field: _1 } }
|
39
|
+
filters = (filters_list || []).map { _1.is_a?(Hash) ? _1 : { field: _1 } }
|
40
|
+
filters = filters.each_with_object({}) { |filter, result| result[filter[:field]] = filter }
|
41
41
|
values = (params['q'] || {})
|
42
42
|
fields.each_with_object({}) do |field, result|
|
43
43
|
result[field] = { value: values[field.name], filter: filters[field.name] } if filters.key?(field.name)
|
@@ -11,9 +11,9 @@ module TinyAdmin
|
|
11
11
|
prepare_record = ->(record_data) { repository.show_record_attrs(record_data, fields: fields_options) }
|
12
12
|
fields = repository.fields(options: fields_options)
|
13
13
|
|
14
|
-
prepare_page(Views::Actions::Show,
|
14
|
+
prepare_page(Views::Actions::Show, context: context) do |page|
|
15
15
|
page.setup_record(record: record, fields: fields, prepare_record: prepare_record)
|
16
|
-
page.actions
|
16
|
+
page.update_attributes(actions: actions, title: repository.show_title(record))
|
17
17
|
end
|
18
18
|
rescue Plugins::BaseRepository::RecordNotFound => _e
|
19
19
|
prepare_page(options[:record_not_found_page] || Views::Pages::RecordNotFound)
|
@@ -25,11 +25,11 @@ module TinyAdmin
|
|
25
25
|
|
26
26
|
def render_login(notices: nil, warnings: nil, errors: nil)
|
27
27
|
page = prepare_page(settings.authentication[:login], options: %i[no_menu compact_layout])
|
28
|
-
page.
|
28
|
+
page.messages = {
|
29
29
|
notices: notices || flash['notices'],
|
30
30
|
warnings: warnings || flash['warnings'],
|
31
31
|
errors: errors || flash['errors']
|
32
|
-
|
32
|
+
}
|
33
33
|
render(inline: page.call)
|
34
34
|
end
|
35
35
|
end
|
data/lib/tiny_admin/basic_app.rb
CHANGED
@@ -17,14 +17,8 @@ module TinyAdmin
|
|
17
17
|
plugin :render, engine: 'html'
|
18
18
|
plugin :sessions, secret: SecureRandom.hex(64)
|
19
19
|
|
20
|
-
plugin authentication_plugin
|
20
|
+
plugin authentication_plugin, TinyAdmin::Settings.instance.authentication
|
21
21
|
|
22
22
|
not_found { prepare_page(settings.page_not_found).call }
|
23
|
-
|
24
|
-
def attach_flash_messages(page)
|
25
|
-
return unless page.respond_to?(:setup_flash_messages)
|
26
|
-
|
27
|
-
page.setup_flash_messages(notices: flash['notices'], warnings: flash['warnings'], errors: flash['errors'])
|
28
|
-
end
|
29
23
|
end
|
30
24
|
end
|
@@ -7,18 +7,19 @@ module TinyAdmin
|
|
7
7
|
module SimpleAuth
|
8
8
|
class << self
|
9
9
|
def configure(app, opts = {})
|
10
|
+
@@opts = opts || {} # rubocop:disable Style/ClassVars
|
11
|
+
@@opts[:password] ||= ENV.fetch('ADMIN_PASSWORD_HASH', nil) # NOTE: fallback value
|
12
|
+
|
10
13
|
Warden::Strategies.add(:secret) do
|
11
14
|
def authenticate!
|
12
|
-
|
13
|
-
secret
|
14
|
-
return fail(:invalid_credentials) if !secret || Digest::SHA512.hexdigest(secret) != hash
|
15
|
+
secret = params['secret'] || ''
|
16
|
+
return fail(:invalid_credentials) if Digest::SHA512.hexdigest(secret) != @@opts[:password]
|
15
17
|
|
16
18
|
success!(app: 'TinyAdmin')
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
20
22
|
app.opts[:login_form] = opts[:login_form] || TinyAdmin::Views::Pages::SimpleAuthLogin
|
21
|
-
|
22
23
|
app.use Warden::Manager do |manager|
|
23
24
|
manager.default_strategies :secret
|
24
25
|
manager.failure_app = TinyAdmin::Authentication
|
data/lib/tiny_admin/router.rb
CHANGED
@@ -16,6 +16,12 @@ module TinyAdmin
|
|
16
16
|
root_route(r)
|
17
17
|
end
|
18
18
|
|
19
|
+
r.is do
|
20
|
+
# :nocov:
|
21
|
+
root_route(r)
|
22
|
+
# :nocov:
|
23
|
+
end
|
24
|
+
|
19
25
|
r.post '' do
|
20
26
|
context.slug = nil
|
21
27
|
r.redirect settings.root_path
|
@@ -35,14 +41,16 @@ module TinyAdmin
|
|
35
41
|
private
|
36
42
|
|
37
43
|
def render_page(page)
|
38
|
-
|
44
|
+
if page.respond_to?(:messages=)
|
45
|
+
page.messages = { notices: flash['notices'], warnings: flash['warnings'], errors: flash['errors'] }
|
46
|
+
end
|
39
47
|
render(inline: page.call)
|
40
48
|
end
|
41
49
|
|
42
50
|
def root_route(router)
|
43
51
|
context.slug = nil
|
44
52
|
if settings.root[:redirect]
|
45
|
-
router.redirect
|
53
|
+
router.redirect route_for(settings.root[:redirect])
|
46
54
|
else
|
47
55
|
page = settings.root[:page]
|
48
56
|
page_class = page.is_a?(String) ? Object.const_get(page) : page
|
@@ -60,66 +68,71 @@ module TinyAdmin
|
|
60
68
|
def setup_resource_routes(router, slug, options:)
|
61
69
|
router.on slug do
|
62
70
|
context.slug = slug
|
63
|
-
setup_collection_routes(router,
|
64
|
-
setup_member_routes(router,
|
71
|
+
setup_collection_routes(router, options: options)
|
72
|
+
setup_member_routes(router, options: options)
|
65
73
|
end
|
66
74
|
end
|
67
75
|
|
68
|
-
def setup_collection_routes(router,
|
76
|
+
def setup_collection_routes(router, options:)
|
69
77
|
repository = options[:repository].new(options[:model])
|
70
|
-
|
71
|
-
custom_actions = []
|
78
|
+
action_options = options[:index] || {}
|
72
79
|
|
73
80
|
# Custom actions
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
render_page custom_action.call(app: self, context: context, options: index_options)
|
81
|
-
end
|
82
|
-
end
|
81
|
+
custom_actions = setup_custom_actions(
|
82
|
+
router,
|
83
|
+
options[:collection_actions],
|
84
|
+
repository: repository,
|
85
|
+
options: action_options
|
86
|
+
)
|
83
87
|
|
84
88
|
# Index
|
85
89
|
actions = options[:only]
|
86
90
|
if !actions || actions.include?(:index) || actions.include?('index')
|
87
91
|
router.is do
|
88
92
|
index_action = TinyAdmin::Actions::Index.new(repository, path: request.path, params: request.params)
|
89
|
-
render_page index_action.call(app: self, context: context, options:
|
93
|
+
render_page index_action.call(app: self, context: context, options: action_options, actions: custom_actions)
|
90
94
|
end
|
91
95
|
end
|
92
96
|
end
|
93
97
|
|
94
|
-
def setup_member_routes(router,
|
98
|
+
def setup_member_routes(router, options:)
|
95
99
|
repository = options[:repository].new(options[:model])
|
96
|
-
|
97
|
-
custom_actions = []
|
100
|
+
action_options = (options[:show] || {}).merge(record_not_found_page: settings.record_not_found)
|
98
101
|
|
99
102
|
router.on String do |reference|
|
100
103
|
context.reference = reference
|
101
104
|
|
102
105
|
# Custom actions
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
custom_action = action_class.new(repository, path: request.path, params: request.params)
|
110
|
-
render_page custom_action.call(app: self, context: context, options: show_options)
|
111
|
-
end
|
112
|
-
end
|
106
|
+
custom_actions = setup_custom_actions(
|
107
|
+
router,
|
108
|
+
options[:member_actions],
|
109
|
+
repository: repository,
|
110
|
+
options: action_options
|
111
|
+
)
|
113
112
|
|
114
113
|
# Show
|
115
114
|
actions = options[:only]
|
116
115
|
if !actions || actions.include?(:show) || actions.include?('show')
|
117
116
|
router.is do
|
118
117
|
show_action = TinyAdmin::Actions::Show.new(repository, path: request.path, params: request.params)
|
119
|
-
render_page show_action.call(app: self, context: context, options:
|
118
|
+
render_page show_action.call(app: self, context: context, options: action_options, actions: custom_actions)
|
120
119
|
end
|
121
120
|
end
|
122
121
|
end
|
123
122
|
end
|
123
|
+
|
124
|
+
def setup_custom_actions(router, custom_actions, repository:, options:)
|
125
|
+
(custom_actions || []).map do |custom_action|
|
126
|
+
action_slug, action = custom_action.first
|
127
|
+
action_class = action.is_a?(String) ? Object.const_get(action) : action
|
128
|
+
|
129
|
+
router.get action_slug.to_s do
|
130
|
+
custom_action = action_class.new(repository, path: request.path, params: request.params)
|
131
|
+
render_page custom_action.call(app: self, context: context, options: options)
|
132
|
+
end
|
133
|
+
|
134
|
+
action_slug.to_s
|
135
|
+
end
|
136
|
+
end
|
124
137
|
end
|
125
138
|
end
|
data/lib/tiny_admin/settings.rb
CHANGED
@@ -35,11 +35,11 @@ module TinyAdmin
|
|
35
35
|
@repository ||= Plugins::ActiveRecordRepository
|
36
36
|
@resources ||= {}
|
37
37
|
@root_path ||= '/admin'
|
38
|
+
@root_path = '/' if @root_path == ''
|
38
39
|
@sections ||= []
|
39
40
|
|
40
41
|
@root ||= {}
|
41
42
|
@root[:title] ||= 'TinyAdmin'
|
42
|
-
@root[:path] ||= root_path
|
43
43
|
@root[:page] ||= Views::Pages::Root
|
44
44
|
|
45
45
|
if @authentication[:plugin] == Plugins::SimpleAuth
|
@@ -52,10 +52,10 @@ module TinyAdmin
|
|
52
52
|
@components[:navbar] ||= Views::Components::Navbar
|
53
53
|
@components[:pagination] ||= Views::Components::Pagination
|
54
54
|
|
55
|
-
@navbar = prepare_navbar(sections,
|
55
|
+
@navbar = prepare_navbar(sections, logout: authentication[:logout])
|
56
56
|
end
|
57
57
|
|
58
|
-
def prepare_navbar(sections,
|
58
|
+
def prepare_navbar(sections, logout:)
|
59
59
|
items = sections.each_with_object({}) do |section, list|
|
60
60
|
slug = section[:slug]
|
61
61
|
case section[:type]&.to_sym
|
@@ -64,7 +64,7 @@ module TinyAdmin
|
|
64
64
|
when :page
|
65
65
|
page = section[:page]
|
66
66
|
pages[slug] = page.is_a?(String) ? Object.const_get(page) : page
|
67
|
-
list[slug] = [section[:name],
|
67
|
+
list[slug] = [section[:name], route_for(slug)]
|
68
68
|
when :resource
|
69
69
|
repository = section[:repository] || settings.repository
|
70
70
|
resources[slug] = {
|
@@ -73,7 +73,7 @@ module TinyAdmin
|
|
73
73
|
}
|
74
74
|
resources[slug].merge! section.slice(:resource, :only, :index, :show, :collection_actions, :member_actions)
|
75
75
|
hidden = section[:options] && (section[:options].include?(:hidden) || section[:options].include?('hidden'))
|
76
|
-
list[slug] = [section[:name],
|
76
|
+
list[slug] = [section[:name], route_for(slug)] unless hidden
|
77
77
|
end
|
78
78
|
end
|
79
79
|
items['auth/logout'] = logout if logout
|
data/lib/tiny_admin/utils.rb
CHANGED
@@ -13,20 +13,18 @@ module TinyAdmin
|
|
13
13
|
list.join('&')
|
14
14
|
end
|
15
15
|
|
16
|
-
def prepare_page(page_class,
|
16
|
+
def prepare_page(page_class, context: nil, options: nil)
|
17
17
|
page_class.new.tap do |page|
|
18
|
-
page.
|
19
|
-
page.
|
20
|
-
context: context,
|
21
|
-
compact_layout: options.include?(:compact_layout),
|
22
|
-
no_menu: options.include?(:no_menu)
|
23
|
-
)
|
18
|
+
page.context = context
|
19
|
+
page.options = options
|
24
20
|
yield(page) if block_given?
|
25
21
|
end
|
26
22
|
end
|
27
23
|
|
28
24
|
def route_for(section, reference: nil, action: nil)
|
29
|
-
|
25
|
+
root_path = settings.root_path == '/' ? nil : settings.root_path
|
26
|
+
route = [root_path, section, reference, action].compact.join("/")
|
27
|
+
route[0] == '/' ? route : route.prepend('/')
|
30
28
|
end
|
31
29
|
|
32
30
|
def context
|
data/lib/tiny_admin/version.rb
CHANGED
@@ -5,7 +5,7 @@ module TinyAdmin
|
|
5
5
|
module Actions
|
6
6
|
class Index < DefaultLayout
|
7
7
|
attr_reader :current_page, :fields, :pages, :prepare_record, :records
|
8
|
-
attr_accessor :actions, :filters
|
8
|
+
attr_accessor :actions, :filters, :query_string
|
9
9
|
|
10
10
|
def setup_pagination(current_page:, pages:)
|
11
11
|
@current_page = current_page
|
@@ -14,7 +14,7 @@ module TinyAdmin
|
|
14
14
|
|
15
15
|
def setup_records(records:, fields:, prepare_record:)
|
16
16
|
@records = records
|
17
|
-
@fields = fields.
|
17
|
+
@fields = fields.each_with_object({}) { |field, result| result[field.name] = field }
|
18
18
|
@prepare_record = prepare_record
|
19
19
|
end
|
20
20
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinyAdmin
|
4
|
+
module Views
|
5
|
+
class BasicLayout < Phlex::HTML
|
6
|
+
include Utils
|
7
|
+
|
8
|
+
def components
|
9
|
+
settings.components
|
10
|
+
end
|
11
|
+
|
12
|
+
def update_attributes(attributes)
|
13
|
+
attributes.each do |key, value|
|
14
|
+
send("#{key}=", value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -6,10 +6,12 @@ module TinyAdmin
|
|
6
6
|
class Flash < Phlex::HTML
|
7
7
|
attr_reader :errors, :notices, :warnings
|
8
8
|
|
9
|
-
def initialize(
|
10
|
-
|
11
|
-
|
12
|
-
@
|
9
|
+
def initialize(messages:)
|
10
|
+
return unless messages
|
11
|
+
|
12
|
+
@notices = messages[:notices]
|
13
|
+
@warnings = messages[:warnings]
|
14
|
+
@errors = messages[:errors]
|
13
15
|
end
|
14
16
|
|
15
17
|
def template
|
@@ -4,10 +4,11 @@ module TinyAdmin
|
|
4
4
|
module Views
|
5
5
|
module Components
|
6
6
|
class Navbar < Phlex::HTML
|
7
|
-
attr_reader :current_slug, :items, :
|
7
|
+
attr_reader :current_slug, :items, :root_path, :root_title
|
8
8
|
|
9
|
-
def initialize(
|
10
|
-
@
|
9
|
+
def initialize(root_path:, root_title:, items: [], current_slug: nil)
|
10
|
+
@root_path = root_path
|
11
|
+
@root_title = root_title
|
11
12
|
@items = items || []
|
12
13
|
@current_slug = current_slug
|
13
14
|
end
|
@@ -15,7 +16,7 @@ module TinyAdmin
|
|
15
16
|
def template
|
16
17
|
nav(class: 'navbar navbar-expand-lg') {
|
17
18
|
div(class: 'container') {
|
18
|
-
a(class: 'navbar-brand', href:
|
19
|
+
a(class: 'navbar-brand', href: root_path) { root_title }
|
19
20
|
button(class: 'navbar-toggler', type: 'button', 'data-bs-toggle' => 'collapse', 'data-bs-target' => '#navbarNav', 'aria-controls' => 'navbarNav', 'aria-expanded' => 'false', 'aria-label' => 'Toggle navigation') {
|
20
21
|
span(class: 'navbar-toggler-icon')
|
21
22
|
}
|
@@ -2,49 +2,25 @@
|
|
2
2
|
|
3
3
|
module TinyAdmin
|
4
4
|
module Views
|
5
|
-
class DefaultLayout <
|
6
|
-
|
7
|
-
|
8
|
-
attr_reader :compact_layout,
|
9
|
-
:context,
|
10
|
-
:errors,
|
11
|
-
:no_menu,
|
12
|
-
:notices,
|
13
|
-
:query_string,
|
14
|
-
:settings,
|
15
|
-
:title,
|
16
|
-
:warnings
|
17
|
-
|
18
|
-
def setup_page(title:, query_string:, settings:)
|
19
|
-
@title = title
|
20
|
-
@query_string = query_string
|
21
|
-
@settings = settings
|
22
|
-
end
|
23
|
-
|
24
|
-
def setup_options(context:, compact_layout:, no_menu:)
|
25
|
-
@context = context
|
26
|
-
@compact_layout = compact_layout
|
27
|
-
@no_menu = no_menu
|
28
|
-
end
|
29
|
-
|
30
|
-
def setup_flash_messages(notices: [], warnings: [], errors: [])
|
31
|
-
@notices = notices
|
32
|
-
@warnings = warnings
|
33
|
-
@errors = errors
|
34
|
-
end
|
5
|
+
class DefaultLayout < BasicLayout
|
6
|
+
attr_accessor :context, :messages, :options, :title
|
35
7
|
|
36
8
|
def template(&block)
|
37
|
-
items = no_menu ? [] : settings.navbar
|
38
|
-
|
39
9
|
doctype
|
40
10
|
html {
|
41
11
|
render components[:head].new(title, style_links: style_links, extra_styles: settings.extra_styles)
|
42
12
|
|
43
13
|
body(class: body_class) {
|
44
|
-
|
14
|
+
navbar_attrs = {
|
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)
|
45
21
|
|
46
22
|
main_content {
|
47
|
-
render components[:flash].new(
|
23
|
+
render components[:flash].new(messages: messages || {})
|
48
24
|
yield_content(&block)
|
49
25
|
}
|
50
26
|
|
@@ -55,29 +31,17 @@ module TinyAdmin
|
|
55
31
|
|
56
32
|
private
|
57
33
|
|
58
|
-
def components
|
59
|
-
settings.components
|
60
|
-
end
|
61
|
-
|
62
|
-
def style_links
|
63
|
-
settings.style_links || [
|
64
|
-
# Bootstrap CDN
|
65
|
-
{
|
66
|
-
href: 'https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css',
|
67
|
-
rel: 'stylesheet',
|
68
|
-
integrity: 'sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65',
|
69
|
-
crossorigin: 'anonymous'
|
70
|
-
}
|
71
|
-
]
|
72
|
-
end
|
73
|
-
|
74
34
|
def body_class
|
75
35
|
"module-#{self.class.to_s.split('::').last.downcase}"
|
76
36
|
end
|
77
37
|
|
38
|
+
def navbar_items
|
39
|
+
options&.include?(:no_menu) ? [] : settings.navbar
|
40
|
+
end
|
41
|
+
|
78
42
|
def main_content
|
79
43
|
div(class: 'container main-content py-4') do
|
80
|
-
if compact_layout
|
44
|
+
if options&.include?(:compact_layout)
|
81
45
|
div(class: 'row justify-content-center') {
|
82
46
|
div(class: 'col-6') {
|
83
47
|
yield
|
@@ -89,10 +53,20 @@ module TinyAdmin
|
|
89
53
|
end
|
90
54
|
end
|
91
55
|
|
92
|
-
def
|
93
|
-
|
56
|
+
def style_links
|
57
|
+
settings.style_links || [
|
58
|
+
# Bootstrap CDN
|
59
|
+
{
|
60
|
+
href: 'https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css',
|
61
|
+
rel: 'stylesheet',
|
62
|
+
integrity: 'sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65',
|
63
|
+
crossorigin: 'anonymous'
|
64
|
+
}
|
65
|
+
]
|
66
|
+
end
|
94
67
|
|
95
|
-
|
68
|
+
def render_scripts
|
69
|
+
(settings.scripts || []).each do |script_attrs|
|
96
70
|
script(**script_attrs)
|
97
71
|
end
|
98
72
|
end
|
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.1
|
4
|
+
version: 0.2.1
|
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-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: phlex
|
@@ -16,43 +16,57 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1
|
19
|
+
version: '1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1
|
26
|
+
version: '1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: roda
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '3
|
33
|
+
version: '3'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '3
|
40
|
+
version: '3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: tilt
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: zeitwerk
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
59
|
- - "~>"
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version: '2
|
61
|
+
version: '2'
|
48
62
|
type: :runtime
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
66
|
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version: '2
|
55
|
-
description: A compact and
|
68
|
+
version: '2'
|
69
|
+
description: A compact and composable dashboard component for Ruby
|
56
70
|
email: mat@blocknot.es
|
57
71
|
executables: []
|
58
72
|
extensions: []
|
@@ -78,6 +92,7 @@ files:
|
|
78
92
|
- lib/tiny_admin/version.rb
|
79
93
|
- lib/tiny_admin/views/actions/index.rb
|
80
94
|
- lib/tiny_admin/views/actions/show.rb
|
95
|
+
- lib/tiny_admin/views/basic_layout.rb
|
81
96
|
- lib/tiny_admin/views/components/filters_form.rb
|
82
97
|
- lib/tiny_admin/views/components/flash.rb
|
83
98
|
- lib/tiny_admin/views/components/head.rb
|
@@ -94,7 +109,7 @@ licenses:
|
|
94
109
|
metadata:
|
95
110
|
homepage_uri: https://github.com/blocknotes/tiny_admin
|
96
111
|
source_code_uri: https://github.com/blocknotes/tiny_admin
|
97
|
-
changelog_uri: https://github.com/blocknotes/tiny_admin/blob/
|
112
|
+
changelog_uri: https://github.com/blocknotes/tiny_admin/blob/main/CHANGELOG.md
|
98
113
|
rubygems_mfa_required: 'true'
|
99
114
|
post_install_message:
|
100
115
|
rdoc_options: []
|
@@ -104,7 +119,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
104
119
|
requirements:
|
105
120
|
- - ">="
|
106
121
|
- !ruby/object:Gem::Version
|
107
|
-
version:
|
122
|
+
version: 3.0.0
|
108
123
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
124
|
requirements:
|
110
125
|
- - ">="
|