tiny_admin 0.1.0 → 0.2.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 +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
|
+
[](https://badge.fury.io/rb/tiny_admin) [](https://github.com/blocknotes/tiny_admin/actions/workflows/linters.yml) [](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
|
+

|
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
|
- - ">="
|