tiny_admin 0.1.0 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: afea7b433e47bdf47e2682a334e2ffe7761e6e341aec9b43746291faf4d16406
4
- data.tar.gz: 6c53fbae6a6c6749ef423bbd190094c25149abae29c6c235bf49f13fb3d6906a
3
+ metadata.gz: aa8eb3987f51f28c4f9a55c62fddfd521accf075aac9be4d0b4fab7e88145770
4
+ data.tar.gz: 98e2f4af557280ca167816fa325f824d5eda99cde96d35407d472ab1e37b370d
5
5
  SHA512:
6
- metadata.gz: 4b9701bccac2aa19c6fb838c037e0522d6ee8d3560ec1d5aa743725a64d1c2e4bfc7369faaef9bdc6b98da280f8f1d82c302fadfae5620fe1e958857238005c4
7
- data.tar.gz: 16763a9cdf6df77cc261f5a5493803a55cd41756888026fb93ccdf66bf7c6fe7412482b39a666fd026892787addeb55c80adef45b1af479b0f062eb7276aaa53
6
+ metadata.gz: 501e766d535a08b80597c7e5e9c5c40c075a74d1ddf347ad6d0858600c37fb945e39616a7d3b62d91a9486484a6a7d5edbd59fdfaca7e373fa099ecb8092edef
7
+ data.tar.gz: cfd95059d66a9f5d83b60086b5b59d01840fc654e929546778d02457605d6bca1b22cf3f587a92db93575464281ef0f1cd7993a2082bd234ef91c124e84b5ad7
data/README.md CHANGED
@@ -1,40 +1,51 @@
1
1
  # Tiny Admin
2
2
 
3
- A compact and composible dashboard component for Ruby.
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
- The main features are:
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
- See (extra)[extra] folder for usage examples.
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
- - For a Rails project: add an initializer and the YAML config - see (configuration)[#configuration] below.
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
- ## Plugins and components
24
+ ```rb
25
+ TinyAdmin.configure do |settings|
26
+ settings.root = {
27
+ title: 'Home',
28
+ page: Admin::PageRoot
29
+ }
30
+ end
31
+ ```
21
32
 
22
- Every plugin or component can be replaced.
33
+ ## Plugins and components
23
34
 
24
35
  ### Authentication
25
36
 
26
- There are 2 plugins included:
27
- - _SimpleAuth_: provides a simple session authentication based on Warden (`warden` gem must be included in the host project);
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
- There is 1 plugin included:
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
- There are 5 view pages included:
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
- There are 5 view components included:
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 programmatically or using a YAML config.
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) + 1
15
+ pages = (total_count / pagination.to_f).ceil
16
16
 
17
- prepare_page(Views::Actions::Index, title: title, context: context, query_string: query_string) do |page|
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 = 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.reject { |k, _v| k == 'p' })
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 } }.index_by { _1[:field] }
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, title: repository.show_title(record), context: context) do |page|
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 = 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.setup_flash_messages(
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
@@ -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
- hash = ENV.fetch('ADMIN_PASSWORD_HASH')
13
- secret = params['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
@@ -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
- attach_flash_messages(page)
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 "#{settings.root_path}/#{settings.root[: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, slug: slug, options: options)
64
- setup_member_routes(router, slug: slug, options: options)
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, slug:, options:)
76
+ def setup_collection_routes(router, options:)
69
77
  repository = options[:repository].new(options[:model])
70
- index_options = options[:index] || {}
71
- custom_actions = []
78
+ action_options = options[:index] || {}
72
79
 
73
80
  # Custom actions
74
- (options[:collection_actions] || []).each do |custom_action|
75
- action_slug, action = custom_action.first
76
- action_class = action.is_a?(String) ? Object.const_get(action) : action
77
- custom_actions << action_slug.to_s
78
- router.get action_slug.to_s do
79
- custom_action = action_class.new(repository, path: request.path, params: request.params)
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: index_options, actions: custom_actions)
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, slug:, options:)
98
+ def setup_member_routes(router, options:)
95
99
  repository = options[:repository].new(options[:model])
96
- show_options = (options[:show] || {}).merge(record_not_found_page: settings.record_not_found)
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
- (options[:member_actions] || []).each do |custom_action|
104
- action_slug, action = custom_action.first
105
- action_class = action.is_a?(String) ? Object.const_get(action) : action
106
- custom_actions << action_slug.to_s
107
-
108
- router.get action_slug.to_s do
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: show_options, actions: custom_actions)
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
@@ -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, root_path: root_path, logout: authentication[:logout])
55
+ @navbar = prepare_navbar(sections, logout: authentication[:logout])
56
56
  end
57
57
 
58
- def prepare_navbar(sections, root_path:, logout:)
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], "#{root_path}/#{slug}"]
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], "#{root_path}/#{slug}"] unless hidden
76
+ list[slug] = [section[:name], route_for(slug)] unless hidden
77
77
  end
78
78
  end
79
79
  items['auth/logout'] = logout if logout
@@ -13,20 +13,18 @@ module TinyAdmin
13
13
  list.join('&')
14
14
  end
15
15
 
16
- def prepare_page(page_class, title: nil, context: nil, query_string: '', options: [])
16
+ def prepare_page(page_class, context: nil, options: nil)
17
17
  page_class.new.tap do |page|
18
- page.setup_page(title: title, query_string: query_string, settings: settings)
19
- page.setup_options(
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
- [settings.root_path, section, reference, action].compact.join("/")
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TinyAdmin
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.1'
5
5
  end
@@ -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.index_by(&:name)
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(notices: [], warnings: [], errors: [])
10
- @notices = notices
11
- @warnings = warnings
12
- @errors = errors
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, :root
7
+ attr_reader :current_slug, :items, :root_path, :root_title
8
8
 
9
- def initialize(root:, items: [], current_slug: nil)
10
- @root = root
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: root[:path]) { root[:title] }
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 < Phlex::HTML
6
- include Utils
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
- render components[:navbar].new(current_slug: context&.slug, root: settings.root, items: items)
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(notices: notices, warnings: warnings, errors: errors)
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 render_scripts
93
- return unless settings.scripts
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
- settings.scripts.each do |script_attrs|
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.0
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-07 00:00:00.000000000 Z
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.6'
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.6'
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.66'
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.66'
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.6'
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.6'
55
- description: A compact and composible dashboard component for Ruby
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/master/CHANGELOG.md
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: 2.7.0
122
+ version: 3.0.0
108
123
  required_rubygems_version: !ruby/object:Gem::Requirement
109
124
  requirements:
110
125
  - - ">="