tiny_admin 0.1.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 +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +171 -0
- data/lib/tiny_admin/actions/basic_action.rb +17 -0
- data/lib/tiny_admin/actions/index.rb +48 -0
- data/lib/tiny_admin/actions/show.rb +23 -0
- data/lib/tiny_admin/authentication.rb +36 -0
- data/lib/tiny_admin/basic_app.rb +30 -0
- data/lib/tiny_admin/context.rb +9 -0
- data/lib/tiny_admin/field.rb +20 -0
- data/lib/tiny_admin/plugins/active_record_repository.rb +83 -0
- data/lib/tiny_admin/plugins/base_repository.rb +15 -0
- data/lib/tiny_admin/plugins/no_auth.rb +25 -0
- data/lib/tiny_admin/plugins/simple_auth.rb +44 -0
- data/lib/tiny_admin/router.rb +125 -0
- data/lib/tiny_admin/settings.rb +83 -0
- data/lib/tiny_admin/utils.rb +40 -0
- data/lib/tiny_admin/version.rb +5 -0
- data/lib/tiny_admin/views/actions/index.rb +106 -0
- data/lib/tiny_admin/views/actions/show.rb +57 -0
- data/lib/tiny_admin/views/components/filters_form.rb +57 -0
- data/lib/tiny_admin/views/components/flash.rb +25 -0
- data/lib/tiny_admin/views/components/head.rb +31 -0
- data/lib/tiny_admin/views/components/navbar.rb +42 -0
- data/lib/tiny_admin/views/components/pagination.rb +33 -0
- data/lib/tiny_admin/views/default_layout.rb +101 -0
- data/lib/tiny_admin/views/pages/page_not_found.rb +21 -0
- data/lib/tiny_admin/views/pages/record_not_found.rb +21 -0
- data/lib/tiny_admin/views/pages/root.rb +17 -0
- data/lib/tiny_admin/views/pages/simple_auth_login.rb +32 -0
- data/lib/tiny_admin.rb +26 -0
- metadata +118 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinyAdmin
|
4
|
+
class Settings
|
5
|
+
include Singleton
|
6
|
+
include Utils
|
7
|
+
|
8
|
+
attr_accessor :authentication,
|
9
|
+
:components,
|
10
|
+
:extra_styles,
|
11
|
+
:navbar,
|
12
|
+
:page_not_found,
|
13
|
+
:record_not_found,
|
14
|
+
:repository,
|
15
|
+
:root,
|
16
|
+
:root_path,
|
17
|
+
:sections,
|
18
|
+
:scripts,
|
19
|
+
:style_links
|
20
|
+
|
21
|
+
attr_reader :pages, :resources
|
22
|
+
|
23
|
+
def load_settings
|
24
|
+
@authentication ||= {}
|
25
|
+
@authentication[:plugin] ||= Plugins::NoAuth
|
26
|
+
@authentication[:login] ||= Views::Pages::SimpleAuthLogin
|
27
|
+
@authentication[:plugin] = Object.const_get(authentication[:plugin]) if authentication[:plugin].is_a?(String)
|
28
|
+
|
29
|
+
@page_not_found ||= Views::Pages::PageNotFound
|
30
|
+
@page_not_found = Object.const_get(@page_not_found) if @page_not_found.is_a?(String)
|
31
|
+
@record_not_found ||= Views::Pages::RecordNotFound
|
32
|
+
@record_not_found = Object.const_get(@record_not_found) if @record_not_found.is_a?(String)
|
33
|
+
|
34
|
+
@pages ||= {}
|
35
|
+
@repository ||= Plugins::ActiveRecordRepository
|
36
|
+
@resources ||= {}
|
37
|
+
@root_path ||= '/admin'
|
38
|
+
@sections ||= []
|
39
|
+
|
40
|
+
@root ||= {}
|
41
|
+
@root[:title] ||= 'TinyAdmin'
|
42
|
+
@root[:path] ||= root_path
|
43
|
+
@root[:page] ||= Views::Pages::Root
|
44
|
+
|
45
|
+
if @authentication[:plugin] == Plugins::SimpleAuth
|
46
|
+
@authentication[:logout] ||= ['logout', "#{root_path}/auth/logout"]
|
47
|
+
end
|
48
|
+
|
49
|
+
@components ||= {}
|
50
|
+
@components[:flash] ||= Views::Components::Flash
|
51
|
+
@components[:head] ||= Views::Components::Head
|
52
|
+
@components[:navbar] ||= Views::Components::Navbar
|
53
|
+
@components[:pagination] ||= Views::Components::Pagination
|
54
|
+
|
55
|
+
@navbar = prepare_navbar(sections, root_path: root_path, logout: authentication[:logout])
|
56
|
+
end
|
57
|
+
|
58
|
+
def prepare_navbar(sections, root_path:, logout:)
|
59
|
+
items = sections.each_with_object({}) do |section, list|
|
60
|
+
slug = section[:slug]
|
61
|
+
case section[:type]&.to_sym
|
62
|
+
when :url
|
63
|
+
list[slug] = [section[:name], section[:url], section[:options]]
|
64
|
+
when :page
|
65
|
+
page = section[:page]
|
66
|
+
pages[slug] = page.is_a?(String) ? Object.const_get(page) : page
|
67
|
+
list[slug] = [section[:name], "#{root_path}/#{slug}"]
|
68
|
+
when :resource
|
69
|
+
repository = section[:repository] || settings.repository
|
70
|
+
resources[slug] = {
|
71
|
+
model: section[:model].is_a?(String) ? Object.const_get(section[:model]) : section[:model],
|
72
|
+
repository: repository.is_a?(String) ? Object.const_get(repository) : repository
|
73
|
+
}
|
74
|
+
resources[slug].merge! section.slice(:resource, :only, :index, :show, :collection_actions, :member_actions)
|
75
|
+
hidden = section[:options] && (section[:options].include?(:hidden) || section[:options].include?('hidden'))
|
76
|
+
list[slug] = [section[:name], "#{root_path}/#{slug}"] unless hidden
|
77
|
+
end
|
78
|
+
end
|
79
|
+
items['auth/logout'] = logout if logout
|
80
|
+
items
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinyAdmin
|
4
|
+
module Utils
|
5
|
+
def params_to_s(params)
|
6
|
+
list = params.each_with_object([]) do |(param, value), result|
|
7
|
+
if value.is_a?(Hash)
|
8
|
+
result.concat(value.map { |k, v| "#{param}[#{k}]=#{v}" })
|
9
|
+
else
|
10
|
+
result.push(["#{param}=#{value}"])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
list.join('&')
|
14
|
+
end
|
15
|
+
|
16
|
+
def prepare_page(page_class, title: nil, context: nil, query_string: '', options: [])
|
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
|
+
)
|
24
|
+
yield(page) if block_given?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def route_for(section, reference: nil, action: nil)
|
29
|
+
[settings.root_path, section, reference, action].compact.join("/")
|
30
|
+
end
|
31
|
+
|
32
|
+
def context
|
33
|
+
TinyAdmin::Context.instance
|
34
|
+
end
|
35
|
+
|
36
|
+
def settings
|
37
|
+
TinyAdmin::Settings.instance
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinyAdmin
|
4
|
+
module Views
|
5
|
+
module Actions
|
6
|
+
class Index < DefaultLayout
|
7
|
+
attr_reader :current_page, :fields, :pages, :prepare_record, :records
|
8
|
+
attr_accessor :actions, :filters
|
9
|
+
|
10
|
+
def setup_pagination(current_page:, pages:)
|
11
|
+
@current_page = current_page
|
12
|
+
@pages = pages
|
13
|
+
end
|
14
|
+
|
15
|
+
def setup_records(records:, fields:, prepare_record:)
|
16
|
+
@records = records
|
17
|
+
@fields = fields.index_by(&:name)
|
18
|
+
@prepare_record = prepare_record
|
19
|
+
end
|
20
|
+
|
21
|
+
def template
|
22
|
+
@filters ||= {}
|
23
|
+
|
24
|
+
super do
|
25
|
+
div(class: 'index') {
|
26
|
+
div(class: 'row') {
|
27
|
+
div(class: 'col-4') {
|
28
|
+
h1(class: 'title') { title }
|
29
|
+
}
|
30
|
+
div(class: 'col-8') {
|
31
|
+
ul(class: 'nav justify-content-end') {
|
32
|
+
(actions || []).each do |action|
|
33
|
+
li(class: 'nav-item') {
|
34
|
+
href = route_for(context.slug, action: action)
|
35
|
+
a(href: href, class: 'nav-link btn btn-outline-secondary') { action }
|
36
|
+
}
|
37
|
+
end
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
div(class: 'row') {
|
43
|
+
div_class = filters.any? ? 'col-9' : 'col-12'
|
44
|
+
div(class: div_class) {
|
45
|
+
table(class: 'table') {
|
46
|
+
table_header if fields.any?
|
47
|
+
|
48
|
+
table_body
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
if filters.any?
|
53
|
+
div(class: 'col-3') {
|
54
|
+
filters_form_attrs = { section_path: route_for(context.slug), filters: filters }
|
55
|
+
render TinyAdmin::Views::Components::FiltersForm.new(**filters_form_attrs)
|
56
|
+
}
|
57
|
+
end
|
58
|
+
}
|
59
|
+
|
60
|
+
if pages
|
61
|
+
render components[:pagination].new(current: current_page, pages: pages, query_string: query_string)
|
62
|
+
end
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def table_header
|
70
|
+
thead {
|
71
|
+
tr {
|
72
|
+
fields.each_value do |field|
|
73
|
+
td(class: "field-header-#{field.name} field-header-type-#{field.type}") { field.title }
|
74
|
+
end
|
75
|
+
td { whitespace }
|
76
|
+
}
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
def table_body
|
81
|
+
tbody {
|
82
|
+
records.each_with_index do |record, index|
|
83
|
+
tr(class: "row_#{index + 1}") {
|
84
|
+
attributes = prepare_record.call(record)
|
85
|
+
attributes.each do |key, value|
|
86
|
+
field = fields[key]
|
87
|
+
td(class: "field-value-#{field.name} field-value-type-#{field.type}") {
|
88
|
+
if field.options && field.options[:link_to]
|
89
|
+
reference = record.send(field.options[:field])
|
90
|
+
a(href: route_for(field.options[:link_to], reference: reference)) { value }
|
91
|
+
else
|
92
|
+
value
|
93
|
+
end
|
94
|
+
}
|
95
|
+
end
|
96
|
+
td(class: 'actions') {
|
97
|
+
a(href: route_for(context.slug, reference: record.id)) { 'show' }
|
98
|
+
}
|
99
|
+
}
|
100
|
+
end
|
101
|
+
}
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinyAdmin
|
4
|
+
module Views
|
5
|
+
module Actions
|
6
|
+
class Show < DefaultLayout
|
7
|
+
attr_reader :fields, :prepare_record, :record
|
8
|
+
attr_accessor :actions
|
9
|
+
|
10
|
+
def setup_record(record:, fields:, prepare_record:)
|
11
|
+
@record = record
|
12
|
+
@fields = fields
|
13
|
+
@prepare_record = prepare_record
|
14
|
+
end
|
15
|
+
|
16
|
+
def template
|
17
|
+
super do
|
18
|
+
div(class: 'show') {
|
19
|
+
div(class: 'row') {
|
20
|
+
div(class: 'col-4') {
|
21
|
+
h1(class: 'title') { title }
|
22
|
+
}
|
23
|
+
div(class: 'col-8') {
|
24
|
+
ul(class: 'nav justify-content-end') {
|
25
|
+
(actions || []).each do |action|
|
26
|
+
li(class: 'nav-item') {
|
27
|
+
href = route_for(context.slug, reference: context.reference, action: action)
|
28
|
+
a(href: href, class: 'nav-link btn btn-outline-secondary') { action }
|
29
|
+
}
|
30
|
+
end
|
31
|
+
}
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
prepare_record.call(record).each_with_index do |(_key, value), index|
|
36
|
+
field = fields[index]
|
37
|
+
div(class: "field-#{field.name} row lh-lg") {
|
38
|
+
if field
|
39
|
+
div(class: 'field-header col-2') { field.title }
|
40
|
+
end
|
41
|
+
div(class: 'field-value col-10') {
|
42
|
+
if field.options && field.options[:link_to]
|
43
|
+
reference = record.send(field.options[:field])
|
44
|
+
a(href: route_for(field.options[:link_to], reference: reference)) { value }
|
45
|
+
else
|
46
|
+
value
|
47
|
+
end
|
48
|
+
}
|
49
|
+
}
|
50
|
+
end
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinyAdmin
|
4
|
+
module Views
|
5
|
+
module Components
|
6
|
+
class FiltersForm < Phlex::HTML
|
7
|
+
attr_reader :filters, :section_path
|
8
|
+
|
9
|
+
def initialize(section_path:, filters:)
|
10
|
+
@section_path = section_path
|
11
|
+
@filters = filters
|
12
|
+
end
|
13
|
+
|
14
|
+
def template
|
15
|
+
form(class: 'form_filters', method: 'get') {
|
16
|
+
filters.each do |field, filter|
|
17
|
+
name = field.name
|
18
|
+
filter_data = filter[:filter]
|
19
|
+
div(class: 'mb-3') {
|
20
|
+
label(for: "filter-#{name}", class: 'form-label') { field.title }
|
21
|
+
case filter_data[:type]&.to_sym || field.type
|
22
|
+
when :boolean
|
23
|
+
select(class: 'form-select', id: "filter-#{name}", name: "q[#{name}]") {
|
24
|
+
option(value: '') { '-' }
|
25
|
+
option(value: '0', selected: filter[:value] == '0') { 'false' }
|
26
|
+
option(value: '1', selected: filter[:value] == '1') { 'true' }
|
27
|
+
}
|
28
|
+
when :date
|
29
|
+
input(type: 'date', class: 'form-control', id: "filter-#{name}", name: "q[#{name}]", value: filter[:value])
|
30
|
+
when :datetime
|
31
|
+
input(type: 'datetime-local', class: 'form-control', id: "filter-#{name}", name: "q[#{name}]", value: filter[:value])
|
32
|
+
when :integer
|
33
|
+
input(type: 'number', class: 'form-control', id: "filter-#{name}", name: "q[#{name}]", value: filter[:value])
|
34
|
+
when :select
|
35
|
+
select(class: 'form-select', id: "filter-#{name}", name: "q[#{name}]") {
|
36
|
+
option(value: '') { '-' }
|
37
|
+
filter_data[:values].each do |value|
|
38
|
+
option(selected: filter[:value] == value) { value }
|
39
|
+
end
|
40
|
+
}
|
41
|
+
else
|
42
|
+
input(type: 'text', class: 'form-control', id: "filter-#{name}", name: "q[#{name}]", value: filter[:value])
|
43
|
+
end
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
div(class: 'mt-3') {
|
48
|
+
a(href: section_path, class: 'button_clear btn btn-secondary text-white') { 'clear' }
|
49
|
+
whitespace
|
50
|
+
button(type: 'submit', class: 'button_filter btn btn-secondary') { 'filter' }
|
51
|
+
}
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinyAdmin
|
4
|
+
module Views
|
5
|
+
module Components
|
6
|
+
class Flash < Phlex::HTML
|
7
|
+
attr_reader :errors, :notices, :warnings
|
8
|
+
|
9
|
+
def initialize(notices: [], warnings: [], errors: [])
|
10
|
+
@notices = notices
|
11
|
+
@warnings = warnings
|
12
|
+
@errors = errors
|
13
|
+
end
|
14
|
+
|
15
|
+
def template
|
16
|
+
div(class: 'flash') {
|
17
|
+
div(class: 'notices alert alert-success', role: 'alert') { notices.join(', ') } if notices&.any?
|
18
|
+
div(class: 'notices alert alert-warning', role: 'alert') { warnings.join(', ') } if warnings&.any?
|
19
|
+
div(class: 'notices alert alert-danger', role: 'alert') { errors.join(', ') } if errors&.any?
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinyAdmin
|
4
|
+
module Views
|
5
|
+
module Components
|
6
|
+
class Head < Phlex::HTML
|
7
|
+
attr_reader :extra_styles, :page_title, :style_links
|
8
|
+
|
9
|
+
def initialize(page_title, style_links: [], extra_styles: nil)
|
10
|
+
@page_title = page_title
|
11
|
+
@style_links = style_links
|
12
|
+
@extra_styles = extra_styles
|
13
|
+
end
|
14
|
+
|
15
|
+
def template
|
16
|
+
head {
|
17
|
+
meta charset: 'utf-8'
|
18
|
+
meta name: 'viewport', content: 'width=device-width, initial-scale=1'
|
19
|
+
title {
|
20
|
+
page_title
|
21
|
+
}
|
22
|
+
style_links.each do |style_link|
|
23
|
+
link(**style_link)
|
24
|
+
end
|
25
|
+
style { extra_styles } if extra_styles
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinyAdmin
|
4
|
+
module Views
|
5
|
+
module Components
|
6
|
+
class Navbar < Phlex::HTML
|
7
|
+
attr_reader :current_slug, :items, :root
|
8
|
+
|
9
|
+
def initialize(root:, items: [], current_slug: nil)
|
10
|
+
@root = root
|
11
|
+
@items = items || []
|
12
|
+
@current_slug = current_slug
|
13
|
+
end
|
14
|
+
|
15
|
+
def template
|
16
|
+
nav(class: 'navbar navbar-expand-lg') {
|
17
|
+
div(class: 'container') {
|
18
|
+
a(class: 'navbar-brand', href: root[:path]) { root[:title] }
|
19
|
+
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
|
+
span(class: 'navbar-toggler-icon')
|
21
|
+
}
|
22
|
+
div(class: 'collapse navbar-collapse', id: 'navbarNav') {
|
23
|
+
ul(class: 'navbar-nav') {
|
24
|
+
items.each do |slug, (name, path, options)|
|
25
|
+
classes = %w[nav-link]
|
26
|
+
classes << 'active' if slug == current_slug
|
27
|
+
link_attributes = { class: classes.join(' '), href: path, 'aria-current' => 'page' }
|
28
|
+
link_attributes.merge!(options) if options
|
29
|
+
|
30
|
+
li(class: 'nav-item') {
|
31
|
+
a(**link_attributes) { name }
|
32
|
+
}
|
33
|
+
end
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinyAdmin
|
4
|
+
module Views
|
5
|
+
module Components
|
6
|
+
class Pagination < Phlex::HTML
|
7
|
+
attr_reader :current, :pages, :query_string
|
8
|
+
|
9
|
+
def initialize(current:, pages:, query_string:)
|
10
|
+
@current = current
|
11
|
+
@pages = pages
|
12
|
+
@query_string = query_string
|
13
|
+
end
|
14
|
+
|
15
|
+
def template
|
16
|
+
div(class: 'pagination-div') {
|
17
|
+
nav('aria-label' => 'Pagination') {
|
18
|
+
ul(class: 'pagination justify-content-center') {
|
19
|
+
1.upto(pages) do |i|
|
20
|
+
li_class = (i == current ? 'page-item active' : 'page-item')
|
21
|
+
li(class: li_class) {
|
22
|
+
href = query_string.empty? ? "?p=#{i}" : "?#{query_string}&p=#{i}"
|
23
|
+
a(class: 'page-link', href: href) { i }
|
24
|
+
}
|
25
|
+
end
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinyAdmin
|
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
|
35
|
+
|
36
|
+
def template(&block)
|
37
|
+
items = no_menu ? [] : settings.navbar
|
38
|
+
|
39
|
+
doctype
|
40
|
+
html {
|
41
|
+
render components[:head].new(title, style_links: style_links, extra_styles: settings.extra_styles)
|
42
|
+
|
43
|
+
body(class: body_class) {
|
44
|
+
render components[:navbar].new(current_slug: context&.slug, root: settings.root, items: items)
|
45
|
+
|
46
|
+
main_content {
|
47
|
+
render components[:flash].new(notices: notices, warnings: warnings, errors: errors)
|
48
|
+
yield_content(&block)
|
49
|
+
}
|
50
|
+
|
51
|
+
render_scripts
|
52
|
+
}
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
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
|
+
def body_class
|
75
|
+
"module-#{self.class.to_s.split('::').last.downcase}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def main_content
|
79
|
+
div(class: 'container main-content py-4') do
|
80
|
+
if compact_layout
|
81
|
+
div(class: 'row justify-content-center') {
|
82
|
+
div(class: 'col-6') {
|
83
|
+
yield
|
84
|
+
}
|
85
|
+
}
|
86
|
+
else
|
87
|
+
yield
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def render_scripts
|
93
|
+
return unless settings.scripts
|
94
|
+
|
95
|
+
settings.scripts.each do |script_attrs|
|
96
|
+
script(**script_attrs)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinyAdmin
|
4
|
+
module Views
|
5
|
+
module Pages
|
6
|
+
class PageNotFound < DefaultLayout
|
7
|
+
def template
|
8
|
+
super do
|
9
|
+
div(class: 'page_not_found') {
|
10
|
+
h1(class: 'title') { title }
|
11
|
+
}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def title
|
16
|
+
'Page not found'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinyAdmin
|
4
|
+
module Views
|
5
|
+
module Pages
|
6
|
+
class RecordNotFound < DefaultLayout
|
7
|
+
def template
|
8
|
+
super do
|
9
|
+
div(class: 'record_not_found') {
|
10
|
+
h1(class: 'title') { title }
|
11
|
+
}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def title
|
16
|
+
'Record not found'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinyAdmin
|
4
|
+
module Views
|
5
|
+
module Pages
|
6
|
+
class SimpleAuthLogin < DefaultLayout
|
7
|
+
def template
|
8
|
+
super do
|
9
|
+
div(class: 'simple_auth_login') {
|
10
|
+
h1(class: 'title') { title }
|
11
|
+
|
12
|
+
form(class: 'form_login', method: 'post') {
|
13
|
+
div(class: 'mt-3') {
|
14
|
+
label(for: 'secret', class: 'form-label') { 'Password' }
|
15
|
+
input(type: 'password', name: 'secret', class: 'form-control', id: 'secret')
|
16
|
+
}
|
17
|
+
|
18
|
+
div(class: 'mt-3') {
|
19
|
+
button(type: 'submit', class: 'button_login btn btn-primary') { 'login' }
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def title
|
27
|
+
'Login'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|