$ rails new . -n ActiveAdminDemo -c tailwind -a propshaft --skip-test --skip-system-test
$ rails g active_admin:install --skip-users
$ rails tailwindcss:install
$ rails generate active_admin:assets
$ cat tailwind-active_admin.config.js | sed 's/require(`@activeadmin\/activeadmin\/plugin`)/require(`${activeAdminPath}\/plugin.js`)/g' > config/tailwind-active_admin.config.js
$ rm tailwind-active_admin.config.js
$ bundle binstub tailwindcss-rails
$ rails generate active_admin:views
$ echo "active_admin: bin/rails active_admin:watch" >> Procfile.dev
$ vim lib/tasks/active_admin.rake
$ vim config/initializers/active_admin.rb
$ rails g model button clicked:boolean clicked_at:timestamp
$ rails generate active_admin:resource Button
$ rails db:create db:migrate db:seed
$ bin/dev
-
-
Save amkisko/c704c1a6462d573dfa4820ae07d807a6 to your computer and use it in GitHub Desktop.
module ActiveAdmin | |
class ActionPolicyAdapter < AuthorizationAdapter | |
def authorized?(action, subject = nil) | |
target = policy_target(subject) | |
policy = ActionPolicy.lookup(target) | |
action = format_action(action, subject) | |
policy.new(target, user: user).apply(action) | |
end | |
def scope_collection(collection, _action = Auth::READ) | |
target = policy_target(collection) | |
policy = ActionPolicy.lookup(target) | |
policy.new(user: user).apply_scope(collection, type: :active_admin) | |
end | |
def format_action(action, subject) | |
case action | |
when Auth::CREATE | |
:create? | |
when Auth::UPDATE | |
:update? | |
when Auth::READ | |
subject.is_a?(Class) ? :index? : :show? | |
when Auth::DESTROY | |
subject.is_a?(Class) ? :destroy_all? : :destroy? | |
else | |
"#{action}?" | |
end | |
end | |
private | |
def policy_target(subject) | |
case subject | |
when nil | |
resource.resource_class | |
when Class | |
subject.new | |
else | |
subject | |
end | |
end | |
end | |
end |
namespace :active_admin do | |
desc "Build Active Admin Tailwind stylesheets" | |
task build: :environment do | |
command = [ | |
Rails.root.join("bin/tailwindcss").to_s, | |
"-i", Rails.root.join("app/assets/stylesheets/active_admin.css").to_s, | |
"-o", Rails.root.join("app/assets/builds/active_admin.css").to_s, | |
"-c", Rails.root.join("config/tailwind-active_admin.config.js").to_s, | |
"-m" | |
] | |
system(*command, exception: true) | |
end | |
desc "Watch Active Admin Tailwind stylesheets" | |
task watch: :environment do | |
command = [ | |
Rails.root.join("bin/tailwindcss").to_s, | |
"--watch", | |
"-i", Rails.root.join("app/assets/stylesheets/active_admin.css").to_s, | |
"-o", Rails.root.join("app/assets/builds/active_admin.css").to_s, | |
"-c", Rails.root.join("config/tailwind-active_admin.config.js").to_s, | |
"-m" | |
] | |
system(*command) | |
end | |
end | |
Rake::Task["assets:precompile"].enhance(["active_admin:build"]) | |
Rake::Task["test:prepare"].enhance(["active_admin:build"]) if Rake::Task.task_defined?("test:prepare") | |
Rake::Task["spec:prepare"].enhance(["tailwindcss:build"]) if Rake::Task.task_defined?("spec:prepare") | |
Rake::Task["db:test:prepare"].enhance(["tailwindcss:build"]) if Rake::Task.task_defined?("db:test:prepare") |
class Current < ActiveSupport::CurrentAttributes | |
attribute :user | |
attribute :request_id, :user_agent, :remote_addr | |
attribute :locale, :time_zone | |
resets do | |
I18n.locale = I18n.default_locale | |
Time.zone = Time.zone_default | |
ActionReporter.reset_context | |
end | |
def user_agent=(user_agent) | |
super | |
ActionReporter.context(user_agent: user_agent) | |
end | |
def remote_addr=(remote_addr) | |
super | |
ActionReporter.context(remote_addr: remote_addr) | |
end | |
def locale=(locale) | |
super | |
I18n.locale = locale | |
ActionReporter.context(locale: locale) | |
end | |
def time_zone=(time_zone) | |
super | |
Time.zone = time_zone | |
ActionReporter.context(time_zone: time_zone) | |
end | |
def user=(user) | |
super | |
ActionReporter.audited_user = user | |
end | |
end |
require_relative "../../app/lib/active_admin/action_policy_adapter" | |
ActiveAdmin.importmap.draw do | |
pin "@rails/actioncable", to: "actioncable.esm.js", preload: true | |
pin "@rails/activestorage", to: "activestorage.esm.js", preload: true | |
pin "@hotwired/turbo-rails", to: "turbo.js", preload: true | |
pin "@hotwired/stimulus", to: "stimulus.js", preload: true | |
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true | |
pin "application", preload: true | |
pin_all_from "app/assets/javascripts/controllers", under: "controllers" | |
end | |
ActiveAdmin.setup do |config| | |
# ... | |
config.authorization_adapter = ActiveAdmin::ActionPolicyAdapter | |
# ... | |
end |
# https://guides.rubyonrails.org/security.html | |
# Define an application-wide content security policy | |
# For further information see the following documentation | |
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy | |
Rails.application.config.content_security_policy do |policy| | |
policy.default_src :self, :https | |
policy.img_src :self, :https, :data | |
policy.script_src :self, :https | |
policy.style_src :self, :https, :unsafe_inline | |
# if Rails.env.production? | |
# policy.report_uri -> { "https://api.honeybadger.io/v1/browser/csp?api_key=#{HoneybadgerConfig.new.api_key}&report_only=true&env=#{EnvConfig.new.environment}&context[user_id]=#{respond_to?(:current_user) ? current_user&.id : nil}" } | |
# end | |
end | |
# If you are using UJS then enable automatic nonce generation | |
Rails.application.config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } | |
# Set the nonce only to specific directives | |
Rails.application.config.content_security_policy_nonce_directives = %w[script-src] | |
# Report CSP violations to a specified URI | |
# For further information see the following documentation: | |
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only | |
# Rails.application.config.content_security_policy_report_only = true |
const execSync = require('child_process').execSync; | |
const activeAdminPath = execSync('bundle show activeadmin', { encoding: 'utf-8' }).trim(); | |
module.exports = { | |
content: [ | |
`${activeAdminPath}/vendor/javascript/flowbite.js`, | |
`${activeAdminPath}/plugin.js`, | |
`${activeAdminPath}/app/views/**/*.{arb,erb,html,rb}`, | |
'./app/admin/**/*.{arb,erb,html,rb}', | |
'./app/views/active_admin/**/*.{arb,erb,html,rb}', | |
'./app/views/admin/**/*.{arb,erb,html,rb}', | |
'./app/javascript/**/*.js' | |
], | |
darkMode: "class", | |
plugins: [ | |
require(`${activeAdminPath}\/plugin.js`) | |
] | |
} |
# NOTE: partial content required for Gemfile | |
gem "rails" | |
gem "propshaft" | |
gem "importmap-rails" | |
gem "stimulus-rails" | |
gem "tailwindcss-rails" | |
gem "action_policy" | |
gem "activeadmin", "4.0.0.beta6" |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width,initial-scale=1"> | |
<%= csrf_meta_tags %> | |
<%= csp_meta_tag %> | |
<%= stylesheet_link_tag "active_admin" %> | |
<% # On page load or when changing themes, best to add inline in `head` to avoid FOUC %> | |
<%= javascript_tag nonce: true do %> | |
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) { | |
document.documentElement.classList.add('dark') | |
} else { | |
document.documentElement.classList.remove('dark') | |
} | |
<% end %> | |
<%= javascript_importmap_tags "active_admin", importmap: ActiveAdmin.importmap %> | |
<%= javascript_import_module_tag "application" %> | |
<%= render partial: "honeybadger", locals: { layout: :active_admin } %> | |
<%= render partial: "favicon" %> | |
<%= render partial: "fonts" %> | |
<%= render partial: "scripts" %> | |
<% if content_for?(:head) %> | |
<%= yield(:head) %> | |
<% end %> |
<%= javascript_include_tag "https://js.honeybadger.io/v6.8/honeybadger.min.js" nonce: true %> | |
<% if ENV["HONEYBADGER_API_KEY"].present? %> | |
<%= javascript_tag nonce: true do %> | |
if (typeof Honeybadger !== "undefined") { | |
Honeybadger.configure({ | |
apiKey: "<%= ENV.fetch("HONEYBADGER_API_KEY") %>", | |
environment: "<%= ENV.fetch("HONEYBADGER_ENV") %>", | |
revision: "<%= ENV.fetch("HONEYBADGER_REVISION", "unknown") %>", | |
}); | |
Honeybadger.setContext({ | |
layout: "<%= layout %>", | |
user_id: "<%= current_user&.id %>" | |
}); | |
} | |
<% end %> | |
<% end %> |
<%= javascript_include_tag "https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js", nonce: true, defer: true %> |
@hannesfostie Hey! I think it is related to extensibility of ActiveAdmin, you can generate view templates and modify default behavior to anything you would like to achieve. Think of ActiveAdmin as an extension or set of ready-made models-views-controllers that you can modify any time. E.g. here: https://github.com/activeadmin/activeadmin/blob/master/app/views/active_admin/shared/_resource_comments.html.erb#L5 -- I just checked one of production projects and we have authorization implemented there.
@amkisko maybe I phrased my question poorly - what I meant was I couldn't find context around what this gist is. Based on your reply it sounds like it's not necessarily going to be a part of ActiveAdmin, and that this is rather a custom adapter/implementation you created?
FWIW: there could be value in submitting a PR for this adapter, for official support! I have a hunch ActionPolicy will gain ground over CanCan and Pundit
@amkisko I came across this while looking to see how I could restrict access to AA comments for particular roles. We use ActionPolicy in our application, so naturally this is interesting... I couldn't find much context though.
Is this going to end up in AA at some point?