web-console 2.0.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of web-console might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.markdown +132 -85
- data/lib/web_console.rb +14 -13
- data/lib/web_console/errors.rb +7 -0
- data/lib/web_console/{repl.rb → evaluator.rb} +7 -10
- data/lib/web_console/helper.rb +22 -0
- data/lib/web_console/integration.rb +8 -0
- data/lib/web_console/{core_ext/exception → integration}/cruby.rb +0 -0
- data/lib/web_console/integration/jruby.rb +111 -0
- data/lib/web_console/integration/rubinius.rb +66 -0
- data/lib/web_console/middleware.rb +117 -0
- data/lib/web_console/railtie.rb +61 -0
- data/lib/web_console/request.rb +30 -0
- data/lib/web_console/session.rb +65 -0
- data/lib/web_console/template.rb +49 -0
- data/lib/web_console/templates/_inner_console_markup.html +3 -0
- data/lib/web_console/templates/_markup.html +4 -0
- data/lib/web_console/templates/_prompt_box_markup.html +2 -0
- data/lib/web_console/templates/console.js +373 -0
- data/lib/web_console/templates/error_page.js +83 -0
- data/lib/web_console/templates/index.html +8 -0
- data/lib/web_console/templates/layouts/inlined_string.erb +1 -0
- data/lib/web_console/templates/layouts/javascript.erb +5 -0
- data/lib/web_console/templates/main.js +24 -0
- data/lib/web_console/templates/style.css +9 -0
- data/lib/web_console/version.rb +1 -1
- data/lib/web_console/whiny_request.rb +38 -0
- data/lib/web_console/whitelist.rb +42 -0
- data/test/dummy/config/environments/test.rb +0 -4
- data/test/dummy/log/development.log +7075 -0
- data/test/dummy/log/test.log +66006 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/test/support/scenarios/bad_custom_error_scenario.rb +17 -0
- data/test/support/scenarios/basic_nested_scenario.rb +15 -0
- data/test/support/scenarios/custom_error_scenario.rb +11 -0
- data/test/support/scenarios/eval_nested_scenario.rb +15 -0
- data/test/support/scenarios/flat_scenario.rb +9 -0
- data/test/support/scenarios/reraised_scenario.rb +21 -0
- data/test/test_helper.rb +50 -3
- data/test/web_console/evaluator_test.rb +73 -0
- data/test/web_console/helper_test.rb +76 -0
- data/test/web_console/integration_test.rb +47 -0
- data/test/web_console/middleware_test.rb +116 -0
- data/test/web_console/railtie_test.rb +99 -0
- data/test/web_console/request_test.rb +52 -0
- data/test/web_console/session_test.rb +59 -0
- data/test/web_console/whiny_request_test.rb +33 -0
- data/test/web_console/whitelist_test.rb +43 -0
- metadata +66 -56
- data/lib/action_dispatch/debug_exceptions.rb +0 -105
- data/lib/action_dispatch/exception_wrapper.rb +0 -38
- data/lib/action_dispatch/templates/rescues/_request_and_response.html.erb +0 -34
- data/lib/action_dispatch/templates/rescues/_request_and_response.text.erb +0 -23
- data/lib/action_dispatch/templates/rescues/_source.erb +0 -29
- data/lib/action_dispatch/templates/rescues/_trace.html.erb +0 -72
- data/lib/action_dispatch/templates/rescues/_trace.text.erb +0 -9
- data/lib/action_dispatch/templates/rescues/_web_console.html.erb +0 -420
- data/lib/action_dispatch/templates/rescues/diagnostics.html.erb +0 -18
- data/lib/action_dispatch/templates/rescues/diagnostics.text.erb +0 -9
- data/lib/action_dispatch/templates/rescues/layout.erb +0 -160
- data/lib/action_dispatch/templates/rescues/missing_template.html.erb +0 -13
- data/lib/action_dispatch/templates/rescues/missing_template.text.erb +0 -3
- data/lib/action_dispatch/templates/rescues/routing_error.html.erb +0 -34
- data/lib/action_dispatch/templates/rescues/routing_error.text.erb +0 -11
- data/lib/action_dispatch/templates/rescues/template_error.html.erb +0 -22
- data/lib/action_dispatch/templates/rescues/template_error.text.erb +0 -7
- data/lib/action_dispatch/templates/rescues/unknown_action.html.erb +0 -6
- data/lib/action_dispatch/templates/rescues/unknown_action.text.erb +0 -3
- data/lib/action_dispatch/templates/routes/_route.html.erb +0 -16
- data/lib/action_dispatch/templates/routes/_table.html.erb +0 -200
- data/lib/assets/javascripts/web-console.js +0 -1
- data/lib/assets/javascripts/web_console.js +0 -41
- data/lib/web_console/controller_helpers.rb +0 -46
- data/lib/web_console/core_ext/exception.rb +0 -7
- data/lib/web_console/core_ext/exception/jruby.rb +0 -25
- data/lib/web_console/core_ext/exception/rubinius.rb +0 -32
- data/lib/web_console/engine.rb +0 -47
- data/lib/web_console/repl_session.rb +0 -89
- data/lib/web_console/unsupported_platforms.rb +0 -28
- data/lib/web_console/view_helpers.rb +0 -16
- data/test/action_pack/exception_wrapper_test.rb +0 -26
- data/test/controllers/tests_controller_test.rb +0 -41
- data/test/web_console/core_ext/exception_test.rb +0 -46
- data/test/web_console/engine_test.rb +0 -108
- data/test/web_console/repl_session_test.rb +0 -32
- data/test/web_console/repl_test.rb +0 -75
@@ -1,105 +0,0 @@
|
|
1
|
-
module ActionDispatch
|
2
|
-
class DebugExceptions
|
3
|
-
RESCUES_TEMPLATE_PATH.replace(File.expand_path('../templates', __FILE__))
|
4
|
-
|
5
|
-
def call(env)
|
6
|
-
request = Request.new(env)
|
7
|
-
|
8
|
-
if request.put? && request.xhr? && allowed?(request) && m = env["PATH_INFO"].match(%r{/repl_sessions/(?<id>.+?)\z})
|
9
|
-
update_repl_session(m[:id], request.params[:input])
|
10
|
-
elsif request.post? && request.xhr? && allowed?(request) && m = env["PATH_INFO"].match(%r{/repl_sessions/(?<id>.+?)/trace\z})
|
11
|
-
change_stack_trace(m[:id], request.params[:frame_id])
|
12
|
-
else
|
13
|
-
middleware_call(env)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def middleware_call(env)
|
18
|
-
_, headers, body = response = @app.call(env)
|
19
|
-
|
20
|
-
if headers['X-Cascade'] == 'pass'
|
21
|
-
body.close if body.respond_to?(:close)
|
22
|
-
raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
|
23
|
-
end
|
24
|
-
|
25
|
-
response
|
26
|
-
rescue Exception => exception
|
27
|
-
raise exception if env['action_dispatch.show_exceptions'] == false
|
28
|
-
render_exception(env, exception)
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
def allowed?(request)
|
34
|
-
request.remote_ip.in?(WebConsole.config.whitelisted_ips)
|
35
|
-
end
|
36
|
-
|
37
|
-
def update_repl_session(id, input)
|
38
|
-
console_session = WebConsole::REPLSession.find(id)
|
39
|
-
response = console_session.save(input: input)
|
40
|
-
[ 200, { "Content-Type" => "text/plain; charset=utf-8" }, [ response.to_json ] ]
|
41
|
-
end
|
42
|
-
|
43
|
-
def change_stack_trace(id, frame_id)
|
44
|
-
console_session = WebConsole::REPLSession.find(id)
|
45
|
-
binding = console_session.binding_stack[frame_id.to_i]
|
46
|
-
console_session.binding = binding
|
47
|
-
[ 200, { "Content-Type" => "text/plain; charset=utf-8" }, [ JSON.dump("success") ] ]
|
48
|
-
end
|
49
|
-
|
50
|
-
def render_exception(env, exception)
|
51
|
-
wrapper = ExceptionWrapper.new(env, exception)
|
52
|
-
log_error(env, wrapper)
|
53
|
-
|
54
|
-
if env['action_dispatch.show_detailed_exceptions']
|
55
|
-
request = Request.new(env)
|
56
|
-
if allowed?(request)
|
57
|
-
console_session = WebConsole::REPLSession.create(
|
58
|
-
binding: wrapper.exception.bindings.first,
|
59
|
-
binding_stack: exception.bindings
|
60
|
-
)
|
61
|
-
end
|
62
|
-
|
63
|
-
traces = wrapper.traces
|
64
|
-
extract_sources = wrapper.extract_sources
|
65
|
-
console_session = WebConsole::REPLSession.create(
|
66
|
-
binding: exception.bindings.first,
|
67
|
-
binding_stack: exception.bindings
|
68
|
-
)
|
69
|
-
|
70
|
-
trace_to_show = 'Application Trace'
|
71
|
-
if traces[trace_to_show].empty? && wrapper.rescue_template != 'routing_error'
|
72
|
-
trace_to_show = 'Full Trace'
|
73
|
-
end
|
74
|
-
|
75
|
-
if source_to_show = traces[trace_to_show].first
|
76
|
-
source_to_show_id = source_to_show[:id]
|
77
|
-
end
|
78
|
-
|
79
|
-
template = ActionView::Base.new([ RESCUES_TEMPLATE_PATH ],
|
80
|
-
request: request,
|
81
|
-
exception: wrapper.exception,
|
82
|
-
show_source_idx: source_to_show_id,
|
83
|
-
trace_to_show: trace_to_show,
|
84
|
-
traces: traces,
|
85
|
-
routes_inspector: routes_inspector(exception),
|
86
|
-
source_extract: wrapper.extract_sources,
|
87
|
-
console_session: console_session
|
88
|
-
)
|
89
|
-
file = "rescues/#{wrapper.rescue_template}"
|
90
|
-
|
91
|
-
if request.xhr?
|
92
|
-
body = template.render(template: file, layout: false, formats: [ :text ])
|
93
|
-
format = "text/plain"
|
94
|
-
else
|
95
|
-
body = template.render(template: file, layout: 'rescues/layout')
|
96
|
-
format = "text/html"
|
97
|
-
end
|
98
|
-
|
99
|
-
[ wrapper.status_code, { 'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s }, [ body ] ]
|
100
|
-
else
|
101
|
-
raise exception
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
module ActionDispatch
|
2
|
-
class ExceptionWrapper
|
3
|
-
def traces
|
4
|
-
appplication_trace_with_ids = []
|
5
|
-
framework_trace_with_ids = []
|
6
|
-
full_trace_with_ids = []
|
7
|
-
|
8
|
-
if full_trace
|
9
|
-
full_trace.each_with_index do |trace, idx|
|
10
|
-
trace_with_id = { id: idx, trace: trace }
|
11
|
-
|
12
|
-
appplication_trace_with_ids << trace_with_id if application_trace.include?(trace)
|
13
|
-
framework_trace_with_ids << trace_with_id if framework_trace.include?(trace)
|
14
|
-
full_trace_with_ids << trace_with_id
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
{
|
19
|
-
"Application Trace" => appplication_trace_with_ids,
|
20
|
-
"Framework Trace" => framework_trace_with_ids,
|
21
|
-
"Full Trace" => full_trace_with_ids
|
22
|
-
}
|
23
|
-
end
|
24
|
-
|
25
|
-
def extract_sources
|
26
|
-
exception.backtrace.map do |trace|
|
27
|
-
file, line = trace.split(":")
|
28
|
-
line_number = line.to_i
|
29
|
-
|
30
|
-
{
|
31
|
-
code: source_fragment(file, line_number) || {},
|
32
|
-
file: file,
|
33
|
-
line_number: line_number
|
34
|
-
}
|
35
|
-
end if exception.backtrace
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
<% unless @exception.blamed_files.blank? %>
|
2
|
-
<% if (hide = @exception.blamed_files.length > 8) %>
|
3
|
-
<a href="#" onclick="return toggleTrace()">Toggle blamed files</a>
|
4
|
-
<% end %>
|
5
|
-
<pre id="blame_trace" <%='style="display:none"' if hide %>><code><%= @exception.describe_blame %></code></pre>
|
6
|
-
<% end %>
|
7
|
-
|
8
|
-
<%
|
9
|
-
clean_params = @request.filtered_parameters.clone
|
10
|
-
clean_params.delete("action")
|
11
|
-
clean_params.delete("controller")
|
12
|
-
|
13
|
-
request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
|
14
|
-
|
15
|
-
def debug_hash(object)
|
16
|
-
object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
|
17
|
-
end unless self.class.method_defined?(:debug_hash)
|
18
|
-
%>
|
19
|
-
|
20
|
-
<h2 style="margin-top: 30px">Request</h2>
|
21
|
-
<p><b>Parameters</b>:</p> <pre><%= request_dump %></pre>
|
22
|
-
|
23
|
-
<div class="details">
|
24
|
-
<div class="summary"><a href="#" onclick="return toggleSessionDump()">Toggle session dump</a></div>
|
25
|
-
<div id="session_dump" style="display:none"><pre><%= debug_hash @request.session %></pre></div>
|
26
|
-
</div>
|
27
|
-
|
28
|
-
<div class="details">
|
29
|
-
<div class="summary"><a href="#" onclick="return toggleEnvDump()">Toggle env dump</a></div>
|
30
|
-
<div id="env_dump" style="display:none"><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></div>
|
31
|
-
</div>
|
32
|
-
|
33
|
-
<h2 style="margin-top: 30px">Response</h2>
|
34
|
-
<p><b>Headers</b>:</p> <pre><%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre>
|
@@ -1,23 +0,0 @@
|
|
1
|
-
<%
|
2
|
-
clean_params = @request.filtered_parameters.clone
|
3
|
-
clean_params.delete("action")
|
4
|
-
clean_params.delete("controller")
|
5
|
-
|
6
|
-
request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
|
7
|
-
|
8
|
-
def debug_hash(object)
|
9
|
-
object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
|
10
|
-
end unless self.class.method_defined?(:debug_hash)
|
11
|
-
%>
|
12
|
-
|
13
|
-
Request parameters
|
14
|
-
<%= request_dump %>
|
15
|
-
|
16
|
-
Session dump
|
17
|
-
<%= debug_hash @request.session %>
|
18
|
-
|
19
|
-
Env dump
|
20
|
-
<%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %>
|
21
|
-
|
22
|
-
Response headers
|
23
|
-
<%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %>
|
@@ -1,29 +0,0 @@
|
|
1
|
-
<% if @source_extract %>
|
2
|
-
<% @source_extract.each_with_index do |extract_source, index| %>
|
3
|
-
<% if extract_source[:code] %>
|
4
|
-
<div class="source <%="hidden" if @show_source_idx != index%>" id="frame-source-<%=index%>">
|
5
|
-
<div class="info">
|
6
|
-
Extracted source (around line <strong>#<%= extract_source[:line_number] %></strong>):
|
7
|
-
</div>
|
8
|
-
<div class="data">
|
9
|
-
<table cellpadding="0" cellspacing="0" class="lines">
|
10
|
-
<tr>
|
11
|
-
<td>
|
12
|
-
<pre class="line_numbers">
|
13
|
-
<% extract_source[:code].each_key do |line_number| %>
|
14
|
-
<span><%= line_number -%></span>
|
15
|
-
<% end %>
|
16
|
-
</pre>
|
17
|
-
</td>
|
18
|
-
<td width="100%">
|
19
|
-
<pre>
|
20
|
-
<% extract_source[:code].each do |line, source| -%><div class="line<%= " active" if line == extract_source[:line_number] -%>"><%= source -%></div><% end -%>
|
21
|
-
</pre>
|
22
|
-
</td>
|
23
|
-
</tr>
|
24
|
-
</table>
|
25
|
-
</div>
|
26
|
-
</div>
|
27
|
-
<% end %>
|
28
|
-
<% end %>
|
29
|
-
<% end %>
|
@@ -1,72 +0,0 @@
|
|
1
|
-
<% names = @traces.keys %>
|
2
|
-
|
3
|
-
<p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
|
4
|
-
|
5
|
-
<div id="traces">
|
6
|
-
<% names.each do |name| %>
|
7
|
-
<%
|
8
|
-
show = "show('#{name.gsub(/\s/, '-')}');"
|
9
|
-
hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}');"}
|
10
|
-
%>
|
11
|
-
<a href="#" onclick="<%= hide.join %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
|
12
|
-
<% end %>
|
13
|
-
|
14
|
-
<% @traces.each do |name, trace| %>
|
15
|
-
<div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == @trace_to_show) ? 'block' : 'none' %>;">
|
16
|
-
<pre><code><% trace.each do |frame| %><a class="trace-frames" data-frame-id="<%= frame[:id] %>" href="#"><%= frame[:trace] %></a><br><% end %></code></pre>
|
17
|
-
</div>
|
18
|
-
<% end %>
|
19
|
-
|
20
|
-
<script type="text/javascript">
|
21
|
-
var traceFrames = document.getElementsByClassName('trace-frames');
|
22
|
-
var selectedFrame, currentSource = document.getElementById('frame-source-0');
|
23
|
-
|
24
|
-
// Add click listeners for all stack frames
|
25
|
-
for (var i = 0; i < traceFrames.length; i++) {
|
26
|
-
traceFrames[i].addEventListener('click', function(e) {
|
27
|
-
e.preventDefault();
|
28
|
-
var target = e.target;
|
29
|
-
var frameId = target.dataset.frameId;
|
30
|
-
|
31
|
-
// Change the binding of the console.
|
32
|
-
changeBinding(frameId, function() {
|
33
|
-
if (selectedFrame) {
|
34
|
-
selectedFrame.className = selectedFrame.className.replace("selected", "");
|
35
|
-
}
|
36
|
-
|
37
|
-
target.className += " selected";
|
38
|
-
selectedFrame = target;
|
39
|
-
});
|
40
|
-
|
41
|
-
// Change the extracted source code
|
42
|
-
changeSourceExtract(frameId);
|
43
|
-
});
|
44
|
-
}
|
45
|
-
|
46
|
-
function changeBinding(frameId, callback) {
|
47
|
-
var consoleEl = document.getElementById('console');
|
48
|
-
if (! consoleEl) { return; }
|
49
|
-
var url = consoleEl.dataset.remotePath + "/trace";
|
50
|
-
var params = "frame_id=" + encodeURIComponent(frameId);
|
51
|
-
var xhr = new XMLHttpRequest();
|
52
|
-
xhr.open("POST", url, true);
|
53
|
-
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
54
|
-
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
55
|
-
xhr.send(params);
|
56
|
-
xhr.onreadystatechange = function() {
|
57
|
-
if (xhr.readyState == 4 && xhr.status == 200) {
|
58
|
-
callback();
|
59
|
-
}
|
60
|
-
}
|
61
|
-
}
|
62
|
-
|
63
|
-
function changeSourceExtract(frameId) {
|
64
|
-
var el = document.getElementById('frame-source-' + frameId);
|
65
|
-
if (currentSource && el) {
|
66
|
-
currentSource.className += " hidden";
|
67
|
-
el.className = el.className.replace(" hidden", "");
|
68
|
-
currentSource = el;
|
69
|
-
}
|
70
|
-
}
|
71
|
-
</script>
|
72
|
-
</div>
|
@@ -1,420 +0,0 @@
|
|
1
|
-
<% if @console_session && @console_session.binding %>
|
2
|
-
<div id="console"
|
3
|
-
data-remote-path='<%= "console/repl_sessions/#{@console_session.id}" %>'
|
4
|
-
data-initial-prompt='<%= @console_session.prompt %>'>
|
5
|
-
</div>
|
6
|
-
|
7
|
-
<script type="text/javascript">
|
8
|
-
(function() {
|
9
|
-
// DOM helpers
|
10
|
-
function hasClass(el, className) {
|
11
|
-
var regex = new RegExp('(?:^|\\s)' + className + '(?!\\S)', 'g');
|
12
|
-
return el.className.match(regex);
|
13
|
-
}
|
14
|
-
|
15
|
-
function addClass(el, className) {
|
16
|
-
el.className += " " + className;
|
17
|
-
}
|
18
|
-
|
19
|
-
function removeClass(el, className) {
|
20
|
-
var regex = new RegExp('(?:^|\\s)' + className + '(?!\\S)', 'g');
|
21
|
-
el.className = el.className.replace(regex, '');
|
22
|
-
}
|
23
|
-
|
24
|
-
function removeAllChildren(el) {
|
25
|
-
while (el.firstChild) {
|
26
|
-
el.removeChild(el.firstChild);
|
27
|
-
}
|
28
|
-
}
|
29
|
-
|
30
|
-
function escapeHTML(html) {
|
31
|
-
return html
|
32
|
-
.replace(/&/g, '&')
|
33
|
-
.replace(/</g, '<')
|
34
|
-
.replace(/>/g, '>')
|
35
|
-
.replace(/"/g, '"')
|
36
|
-
.replace(/'/g, ''')
|
37
|
-
.replace(/`/g, '`');
|
38
|
-
}
|
39
|
-
|
40
|
-
// Add CSS styles dynamically. This probably doesnt work for IE <8.
|
41
|
-
var style = document.createElement('style');
|
42
|
-
style.type = 'text/css';
|
43
|
-
style.innerHTML =
|
44
|
-
"#console { position: fixed; left: 0; bottom: 0; width: 100%; height: 150px; border: 1px solid; padding: 0 0 20px 0; overflow: none; margin: 0; background: none repeat scroll 0% 0% #333; } " +
|
45
|
-
"#console div.console-inner { font-family: monospace; font-size: 11px; height: 100%; overflow: auto; background: #333; border: 0; padding: 0 margin: 0; } " +
|
46
|
-
"#console div.console-prompt-box { color: #FFF; } " +
|
47
|
-
"#console pre.console-message { color: #1AD027; margin: 0; border: 0; white-space: pre-wrap; background-color: #333; padding: 0; } " +
|
48
|
-
"#console div.console-focus span.console-cursor { background: #FEFEFE; color: #333; font-weight: bold; }";
|
49
|
-
document.getElementsByTagName('head')[0].appendChild(style);
|
50
|
-
|
51
|
-
/**
|
52
|
-
* Constructor for command storage.
|
53
|
-
* It uses localStorage if available. Otherwise fallback to normal JS array.
|
54
|
-
*/
|
55
|
-
function CommandStorage() {
|
56
|
-
this.previousCommands = [];
|
57
|
-
var previousCommandOffset = 0;
|
58
|
-
var hasLocalStorage = typeof window.localStorage !== 'undefined';
|
59
|
-
var STORAGE_KEY = "web_console_previous_commands";
|
60
|
-
var MAX_STORAGE = 100;
|
61
|
-
|
62
|
-
if (hasLocalStorage) {
|
63
|
-
this.previousCommands = JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
|
64
|
-
previousCommandOffset = this.previousCommands.length;
|
65
|
-
}
|
66
|
-
|
67
|
-
this.addCommand = function(command) {
|
68
|
-
previousCommandOffset = this.previousCommands.push(command);
|
69
|
-
|
70
|
-
if (previousCommandOffset > MAX_STORAGE) {
|
71
|
-
this.previousCommands.splice(0, 1);
|
72
|
-
previousCommandOffset = MAX_STORAGE;
|
73
|
-
}
|
74
|
-
|
75
|
-
if (hasLocalStorage) {
|
76
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(this.previousCommands));
|
77
|
-
}
|
78
|
-
};
|
79
|
-
|
80
|
-
this.navigate = function(offset) {
|
81
|
-
previousCommandOffset += offset;
|
82
|
-
|
83
|
-
if (previousCommandOffset < 0) {
|
84
|
-
previousCommandOffset = -1;
|
85
|
-
return null;
|
86
|
-
}
|
87
|
-
|
88
|
-
if (previousCommandOffset >= this.previousCommands.length) {
|
89
|
-
previousCommandOffset = this.previousCommands.length;
|
90
|
-
return null;
|
91
|
-
}
|
92
|
-
|
93
|
-
return this.previousCommands[previousCommandOffset];
|
94
|
-
}
|
95
|
-
}
|
96
|
-
|
97
|
-
// HTML strings for dynamic elements.
|
98
|
-
var consoleInnerHtml = [
|
99
|
-
"<div id='resizer' style='width: 100%; height: 3px; cursor: ns-resize;'></div>",
|
100
|
-
"<div class='console-inner'></div>",
|
101
|
-
"<input id='clipboard' type='text' style='visibility: hidden;'>"
|
102
|
-
].join("");
|
103
|
-
|
104
|
-
var promptBoxHtml = [
|
105
|
-
"<span class='console-prompt-label' style='display: inline; color: #FFF; background: none repeat scroll 0% 0% #333; border: 0; padding: 0;'></span>",
|
106
|
-
"<pre class='console-prompt-display' style='display: inline; color: #FFF; background: none repeat scroll 0% 0% #333; border: 0; padding: 0;'></pre>"
|
107
|
-
].join("");
|
108
|
-
|
109
|
-
// REPLConsole Constructor
|
110
|
-
function REPLConsole(config) {
|
111
|
-
this.commandStorage = new CommandStorage();
|
112
|
-
this.prompt = config && config.promptLabel ? config.promptLabel : ' >>';
|
113
|
-
this.commandHandle = config && config.commandHandle ? config.commandHandle : function() { return this; }
|
114
|
-
}
|
115
|
-
|
116
|
-
|
117
|
-
REPLConsole.prototype.install = function(container) {
|
118
|
-
var _this = this;
|
119
|
-
|
120
|
-
document.onkeydown = function(ev) {
|
121
|
-
if (_this.focused) { _this.onKeyDown(ev); }
|
122
|
-
};
|
123
|
-
|
124
|
-
document.onkeypress = function(ev) {
|
125
|
-
if (_this.focused) { _this.onKeyPress(ev); }
|
126
|
-
};
|
127
|
-
|
128
|
-
document.addEventListener('mousedown', function(ev) {
|
129
|
-
var el = ev.target || ev.srcElement;
|
130
|
-
|
131
|
-
if (el) {
|
132
|
-
do {
|
133
|
-
if (el === container) {
|
134
|
-
_this.focus();
|
135
|
-
return;
|
136
|
-
}
|
137
|
-
} while (el = el.parentNode);
|
138
|
-
|
139
|
-
_this.blur();
|
140
|
-
}
|
141
|
-
});
|
142
|
-
|
143
|
-
// Render the console.
|
144
|
-
container.innerHTML = consoleInnerHtml;
|
145
|
-
// Make the console resizable.
|
146
|
-
document.getElementById('resizer').addEventListener('mousedown', function(ev) {
|
147
|
-
var startY = ev.clientY;
|
148
|
-
var startHeight = parseInt(document.defaultView.getComputedStyle(consoleDiv).height, 10);
|
149
|
-
|
150
|
-
var doDrag = function(e) {
|
151
|
-
consoleDiv.style.height = (startHeight + startY - e.clientY) + 'px';
|
152
|
-
};
|
153
|
-
|
154
|
-
var stopDrag = function(e) {
|
155
|
-
document.documentElement.removeEventListener('mousemove', doDrag, false);
|
156
|
-
document.documentElement.removeEventListener('mouseup', stopDrag, false);
|
157
|
-
};
|
158
|
-
|
159
|
-
document.documentElement.addEventListener('mousemove', doDrag, false);
|
160
|
-
document.documentElement.addEventListener('mouseup', stopDrag, false);
|
161
|
-
});
|
162
|
-
|
163
|
-
// Initialize
|
164
|
-
this.inner = container.getElementsByClassName('console-inner')[0];
|
165
|
-
this.clipboard = document.getElementById('clipboard');
|
166
|
-
this.newPromptBox();
|
167
|
-
this.focus();
|
168
|
-
};
|
169
|
-
|
170
|
-
REPLConsole.prototype.focus = function() {
|
171
|
-
if (! this.focused) {
|
172
|
-
this.focused = true;
|
173
|
-
if (! hasClass(this.inner, "console-focus")) {
|
174
|
-
addClass(this.inner, "console-focus");
|
175
|
-
}
|
176
|
-
this.scrollToBottom();
|
177
|
-
}
|
178
|
-
};
|
179
|
-
|
180
|
-
REPLConsole.prototype.blur = function() {
|
181
|
-
this.focused = false;
|
182
|
-
removeClass(this.inner, "console-focus");
|
183
|
-
};
|
184
|
-
|
185
|
-
/**
|
186
|
-
* Add a new empty prompt box to the console.
|
187
|
-
*/
|
188
|
-
REPLConsole.prototype.newPromptBox = function() {
|
189
|
-
// Remove the caret from previous prompt display if any.
|
190
|
-
if (this.promptDisplay) {
|
191
|
-
this.removeCaretFromPrompt();
|
192
|
-
}
|
193
|
-
|
194
|
-
var promptBox = document.createElement('div');
|
195
|
-
promptBox.className = "console-prompt-box";
|
196
|
-
promptBox.innerHTML = promptBoxHtml;
|
197
|
-
this.promptLabel = promptBox.getElementsByClassName('console-prompt-label')[0];
|
198
|
-
this.promptDisplay = promptBox.getElementsByClassName('console-prompt-display')[0];
|
199
|
-
// Render the prompt box
|
200
|
-
this.setInput("");
|
201
|
-
this.promptLabel.innerHTML = this.prompt;
|
202
|
-
this.inner.appendChild(promptBox);
|
203
|
-
this.scrollToBottom();
|
204
|
-
};
|
205
|
-
|
206
|
-
/**
|
207
|
-
* Remove the caret from the prompt box,
|
208
|
-
* mainly before adding a new prompt box.
|
209
|
-
* For simplicity, just re-render the prompt box
|
210
|
-
* with caret position -1.
|
211
|
-
*/
|
212
|
-
REPLConsole.prototype.removeCaretFromPrompt = function() {
|
213
|
-
this.setInput(this._input, -1);
|
214
|
-
};
|
215
|
-
|
216
|
-
REPLConsole.prototype.setInput = function(input, caretPos) {
|
217
|
-
this._caretPos = caretPos === undefined ? input.length : caretPos;
|
218
|
-
this._input = input;
|
219
|
-
this.renderInput();
|
220
|
-
};
|
221
|
-
|
222
|
-
/**
|
223
|
-
* Add some text to the existing input.
|
224
|
-
*/
|
225
|
-
REPLConsole.prototype.addToInput = function(val, caretPos) {
|
226
|
-
caretPos = caretPos || this._caretPos;
|
227
|
-
var before = this._input.substring(0, caretPos);
|
228
|
-
var after = this._input.substring(caretPos, this._input.length);
|
229
|
-
var newInput = before + val + after;
|
230
|
-
this.setInput(newInput, caretPos + val.length);
|
231
|
-
};
|
232
|
-
|
233
|
-
/**
|
234
|
-
* Render the input prompt. This is called whenever
|
235
|
-
* the user input changes, sometimes not very efficient.
|
236
|
-
*/
|
237
|
-
REPLConsole.prototype.renderInput = function() {
|
238
|
-
// Clear the current input.
|
239
|
-
removeAllChildren(this.promptDisplay);
|
240
|
-
|
241
|
-
var promptCursor = document.createElement('span');
|
242
|
-
promptCursor.className = "console-cursor";
|
243
|
-
var before, current, after;
|
244
|
-
|
245
|
-
if (this._caretPos < 0) {
|
246
|
-
before = this._input;
|
247
|
-
current = after = "";
|
248
|
-
} else if (this._caretPos === this._input.length) {
|
249
|
-
before = this._input;
|
250
|
-
current = "\u00A0";
|
251
|
-
after = "";
|
252
|
-
} else {
|
253
|
-
before = this._input.substring(0, this._caretPos);
|
254
|
-
current = this._input.charAt(this._caretPos);
|
255
|
-
after = this._input.substring(this._caretPos + 1, this._input.length);
|
256
|
-
}
|
257
|
-
|
258
|
-
this.promptDisplay.appendChild(document.createTextNode(before));
|
259
|
-
promptCursor.appendChild(document.createTextNode(current));
|
260
|
-
this.promptDisplay.appendChild(promptCursor);
|
261
|
-
this.promptDisplay.appendChild(document.createTextNode(after));
|
262
|
-
};
|
263
|
-
|
264
|
-
REPLConsole.prototype.writeOutput = function(output) {
|
265
|
-
var consoleMessage = document.createElement('pre');
|
266
|
-
consoleMessage.className = "console-message";
|
267
|
-
consoleMessage.innerHTML = escapeHTML(output);
|
268
|
-
this.inner.appendChild(consoleMessage);
|
269
|
-
this.newPromptBox();
|
270
|
-
};
|
271
|
-
|
272
|
-
REPLConsole.prototype.onEnterKey = function() {
|
273
|
-
var input = this._input;
|
274
|
-
|
275
|
-
if(input != "" && input !== undefined) {
|
276
|
-
this.commandStorage.addCommand(input);
|
277
|
-
}
|
278
|
-
|
279
|
-
this.commandHandle(input);
|
280
|
-
};
|
281
|
-
|
282
|
-
REPLConsole.prototype.onNavigateHistory = function(offset) {
|
283
|
-
var command = this.commandStorage.navigate(offset) || "";
|
284
|
-
this.setInput(command);
|
285
|
-
};
|
286
|
-
|
287
|
-
/**
|
288
|
-
* Handle control keys like up, down, left, right.
|
289
|
-
*/
|
290
|
-
REPLConsole.prototype.onKeyDown = function(ev) {
|
291
|
-
switch (ev.keyCode) {
|
292
|
-
case 13:
|
293
|
-
// Enter key
|
294
|
-
this.onEnterKey();
|
295
|
-
ev.preventDefault();
|
296
|
-
break;
|
297
|
-
case 80:
|
298
|
-
// Ctrl-P
|
299
|
-
if (! ev.ctrlKey) break;
|
300
|
-
case 38:
|
301
|
-
// Up arrow
|
302
|
-
this.onNavigateHistory(-1);
|
303
|
-
ev.preventDefault();
|
304
|
-
break;
|
305
|
-
case 78:
|
306
|
-
// Ctrl-N
|
307
|
-
if (! ev.ctrlKey) break;
|
308
|
-
case 40:
|
309
|
-
// Down arrow
|
310
|
-
this.onNavigateHistory(1);
|
311
|
-
ev.preventDefault();
|
312
|
-
break;
|
313
|
-
case 37:
|
314
|
-
// Left arrow
|
315
|
-
var caretPos = this._caretPos > 0 ? this._caretPos - 1 : this._caretPos;
|
316
|
-
this.setInput(this._input, caretPos);
|
317
|
-
ev.preventDefault();
|
318
|
-
break;
|
319
|
-
case 39:
|
320
|
-
// Right arrow
|
321
|
-
var length = this._input.length;
|
322
|
-
var caretPos = this._caretPos < length ? this._caretPos + 1 : this._caretPos;
|
323
|
-
this.setInput(this._input, caretPos);
|
324
|
-
ev.preventDefault();
|
325
|
-
break;
|
326
|
-
case 8:
|
327
|
-
// Delete
|
328
|
-
this.deleteAtCurrent();
|
329
|
-
ev.preventDefault();
|
330
|
-
break;
|
331
|
-
case 86:
|
332
|
-
// ctrl/command-v
|
333
|
-
if (ev.ctrlKey || ev.metaKey) {
|
334
|
-
// Let the pasted text go to our clipboard.
|
335
|
-
this.clipboard.focus();
|
336
|
-
|
337
|
-
// Pasting to clipboard doesn't happen immediately,
|
338
|
-
// so we have to wait for a while to get the pasted text.
|
339
|
-
var _this = this;
|
340
|
-
setTimeout(function() {
|
341
|
-
_this.addToInput(_this.clipboard.value);
|
342
|
-
_this.clipboard.value = "";
|
343
|
-
_this.clipboard.blur();
|
344
|
-
}, 10);
|
345
|
-
}
|
346
|
-
break;
|
347
|
-
default:
|
348
|
-
break;
|
349
|
-
}
|
350
|
-
|
351
|
-
ev.stopPropagation();
|
352
|
-
};
|
353
|
-
|
354
|
-
/**
|
355
|
-
* Handle input key press.
|
356
|
-
*/
|
357
|
-
REPLConsole.prototype.onKeyPress = function(ev) {
|
358
|
-
// Only write to the console if it's a single key press.
|
359
|
-
if (ev.ctrlKey || ev.metaKey) { return; }
|
360
|
-
var keyCode = ev.keyCode || ev.which;
|
361
|
-
this.insertAtCurrent(String.fromCharCode(keyCode));
|
362
|
-
ev.stopPropagation();
|
363
|
-
ev.preventDefault();
|
364
|
-
};
|
365
|
-
|
366
|
-
/**
|
367
|
-
* Delete a character at the current position.
|
368
|
-
*/
|
369
|
-
REPLConsole.prototype.deleteAtCurrent = function() {
|
370
|
-
if (this._caretPos > 0) {
|
371
|
-
var caretPos = this._caretPos - 1;
|
372
|
-
var before = this._input.substring(0, caretPos);
|
373
|
-
var after = this._input.substring(this._caretPos, this._input.length);
|
374
|
-
this.setInput(before + after, caretPos);
|
375
|
-
}
|
376
|
-
};
|
377
|
-
|
378
|
-
/**
|
379
|
-
* Insert a character at the current position.
|
380
|
-
*/
|
381
|
-
REPLConsole.prototype.insertAtCurrent = function(char) {
|
382
|
-
var before = this._input.substring(0, this._caretPos);
|
383
|
-
var after = this._input.substring(this._caretPos, this._input.length);
|
384
|
-
this.setInput(before + char + after, this._caretPos + 1);
|
385
|
-
};
|
386
|
-
|
387
|
-
REPLConsole.prototype.scrollToBottom = function() {
|
388
|
-
this.inner.scrollTop = this.inner.scrollHeight;
|
389
|
-
};
|
390
|
-
|
391
|
-
window.REPLConsole = REPLConsole;
|
392
|
-
})();
|
393
|
-
|
394
|
-
// Install the console
|
395
|
-
var consoleDiv = document.getElementById('console');
|
396
|
-
var replConsole = new REPLConsole({
|
397
|
-
promptLabel: consoleDiv.dataset.initialPrompt,
|
398
|
-
commandHandle: function(line) {
|
399
|
-
var _this = this;
|
400
|
-
var xhr = new XMLHttpRequest();
|
401
|
-
var url = consoleDiv.dataset.remotePath;
|
402
|
-
var params = "input=" + encodeURIComponent(line);
|
403
|
-
|
404
|
-
xhr.open("PUT", url, true);
|
405
|
-
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
406
|
-
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
407
|
-
xhr.send(params);
|
408
|
-
|
409
|
-
xhr.onreadystatechange = function() {
|
410
|
-
if(xhr.readyState == 4 && xhr.status == 200) {
|
411
|
-
var response = JSON.parse(xhr.responseText);
|
412
|
-
_this.writeOutput(response.output);
|
413
|
-
}
|
414
|
-
}
|
415
|
-
}
|
416
|
-
});
|
417
|
-
|
418
|
-
replConsole.install(consoleDiv);
|
419
|
-
</script>
|
420
|
-
<% end %>
|