tiny_admin 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +115 -19
- data/lib/tiny_admin/actions/index.rb +5 -5
- 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: 10d229ad7389d1fe4392d6f96ec993550470ba67d1b4e140df21b9d247ae3657
|
4
|
+
data.tar.gz: 3f625daa624cdff4e7fe4f13618ac753cce120995daa9140dac17e55d79ef1ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7078779b3478f2e28f5864b43839a69ee918a452a97c0851b3be45a75802fd01600f2df6d771f093032e021f98c408b40fcaffac0b4ba5fca61e5e92693bfe86
|
7
|
+
data.tar.gz: c120746d7363cbda9dd39f9de6910181645da4d63bf8325c0e4d8619b2892b008c882a32e4e8b50f63ec9fc6486837843e920f4e4c917424390346fb5af67877
|
data/README.md
CHANGED
@@ -1,40 +1,41 @@
|
|
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
|
+
The main features are:
|
8
|
+
- a Rack app that can be mounted in any Rack-enabled framework, it can even work 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 and 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.1'`
|
20
|
+
- Mount the app in a route (check some examples with: Hanami, Rails, Roda and standalone in [extra](extra))
|
21
|
+
- Configure the dashboard using `TinyAdmin.configure` and/or `TinyAdmin.configure_from_file` (see [configuration](#configuration) below)
|
19
22
|
|
20
23
|
## Plugins and components
|
21
24
|
|
22
|
-
Every plugin or component can be replaced.
|
23
|
-
|
24
25
|
### Authentication
|
25
26
|
|
26
|
-
|
27
|
-
- _SimpleAuth_:
|
27
|
+
Plugins available:
|
28
|
+
- _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
29
|
- _NoAuth_: no authentication.
|
29
30
|
|
30
31
|
### Repository
|
31
32
|
|
32
|
-
|
33
|
+
Plugin available:
|
33
34
|
- _ActiveRecordRepository_: isolates the query layer to expose the resources in the admin interface.
|
34
35
|
|
35
36
|
### View pages
|
36
37
|
|
37
|
-
|
38
|
+
Pages available:
|
38
39
|
- _Root_: define how to present the content in the main page of the interface;
|
39
40
|
- _PageNotFound_: define how to present pages not found;
|
40
41
|
- _RecordNotFound_: define how to present record not found page;
|
@@ -44,7 +45,7 @@ There are 5 view pages included:
|
|
44
45
|
|
45
46
|
### View components
|
46
47
|
|
47
|
-
|
48
|
+
Components available:
|
48
49
|
- _FiltersForm_: define how to present the filters form in the resource collection pages;
|
49
50
|
- _Flash_: define how to present the flash messages;
|
50
51
|
- _Head_: define how to present the Head tag;
|
@@ -53,23 +54,118 @@ There are 5 view components included:
|
|
53
54
|
|
54
55
|
## Configuration
|
55
56
|
|
56
|
-
TinyAdmin can be configured
|
57
|
+
TinyAdmin can be configured using a YAML file and/or programmatically.
|
58
|
+
See [extra](extra) folder for some usage examples.
|
59
|
+
|
60
|
+
The following options are supported:
|
61
|
+
|
62
|
+
`root` (Hash): define the root section of the admin, properties:
|
63
|
+
- `title` (String): root section's title;
|
64
|
+
- `page` (String): a view object to render;
|
65
|
+
- `redirect` (String): alternative to _page_ option - redirects to a specific slug;
|
66
|
+
|
67
|
+
Example:
|
68
|
+
|
69
|
+
```yml
|
70
|
+
root:
|
71
|
+
title: MyAdmin
|
72
|
+
redirect: posts
|
73
|
+
```
|
74
|
+
|
75
|
+
`authentication` (Hash): define the authentication method, properties:
|
76
|
+
- `plugin` (String): a plugin class to use (ex. `TinyAdmin::Plugins::SimpleAuth`);
|
77
|
+
- `password` (String): a password hash used by _SimpleAuth_ plugin (generated with `Digest::SHA512.hexdigest("some password")`).
|
78
|
+
|
79
|
+
Example:
|
80
|
+
|
81
|
+
```yml
|
82
|
+
authentication:
|
83
|
+
plugin: TinyAdmin::Plugins::SimpleAuth
|
84
|
+
password: 'f1891cea80fc05e433c943254c6bdabc159577a02a7395dfebbfbc4f7661d4af56f2d372131a45936de40160007368a56ef216a30cb202c66d3145fd24380906'
|
85
|
+
```
|
86
|
+
|
87
|
+
`sections` (Array of hashes): define the admin sections, properties:
|
88
|
+
- `slug` (String): section reference identifier;
|
89
|
+
- `name` (String): section's title;
|
90
|
+
- `type` (String): the type of section: `url`, `page` or `resource`;
|
91
|
+
- other properties depends on the section's type.
|
92
|
+
|
93
|
+
For _url_ sections:
|
94
|
+
- `url` (String): the URL to load when clicking on the section's menu item;
|
95
|
+
- `options` (Hash): properties:
|
96
|
+
+ `target` (String): link _target_ attributes (ex. `_blank`).
|
57
97
|
|
58
98
|
Example:
|
59
99
|
|
100
|
+
```yml
|
101
|
+
slug: google
|
102
|
+
name: Google.it
|
103
|
+
type: url
|
104
|
+
url: https://www.google.it
|
105
|
+
options:
|
106
|
+
target: '_blank'
|
107
|
+
```
|
108
|
+
|
109
|
+
For _page_ sections:
|
110
|
+
- `page` (String): a view object to render.
|
111
|
+
|
112
|
+
Example:
|
113
|
+
|
114
|
+
```yml
|
115
|
+
slug: stats
|
116
|
+
name: Stats
|
117
|
+
type: page
|
118
|
+
page: Admin::Stats
|
119
|
+
```
|
120
|
+
|
121
|
+
For _resource_ sections:
|
122
|
+
- `model` (String): the class to use to fetch the data on an item of a collection;
|
123
|
+
- `repository` (String): the class to get the properties related to the model;
|
124
|
+
- `index` (Hash): collection's action options;
|
125
|
+
- `show` (Hash): detail's action options;
|
126
|
+
- `collection_actions` (Array of hashes): custom collection's actions;
|
127
|
+
- `member_actions` (Array of hashes): custom details's actions;
|
128
|
+
- `only` (Array of strings): list of supported actions (ex. `index`);
|
129
|
+
- `options` (Array of strings): resource options (ex. `hidden`).
|
130
|
+
|
131
|
+
Example:
|
132
|
+
|
133
|
+
```yml
|
134
|
+
slug: posts
|
135
|
+
name: Posts
|
136
|
+
type: resource
|
137
|
+
model: Post
|
138
|
+
```
|
139
|
+
|
140
|
+
`style_links` (Array of hashes): list of styles files to include, properties:
|
141
|
+
- `href` (String): URL for the style file;
|
142
|
+
- `rel` (String): type of style file.
|
143
|
+
|
144
|
+
`scripts` (Array of hashes): list of scripts to include, properties:
|
145
|
+
- `src` (String): source URL for the script.
|
146
|
+
|
147
|
+
`extra_styles` (String): inline CSS styles.
|
148
|
+
|
149
|
+
### Sample
|
150
|
+
|
60
151
|
```rb
|
61
152
|
# config/initializers/tiny_admin.rb
|
62
153
|
|
63
|
-
# hash generated using: Digest::SHA512.hexdigest("changeme")
|
64
|
-
ENV['ADMIN_PASSWORD_HASH'] = 'f1891cea80fc05e433c943254c6bdabc159577a02a7395dfebbfbc4f7661d4af56f2d372131a45936de40160007368a56ef216a30cb202c66d3145fd24380906'
|
65
154
|
config = Rails.root.join('config/tiny_admin.yml').to_s
|
66
155
|
TinyAdmin.configure_from_file(config)
|
156
|
+
|
157
|
+
# Change some settings programmatically
|
158
|
+
TinyAdmin.configure do |settings|
|
159
|
+
settings.authentication[:password] = Digest::SHA512.hexdigest('changeme')
|
160
|
+
end
|
67
161
|
```
|
68
162
|
|
69
163
|
```yml
|
164
|
+
# config/tiny_admin.yml
|
70
165
|
---
|
71
166
|
authentication:
|
72
167
|
plugin: TinyAdmin::Plugins::SimpleAuth
|
168
|
+
# password: 'f1891cea80fc05e433c943254c6bdabc159577a02a7395df...' <= SHA512
|
73
169
|
page_not_found: Admin::PageNotFound
|
74
170
|
record_not_found: Admin::RecordNotFound
|
75
171
|
root:
|
@@ -14,11 +14,10 @@ module TinyAdmin
|
|
14
14
|
title = repository.index_title
|
15
15
|
pages = (total_count / pagination) + 1
|
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') # 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.
|
4
|
+
version: 0.2.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-10 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
|
- - ">="
|