Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions lib/tiny_admin/plugins/simple_auth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ module Plugins
module SimpleAuth
class << self
def configure(app, opts = {})
@@opts = opts || {} # rubocop:disable Style/ClassVars
@@opts[:password] ||= ENV.fetch("ADMIN_PASSWORD_HASH", nil) # NOTE: fallback value
opts ||= {}
password_hash = opts[:password] || ENV.fetch("ADMIN_PASSWORD_HASH", nil)

Warden::Strategies.add(:secret) do
def authenticate!
define_method(:authenticate!) do
secret = params["secret"] || ""
return fail(:invalid_credentials) if Digest::SHA512.hexdigest(secret) != @@opts[:password]
return fail(:invalid_credentials) if Digest::SHA512.hexdigest(secret) != password_hash

success!(app: "TinyAdmin")
end
Expand Down
27 changes: 18 additions & 9 deletions lib/tiny_admin/router.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ def setup_page_route(req, slug, page_data)

def setup_resource_routes(req, slug, options:)
req.on slug do
setup_collection_routes(req, slug, options: options)
setup_member_routes(req, slug, options: options)
repository = options[:repository].new(options[:model])
setup_collection_routes(req, slug, options: options, repository: repository)
setup_member_routes(req, slug, options: options, repository: repository)
end
end

def setup_collection_routes(req, slug, options:)
repository = options[:repository].new(options[:model])
def setup_collection_routes(req, slug, options:, repository:)
action_options = options[:index] || {}

# Custom actions
Expand Down Expand Up @@ -112,8 +112,7 @@ def setup_collection_routes(req, slug, options:)
end
end

def setup_member_routes(req, slug, options:)
repository = options[:repository].new(options[:model])
def setup_member_routes(req, slug, options:, repository:)
action_options = (options[:show] || {}).merge(record_not_found_page: TinyAdmin.settings.record_not_found)

req.on String do |reference|
Expand Down Expand Up @@ -149,10 +148,10 @@ def setup_member_routes(req, slug, options:)

def setup_custom_actions(req, custom_actions = nil, options:, repository:, slug:, reference: nil)
(custom_actions || []).each_with_object({}) do |custom_action, result|
action_slug, action = custom_action.first
action_class = to_class(action)
action_slug, action_config = custom_action.first
action_class, http_method = parse_action_config(action_config)

req.get action_slug.to_s do
req.public_send(http_method, action_slug.to_s) do
authorize!(:custom_action, action_slug.to_s) do
context = Context.new(
actions: {},
Expand All @@ -171,6 +170,16 @@ def setup_custom_actions(req, custom_actions = nil, options:, repository:, slug:
end
end

def parse_action_config(config)
if config.is_a?(Hash)
action_class = to_class(config[:action] || config["action"])
http_method = (config[:method] || config["method"] || "get").to_sym
[action_class, http_method]
else
[to_class(config), :get]
end
end

def authorization
TinyAdmin.settings.authorization_class
end
Expand Down
10 changes: 8 additions & 2 deletions lib/tiny_admin/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,18 @@ def convert_value(key, value)
value.each_key do |key2|
path = [key, key2]
if (DEFAULTS[path].is_a?(Class) || DEFAULTS[path].is_a?(Module)) && self[key][key2].is_a?(String)
self[key][key2] = Object.const_get(self[key][key2])
self[key][key2] = resolve_class(self[key][key2], setting: "#{key}.#{key2}")
end
end
elsif value.is_a?(String) && (DEFAULTS[[key]].is_a?(Class) || DEFAULTS[[key]].is_a?(Module))
self[key] = Object.const_get(self[key])
self[key] = resolve_class(self[key], setting: key.to_s)
end
end

def resolve_class(class_name, setting:)
Object.const_get(class_name)
rescue NameError => e
raise NameError, "TinyAdmin: invalid class '#{class_name}' for setting '#{setting}' - #{e.message}"
end
end
end
2 changes: 1 addition & 1 deletion lib/tiny_admin/store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def prepare_sections(sections, logout:)
end
list << item if item
end
navbar << logout if logout
@navbar << logout if logout
end

private
Expand Down
3 changes: 2 additions & 1 deletion lib/tiny_admin/views/actions/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ def view_template
end
}

render TinyAdmin::Views::Components::Widgets.new(widgets)
context = { slug: slug, records: records, params: params }
render TinyAdmin::Views::Components::Widgets.new(widgets, context: context)
}
end
end
Expand Down
3 changes: 2 additions & 1 deletion lib/tiny_admin/views/actions/show.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ def view_template
}
end

render TinyAdmin::Views::Components::Widgets.new(widgets)
context = { slug: slug, record: record, reference: reference, params: params }
render TinyAdmin::Views::Components::Widgets.new(widgets, context: context)
}
end
end
Expand Down
7 changes: 6 additions & 1 deletion lib/tiny_admin/views/attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ module Views
module Attributes
def update_attributes(attributes)
attributes.each do |key, value|
send("#{key}=", value)
setter = "#{key}="
unless respond_to?(setter)
raise ArgumentError, "#{self.class.name} does not support attribute '#{key}'"
end

send(setter, value)
end
end
end
Expand Down
5 changes: 3 additions & 2 deletions lib/tiny_admin/views/components/field_value.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@ def initialize(field, value, record:)

def view_template
translated_value = field.translate_value(value)
display_value = field.apply_call_option(record) || translated_value
value_class = field.options[:options]&.include?("value_class") ? "value-#{value}" : nil
if field.options[:link_to]
a(href: TinyAdmin.route_for(field.options[:link_to], reference: translated_value)) {
span(class: value_class) {
render_value(field.apply_call_option(record) || translated_value)
render_value(display_value)
}
}
else
span(class: value_class) {
render_value(translated_value)
render_value(display_value)
}
end
end
Expand Down
6 changes: 3 additions & 3 deletions lib/tiny_admin/views/components/pagination.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ def view_template
if pages <= 10
pages_range(1..pages)
elsif current <= 4 || current >= pages - 3
pages_range(1..(current <= 4 ? current + 2 : 4), with_dots: true)
pages_range((current > pages - 4 ? current - 2 : pages - 2)..pages)
pages_range(1..(current <= 4 ? (current + 2) : 4), with_dots: true)
pages_range((current > pages - 4 ? (current - 2) : (pages - 2))..pages)
else
pages_range(1..1, with_dots: true)
pages_range((current - 2)..(current + 2), with_dots: true)
Expand All @@ -39,7 +39,7 @@ def pages_range(range, with_dots: false)
range.each do |page|
li(class: page == current ? "page-item active" : "page-item") {
href = query_string.empty? ? "?p=#{page}" : "?#{query_string}&p=#{page}"
a(class: "page-link", href: href) { page }
a(class: "page-link", href: href) { page.to_s }
}
end
dots if with_dots
Expand Down
19 changes: 17 additions & 2 deletions lib/tiny_admin/views/components/widgets.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ module TinyAdmin
module Views
module Components
class Widgets < BasicComponent
def initialize(widgets)
def initialize(widgets, context: {})
@widgets = widgets
@context = context
end

def view_template
Expand All @@ -20,7 +21,7 @@ def view_template
div(class: "col") {
div(class: "card") {
div(class: "card-body") {
render widget.new
render build_widget(widget)
}
}
}
Expand All @@ -29,6 +30,20 @@ def view_template
end
}
end

private

def build_widget(widget)
key_params = [:key, :keyreq]
if widget.instance_method(:initialize).arity != 0 ||
widget.instance_method(:initialize).parameters.any? { |type, _| key_params.include?(type) }
widget.new(context: @context)
else
widget.new
end
rescue ArgumentError
widget.new
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/tiny_admin/views/pages/content.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def view_template
unsafe_raw(content)
}

render TinyAdmin::Views::Components::Widgets.new(widgets)
render TinyAdmin::Views::Components::Widgets.new(widgets, context: { params: params })
}
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/tiny_admin/views/pages/error_page.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ def view_template

def css_class
self.class.name.split("::").last
.gsub(/([A-Z])/, '_\1')
.sub(/^_/, "")
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
.downcase
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/tiny_admin/views/pages/root.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class Root < DefaultLayout
def view_template
super do
div(class: "root") {
render TinyAdmin::Views::Components::Widgets.new(widgets)
render TinyAdmin::Views::Components::Widgets.new(widgets, context: { params: params })
}
end
end
Expand Down
6 changes: 4 additions & 2 deletions sig/tiny_admin/router.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ module TinyAdmin

def root_route: (untyped) -> void

def setup_collection_routes: (untyped, String, options: Hash[Symbol, untyped]) -> void
def setup_collection_routes: (untyped, String, options: Hash[Symbol, untyped], repository: untyped) -> void

def setup_custom_actions: (untyped, Array[Hash[Symbol, untyped]]?, options: Hash[Symbol, untyped], repository: untyped, slug: String, ?reference: untyped?) -> Hash[String, untyped]

def setup_member_routes: (untyped, String, Hash[Symbol, untyped]) -> void
def parse_action_config: (untyped) -> [Class, Symbol]

def setup_member_routes: (untyped, String, options: Hash[Symbol, untyped], repository: untyped) -> void

def setup_page_route: (untyped, String, Hash[Symbol, untyped]) -> void

Expand Down
7 changes: 6 additions & 1 deletion sig/tiny_admin/views/components/widgets.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ module TinyAdmin
module Components
class Widgets
@widgets: Array[untyped]?
@context: Hash[Symbol, untyped]

def initialize: (Array[untyped]?) -> void
def initialize: (Array[untyped]?, ?context: Hash[Symbol, untyped]) -> void

def view_template: () ?{ (untyped) -> void } -> void

private

def build_widget: (Class) -> untyped
end
end
end
Expand Down
10 changes: 10 additions & 0 deletions spec/lib/tiny_admin/settings_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@
settings[:root_path] = "/admin"
expect(settings[:root_path]).to eq("/admin")
end

it "raises a descriptive error for invalid class names" do
expect { settings[:helper_class] = "NonExistent::Klass" }
.to raise_error(NameError, /TinyAdmin: invalid class 'NonExistent::Klass' for setting 'helper_class'/)
end

it "raises a descriptive error for invalid nested class names" do
expect { settings[:authentication] = { plugin: "NonExistent::Auth" } }
.to raise_error(NameError, /TinyAdmin: invalid class 'NonExistent::Auth' for setting 'authentication.plugin'/)
end
end

describe "dynamic option methods" do
Expand Down
12 changes: 12 additions & 0 deletions spec/lib/tiny_admin/views/components/field_value_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@
end
end

describe "with call option (no link_to)" do
let(:field) { TinyAdmin::Field.new(name: "author_id", type: :integer, title: "Author", options: { call: "author, name" }) }
let(:author) { double("author", name: "John") } # rubocop:disable RSpec/VerifiedDoubles
let(:record) { double("record", id: 1, author: author) } # rubocop:disable RSpec/VerifiedDoubles

it "renders the call result instead of the raw value", :aggregate_failures do
html = described_class.new(field, 42, record: record).call
expect(html).to include("John")
expect(html).not_to include("42")
end
end

describe "with value_class option" do
let(:field) { TinyAdmin::Field.new(name: "status", type: :string, title: "Status", options: { options: ["value_class"] }) }
let(:record) { double("record", id: 1) } # rubocop:disable RSpec/VerifiedDoubles
Expand Down
Loading