0.01
A long-lived project that still receives updates
Command Deck is a Rails engine that provides a floating panel UI for running custom admin actions and tasks without opening the Rails console. Define panels/tabs/actions with a class-based DSL.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Runtime

>= 7.0, < 9.0
>= 7.0, < 9.0
 Project Readme

Command Deck

Convenient, unobtrusive, dev-oriented Rails engine that allows you to run custom actions and quick admin tasks through a floating panel without opening the Rails console.

Define panels/tabs/actions with a minimal class-based DSL. Each action can declare parameters (text, boolean, number, selector), run Ruby code, and return a JSON result shown in the UI.

Demo

Installation

Add the gem to your application:

# Gemfile (development and test recommended for Sorbet/Tapioca)
group :development, :test do
  gem 'command_deck'
end

Mount the engine:

# config/routes.rb
mount CommandDeck::Engine => '/command_deck' if defined?(CommandDeck::Engine)

Enable it in your development environment:

# .env.development.local (or your environment config)
COMMAND_DECK_ENABLED=true

That's it! The gem automatically:

  • Discovers panel files in app/command_deck/**/*.rb and packs/**/command_deck/**/*.rb
  • Adds them to autoload paths
  • Inserts middleware when COMMAND_DECK_ENABLED=true in development
  • Stays completely inactive in production

Define actions

Create panel classes anywhere under a command_deck directory. Use descriptive namespaces:

# app/command_deck/panels/utilities.rb
module CommandDeck::Panels
  class Utilities < CommandDeck::BasePanel
    panel 'Utilities' do
      tab 'Demo' do
        action 'Greet', key: 'utils.greet' do
          param :name, :string, label: 'Your name', required: true

          perform do |p, _ctx|
            { message: "Hello, #{p[:name]}!" }
          end
        end

        action 'Pick a Color', key: 'utils.color' do
          # You can pass simple arrays, pairs, or hashes as choices
          param :color, :selector,
            options: [
              ['Red',   'red'],
              ['Green', 'green'],
              ['Blue',  'blue']
            ],
            include_blank: true

          perform do |p, _ctx|
            { chosen: p[:color] }
          end
        end

        action 'Update Setting', key: 'utils.update_setting' do
          # Selector with current values in meta
          param :setting_name, :selector,
            collection: -> {
              # Example: fetch settings with current values
              [
                { label: 'Max Users (100)', value: 'max_users', meta: { value: 100 } },
                { label: 'Timeout (30)', value: 'timeout', meta: { value: 30 } }
              ]
            }

          # Auto-populate this input with the value from meta
          param :new_value, :string, required: true, auto_populate: true

          perform do |p, _ctx|
            # Update the setting with new value
            { ok: true, setting: p[:setting_name], new_value: p[:new_value] }
          end
        end
      end
    end
  end
end

For modularized monoliths (packwerk/pack-rails)

You can organize panels within your packs:

# packs/dev_tools/command_deck/panels/database.rb
module DevTools::Panels
  class Database < CommandDeck::BasePanel
    panel 'Database Tools' do
      tab 'Maintenance' do
        action 'Rebuild Cache', key: 'db.rebuild_cache' do
          perform do |_p, _ctx|
            Rails.cache.clear
            { ok: true, message: 'Cache cleared' }
          end
        end
      end
    end
  end
end

The gem auto-discovers panels in any command_deck directory under your app or packs. Use namespaces that match your project structure.

Demo

DSL API

Panels inherit from CommandDeck::BasePanel. Define tabs within a panel:

tab(title) { ... }

Define actions within a tab:

action(title, key:) { ... }

Declare params and execution logic within an action:

param(name, type, **opts)
perform { |params, ctx| ... }

Supported param types:

  • :string – free text input.
  • :boolean – checkbox; coerces to true/false.
  • :integer – number input; coerces to Integer or nil when blank.
  • :selector – dropdown. See options below.

Common param options:

  • label: String – UI label. Defaults to a humanized name.
  • required: true/false – disables Run button until filled. Default: false.
  • auto_populate: true/false – for text/number inputs, automatically populate with value from previous selector's meta: { value: ... }. Default: false.

Selector-specific options:

  • options: Enumerable – static choices.
  • collection: -> Enumerable – dynamic choices (block is called each render).
  • include_blank: true/false – prepend an empty choice. Default: false.
  • return: :value (default), :label, or :both ({ label:, value: }).
  • Choice shapes accepted:
    • Values: %w[a b c]
    • Pairs: [['Label A', 'a'], ['Label B', 'b']]
    • Objects: { label:, value:, meta?: { ... } }

The perform block receives coerced params and a context hash. Return any JSON-serializable object (Hash recommended).

Context Provider

The ctx parameter in perform blocks is populated via a configurable context provider. This allows you to inject application-specific context like the current user:

# config/initializers/command_deck.rb
CommandDeck.configure do |config|
  config.context_provider = ->(request) do
    {
      current_user: request.env['warden']&.user,  # Works with Devise
      session: request.session,
      # Add any other context your actions need
    }
  end
end

Then in your panels:

action 'Toggle Admin', key: 'user.toggle_admin' do
  perform do |_params, ctx|
    user = ctx[:current_user]
    return { error: 'Not logged in' } unless user

    user.update!(admin: !user.admin?)
    { ok: true, admin: user.admin? }
  end
end

The context provider receives the full ActionDispatch::Request object, giving you access to:

  • request.env['warden'] - Warden/Devise user
  • request.session - Rails session
  • request.cookies - Request cookies
  • request.env - Full Rack environment

Security

Intended for development only. DO NOT ENABLE IN PRODUCTION.

Development

Run tests and lint:

bundle install
bundle exec rake test
bundle exec rubocop

License

MIT License. See LICENSE.txt.

Code of Conduct

Everyone interacting in this project is expected to follow the Code of Conduct.