tiny_admin 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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
  - - ">="