logical-insight 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +45 -0
- data/MIT-LICENSE.txt +19 -0
- data/README.md +123 -0
- data/Rakefile +24 -0
- data/Thorfile +113 -0
- data/lib/insight.rb +17 -0
- data/lib/insight/app.rb +189 -0
- data/lib/insight/database.rb +186 -0
- data/lib/insight/enable-button.rb +43 -0
- data/lib/insight/filtered_backtrace.rb +45 -0
- data/lib/insight/instrumentation.rb +9 -0
- data/lib/insight/instrumentation/backstage.rb +10 -0
- data/lib/insight/instrumentation/client.rb +20 -0
- data/lib/insight/instrumentation/instrument.rb +109 -0
- data/lib/insight/instrumentation/package-definition.rb +58 -0
- data/lib/insight/instrumentation/probe-definition.rb +20 -0
- data/lib/insight/instrumentation/probe.rb +199 -0
- data/lib/insight/instrumentation/setup.rb +32 -0
- data/lib/insight/logger.rb +55 -0
- data/lib/insight/options.rb +102 -0
- data/lib/insight/panel.rb +119 -0
- data/lib/insight/panel_app.rb +31 -0
- data/lib/insight/panels-content.rb +22 -0
- data/lib/insight/panels-header.rb +18 -0
- data/lib/insight/panels/active_record_panel.rb +46 -0
- data/lib/insight/panels/cache_panel.rb +69 -0
- data/lib/insight/panels/cache_panel/panel_app.rb +46 -0
- data/lib/insight/panels/cache_panel/stats.rb +98 -0
- data/lib/insight/panels/log_panel.rb +54 -0
- data/lib/insight/panels/memory_panel.rb +32 -0
- data/lib/insight/panels/rails_info_panel.rb +19 -0
- data/lib/insight/panels/redis_panel.rb +42 -0
- data/lib/insight/panels/redis_panel/redis_extension.rb +23 -0
- data/lib/insight/panels/redis_panel/stats.rb +50 -0
- data/lib/insight/panels/request_variables_panel.rb +70 -0
- data/lib/insight/panels/speedtracer_panel.rb +89 -0
- data/lib/insight/panels/speedtracer_panel/trace-app.rb +52 -0
- data/lib/insight/panels/speedtracer_panel/tracer.rb +212 -0
- data/lib/insight/panels/sql_panel.rb +53 -0
- data/lib/insight/panels/sql_panel/panel_app.rb +37 -0
- data/lib/insight/panels/sql_panel/query.rb +94 -0
- data/lib/insight/panels/templates_panel.rb +58 -0
- data/lib/insight/panels/templates_panel/rendering.rb +81 -0
- data/lib/insight/panels/timer_panel.rb +40 -0
- data/lib/insight/params_signature.rb +61 -0
- data/lib/insight/public/__insight__/bookmarklet.html +10 -0
- data/lib/insight/public/__insight__/bookmarklet.js +223 -0
- data/lib/insight/public/__insight__/insight.css +235 -0
- data/lib/insight/public/__insight__/insight.js +123 -0
- data/lib/insight/public/__insight__/jquery-1.3.2.js +4376 -0
- data/lib/insight/public/__insight__/jquery.tablesorter.min.js +1 -0
- data/lib/insight/public/__insight__/spinner.gif +0 -0
- data/lib/insight/rack_static_bug_avoider.rb +16 -0
- data/lib/insight/redirect_interceptor.rb +25 -0
- data/lib/insight/render.rb +72 -0
- data/lib/insight/request-recorder.rb +23 -0
- data/lib/insight/toolbar.rb +63 -0
- data/lib/insight/views/enable-button.html.erb +1 -0
- data/lib/insight/views/error.html.erb +17 -0
- data/lib/insight/views/headers_fragment.html.erb +20 -0
- data/lib/insight/views/panels/active_record.html.erb +17 -0
- data/lib/insight/views/panels/cache.html.erb +93 -0
- data/lib/insight/views/panels/execute_sql.html.erb +32 -0
- data/lib/insight/views/panels/explain_sql.html.erb +32 -0
- data/lib/insight/views/panels/log.html.erb +21 -0
- data/lib/insight/views/panels/profile_sql.html.erb +32 -0
- data/lib/insight/views/panels/rails_info.html.erb +19 -0
- data/lib/insight/views/panels/redis.html.erb +46 -0
- data/lib/insight/views/panels/request_variables.html.erb +25 -0
- data/lib/insight/views/panels/speedtracer/serverevent.html.erb +10 -0
- data/lib/insight/views/panels/speedtracer/servertrace.html.erb +12 -0
- data/lib/insight/views/panels/speedtracer/traces.html.erb +18 -0
- data/lib/insight/views/panels/sql.html.erb +43 -0
- data/lib/insight/views/panels/templates.html.erb +6 -0
- data/lib/insight/views/panels/timer.html.erb +19 -0
- data/lib/insight/views/panels/view_cache.html.erb +19 -0
- data/lib/insight/views/redirect.html.erb +16 -0
- data/lib/insight/views/request_fragment.html.erb +25 -0
- data/lib/insight/views/toolbar.html.erb +29 -0
- data/lib/logical-insight.rb +1 -0
- data/spec/custom_matchers.rb +31 -0
- data/spec/fixtures/config.ru +8 -0
- data/spec/fixtures/dummy_panel.rb +2 -0
- data/spec/fixtures/sample_app.rb +72 -0
- data/spec/insight/panels/active_record_panel_spec.rb +42 -0
- data/spec/insight/panels/cache_panel_spec.rb +176 -0
- data/spec/insight/panels/log_panel_spec.rb +44 -0
- data/spec/insight/panels/memory_panel_spec.rb +19 -0
- data/spec/insight/panels/mongo_panel_spec_pending.rb +50 -0
- data/spec/insight/panels/rails_info_panel_spec.rb +27 -0
- data/spec/insight/panels/redis_panel_spec.rb +66 -0
- data/spec/insight/panels/sql_panel_spec.rb +145 -0
- data/spec/insight/panels/templates_panel_spec.rb +84 -0
- data/spec/insight/panels/timer_panel_spec.rb +36 -0
- data/spec/insight_spec.rb +141 -0
- data/spec/instrumentation_spec.rb +188 -0
- data/spec/rcov.opts +1 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +93 -0
- metadata +187 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Insight
|
|
2
|
+
|
|
3
|
+
class PanelApp
|
|
4
|
+
include Insight::Render
|
|
5
|
+
|
|
6
|
+
attr_reader :request
|
|
7
|
+
|
|
8
|
+
def call(env)
|
|
9
|
+
@request = Rack::Request.new(env)
|
|
10
|
+
dispatch
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def render_template(*args)
|
|
14
|
+
Rack::Response.new([super]).to_a
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def params
|
|
18
|
+
@request.GET
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def not_found(message="")
|
|
22
|
+
[404, {}, [message]]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def validate_params
|
|
26
|
+
ParamsSignature.new(request).validate!
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Insight
|
|
2
|
+
class PanelsContent < PanelApp
|
|
3
|
+
def initialize(insight_app)
|
|
4
|
+
@insight_app = insight_app
|
|
5
|
+
@request_table = Database::RequestTable.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def dispatch
|
|
9
|
+
return not_found("not get") unless @request.get?
|
|
10
|
+
return not_found("id nil") if params['request_id'].nil?
|
|
11
|
+
request = @request_table.select("*", "id = #{params['request_id']}").first
|
|
12
|
+
return not_found("id not found") if request.nil?
|
|
13
|
+
requests = @request_table.to_a.map do |row|
|
|
14
|
+
{ :id => row[0], :method => row[1], :path => row[2] }
|
|
15
|
+
end
|
|
16
|
+
render_template("request_fragment",
|
|
17
|
+
:request_id => params['request_id'].to_i,
|
|
18
|
+
:requests => requests,
|
|
19
|
+
:panels => @insight_app.panels)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Insight
|
|
2
|
+
class PanelsHeader < PanelApp
|
|
3
|
+
def initialize(insight_app)
|
|
4
|
+
@insight_app = insight_app
|
|
5
|
+
@request_table = Database::RequestTable.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def dispatch
|
|
9
|
+
return not_found("not get") unless @request.get?
|
|
10
|
+
return not_found("id nil") if params['request_id'].nil?
|
|
11
|
+
request = @request_table.select("*", "id = #{params['request_id']}").first
|
|
12
|
+
return not_found("id not found") if request.nil?
|
|
13
|
+
render_template("headers_fragment",
|
|
14
|
+
:request_id => params['request_id'].to_i,
|
|
15
|
+
:panels => @insight_app.panels)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Insight
|
|
2
|
+
class ActiveRecordPanel < Panel
|
|
3
|
+
def initialize(app)
|
|
4
|
+
super
|
|
5
|
+
|
|
6
|
+
table_setup("active_record")
|
|
7
|
+
|
|
8
|
+
probe(self) do
|
|
9
|
+
instrument "ActiveRecord::Base" do
|
|
10
|
+
class_probe :allocate
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def request_start(env, start)
|
|
16
|
+
@records = Hash.new{ 0 }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def after_detect(method_call, timing, results, args)
|
|
20
|
+
@records[method_call.object.base_class.name] += 1
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def request_finish(env, status, headers, body, timing)
|
|
24
|
+
store(env, @records)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def name
|
|
28
|
+
"active_record"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def heading_for_request(number)
|
|
32
|
+
record = retrieve(number).first
|
|
33
|
+
total = record.inject(0) do |memo, (key, value)|
|
|
34
|
+
memo + value
|
|
35
|
+
end
|
|
36
|
+
"#{total} AR Objects"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def content_for_request(number)
|
|
40
|
+
records = retreive(number).first.to_a.sort_by { |key, value| value }.reverse
|
|
41
|
+
render_template "panels/active_record", :records => records
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
|
|
2
|
+
module Insight
|
|
3
|
+
|
|
4
|
+
class CachePanel < Panel
|
|
5
|
+
require "insight/panels/cache_panel/panel_app"
|
|
6
|
+
require "insight/panels/cache_panel/stats"
|
|
7
|
+
|
|
8
|
+
def initialize(app)
|
|
9
|
+
super
|
|
10
|
+
|
|
11
|
+
probe(self) do
|
|
12
|
+
instrument("Memcached") do
|
|
13
|
+
instance_probe :decrement, :get, :increment, :set, :add,
|
|
14
|
+
:replace, :delete, :prepend, :append
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
instrument("MemCache") do
|
|
18
|
+
instance_probe :decr, :get, :get_multi, :incr, :set, :add, :delete
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
instrument("Dalli::Client") do
|
|
22
|
+
instance_probe :perform
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
table_setup("cache")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def request_start(env, start)
|
|
30
|
+
@stats = Stats.new
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def request_finish(env, st, hd, bd, timing)
|
|
34
|
+
logger(env).debug{ "Stats: #@stats" }
|
|
35
|
+
store(env, @stats)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def after_detect(method_call, timing, args, result)
|
|
39
|
+
method, key = method_call.method, args.first
|
|
40
|
+
if(defined? Dalli and Dalli::Client === method_call.object)
|
|
41
|
+
method, key = args[0], args[1]
|
|
42
|
+
end
|
|
43
|
+
logger.info{ "Cache panel got #{method} #{key.inspect}" }
|
|
44
|
+
@stats.record_call(method, timing.duration, !result.nil?, key)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def panel_app
|
|
48
|
+
PanelApp.new
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def name
|
|
52
|
+
"cache"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def heading_for_request(number)
|
|
56
|
+
stats = retreive(number).first
|
|
57
|
+
|
|
58
|
+
"Cache: %.2fms (#{stats.queries.size} calls)" % stats.time
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def content_for_request(number)
|
|
62
|
+
logger.debug{{ :req_num => number }}
|
|
63
|
+
stats = retreive(number).first
|
|
64
|
+
render_template "panels/cache", :stats => stats
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Insight
|
|
2
|
+
class CachePanel
|
|
3
|
+
|
|
4
|
+
class PanelApp < ::Insight::PanelApp
|
|
5
|
+
|
|
6
|
+
def dispatch
|
|
7
|
+
case request.path_info
|
|
8
|
+
when "/__insight__/view_cache" then view_cache
|
|
9
|
+
when "/__insight__/delete_cache" then delete_cache
|
|
10
|
+
when "/__insight__/delete_cache_list" then delete_cache_list
|
|
11
|
+
else not_found
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def ok
|
|
16
|
+
Rack::Response.new(["OK"]).to_a
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def view_cache
|
|
20
|
+
validate_params
|
|
21
|
+
render_template "panels/view_cache", :key => params["key"], :value => Rails.cache.read(params["key"])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def delete_cache
|
|
25
|
+
validate_params
|
|
26
|
+
raise "Rails not found... can't delete key" unless defined?(Rails)
|
|
27
|
+
Rails.cache.delete(params["key"])
|
|
28
|
+
ok
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def delete_cache_list
|
|
32
|
+
validate_params
|
|
33
|
+
raise "Rails not found... can't delete key" unless defined?(Rails)
|
|
34
|
+
|
|
35
|
+
params.each do |key, value|
|
|
36
|
+
next unless key =~ /^keys_/
|
|
37
|
+
Rails.cache.delete(value)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
ok
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
module Insight
|
|
2
|
+
class CachePanel
|
|
3
|
+
|
|
4
|
+
class Stats
|
|
5
|
+
class Query
|
|
6
|
+
attr_reader :method, :time, :hit, :keys
|
|
7
|
+
|
|
8
|
+
def initialize(method, time, hit, keys)
|
|
9
|
+
@method = method
|
|
10
|
+
@time = time
|
|
11
|
+
@hit = hit
|
|
12
|
+
@keys = keys
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def display_time
|
|
16
|
+
"%.2fms" % time
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def display_keys
|
|
20
|
+
if keys.size == 1
|
|
21
|
+
keys.first
|
|
22
|
+
else
|
|
23
|
+
keys.join(", ")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
attr_reader :calls
|
|
29
|
+
attr_reader :keys
|
|
30
|
+
attr_reader :queries
|
|
31
|
+
|
|
32
|
+
def initialize
|
|
33
|
+
@queries = []
|
|
34
|
+
@misses =
|
|
35
|
+
@calls = 0
|
|
36
|
+
@time = 0.0
|
|
37
|
+
@keys = []
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def record_call(method, time, hit, key)
|
|
41
|
+
if Array === key
|
|
42
|
+
@queries << Query.new(:get_multi, time, hit, key)
|
|
43
|
+
else
|
|
44
|
+
@queries << Query.new(method.to_sym, time, hit, [key])
|
|
45
|
+
end
|
|
46
|
+
@calls += 1
|
|
47
|
+
@time += time
|
|
48
|
+
@keys += keys
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def display_time
|
|
52
|
+
"%.2fms" % time
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def time
|
|
56
|
+
@queries.inject(0) do |memo, query|
|
|
57
|
+
memo + query.time
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def gets
|
|
62
|
+
count_queries(:get)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def sets
|
|
66
|
+
count_queries(:set)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def deletes
|
|
70
|
+
count_queries(:delete)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def get_multis
|
|
74
|
+
count_queries(:get_multi)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def hits
|
|
78
|
+
@queries.select { |q| [:get, :get_multi].include?(q.method) && q.hit }.size
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def misses
|
|
82
|
+
@queries.select { |q| [:get, :get_multi].include?(q.method) && !q.hit }.size
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def count_queries(method)
|
|
86
|
+
@queries.select { |q| q.method == method }.size
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def queries_to_param
|
|
90
|
+
params = {}
|
|
91
|
+
@queries.each_with_index do |query, index|
|
|
92
|
+
params["keys_#{index}"] = query.keys.first
|
|
93
|
+
end
|
|
94
|
+
params
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Insight
|
|
2
|
+
class LogPanel < Panel
|
|
3
|
+
class LogEntry
|
|
4
|
+
attr_reader :level, :time, :message
|
|
5
|
+
LEVELS = ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL']
|
|
6
|
+
|
|
7
|
+
def initialize(level, time, message)
|
|
8
|
+
@level = LEVELS[level]
|
|
9
|
+
@time = time
|
|
10
|
+
@message = message
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def cleaned_message
|
|
14
|
+
@message.to_s.gsub(/\e\[[;\d]+m/, "")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def after_detect(method_call, timing, args, message)
|
|
20
|
+
message = args[1] || args[2] unless message.is_a?(String)
|
|
21
|
+
log_level = args[0]
|
|
22
|
+
store(@env, LogEntry.new(log_level, timing.delta_t, message))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initialize(app)
|
|
26
|
+
probe(self) do
|
|
27
|
+
instrument "ActiveSupport::BufferedLogger" do
|
|
28
|
+
instance_probe :add
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
instrument "Logger" do
|
|
32
|
+
instance_probe :add
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
table_setup("log_entries")
|
|
37
|
+
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def name
|
|
42
|
+
"log"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def heading
|
|
46
|
+
"Log"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def content_for_request(number)
|
|
50
|
+
render_template "panels/log", :logs => retrieve(number)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Insight
|
|
2
|
+
|
|
3
|
+
class MemoryPanel < Panel
|
|
4
|
+
def initialize(app)
|
|
5
|
+
super
|
|
6
|
+
table_setup("memory_records")
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def before(env)
|
|
10
|
+
@original_memory = `ps -o rss= -p #{$$}`.to_i
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def after(env, status, headers, body)
|
|
14
|
+
total_memory = `ps -o rss= -p #{$$}`.to_i
|
|
15
|
+
store(env, {:total_memory => total_memory,
|
|
16
|
+
:memory_increase => total_memory - @original_memory,
|
|
17
|
+
:original_memory => @original_memory})
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def heading_for_request(number)
|
|
21
|
+
record = retrieve(number).first
|
|
22
|
+
|
|
23
|
+
"#{record[:memory_increase]} KB Δ, #{record[:total_memory]} KB total"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def has_content?
|
|
27
|
+
false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Insight
|
|
2
|
+
class RailsInfoPanel < Panel
|
|
3
|
+
|
|
4
|
+
def name
|
|
5
|
+
"rails_info"
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def heading
|
|
9
|
+
return unless (defined?(Rails) && defined?(Rails::Info))
|
|
10
|
+
"Rails #{Rails.version}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def content
|
|
14
|
+
return unless (defined?(Rails) && defined?(Rails::Info))
|
|
15
|
+
render_template "panels/rails_info"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
end
|