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