├── test ├── dummy │ ├── log │ │ └── .keep │ ├── public │ │ ├── favicon.ico │ │ ├── 500.html │ │ ├── 422.html │ │ └── 404.html │ ├── app │ │ ├── helpers │ │ │ └── application_helper.rb │ │ ├── views │ │ │ ├── home │ │ │ │ └── show.html.erb │ │ │ └── layouts │ │ │ │ └── application.html.erb │ │ ├── controllers │ │ │ ├── home_controller.rb │ │ │ └── application_controller.rb │ │ └── assets │ │ │ ├── stylesheets │ │ │ └── application.css │ │ │ └── javascripts │ │ │ └── application.js │ ├── config │ │ ├── routes.rb │ │ ├── initializers │ │ │ ├── session_store.rb │ │ │ ├── filter_parameter_logging.rb │ │ │ ├── mime_types.rb │ │ │ ├── backtrace_silencers.rb │ │ │ ├── wrap_parameters.rb │ │ │ ├── secret_token.rb │ │ │ └── inflections.rb │ │ ├── environment.rb │ │ ├── boot.rb │ │ ├── locales │ │ │ └── en.yml │ │ ├── environments │ │ │ ├── development.rb │ │ │ └── test.rb │ │ └── application.rb │ ├── bin │ │ ├── rake │ │ ├── bundle │ │ └── rails │ ├── config.ru │ ├── Rakefile │ └── README.rdoc ├── controllers │ └── requests_test.rb ├── test_helper.rb ├── peek │ └── views │ │ └── view_test.rb └── peek_test.rb ├── lib ├── peek │ ├── version.rb │ ├── adapters │ │ ├── base.rb │ │ ├── memory.rb │ │ ├── redis.rb │ │ └── memcache.rb │ ├── controller_helpers.rb │ ├── railtie.rb │ └── views │ │ └── view.rb └── peek.rb ├── config └── routes.rb ├── app ├── assets │ ├── images │ │ └── peek │ │ │ └── bar │ │ │ ├── staging.gif │ │ │ ├── development.gif │ │ │ └── production.gif │ ├── stylesheets │ │ ├── peek.scss │ │ └── peek │ │ │ └── vendor │ │ │ └── tipsy.scss │ └── javascripts │ │ ├── peek.coffee │ │ └── peek │ │ └── vendor │ │ └── jquery.tipsy.js ├── views │ └── peek │ │ └── _bar.html.erb └── controllers │ └── peek │ └── results_controller.rb ├── .gitignore ├── Rakefile ├── Gemfile ├── peek.gemspec ├── LICENSE.txt ├── CHANGELOG.md └── README.md /test/dummy/log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/peek/version.rb: -------------------------------------------------------------------------------- 1 | module Peek 2 | VERSION = '0.1.8' 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.routes.draw do 2 | root :to => 'home#show' 3 | end 4 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Peek::Railtie.routes.draw do 2 | get '/results' => 'results#show', :as => :results 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/app/views/home/show.html.erb: -------------------------------------------------------------------------------- 1 |

Home#show

2 |

Find me in app/views/home/show.html.erb

3 | -------------------------------------------------------------------------------- /app/assets/images/peek/bar/staging.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preact/peek/master/app/assets/images/peek/bar/staging.gif -------------------------------------------------------------------------------- /test/dummy/app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | def show 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /app/assets/images/peek/bar/development.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preact/peek/master/app/assets/images/peek/bar/development.gif -------------------------------------------------------------------------------- /app/assets/images/peek/bar/production.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preact/peek/master/app/assets/images/peek/bar/production.gif -------------------------------------------------------------------------------- /test/dummy/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /test/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /test/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Dummy::Application.config.session_store :cookie_store, key: '_dummy_session' 4 | -------------------------------------------------------------------------------- /test/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application. 5 | Dummy::Application.initialize! 6 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/dummy/log 16 | test/tmp 17 | test/version_tmp 18 | tmp 19 | /bin 20 | -------------------------------------------------------------------------------- /test/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 5 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__) 6 | -------------------------------------------------------------------------------- /test/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Dummy::Application.load_tasks 7 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rake/testtask' 3 | 4 | desc 'Default: run tests' 5 | task :default => :test 6 | 7 | desc 'Run Peek tests.' 8 | Rake::TestTask.new do |t| 9 | t.libs << 'lib' 10 | t.libs << 'test' 11 | t.test_files = FileList['test/**/*_test.rb'] 12 | t.verbose = true 13 | end 14 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | 6 | def peek_enabled? 7 | true 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/controllers/requests_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RequestsTest < ActionDispatch::IntegrationTest 4 | setup do 5 | Peek.adapter.reset 6 | Peek.reset 7 | end 8 | 9 | test "the request id is set" do 10 | assert_empty Peek.adapter.requests 11 | get '/' 12 | assert_not_empty Peek.adapter.requests 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/peek/adapters/base.rb: -------------------------------------------------------------------------------- 1 | module Peek 2 | module Adapters 3 | class Base 4 | def initialize(options = {}) 5 | 6 | end 7 | 8 | def get(request_id) 9 | raise "#{self.class}#get(request_id) is not yet implemented" 10 | end 11 | 12 | def save 13 | raise "#{self.class}#save is not yet implemented" 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dummy 5 | <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> 6 | <%= javascript_include_tag "application", "data-turbolinks-track" => true %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 | <%= render 'peek/bar' %> 11 | <%= yield %> 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/views/peek/_bar.html.erb: -------------------------------------------------------------------------------- 1 | <% if peek_enabled? %> 2 |
3 |
4 | <% Peek.views.each do |view| %> 5 |
6 | <%= render view.partial_path, :view => view %> 7 |
8 | <% end %> 9 |
10 |
11 | <% end %> 12 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /lib/peek/controller_helpers.rb: -------------------------------------------------------------------------------- 1 | module Peek 2 | module ControllerHelpers 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | prepend_before_filter :set_peek_request_id, :if => :peek_enabled? 7 | helper_method :peek_enabled? 8 | end 9 | 10 | protected 11 | 12 | def set_peek_request_id 13 | Peek.request_id = env['action_dispatch.request_id'] 14 | end 15 | 16 | def peek_enabled? 17 | Peek.enabled? 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/peek/adapters/memory.rb: -------------------------------------------------------------------------------- 1 | require 'peek/adapters/base' 2 | 3 | module Peek 4 | module Adapters 5 | class Memory < Base 6 | attr_accessor :requests 7 | 8 | def initialize(options = {}) 9 | @requests = {} 10 | end 11 | 12 | def get(request_id) 13 | @requests[request_id] 14 | end 15 | 16 | def save 17 | @requests[Peek.request_id] = Peek.results 18 | end 19 | 20 | def reset 21 | @requests.clear 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/controllers/peek/results_controller.rb: -------------------------------------------------------------------------------- 1 | module Peek 2 | class ResultsController < ApplicationController 3 | before_filter :restrict_non_access 4 | respond_to :json 5 | 6 | def show 7 | if request.xhr? 8 | render :json => Peek.adapter.get(params[:request_id]) 9 | else 10 | render :nothing => true, :status => :not_found 11 | end 12 | end 13 | 14 | private 15 | 16 | def restrict_non_access 17 | unless peek_enabled? 18 | raise ActionController::RoutingError.new('Not Found') 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/peek/adapters/redis.rb: -------------------------------------------------------------------------------- 1 | require 'peek/adapters/base' 2 | require 'redis' 3 | 4 | module Peek 5 | module Adapters 6 | class Redis < Base 7 | def initialize(options = {}) 8 | @client = options.fetch(:client, ::Redis.new) 9 | @expires_in = Integer(options.fetch(:expires_in, 60 * 30)) 10 | end 11 | 12 | def get(request_id) 13 | @client.get("peek:requests:#{request_id}") 14 | end 15 | 16 | def save 17 | @client.setex("peek:requests:#{Peek.request_id}", @expires_in, Peek.results.to_json) 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/peek/adapters/memcache.rb: -------------------------------------------------------------------------------- 1 | require 'peek/adapters/base' 2 | require 'dalli' 3 | 4 | module Peek 5 | module Adapters 6 | class Memcache < Base 7 | def initialize(options = {}) 8 | @client = options.fetch(:client, ::Dalli::Client.new) 9 | @expires_in = options.fetch(:expires_in, 60 * 30) 10 | end 11 | 12 | def get(request_id) 13 | @client.get("peek:requests:#{request_id}") 14 | end 15 | 16 | def save 17 | @client.add("peek:requests:#{Peek.request_id}", Peek.results.to_json, @expires_in) 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /test/dummy/README.rdoc: -------------------------------------------------------------------------------- 1 | == README 2 | 3 | This README would normally document whatever steps are necessary to get the 4 | application up and running. 5 | 6 | Things you may want to cover: 7 | 8 | * Ruby version 9 | 10 | * System dependencies 11 | 12 | * Configuration 13 | 14 | * Database creation 15 | 16 | * Database initialization 17 | 18 | * How to run the test suite 19 | 20 | * Services (job queues, cache servers, search engines, etc.) 21 | 22 | * Deployment instructions 23 | 24 | * ... 25 | 26 | 27 | Please feel free to use a different markup language if you do not plan to run 28 | rake doc:app. 29 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV['RAILS_ENV'] = 'test' 2 | 3 | require File.expand_path('../dummy/config/environment.rb', __FILE__) 4 | require 'rails/test_help' 5 | 6 | Rails.backtrace_cleaner.remove_silencers! 7 | 8 | # Load support files 9 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } 10 | 11 | # Load fixtures from the engine 12 | if ActiveSupport::TestCase.method_defined?(:fixture_path=) 13 | ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) 14 | end 15 | 16 | require 'minitest/autorun' 17 | 18 | begin 19 | require 'turn' 20 | rescue LoadError 21 | # Not installed. 22 | end 23 | -------------------------------------------------------------------------------- /test/dummy/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require peek 12 | *= require_self 13 | *= require_tree . 14 | */ 15 | -------------------------------------------------------------------------------- /test/dummy/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD 11 | // GO AFTER THE REQUIRES BELOW. 12 | // 13 | //= require peek 14 | //= require_tree . 15 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure your secret_key_base is kept private 11 | # if you're sharing your code publicly. 12 | Rails.application.config.secret_key_base = '94c84623660ec36e05fa8584f7dad694c280aae1894eedb73fc017564933a4eea53962e7d8aba5dc33be928373045982ec9af92cef8150bb9576eaa55ad36d5b' 13 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /test/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in peek.gemspec 4 | gemspec 5 | 6 | gem 'rake' 7 | gem 'json', '~> 1.7.7' 8 | 9 | gem 'rails', '~> 4.0.0.rc1' 10 | 11 | # Use SCSS for stylesheets 12 | gem 'sass-rails', '~> 4.0.0.rc1' 13 | 14 | # Use Uglifier as compressor for JavaScript assets 15 | gem 'uglifier', '>= 1.3.0' 16 | 17 | # Use CoffeeScript for .js.coffee assets and views 18 | gem 'coffee-rails', '~> 4.0.0' 19 | 20 | # See https://github.com/sstephenson/execjs#readme for more supported runtimes 21 | # gem 'therubyracer', platforms: :ruby 22 | 23 | # Use jquery as the JavaScript library 24 | gem 'jquery-rails' 25 | 26 | # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks 27 | gem 'turbolinks' 28 | -------------------------------------------------------------------------------- /test/peek/views/view_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | describe Peek::Views::View do 4 | before do 5 | @view = Peek::Views::View.new 6 | end 7 | 8 | describe "partial path" do 9 | it "should return correct partial class" do 10 | assert_equal 'peek/views/view', @view.partial_path 11 | end 12 | end 13 | 14 | describe "dom_id" do 15 | it "should return correct dom_id" do 16 | assert_equal 'peek-view-view', @view.dom_id 17 | end 18 | end 19 | 20 | describe "key" do 21 | it "should return correct key" do 22 | assert_equal 'view', @view.key 23 | end 24 | end 25 | 26 | describe "context" do 27 | it "should return correct context_id" do 28 | assert_equal 'peek-context-view', @view.context_id 29 | end 30 | end 31 | 32 | describe "toggling off and on" do 33 | it "should be enabled by default" do 34 | assert @view.enabled? 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /peek.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'peek/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = 'peek' 8 | gem.version = Peek::VERSION 9 | gem.authors = ['Garrett Bjerkhoel'] 10 | gem.email = ['me@garrettbjerkhoel.com'] 11 | gem.description = %q{Take a peek into your Rails application.} 12 | gem.summary = %q{Take a peek into your Rails application.} 13 | gem.homepage = 'https://github.com/peek/peek' 14 | gem.license = 'MIT' 15 | 16 | gem.files = `git ls-files`.split($/) 17 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 18 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 19 | gem.require_paths = ['lib'] 20 | 21 | gem.add_dependency 'rails', '>= 3.0.0' 22 | gem.add_dependency 'atomic', '>= 1.0.0' 23 | end 24 | -------------------------------------------------------------------------------- /lib/peek/railtie.rb: -------------------------------------------------------------------------------- 1 | require 'peek/controller_helpers' 2 | 3 | module Peek 4 | class Railtie < ::Rails::Engine 5 | isolate_namespace Peek 6 | engine_name :peek 7 | 8 | config.peek = ActiveSupport::OrderedOptions.new 9 | 10 | # Default adapter 11 | config.peek.adapter = :memory 12 | 13 | initializer 'peek.set_configs' do |app| 14 | ActiveSupport.on_load(:peek) do 15 | app.config.peek.each do |k,v| 16 | send "#{k}=", v 17 | end 18 | end 19 | end 20 | 21 | initializer 'peek.persist_request_data' do 22 | ActiveSupport::Notifications.subscribe('process_action.action_controller') do 23 | Peek.adapter.save 24 | Peek.clear 25 | end 26 | end 27 | 28 | initializer 'peek.include_controller_helpers' do 29 | ActiveSupport.on_load(:action_controller) do 30 | include Peek::ControllerHelpers 31 | end 32 | 33 | config.to_prepare do 34 | Peek.views 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Debug mode disables concatenation and preprocessing of assets. 20 | # This option may cause significant delays in view rendering with a large 21 | # number of complex assets. 22 | config.assets.debug = true 23 | end 24 | -------------------------------------------------------------------------------- /test/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'action_controller/railtie' 4 | require 'action_mailer/railtie' 5 | require 'sprockets/railtie' 6 | require 'rails/test_unit/railtie' 7 | 8 | Bundler.require(*Rails.groups) 9 | require 'peek' 10 | 11 | module Dummy 12 | class Application < Rails::Application 13 | # Settings in config/environments/* take precedence over those specified here. 14 | # Application configuration should go into files in config/initializers 15 | # -- all .rb files in that directory are automatically loaded. 16 | 17 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 18 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 19 | # config.time_zone = 'Central Time (US & Canada)' 20 | 21 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 22 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 23 | # config.i18n.default_locale = :de 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Garrett Bjerkhoel 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /app/assets/stylesheets/peek.scss: -------------------------------------------------------------------------------- 1 | //= require peek/vendor/tipsy 2 | 3 | #peek { 4 | background: #000; 5 | height: 35px; 6 | line-height: 35px; 7 | color: #999; 8 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.75); 9 | 10 | .hidden { 11 | display: none; 12 | visibility: visible; 13 | } 14 | 15 | &.disabled { 16 | display: none; 17 | } 18 | 19 | &.production { 20 | background: image-url('peek/bar/production.gif') repeat 0 0; 21 | } 22 | 23 | &.staging { 24 | background: image-url('peek/bar/staging.gif') repeat 0 0; 25 | } 26 | 27 | &.development { 28 | background: image-url('peek/bar/development.gif') repeat 0 0; 29 | } 30 | 31 | .wrapper { 32 | width: 800px; 33 | margin: 0 auto; 34 | } 35 | 36 | // UI Elements 37 | .bucket { 38 | background: #111; 39 | display: inline-block; 40 | padding: 4px 6px; 41 | font-family: Consolas, "Liberation Mono", Courier, monospace; 42 | line-height: 1; 43 | color: #ccc; 44 | border-radius: 3px; 45 | box-shadow: 0 1px 0 rgba(255,255,255,.2), inset 0 1px 2px rgba(0,0,0,.25); 46 | 47 | .hidden { 48 | display: none; 49 | } 50 | 51 | &:hover .hidden { 52 | display: inline; 53 | } 54 | } 55 | 56 | strong { 57 | color: #fff; 58 | } 59 | 60 | .view { 61 | margin-right: 15px; 62 | float: left; 63 | 64 | &:last-child { 65 | margin-right: 0; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 48 | 49 | 50 | 51 | 52 |
53 |

We're sorry, but something went wrong.

54 |
55 |

If you are the application owner check the logs for more information.

56 | 57 | 58 | -------------------------------------------------------------------------------- /test/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 48 | 49 | 50 | 51 | 52 |
53 |

The change you wanted was rejected.

54 |

Maybe you tried to change something you didn't have access to.

55 |
56 |

If you are the application owner check the logs for more information.

57 | 58 | 59 | -------------------------------------------------------------------------------- /test/dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 48 | 49 | 50 | 51 | 52 |
53 |

The page you were looking for doesn't exist.

54 |

You may have mistyped the address or the page may have moved.

55 |
56 |

If you are the application owner check the logs for more information.

57 | 58 | 59 | -------------------------------------------------------------------------------- /test/peek_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Staff < Peek::Views::View 4 | def initialize(options = {}) 5 | @username = options.delete(:username) 6 | end 7 | 8 | def username 9 | @username 10 | end 11 | 12 | def enabled? 13 | !!@username 14 | end 15 | end 16 | 17 | describe Peek do 18 | describe "enabled?" do 19 | it "should not be enabled in test" do 20 | refute Peek.enabled? 21 | end 22 | end 23 | 24 | describe "env" do 25 | it "should return the current environment" do 26 | assert_equal 'test', Peek.env 27 | end 28 | end 29 | 30 | describe "views" do 31 | before do 32 | Peek.reset 33 | end 34 | 35 | it "should have none by default" do 36 | assert_equal [], Peek.views 37 | end 38 | 39 | it "should be able to append views" do 40 | Peek.into Staff, :username => 'dewski' 41 | assert_kind_of Staff, Peek.views.first 42 | end 43 | 44 | it "should be able to append views with options" do 45 | Peek.into Staff, :username => 'dewski' 46 | @staff = Peek.views.first 47 | assert_kind_of Staff, @staff 48 | assert_equal 'dewski', @staff.username 49 | end 50 | 51 | it "should only return enabled views" do 52 | Peek.into Staff, :username => false 53 | assert_equal [], Peek.views 54 | end 55 | end 56 | 57 | describe "reset" do 58 | before do 59 | Peek.reset 60 | end 61 | 62 | it "should clear any current views" do 63 | Peek.into Staff, :username => 'dewski' 64 | assert_kind_of Staff, Peek.views.first 65 | Peek.reset 66 | assert_equal [], Peek.views 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /test/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | config.serve_static_assets = true 17 | config.static_cache_control = "public, max-age=3600" 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Print deprecation notices to the stderr. 35 | config.active_support.deprecation = :stderr 36 | end 37 | -------------------------------------------------------------------------------- /app/assets/javascripts/peek.coffee: -------------------------------------------------------------------------------- 1 | #= require peek/vendor/jquery.tipsy 2 | 3 | requestId = null 4 | 5 | getRequestId = -> 6 | if requestId? then requestId else $('#peek').data('request-id') 7 | 8 | peekEnabled = -> 9 | $('#peek').length 10 | 11 | updatePerformanceBar = (results) -> 12 | for key of results.data 13 | for label of results.data[key] 14 | $("[data-defer-to=#{key}-#{label}]").text results.data[key][label] 15 | $(document).trigger 'peek:render', [getRequestId(), results] 16 | 17 | initializeTipsy = -> 18 | $('#peek .peek-tooltip, #peek .tooltip').each -> 19 | el = $(this) 20 | gravity = if el.hasClass('rightwards') || el.hasClass('leftwards') 21 | $.fn.tipsy.autoWE 22 | else 23 | $.fn.tipsy.autoNS 24 | 25 | el.tipsy 26 | gravity: gravity 27 | 28 | toggleBar = (event) -> 29 | return if $(event.target).is ':input' 30 | 31 | if event.which == 96 && !event.metaKey 32 | wrapper = $('#peek') 33 | if wrapper.hasClass 'disabled' 34 | wrapper.removeClass 'disabled' 35 | document.cookie = "peek=true; path=/"; 36 | else 37 | wrapper.addClass 'disabled' 38 | document.cookie = "peek=false; path=/"; 39 | 40 | fetchRequestResults = -> 41 | $.ajax '/peek/results', 42 | data: 43 | request_id: getRequestId() 44 | success: (data, textStatus, xhr) -> 45 | updatePerformanceBar data 46 | error: (xhr, textStatus, error) -> 47 | # Swallow the error 48 | 49 | $(document).on 'keypress', toggleBar 50 | 51 | $(document).on 'peek:update', initializeTipsy 52 | $(document).on 'peek:update', fetchRequestResults 53 | 54 | # Fire the event for our own listeners. 55 | $(document).on 'pjax:end', (event, xhr, options) -> 56 | if xhr? 57 | requestId = xhr.getResponseHeader 'X-Request-Id' 58 | 59 | if peekEnabled() 60 | $(this).trigger 'peek:update' 61 | 62 | # Also listen to turbolinks page change event 63 | $(document).on 'page:change', -> 64 | if peekEnabled() 65 | $(this).trigger 'peek:update' 66 | 67 | $ -> 68 | if peekEnabled() 69 | $(this).trigger 'peek:update' 70 | -------------------------------------------------------------------------------- /app/assets/stylesheets/peek/vendor/tipsy.scss: -------------------------------------------------------------------------------- 1 | .tipsy { font-size: 10px; position: absolute; padding: 5px; z-index: 100000; } 2 | .tipsy-inner { background-color: #000; color: #FFF; max-width: 200px; padding: 5px 8px 4px 8px; text-align: center; } 3 | 4 | /* Rounded corners */ 5 | .tipsy-inner { border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; } 6 | 7 | .tipsy-arrow { position: absolute; width: 0; height: 0; line-height: 0; border: 5px dashed #000; } 8 | 9 | /* Rules to colour arrows */ 10 | .tipsy-arrow-n { border-bottom-color: #000; } 11 | .tipsy-arrow-s { border-top-color: #000; } 12 | .tipsy-arrow-e { border-left-color: #000; } 13 | .tipsy-arrow-w { border-right-color: #000; } 14 | 15 | .tipsy-n .tipsy-arrow { top: 0px; left: 50%; margin-left: -5px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent; } 16 | .tipsy-nw .tipsy-arrow { top: 0; left: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;} 17 | .tipsy-ne .tipsy-arrow { top: 0; right: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;} 18 | .tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -5px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; } 19 | .tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; } 20 | .tipsy-se .tipsy-arrow { bottom: 0; right: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; } 21 | .tipsy-e .tipsy-arrow { right: 0; top: 50%; margin-top: -5px; border-left-style: solid; border-right: none; border-top-color: transparent; border-bottom-color: transparent; } 22 | .tipsy-w .tipsy-arrow { left: 0; top: 50%; margin-top: -5px; border-right-style: solid; border-left: none; border-top-color: transparent; border-bottom-color: transparent; } 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.0.1 2 | 3 | - Initial release. 4 | 5 | # 0.0.2 6 | 7 | - Add own tipsy plugin to allow for tooltips. 8 | 9 | # 0.0.3 10 | 11 | - Change the scope of the .tipsy selector as it's inserted outside of the Glimpse div. 12 | 13 | # 0.0.4 14 | 15 | - Don't capture ` being pressed when in combination with `cmd` 16 | - Support for [Turbolinks](https://github.com/rails/turbolinks) (#14) 17 | 18 | # 0.0.5 19 | 20 | - Namespace the tooltips to the `.glimpse-tooltip` class name to not conflict with any application styles for `.tooltip`. (#18) 21 | 22 | # 0.0.6 23 | 24 | - Added Peek::Views::View#parse_options that gets called within initialize for subclasses to use to parse their options. 25 | 26 | # 0.1.0 27 | 28 | - Introduced a new JS event `peek:render` that includes the request id and request payload data that is used to update the information in the bar. 29 | - Request information has moved from the `peek/results` partial to an AJAX request that happens on page load, and when PJAX/Turbolinks change pages. 30 | - Removed the need for `peek/results` partial. 31 | - Introduced a Redis and Memcache adapter for multi-server environments to store request payloads. 32 | - Tooltips automatically repositions depending on where the Peek bar is. 33 | 34 | # 0.1.1 35 | 36 | - Fix bug with how `peek:render` was passing arguments around. 37 | 38 | # 0.1.2 39 | 40 | - Fix path to memcache adapter - [#34](https://github.com/peek/peek/pull/34) [@grk](https://github.com/grk) 41 | - Prevent namespace collision when using [peek-dalli](https://github.com/peek/peek-dalli) - [#34](https://github.com/peek/peek/pull/34) [@grk](https://github.com/grk) 42 | 43 | # 0.1.3 44 | 45 | - Remove Redis dependency from Gemfile 46 | 47 | # 0.1.4 48 | 49 | - Don't access xhr object when not present in pjax:end 50 | 51 | # 0.1.5 52 | 53 | - Don't trigger `peek:update` event when the peek bar isn't present - [#37](https://github.com/peek/peek/issues/37) [@dewski](https://github.com/dewski) 54 | - Add `after_request` helper method for Peek::Views::View to help reset state 55 | 56 | # 0.1.6 57 | 58 | - Use `event.which` for normalization between `event.keyCode` and `event.charCode` - [#38](https://github.com/peek/peek/pull/38) [@leongersing](https://github.com/leongersing) 59 | 60 | # 0.1.7 61 | 62 | - Support all Rails 3.x.x versions by not using `request.uuid` instead `env` - [#39](https://github.com/peek/peek/pull/39) [@bryanmikaelian](https://github.com/bryanmikaelian) 63 | 64 | # 0.1.8 65 | 66 | - Include the ControllerHelpers directly into `ActionController::Base` - [#41](https://github.com/peek/peek/pull/41) [@lucasmazza](https://github.com/lucasmazza) 67 | -------------------------------------------------------------------------------- /lib/peek.rb: -------------------------------------------------------------------------------- 1 | require 'peek/version' 2 | require 'rails' 3 | require 'atomic' 4 | 5 | require 'peek/adapters/memory' 6 | require 'peek/views/view' 7 | 8 | module Peek 9 | def self._request_id 10 | @_request_id ||= Atomic.new 11 | end 12 | 13 | def self.request_id 14 | _request_id.get 15 | end 16 | 17 | def self.request_id=(id) 18 | _request_id.update { id } 19 | end 20 | 21 | def self.adapter 22 | @adapter 23 | end 24 | 25 | def self.adapter=(*adapter_options) 26 | adapter, *parameters = *Array.wrap(adapter_options).flatten 27 | 28 | @adapter = case adapter 29 | when Symbol 30 | adapter_class_name = adapter.to_s.camelize 31 | adapter_class = 32 | begin 33 | require "peek/adapters/#{adapter}" 34 | rescue LoadError => e 35 | raise "Could not find adapter for #{adapter} (#{e})" 36 | else 37 | Peek::Adapters.const_get(adapter_class_name) 38 | end 39 | adapter_class.new(*parameters) 40 | when nil 41 | Peek::Adapters::Memory.new 42 | else 43 | adapter 44 | end 45 | 46 | @adapter 47 | end 48 | 49 | def self.enabled? 50 | ['development', 'staging'].include?(env) 51 | end 52 | 53 | def self.env 54 | Rails.env 55 | end 56 | 57 | def self.views 58 | @cached_views ||= if @views && @views.any? 59 | @views.collect { |klass, options| klass.new(options.dup) }.select(&:enabled?) 60 | else 61 | [] 62 | end 63 | end 64 | 65 | def self.results 66 | results = { 67 | :context => {}, 68 | :data => Hash.new { |h, k| h[k] = {} } 69 | } 70 | 71 | views.each do |view| 72 | if view.context? 73 | results[:context][view.key] = view.context 74 | end 75 | 76 | view.results.each do |key, value| 77 | results[:data][view.key][key] = value 78 | end 79 | end 80 | 81 | results 82 | end 83 | 84 | def self.into(klass, options = {}) 85 | @views ||= [] 86 | @views << [klass, options] 87 | end 88 | 89 | # Clears out any and all views. 90 | # 91 | # Returns nothing. 92 | def self.reset 93 | @views = nil 94 | @cached_views = nil 95 | end 96 | 97 | # Hook that happens after every request. It is expected to reset 98 | # any state that Peek managed throughout the requests lifecycle. 99 | # 100 | # Returns nothing. 101 | def self.clear 102 | _request_id.update { '' } 103 | end 104 | 105 | def self.setup 106 | ActiveSupport::Deprecation.warn "'Peek.setup' is deprecated and does nothing.", caller 107 | end 108 | end 109 | 110 | require 'peek/railtie' 111 | 112 | ActiveSupport.run_load_hooks(:peek, Peek) 113 | -------------------------------------------------------------------------------- /lib/peek/views/view.rb: -------------------------------------------------------------------------------- 1 | module Peek 2 | module Views 3 | class View 4 | def initialize(options = {}) 5 | @options = options 6 | 7 | parse_options 8 | setup_subscribers 9 | end 10 | 11 | # Where any subclasses should pick and pull from @options to set any and 12 | # all instance variables they like. 13 | # 14 | # Returns nothing. 15 | def parse_options 16 | # pass 17 | end 18 | 19 | # Conditionally enable views based on any gathered data. Helpful 20 | # if you don't want views to show up when they return 0 or are 21 | # touched during the request. 22 | # 23 | # Returns true. 24 | def enabled? 25 | true 26 | end 27 | 28 | # The path to the partial that will be rendered to the Peek bar. 29 | # 30 | # Examples: 31 | # 32 | # Peek::Views::PerformanceBar.partial_path => "peek/views/performance_bar" 33 | # CustomResque.partial_path => "performance_bar" 34 | # 35 | # Returns String. 36 | def partial_path 37 | self.class.to_s.underscore 38 | end 39 | 40 | # The defer key that is derived from the classname. 41 | # 42 | # Examples: 43 | # 44 | # Peek::Views::PerformanceBar => "performance-bar" 45 | # Peek::Views::Resque => "resque" 46 | # 47 | # Returns String. 48 | def key 49 | self.class.to_s.split('::').last.underscore.gsub(/\_/, '-') 50 | end 51 | alias defer_key key 52 | 53 | # The context id that is derived from the classname. 54 | # 55 | # Examples: 56 | # 57 | # Peek::Views::PerformanceBar => "peek-context-performance-bar" 58 | # Peek::Views::Resque => "peek-context-resque" 59 | # 60 | # Returns String. 61 | def context_id 62 | "peek-context-#{key}" 63 | end 64 | 65 | # The wrapper ID for the individual view in the Peek bar. 66 | # 67 | # Returns String. 68 | def dom_id 69 | "peek-view-#{key}" 70 | end 71 | 72 | # Additional context for any view to render tooltips for. 73 | # 74 | # Returns Hash. 75 | def context 76 | {} 77 | end 78 | 79 | def context? 80 | context.any? 81 | end 82 | 83 | # The data results that are inserted at the end of the request for use in 84 | # deferred placeholders in the Peek the bar. 85 | # 86 | # Returns Hash. 87 | def results 88 | {} 89 | end 90 | 91 | def results? 92 | results.any? 93 | end 94 | 95 | def subscribe(*args) 96 | ActiveSupport::Notifications.subscribe(*args) do |name, start, finish, id, payload| 97 | yield name, start, finish, id, payload 98 | end 99 | end 100 | 101 | private 102 | 103 | def setup_subscribers 104 | # pass 105 | end 106 | 107 | # Helper method for subscribing to the event that is fired when new 108 | # requests are made. 109 | def before_request 110 | subscribe 'start_processing.action_controller' do |name, start, finish, id, payload| 111 | yield name, start, finish, id, payload 112 | end 113 | end 114 | 115 | # Helper method for subscribing to the event that is fired when requests 116 | # are finished. 117 | def after_request 118 | subscribe 'process_action.action_controller' do |name, start, finish, id, payload| 119 | yield name, start, finish, id, payload 120 | end 121 | end 122 | end 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Peek 2 | 3 | [![Build Status](https://travis-ci.org/peek/peek.png?branch=master)](https://travis-ci.org/peek/peek) [![Gem Version](https://badge.fury.io/rb/peek.png)](http://badge.fury.io/rb/peek) [![Inline docs](http://inch-pages.github.io/github/peek/peek.png)](http://inch-pages.github.io/github/peek/peek) 4 | 5 | Take a peek into your Rails application. 6 | 7 | ![Preview](https://f.cloud.github.com/assets/79995/244991/03cee1fa-8a74-11e2-8e33-283cf1298a60.png) 8 | 9 | This was originally built at GitHub to help us get insight into what's going 10 | on, this is just an extraction so other Rails applications can have the same. 11 | 12 | ## Installation 13 | 14 | Add this line to your application's Gemfile: 15 | 16 | gem 'peek' 17 | 18 | And then execute: 19 | 20 | $ bundle 21 | 22 | Or install it yourself as: 23 | 24 | $ gem install peek 25 | 26 | ## Usage 27 | 28 | Now that Peek is installed, you'll need to mount the engine within your `config/routes.rb` 29 | file: 30 | 31 | ```ruby 32 | Some::Application.routes.draw do 33 | mount Peek::Railtie => '/peek' 34 | root :to => 'home#show' 35 | end 36 | ``` 37 | 38 | To pick which views you want to see in your Peek bar, just create a file at 39 | `config/initializers/peek.rb` that has a list of the views you'd like to include: 40 | 41 | ```ruby 42 | Peek.into Peek::Views::Git, :nwo => 'github/janky' 43 | Peek.into Peek::Views::Mysql2 44 | Peek.into Peek::Views::Redis 45 | Peek.into Peek::Views::Dalli 46 | ``` 47 | 48 | Feel free to pick and install from the [list](https://github.com/peek/peek#available-peek-views) or create your own. The order they 49 | are added to Peek, the order they will appear in your bar. 50 | 51 | Next, to render the Peek bar in your application just add the following snippet 52 | just after the opening `` tag in your application layout. 53 | 54 | ```erb 55 | <%= render 'peek/bar' %> 56 | ``` 57 | 58 | It will look like: 59 | 60 | ```erb 61 | 62 | 63 | Application 64 | 65 | 66 | <%= render 'peek/bar' %> 67 | <%= yield %> 68 | 69 | 70 | ``` 71 | 72 | Peek fetches the data collected throughout your requests by using the unique request id 73 | that was assigned to the request by Rails. It will call out to its own controller at 74 | [Peek::ResultsController](https://github.com/peek/peek/blob/master/app/controllers/peek/results_controller.rb) which will render the data and be inserted into the bar. 75 | 76 | Now that you have the partials in your application, you will need to include the 77 | CSS and JS that help make Peek :sparkles: 78 | 79 | In `app/assets/stylesheets/application.scss`: 80 | 81 | ```scss 82 | //= require peek 83 | ``` 84 | 85 | In `app/assets/javascripts/application.coffee`: 86 | 87 | ```coffeescript 88 | #= require jquery 89 | #= require jquery_ujs 90 | #= require peek 91 | ``` 92 | 93 | Note: Each additional view my have their own CSS and JS you need to require 94 | which should be stated in their usage documentation. 95 | 96 | ### Configuring the default adapter 97 | 98 | For Peek to work, it keeps track of all requests made in your application 99 | so it can report back and display that information in the Peek bar. By default 100 | it stores this information in memory, which is not recommended for production environments. 101 | 102 | In production environments you may have application servers on multiple hosts, 103 | at which Peek will not be able to access the request data if it was saved in memory on 104 | another host. Peek provides 2 additional adapters for multi server environments. 105 | 106 | You can configure which adapter Peek uses by updating your application 107 | config or an individual environment config file. We'll use production as an example. 108 | 109 | Note: Peek does not provide the dependencies for each of these adapters. If you use these 110 | adapters be sure to include their dependencies in your application. 111 | 112 | - Redis - The [redis](https://github.com/redis/redis-rb) gem 113 | - Dalli - The [dalli](https://github.com/mperham/dalli) gem 114 | 115 | ```ruby 116 | Peeked::Application.configure do 117 | # ... 118 | 119 | # Redis with no options 120 | config.peek.adapter = :redis 121 | 122 | # Redis with options 123 | config.peek.adapter = :redis, { 124 | :client => Redis.new, 125 | :expires_in => 60 * 30 # => 30 minutes in seconds 126 | } 127 | 128 | # Memcache with no options 129 | config.peek.adapter = :memcache 130 | 131 | # Memcache with options 132 | config.peek.adapter = :memcache, { 133 | :client => Dalli::Client.new, 134 | :expires_in => 60 * 30 # => 30 minutes in seconds 135 | } 136 | 137 | # ... 138 | end 139 | ``` 140 | 141 | Peek doesn't persist the request data forever. It uses a safe 30 minute 142 | cache length that way data will be available if you'd like to aggregate it or 143 | use it for other Peek views. You can update this to be 30 seconds if you don't 144 | want the data to be available to stick around. 145 | 146 | ## Using Peek with PJAX 147 | 148 | It just works. 149 | 150 | ## Using Peek with Turbolinks 151 | 152 | It just works. 153 | 154 | ## Access Control 155 | 156 | Peek will only render in development and staging environments. If you'd 157 | like to whitelist a select number of users to view Peek in production you 158 | can override the `peek_enabled?` guard in `ApplicationController`: 159 | 160 | ```ruby 161 | class ApplicationController < ActionController::Base 162 | def peek_enabled? 163 | current_user.staff? 164 | end 165 | end 166 | ``` 167 | 168 | ## Available Peek views 169 | 170 | - [peek-active_resource](https://github.com/gotmayonase/peek-active_resource) 171 | - [peek-dalli](https://github.com/peek/peek-dalli) 172 | - [peek-gc](https://github.com/peek/peek-gc) 173 | - [peek-git](https://github.com/peek/peek-git) 174 | - [peek-mongo](https://github.com/peek/peek-mongo) 175 | - [peek-moped](https://github.com/nodkz/peek-moped) 176 | - [peek-mysql2](https://github.com/peek/peek-mysql2) 177 | - [peek-performance_bar](https://github.com/peek/peek-performance_bar) 178 | - [peek-pg](https://github.com/peek/peek-pg) 179 | - [peek-rblineprof](https://github.com/peek/peek-rblineprof) 180 | - [peek-redis](https://github.com/peek/peek-redis) 181 | - [peek-resque](https://github.com/peek/peek-resque) 182 | - [peek-sidekiq](https://github.com/suranyami/peek-sidekiq) 183 | - [peek-faraday](https://github.com/grk/peek-faraday) 184 | - [peek-svn](https://github.com/neilco/peek-svn) 185 | - Unicorn :soon: 186 | 187 | Feel free to submit a Pull Request adding your own Peek item to this list. 188 | 189 | ## Creating your own Peek item 190 | 191 | Each Peek item is a self contained Rails engine which gives you the power to 192 | use all features of Ruby on Rails to dig in deep within your application and 193 | report it back to the Peek bar. A Peek item is just a custom class that 194 | is responsible for fetching and building the data that should be reported back 195 | to the user. 196 | 197 | There are still some docs to be written, but if you'd like to checkout a simple 198 | example of how to create your own, just checkout [peek-git](https://github.com/peek/peek-git). 199 | To just look at an example view, there is [Peek::Views::Git](https://github.com/peek/peek-git/blob/master/lib/peek/views/git.rb). 200 | 201 | ## Contributing 202 | 203 | 1. Fork it 204 | 2. Create your feature branch (`git checkout -b my-new-feature`) 205 | 3. Commit your changes (`git commit -am 'Add some feature'`) 206 | 4. Push to the branch (`git push origin my-new-feature`) 207 | 5. Create new Pull Request 208 | -------------------------------------------------------------------------------- /app/assets/javascripts/peek/vendor/jquery.tipsy.js: -------------------------------------------------------------------------------- 1 | // tipsy, facebook style tooltips for jquery 2 | // version 1.0.0a 3 | // (c) 2008-2010 jason frame [jason@onehackoranother.com] 4 | // released under the MIT license 5 | 6 | (function($) { 7 | 8 | function maybeCall(thing, ctx) { 9 | return (typeof thing == 'function') ? (thing.call(ctx)) : thing; 10 | }; 11 | 12 | function isElementInDOM(ele) { 13 | while (ele = ele.parentNode) { 14 | if (ele == document) return true; 15 | } 16 | return false; 17 | }; 18 | 19 | function Tipsy(element, options) { 20 | this.$element = $(element); 21 | this.options = options; 22 | this.enabled = true; 23 | this.fixTitle(); 24 | }; 25 | 26 | Tipsy.prototype = { 27 | show: function() { 28 | var title = this.getTitle(); 29 | if (title && this.enabled) { 30 | var $tip = this.tip(); 31 | 32 | $tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title); 33 | $tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity 34 | $tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body); 35 | 36 | var pos = $.extend({}, this.$element.offset(), { 37 | width: this.$element[0].offsetWidth, 38 | height: this.$element[0].offsetHeight 39 | }); 40 | 41 | var actualWidth = $tip[0].offsetWidth, 42 | actualHeight = $tip[0].offsetHeight, 43 | gravity = maybeCall(this.options.gravity, this.$element[0]); 44 | 45 | var tp; 46 | switch (gravity.charAt(0)) { 47 | case 'n': 48 | tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2}; 49 | break; 50 | case 's': 51 | tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2}; 52 | break; 53 | case 'e': 54 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset}; 55 | break; 56 | case 'w': 57 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset}; 58 | break; 59 | } 60 | 61 | if (gravity.length == 2) { 62 | if (gravity.charAt(1) == 'w') { 63 | tp.left = pos.left + pos.width / 2 - 15; 64 | } else { 65 | tp.left = pos.left + pos.width / 2 - actualWidth + 15; 66 | } 67 | } 68 | 69 | $tip.css(tp).addClass('tipsy-' + gravity); 70 | $tip.find('.tipsy-arrow')[0].className = 'tipsy-arrow tipsy-arrow-' + gravity.charAt(0); 71 | if (this.options.className) { 72 | $tip.addClass(maybeCall(this.options.className, this.$element[0])); 73 | } 74 | 75 | if (this.options.fade) { 76 | $tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity}); 77 | } else { 78 | $tip.css({visibility: 'visible', opacity: this.options.opacity}); 79 | } 80 | } 81 | }, 82 | 83 | hide: function() { 84 | if (this.options.fade) { 85 | this.tip().stop().fadeOut(function() { $(this).remove(); }); 86 | } else { 87 | this.tip().remove(); 88 | } 89 | }, 90 | 91 | fixTitle: function() { 92 | var $e = this.$element; 93 | if ($e.attr('title') || typeof($e.attr('original-title')) != 'string') { 94 | $e.attr('original-title', $e.attr('title') || '').removeAttr('title'); 95 | } 96 | }, 97 | 98 | getTitle: function() { 99 | var title, $e = this.$element, o = this.options; 100 | this.fixTitle(); 101 | var title, o = this.options; 102 | if (typeof o.title == 'string') { 103 | title = $e.attr(o.title == 'title' ? 'original-title' : o.title); 104 | } else if (typeof o.title == 'function') { 105 | title = o.title.call($e[0]); 106 | } 107 | title = ('' + title).replace(/(^\s*|\s*$)/, ""); 108 | return title || o.fallback; 109 | }, 110 | 111 | tip: function() { 112 | if (!this.$tip) { 113 | this.$tip = $('
').html('
'); 114 | this.$tip.data('tipsy-pointee', this.$element[0]); 115 | } 116 | return this.$tip; 117 | }, 118 | 119 | validate: function() { 120 | if (!this.$element[0].parentNode) { 121 | this.hide(); 122 | this.$element = null; 123 | this.options = null; 124 | } 125 | }, 126 | 127 | enable: function() { this.enabled = true; }, 128 | disable: function() { this.enabled = false; }, 129 | toggleEnabled: function() { this.enabled = !this.enabled; } 130 | }; 131 | 132 | $.fn.tipsy = function(options) { 133 | 134 | if (options === true) { 135 | return this.data('tipsy'); 136 | } else if (typeof options == 'string') { 137 | var tipsy = this.data('tipsy'); 138 | if (tipsy) tipsy[options](); 139 | return this; 140 | } 141 | 142 | options = $.extend({}, $.fn.tipsy.defaults, options); 143 | 144 | function get(ele) { 145 | var tipsy = $.data(ele, 'tipsy'); 146 | if (!tipsy) { 147 | tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options)); 148 | $.data(ele, 'tipsy', tipsy); 149 | } 150 | return tipsy; 151 | } 152 | 153 | function enter() { 154 | var tipsy = get(this); 155 | tipsy.hoverState = 'in'; 156 | if (options.delayIn == 0) { 157 | tipsy.show(); 158 | } else { 159 | tipsy.fixTitle(); 160 | setTimeout(function() { if (tipsy.hoverState == 'in') tipsy.show(); }, options.delayIn); 161 | } 162 | }; 163 | 164 | function leave() { 165 | var tipsy = get(this); 166 | tipsy.hoverState = 'out'; 167 | if (options.delayOut == 0) { 168 | tipsy.hide(); 169 | } else { 170 | setTimeout(function() { if (tipsy.hoverState == 'out') tipsy.hide(); }, options.delayOut); 171 | } 172 | }; 173 | 174 | if (!options.live) this.each(function() { get(this); }); 175 | 176 | if (options.trigger != 'manual') { 177 | var binder = options.live ? 'live' : 'bind', 178 | eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus', 179 | eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur'; 180 | this[binder](eventIn, enter)[binder](eventOut, leave); 181 | } 182 | 183 | return this; 184 | 185 | }; 186 | 187 | $.fn.tipsy.defaults = { 188 | className: null, 189 | delayIn: 0, 190 | delayOut: 0, 191 | fade: false, 192 | fallback: '', 193 | gravity: 'n', 194 | html: false, 195 | live: false, 196 | offset: 0, 197 | opacity: 0.8, 198 | title: 'title', 199 | trigger: 'hover' 200 | }; 201 | 202 | $.fn.tipsy.revalidate = function() { 203 | $('.tipsy').each(function() { 204 | var pointee = $.data(this, 'tipsy-pointee'); 205 | if (!pointee || !isElementInDOM(pointee)) { 206 | $(this).remove(); 207 | } 208 | }); 209 | }; 210 | 211 | // Overwrite this method to provide options on a per-element basis. 212 | // For example, you could store the gravity in a 'tipsy-gravity' attribute: 213 | // return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' }); 214 | // (remember - do not modify 'options' in place!) 215 | $.fn.tipsy.elementOptions = function(ele, options) { 216 | return $.metadata ? $.extend({}, options, $(ele).metadata()) : options; 217 | }; 218 | 219 | $.fn.tipsy.autoNS = function() { 220 | return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n'; 221 | }; 222 | 223 | $.fn.tipsy.autoWE = function() { 224 | return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w'; 225 | }; 226 | 227 | /** 228 | * yields a closure of the supplied parameters, producing a function that takes 229 | * no arguments and is suitable for use as an autogravity function like so: 230 | * 231 | * @param margin (int) - distance from the viewable region edge that an 232 | * element should be before setting its tooltip's gravity to be away 233 | * from that edge. 234 | * @param prefer (string, e.g. 'n', 'sw', 'w') - the direction to prefer 235 | * if there are no viewable region edges effecting the tooltip's 236 | * gravity. It will try to vary from this minimally, for example, 237 | * if 'sw' is preferred and an element is near the right viewable 238 | * region edge, but not the top edge, it will set the gravity for 239 | * that element's tooltip to be 'se', preserving the southern 240 | * component. 241 | */ 242 | $.fn.tipsy.autoBounds = function(margin, prefer) { 243 | return function() { 244 | var dir = {ns: prefer[0], ew: (prefer.length > 1 ? prefer[1] : false)}, 245 | boundTop = $(document).scrollTop() + margin, 246 | boundLeft = $(document).scrollLeft() + margin, 247 | $this = $(this); 248 | 249 | if ($this.offset().top < boundTop) dir.ns = 'n'; 250 | if ($this.offset().left < boundLeft) dir.ew = 'w'; 251 | if ($(window).width() + $(document).scrollLeft() - $this.offset().left < margin) dir.ew = 'e'; 252 | if ($(window).height() + $(document).scrollTop() - $this.offset().top < margin) dir.ns = 's'; 253 | 254 | return dir.ns + (dir.ew ? dir.ew : ''); 255 | } 256 | }; 257 | 258 | })(jQuery); 259 | --------------------------------------------------------------------------------