tiny_admin 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: afea7b433e47bdf47e2682a334e2ffe7761e6e341aec9b43746291faf4d16406
4
- data.tar.gz: 6c53fbae6a6c6749ef423bbd190094c25149abae29c6c235bf49f13fb3d6906a
3
+ metadata.gz: 10d229ad7389d1fe4392d6f96ec993550470ba67d1b4e140df21b9d247ae3657
4
+ data.tar.gz: 3f625daa624cdff4e7fe4f13618ac753cce120995daa9140dac17e55d79ef1ba
5
5
  SHA512:
6
- metadata.gz: 4b9701bccac2aa19c6fb838c037e0522d6ee8d3560ec1d5aa743725a64d1c2e4bfc7369faaef9bdc6b98da280f8f1d82c302fadfae5620fe1e958857238005c4
7
- data.tar.gz: 16763a9cdf6df77cc261f5a5493803a55cd41756888026fb93ccdf66bf7c6fe7412482b39a666fd026892787addeb55c80adef45b1af479b0f062eb7276aaa53
6
+ metadata.gz: 7078779b3478f2e28f5864b43839a69ee918a452a97c0851b3be45a75802fd01600f2df6d771f093032e021f98c408b40fcaffac0b4ba5fca61e5e92693bfe86
7
+ data.tar.gz: c120746d7363cbda9dd39f9de6910181645da4d63bf8325c0e4d8619b2892b008c882a32e4e8b50f63ec9fc6486837843e920f4e4c917424390346fb5af67877
data/README.md CHANGED
@@ -1,40 +1,41 @@
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
+ The main features are:
8
+ - a Rack app that can be mounted in any Rack-enabled framework, it can even work standalone;
9
+ - structured with plugins also for main components that can be replaced with little effort;
10
+ - routing is provided by Roda, which is small and performant;
11
+ - views are Phlex components, so plain Ruby objects for views and no assets are needed.
12
12
 
13
13
  Please ⭐ if you like it.
14
14
 
15
+ ![screenshot](extra/screenshot.png)
16
+
15
17
  ## Install
16
18
 
17
- - Add to your Gemfile: `gem 'tiny_admin'`
18
- - For a Rails project: add an initializer and the YAML config - see (configuration)[#configuration] below.
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
- There are 2 plugins included:
27
- - _SimpleAuth_: provides a simple session authentication based on Warden (`warden` gem must be included in the host project);
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
- There is 1 plugin included:
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
- There are 5 view pages included:
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
- There are 5 view components included:
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 programmatically or using a YAML config.
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, 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') # 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.0'
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.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-07 00:00:00.000000000 Z
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.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
  - - ">="