web-console 1.0.4 → 2.0.0.beta1
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 +26 -247
- data/lib/action_dispatch/debug_exceptions.rb +115 -0
- data/lib/action_dispatch/exception_wrapper.rb +15 -0
- data/lib/action_dispatch/templates/rescues/_request_and_response.html.erb +34 -0
- data/lib/action_dispatch/templates/rescues/_request_and_response.text.erb +23 -0
- data/lib/action_dispatch/templates/rescues/_source.erb +27 -0
- data/lib/action_dispatch/templates/rescues/_trace.html.erb +76 -0
- data/lib/action_dispatch/templates/rescues/_trace.text.erb +15 -0
- data/lib/action_dispatch/templates/rescues/_web_console.html.erb +382 -0
- data/lib/action_dispatch/templates/rescues/diagnostics.html.erb +18 -0
- data/lib/action_dispatch/templates/rescues/diagnostics.text.erb +9 -0
- data/lib/action_dispatch/templates/rescues/layout.erb +162 -0
- data/lib/action_dispatch/templates/rescues/missing_template.html.erb +7 -0
- data/lib/action_dispatch/templates/rescues/missing_template.text.erb +3 -0
- data/lib/action_dispatch/templates/rescues/routing_error.html.erb +30 -0
- data/lib/action_dispatch/templates/rescues/routing_error.text.erb +11 -0
- data/lib/action_dispatch/templates/rescues/template_error.html.erb +22 -0
- data/lib/action_dispatch/templates/rescues/template_error.text.erb +8 -0
- data/lib/action_dispatch/templates/rescues/unknown_action.html.erb +6 -0
- data/lib/action_dispatch/templates/rescues/unknown_action.text.erb +3 -0
- data/lib/web_console.rb +17 -7
- data/lib/web_console/exception_extension.rb +22 -0
- data/lib/web_console/railtie.rb +15 -0
- data/lib/web_console/repl.rb +24 -0
- data/lib/web_console/repl_session.rb +90 -0
- data/lib/web_console/version.rb +1 -1
- data/lib/web_console/view_helpers.rb +21 -0
- data/test/action_pack/exception_wrapper_test.rb +26 -0
- data/test/dummy/app/controllers/exception_test_controller.rb +11 -0
- data/test/dummy/app/controllers/helper_test_controller.rb +5 -0
- data/test/dummy/app/views/helper_test/index.html.erb +220 -0
- data/test/dummy/app/views/layouts/application.html.erb +2 -0
- data/test/dummy/config/application.rb +0 -34
- data/test/dummy/config/routes.rb +3 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/log/development.log +61270 -0
- data/test/dummy/log/test.log +3917 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/038461854af2e8bccdb29768efd4768f +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/0ec396634a5f6808b026257fd107c355 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/127a54171eea8d294e4673599861787d +0 -0
- data/{app/assets/stylesheets/web_console/application.css → test/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705} +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/17c571144b4e44da39bddb2d2c412414 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/1cb77d8cf661ccbc9de08f347c89b9f1 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/204edd12a29660722d4e0d8de9bd6652 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/2b96b037f3dfeccfe27113eb95b06ea1 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/2c853768baf811357d81d41bdfd05dcf +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/314d48e543146f617c4d3439a4d8d40d +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/34f21019a876722b8c24a6da4f0ef50b +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
- data/{vendor/assets/javascripts/term.js → test/dummy/tmp/cache/assets/development/sprockets/36341e42f23669574fa1027d0958ff3e} +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/44117154e909436e7eeaf10cdb18d2b4 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/496864a905d53afd8e176f29500f96a8 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/55b7b76605fdffe31d737d4ac1f1ef7b +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/5ac98782fe3dfd0a766f75ce1801f0a0 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/6088d6f344b38303cc8028057d69e0f9 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/676dcf9b2d01b9dc7bd3183d8da88463 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/680381170dc160e358fc28076ea6886c +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/6ad7acc9a22fe2a67ec24a1fc866c20e +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/6bdb0d0c602e0e1bc304dc697e2cc6de +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/6dc8d7aa69668fce85683aaad6615432 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/6e4d5b32cc444226f6597198994ccd5e +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/74db0ca5cb8c8c347c9131a3ff516748 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/7999e525c88173c1beb785f002effc1d +0 -0
- data/{lib/assets/javascripts/web_console.js → test/dummy/tmp/cache/assets/development/sprockets/7a50a9e605754e99783de95715b976b0} +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/806b0e33a2fe8e1245534345fa27c30a +0 -0
- data/{app/assets/javascripts/web_console/console_sessions.js → test/dummy/tmp/cache/assets/development/sprockets/8aa4c7aabff23c8089d41e9e54193483} +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/90396626cba6cbec37e32038e6c54e76 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/976b28910aa72c90a3b30c6e940f51df +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/99e1bd7cbc437505bc8f07bc528c721c +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/aaccf2c9ae2add0863c9a49e0042a097 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/ae4677d24a79d9411f2fced5011d5807 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/b2401118729720034b6f3eda0b4c5025 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/c649837df826fc310cb80f1adafd6b8d +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/cac185d59612fae451a12df3fc21bb51 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/cb0065359d3b5b296f71d673f4b276e9 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/cee8c6b09c33d2b276753e959712724e +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/d1f6e06bc2f112c4ec3a4c3f68351878 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/d20d83fd7ffa378b1b2b901786d640f3 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/d38c7c3aa1e72b55769ccb3607641ef4 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/d6b85d8b0b5c569388b89e56e9f6fed7 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/d982412def520c434e2240eae6d29cf2 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/df048a8b0897b9c04acdf59c8f95b18f +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/df600f50f002512c95d93bcfbab891ed +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/e6d6b8bde546349764be7b44ffcf5807 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/eb25265794d2f7afd1684779d84efdac +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/ee8826b12b7d9bfd717df950b58f82ab +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/ef9824789c6ed3483590e0564a12e1d1 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/fc7201c6cbef32453aa4175c520c8eae +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/17c571144b4e44da39bddb2d2c412414 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/36341e42f23669574fa1027d0958ff3e +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/55b7b76605fdffe31d737d4ac1f1ef7b +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/5ac98782fe3dfd0a766f75ce1801f0a0 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/680381170dc160e358fc28076ea6886c +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/6ad7acc9a22fe2a67ec24a1fc866c20e +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/6e4d5b32cc444226f6597198994ccd5e +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/7a50a9e605754e99783de95715b976b0 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/8aa4c7aabff23c8089d41e9e54193483 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/b2401118729720034b6f3eda0b4c5025 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/cb0065359d3b5b296f71d673f4b276e9 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/d1f6e06bc2f112c4ec3a4c3f68351878 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/d6b85d8b0b5c569388b89e56e9f6fed7 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/d982412def520c434e2240eae6d29cf2 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/df048a8b0897b9c04acdf59c8f95b18f +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/e6d6b8bde546349764be7b44ffcf5807 +0 -0
- data/test/web_console/exception_extention_test.rb +16 -0
- data/test/web_console/repl_session_test.rb +32 -0
- data/test/web_console/repl_test.rb +26 -0
- metadata +191 -58
- data/app/assets/javascripts/web_console/application.js +0 -1
- data/app/assets/stylesheets/web_console/console_sessions.css.erb +0 -6
- data/app/controllers/web_console/application_controller.rb +0 -13
- data/app/controllers/web_console/console_sessions_controller.rb +0 -43
- data/app/helpers/web_console/application_helper.rb +0 -4
- data/app/helpers/web_console/console_session_helper.rb +0 -4
- data/app/models/web_console/console_session.rb +0 -96
- data/app/views/layouts/web_console/application.html.erb +0 -14
- data/app/views/web_console/console_sessions/index.html.erb +0 -15
- data/config/routes.rb +0 -11
- data/lib/assets/javascripts/web-console.js +0 -1
- data/lib/web_console/colors.rb +0 -87
- data/lib/web_console/colors/light.rb +0 -24
- data/lib/web_console/colors/monokai.rb +0 -24
- data/lib/web_console/colors/solarized.rb +0 -47
- data/lib/web_console/colors/tango.rb +0 -24
- data/lib/web_console/colors/xterm.rb +0 -24
- data/lib/web_console/engine.rb +0 -77
- data/lib/web_console/slave.rb +0 -139
- data/test/controllers/web_console/console_sessions_controller_test.rb +0 -95
- data/test/helpers/web_console/console_session_helper_test.rb +0 -6
- data/test/models/console_session_test.rb +0 -58
- data/test/web_console/colors_test.rb +0 -58
- data/test/web_console/engine_test.rb +0 -136
- data/test/web_console/slave_test.rb +0 -71
@@ -0,0 +1,34 @@
|
|
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>
|
@@ -0,0 +1,23 @@
|
|
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' %>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<% if @extract_sources %>
|
2
|
+
<% @extract_sources.each_with_index do |extract_source, index| %>
|
3
|
+
<div class="source <%="hidden" if index != 0%>" id="frame-source-<%=index%>">
|
4
|
+
<div class="info">
|
5
|
+
Extracted source (around line <strong>#<%= extract_source[:line_number] %></strong>):
|
6
|
+
</div>
|
7
|
+
<div class="data">
|
8
|
+
<table cellpadding="0" cellspacing="0" class="lines">
|
9
|
+
<tr>
|
10
|
+
<td>
|
11
|
+
<pre class="line_numbers">
|
12
|
+
<% extract_source[:code].keys.each do |line_number| %>
|
13
|
+
<span><%= line_number -%></span>
|
14
|
+
<% end %>
|
15
|
+
</pre>
|
16
|
+
</td>
|
17
|
+
<td width="100%">
|
18
|
+
<pre>
|
19
|
+
<% extract_source[:code].each do |line, source| -%><div class="line<%= " active" if line == extract_source[:line_number] -%>"><%= source -%></div><% end -%>
|
20
|
+
</pre>
|
21
|
+
</td>
|
22
|
+
</tr>
|
23
|
+
</table>
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
<% end %>
|
27
|
+
<% end %>
|
@@ -0,0 +1,76 @@
|
|
1
|
+
<%
|
2
|
+
traces = { "Application Trace" => @application_trace,
|
3
|
+
"Framework Trace" => @framework_trace,
|
4
|
+
"Full Trace" => @full_trace }
|
5
|
+
names = traces.keys
|
6
|
+
%>
|
7
|
+
|
8
|
+
<p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
|
9
|
+
|
10
|
+
<div id="traces">
|
11
|
+
<% names.each do |name| %>
|
12
|
+
<%
|
13
|
+
show = "show('#{name.gsub(/\s/, '-')}');"
|
14
|
+
hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}');"}
|
15
|
+
%>
|
16
|
+
<a href="#" onclick="<%= hide.join %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
|
17
|
+
<% end %>
|
18
|
+
|
19
|
+
<% traces.each do |name, trace| %>
|
20
|
+
<div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == "Application Trace") ? 'block' : 'none' %>;">
|
21
|
+
<pre><code><% trace.each do |frame| %><a class="trace-frames" data-frame-id="<%= frame[:id] %>" href="#"><%= frame[:trace] %></a><br><% end %></code></pre>
|
22
|
+
</div>
|
23
|
+
<% end %>
|
24
|
+
|
25
|
+
<script type="text/javascript">
|
26
|
+
var traceFrames = document.getElementsByClassName('trace-frames');
|
27
|
+
var selectedFrame, currentSource = document.getElementById('frame-source-0');
|
28
|
+
|
29
|
+
// Add click listeners for all stack frames
|
30
|
+
for (var i = 0; i < traceFrames.length; i++) {
|
31
|
+
traceFrames[i].addEventListener('click', function(e) {
|
32
|
+
e.preventDefault();
|
33
|
+
var target = e.target;
|
34
|
+
var frame_id = target.dataset.frameId;
|
35
|
+
|
36
|
+
// Change the binding of the console.
|
37
|
+
changeBinding(frame_id, function() {
|
38
|
+
if (selectedFrame) {
|
39
|
+
selectedFrame.className = selectedFrame.className.replace("selected", "");
|
40
|
+
}
|
41
|
+
|
42
|
+
target.className += " selected";
|
43
|
+
selectedFrame = target;
|
44
|
+
});
|
45
|
+
|
46
|
+
// Change the extracted source code
|
47
|
+
changeSourceExtract(frame_id);
|
48
|
+
});
|
49
|
+
}
|
50
|
+
|
51
|
+
function changeBinding(frame_id, callback) {
|
52
|
+
var consoleEl = document.getElementById('console');
|
53
|
+
if (! consoleEl) { return; }
|
54
|
+
var url = consoleEl.dataset.remotePath + "/trace";
|
55
|
+
var params = "frame_id=" + frame_id;
|
56
|
+
var xhr = new XMLHttpRequest();
|
57
|
+
xhr.open("POST", url, true);
|
58
|
+
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
59
|
+
xhr.send(params);
|
60
|
+
xhr.onreadystatechange = function() {
|
61
|
+
if(xhr.readyState == 4 && xhr.status == 200) {
|
62
|
+
callback();
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
function changeSourceExtract(frame_id) {
|
68
|
+
var el = document.getElementById('frame-source-' + frame_id);
|
69
|
+
if (currentSource && el) {
|
70
|
+
currentSource.className += " hidden";
|
71
|
+
el.className = el.className.replace(" hidden", "");
|
72
|
+
currentSource = el;
|
73
|
+
}
|
74
|
+
}
|
75
|
+
</script>
|
76
|
+
</div>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<%
|
2
|
+
traces = { "Application Trace" => @application_trace,
|
3
|
+
"Framework Trace" => @framework_trace,
|
4
|
+
"Full Trace" => @full_trace }
|
5
|
+
%>
|
6
|
+
|
7
|
+
Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %>
|
8
|
+
|
9
|
+
<% traces.each do |name, trace| %>
|
10
|
+
<% if trace.any? %>
|
11
|
+
<%= name %>
|
12
|
+
<%= trace.join("\n") %>
|
13
|
+
|
14
|
+
<% end %>
|
15
|
+
<% end %>
|
@@ -0,0 +1,382 @@
|
|
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 id="resizer" style="width:100%; height: 3px; cursor: ns-resize;"></div>
|
6
|
+
</div>
|
7
|
+
|
8
|
+
<script type="text/javascript">
|
9
|
+
|
10
|
+
(function() {
|
11
|
+
// DOM helpers
|
12
|
+
function hasClass(el, className) {
|
13
|
+
var regex = new RegExp('(?:^|\\s)' + className + '(?!\\S)', 'g');
|
14
|
+
return el.className.match(regex);
|
15
|
+
}
|
16
|
+
|
17
|
+
function addClass(el, className) {
|
18
|
+
el.className += " " + className;
|
19
|
+
}
|
20
|
+
|
21
|
+
function removeClass(el, className) {
|
22
|
+
var regex = new RegExp('(?:^|\\s)' + className + '(?!\\S)', 'g');
|
23
|
+
el.className = el.className.replace(regex, '');
|
24
|
+
}
|
25
|
+
|
26
|
+
function removeAllChildren(el) {
|
27
|
+
while (el.firstChild) {
|
28
|
+
el.removeChild(el.firstChild);
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
function escapeHTML(html) {
|
33
|
+
return html.replace(/&/, "&").replace(/</g, "<");
|
34
|
+
}
|
35
|
+
|
36
|
+
// Add CSS styles dynamically. This probably doesnt work for IE <8.
|
37
|
+
var style = document.createElement('style');
|
38
|
+
style.type = 'text/css';
|
39
|
+
style.innerHTML = "#console { position: fixed; bottom: 0; width: 100%; height: 150px; border: 1px solid; padding-bottom: 20px; } " +
|
40
|
+
"#console div.console-inner { height: 100%; overflow: auto; background: #333; padding: 10px; } " +
|
41
|
+
"#console div.console-prompt-box { color: #fff; } " +
|
42
|
+
"#console div.console-message { color: #1ad027; } " +
|
43
|
+
"#console div.console-focus span.console-cursor { background:#fefefe; color:#333; font-weight:bold; }";
|
44
|
+
document.getElementsByTagName('head')[0].appendChild(style);
|
45
|
+
|
46
|
+
/**
|
47
|
+
* Constructor for command storage.
|
48
|
+
* It uses localStorage if available. Otherwise fallback to normal JS array.
|
49
|
+
*/
|
50
|
+
function CommandStorage() {
|
51
|
+
this.previousCommands = [];
|
52
|
+
var previousCommandOffset = 0;
|
53
|
+
var hasLocalStorage = typeof window.localStorage !== 'undefined';
|
54
|
+
var STORAGE_KEY = "web_console_previous_commands";
|
55
|
+
var MAX_STORAGE = 100;
|
56
|
+
|
57
|
+
if (hasLocalStorage) {
|
58
|
+
this.previousCommands = JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
|
59
|
+
previousCommandOffset = this.previousCommands.length;
|
60
|
+
}
|
61
|
+
|
62
|
+
this.addCommand = function(command) {
|
63
|
+
previousCommandOffset = this.previousCommands.push(command);
|
64
|
+
|
65
|
+
if (previousCommandOffset > MAX_STORAGE) {
|
66
|
+
this.previousCommands.splice(0, 1);
|
67
|
+
previousCommandOffset = MAX_STORAGE;
|
68
|
+
}
|
69
|
+
|
70
|
+
if (hasLocalStorage) {
|
71
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(this.previousCommands));
|
72
|
+
}
|
73
|
+
};
|
74
|
+
|
75
|
+
this.navigate = function(offset) {
|
76
|
+
previousCommandOffset += offset;
|
77
|
+
|
78
|
+
if (previousCommandOffset < 0) {
|
79
|
+
previousCommandOffset = -1;
|
80
|
+
return null;
|
81
|
+
}
|
82
|
+
|
83
|
+
if (previousCommandOffset >= this.previousCommands.length) {
|
84
|
+
previousCommandOffset = this.previousCommands.length;
|
85
|
+
return null;
|
86
|
+
}
|
87
|
+
|
88
|
+
return this.previousCommands[previousCommandOffset];
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
// REPLConsole Constructor
|
93
|
+
function REPLConsole(config) {
|
94
|
+
this.commandStorage = new CommandStorage();
|
95
|
+
this.prompt = config && config.promptLabel ? config.promptLabel : ' >>';
|
96
|
+
this.commandHandle = config && config.commandHandle ? config.commandHandle : function() { return this; }
|
97
|
+
}
|
98
|
+
|
99
|
+
REPLConsole.prototype.install = function(container) {
|
100
|
+
var _this = this;
|
101
|
+
|
102
|
+
document.onkeydown = function(ev) {
|
103
|
+
if (_this.focused) { _this.onKeyDown(ev); }
|
104
|
+
};
|
105
|
+
|
106
|
+
document.onkeypress = function(ev) {
|
107
|
+
if (_this.focused) { _this.onKeyPress(ev); }
|
108
|
+
};
|
109
|
+
|
110
|
+
document.addEventListener('mousedown', function(ev) {
|
111
|
+
var el = ev.target || ev.srcElement;
|
112
|
+
|
113
|
+
if (el) {
|
114
|
+
do {
|
115
|
+
if (el === container) {
|
116
|
+
_this.focus();
|
117
|
+
return;
|
118
|
+
}
|
119
|
+
} while (el = el.parentNode);
|
120
|
+
|
121
|
+
_this.blur();
|
122
|
+
}
|
123
|
+
});
|
124
|
+
|
125
|
+
// Render the console.
|
126
|
+
this.inner = document.createElement('div');
|
127
|
+
this.inner.className = "console-inner";
|
128
|
+
this.newPromptBox();
|
129
|
+
container.appendChild(this.inner);
|
130
|
+
this.focus();
|
131
|
+
};
|
132
|
+
|
133
|
+
REPLConsole.prototype.focus = function() {
|
134
|
+
this.focused = true;
|
135
|
+
|
136
|
+
if (! hasClass(this.inner, "console-focus")) {
|
137
|
+
addClass(this.inner, "console-focus");
|
138
|
+
}
|
139
|
+
this.scrollToBottom();
|
140
|
+
};
|
141
|
+
|
142
|
+
REPLConsole.prototype.blur = function() {
|
143
|
+
this.focused = false;
|
144
|
+
removeClass(this.inner, "console-focus");
|
145
|
+
};
|
146
|
+
|
147
|
+
/**
|
148
|
+
* Add a new empty prompt box to the console.
|
149
|
+
*/
|
150
|
+
REPLConsole.prototype.newPromptBox = function() {
|
151
|
+
// Remove the caret from previous prompt box if any.
|
152
|
+
if (this.promptInput) {
|
153
|
+
this.removeCaretFromPrompt();
|
154
|
+
}
|
155
|
+
|
156
|
+
var promptBox = document.createElement('div');
|
157
|
+
promptBox.className = "console-prompt-box";
|
158
|
+
var promptLabel = document.createElement('span');
|
159
|
+
promptLabel.className ="console-prompt-label";
|
160
|
+
promptLabel.style.display = "inline";
|
161
|
+
promptLabel.innerHTML = this.prompt;
|
162
|
+
var promptInput = document.createElement('span');
|
163
|
+
promptInput.className = "console-prompt";
|
164
|
+
promptBox.appendChild(promptLabel);
|
165
|
+
promptBox.appendChild(promptInput);
|
166
|
+
this.inner.appendChild(promptBox);
|
167
|
+
|
168
|
+
this.promptLabel = promptLabel;
|
169
|
+
this.promptInput = promptInput;
|
170
|
+
this.setInput("");
|
171
|
+
this.scrollToBottom();
|
172
|
+
};
|
173
|
+
|
174
|
+
/**
|
175
|
+
* Remove the caret from the prompt box,
|
176
|
+
* mainly before adding a new prompt box.
|
177
|
+
* For simplicity, just re-render the prompt box
|
178
|
+
* with caret position -1.
|
179
|
+
*/
|
180
|
+
REPLConsole.prototype.removeCaretFromPrompt = function() {
|
181
|
+
this.setInput(this._input, -1);
|
182
|
+
};
|
183
|
+
|
184
|
+
REPLConsole.prototype.setInput = function(input, caretPos) {
|
185
|
+
this._caretPos = caretPos === undefined ? input.length : caretPos;
|
186
|
+
this._input = input;
|
187
|
+
this.renderInput();
|
188
|
+
}
|
189
|
+
|
190
|
+
/**
|
191
|
+
* Render the input prompt. This is called whenever
|
192
|
+
* the user input changes, sometimes not very efficient.
|
193
|
+
*/
|
194
|
+
REPLConsole.prototype.renderInput = function() {
|
195
|
+
// Clear the current input.
|
196
|
+
removeAllChildren(this.promptInput);
|
197
|
+
|
198
|
+
var promptCursor = document.createElement('span');
|
199
|
+
promptCursor.className = "console-cursor";
|
200
|
+
var before, current, master;
|
201
|
+
|
202
|
+
if (this._caretPos < 0) {
|
203
|
+
before = this._input;
|
204
|
+
current = after = "";
|
205
|
+
} else if (this._caretPos === this._input.length) {
|
206
|
+
before = this._input;
|
207
|
+
current = "\u00A0";
|
208
|
+
after = "";
|
209
|
+
} else {
|
210
|
+
before = this._input.substring(0, this._caretPos);
|
211
|
+
current = this._input.charAt(this._caretPos);
|
212
|
+
after = this._input.substring(this._caretPos + 1, this._input.length);
|
213
|
+
}
|
214
|
+
|
215
|
+
this.promptInput.appendChild(document.createTextNode(before));
|
216
|
+
promptCursor.appendChild(document.createTextNode(current));
|
217
|
+
this.promptInput.appendChild(promptCursor);
|
218
|
+
this.promptInput.appendChild(document.createTextNode(after));
|
219
|
+
};
|
220
|
+
|
221
|
+
REPLConsole.prototype.writeOutput = function(output) {
|
222
|
+
var consoleMessage = document.createElement('div');
|
223
|
+
consoleMessage.className = "console-message";
|
224
|
+
consoleMessage.innerHTML = escapeHTML(output);
|
225
|
+
this.inner.appendChild(consoleMessage);
|
226
|
+
this.newPromptBox();
|
227
|
+
};
|
228
|
+
|
229
|
+
REPLConsole.prototype.onEnterKey = function() {
|
230
|
+
var input = this._input;
|
231
|
+
|
232
|
+
if(input != "" && input !== undefined) {
|
233
|
+
this.commandStorage.addCommand(input);
|
234
|
+
}
|
235
|
+
|
236
|
+
this.commandHandle(input);
|
237
|
+
};
|
238
|
+
|
239
|
+
REPLConsole.prototype.onNavigateHistory = function(offset) {
|
240
|
+
var command = this.commandStorage.navigate(offset) || "";
|
241
|
+
this.setInput(command);
|
242
|
+
};
|
243
|
+
|
244
|
+
/**
|
245
|
+
* Handle control keys like up, down, left, right.
|
246
|
+
*/
|
247
|
+
REPLConsole.prototype.onKeyDown = function(ev) {
|
248
|
+
switch (ev.keyCode) {
|
249
|
+
case 13:
|
250
|
+
// Enter key
|
251
|
+
this.onEnterKey();
|
252
|
+
ev.preventDefault();
|
253
|
+
break;
|
254
|
+
case 80:
|
255
|
+
// Ctrl-P
|
256
|
+
if (! ev.ctrlKey) break;
|
257
|
+
case 38:
|
258
|
+
// Up arrow
|
259
|
+
this.onNavigateHistory(-1);
|
260
|
+
ev.preventDefault();
|
261
|
+
break;
|
262
|
+
case 78:
|
263
|
+
// Ctrl-N
|
264
|
+
if (! ev.ctrlKey) break;
|
265
|
+
case 40:
|
266
|
+
// Down arrow
|
267
|
+
this.onNavigateHistory(1);
|
268
|
+
ev.preventDefault();
|
269
|
+
break;
|
270
|
+
case 37:
|
271
|
+
// Left arrow
|
272
|
+
var caretPos = this._caretPos > 0 ? this._caretPos - 1 : this._caretPos;
|
273
|
+
this.setInput(this._input, caretPos);
|
274
|
+
ev.preventDefault();
|
275
|
+
break;
|
276
|
+
case 39:
|
277
|
+
// Right arrow
|
278
|
+
var length = this._input.length;
|
279
|
+
var caretPos = this._caretPos < length ? this._caretPos + 1 : this._caretPos;
|
280
|
+
this.setInput(this._input, caretPos);
|
281
|
+
ev.preventDefault();
|
282
|
+
break;
|
283
|
+
case 8:
|
284
|
+
// Delete
|
285
|
+
this.deleteAtCurrent();
|
286
|
+
ev.preventDefault();
|
287
|
+
break;
|
288
|
+
default:
|
289
|
+
break;
|
290
|
+
}
|
291
|
+
|
292
|
+
ev.stopPropagation();
|
293
|
+
};
|
294
|
+
|
295
|
+
/**
|
296
|
+
* Handle input key press.
|
297
|
+
*/
|
298
|
+
REPLConsole.prototype.onKeyPress = function(ev) {
|
299
|
+
var keyCode = ev.keyCode || ev.which;
|
300
|
+
this.insertAtCurrent(String.fromCharCode(keyCode));
|
301
|
+
ev.stopPropagation();
|
302
|
+
ev.preventDefault();
|
303
|
+
};
|
304
|
+
|
305
|
+
/**
|
306
|
+
* Delete a character at the current position.
|
307
|
+
*/
|
308
|
+
REPLConsole.prototype.deleteAtCurrent = function() {
|
309
|
+
if (this._caretPos > 0) {
|
310
|
+
var caretPos = this._caretPos - 1;
|
311
|
+
var before = this._input.substring(0, caretPos);
|
312
|
+
var after = this._input.substring(this._caretPos, this._input.length);
|
313
|
+
this.setInput(before + after, caretPos);
|
314
|
+
}
|
315
|
+
};
|
316
|
+
|
317
|
+
/**
|
318
|
+
* Insert a character at the current position.
|
319
|
+
*/
|
320
|
+
REPLConsole.prototype.insertAtCurrent = function(char) {
|
321
|
+
var before = this._input.substring(0, this._caretPos);
|
322
|
+
var after = this._input.substring(this._caretPos, this._input.length);
|
323
|
+
this.setInput(before + char + after, this._caretPos + 1);
|
324
|
+
};
|
325
|
+
|
326
|
+
REPLConsole.prototype.scrollToBottom = function() {
|
327
|
+
this.inner.scrollTop = this.inner.scrollHeight;
|
328
|
+
};
|
329
|
+
|
330
|
+
window.REPLConsole = REPLConsole;
|
331
|
+
})();
|
332
|
+
|
333
|
+
// Make the console resizable (not sure if works in all browser)
|
334
|
+
var consoleDiv = document.getElementById('console');
|
335
|
+
var resizer = document.getElementById('resizer');
|
336
|
+
|
337
|
+
var initDrag = function(e) {
|
338
|
+
var startY = e.clientY;
|
339
|
+
var startHeight = parseInt(document.defaultView.getComputedStyle(consoleDiv).height, 10);
|
340
|
+
|
341
|
+
var doDrag = function(e) {
|
342
|
+
consoleDiv.style.height = (startHeight + startY - e.clientY) + 'px';
|
343
|
+
};
|
344
|
+
|
345
|
+
var stopDrag = function(e) {
|
346
|
+
document.documentElement.removeEventListener('mousemove', doDrag, false);
|
347
|
+
document.documentElement.removeEventListener('mouseup', stopDrag, false);
|
348
|
+
};
|
349
|
+
|
350
|
+
document.documentElement.addEventListener('mousemove', doDrag, false);
|
351
|
+
document.documentElement.addEventListener('mouseup', stopDrag, false);
|
352
|
+
}
|
353
|
+
|
354
|
+
resizer.addEventListener('mousedown', initDrag, false);
|
355
|
+
|
356
|
+
// Install the console
|
357
|
+
var consoleDiv = document.getElementById('console');
|
358
|
+
var replConsole = new REPLConsole({
|
359
|
+
promptLabel: consoleDiv.dataset.initialPrompt,
|
360
|
+
commandHandle: function(line) {
|
361
|
+
var _this = this;
|
362
|
+
var xhr = new XMLHttpRequest();
|
363
|
+
var url = consoleDiv.dataset.remotePath;
|
364
|
+
var params = "input=" + line;
|
365
|
+
|
366
|
+
xhr.open("PUT", url, true);
|
367
|
+
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
368
|
+
xhr.send(params);
|
369
|
+
|
370
|
+
xhr.onreadystatechange = function() {
|
371
|
+
if(xhr.readyState == 4 && xhr.status == 200) {
|
372
|
+
var response = JSON.parse(xhr.responseText);
|
373
|
+
console.log(response.output);
|
374
|
+
_this.writeOutput(response.output);
|
375
|
+
}
|
376
|
+
}
|
377
|
+
}
|
378
|
+
});
|
379
|
+
|
380
|
+
replConsole.install(consoleDiv);
|
381
|
+
</script>
|
382
|
+
<% end %>
|