web-console 2.0.0.beta2 → 2.0.0.beta3
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.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/README.markdown +304 -19
- data/app/assets/javascripts/web_console/application.js +1 -0
- data/app/assets/javascripts/web_console/console_sessions.js +172 -0
- data/app/assets/stylesheets/web_console/application.css +13 -0
- data/app/assets/stylesheets/web_console/console_sessions.css.erb +6 -0
- data/app/controllers/web_console/application_controller.rb +13 -0
- data/app/controllers/web_console/console_sessions_controller.rb +43 -0
- data/app/helpers/web_console/application_helper.rb +4 -0
- data/app/helpers/web_console/console_session_helper.rb +4 -0
- data/app/models/web_console/console_session.rb +96 -0
- data/app/views/layouts/web_console/application.html.erb +14 -0
- data/app/views/web_console/console_sessions/index.html.erb +15 -0
- data/config/routes.rb +11 -0
- data/lib/action_dispatch/debug_exceptions.rb +69 -69
- data/lib/action_dispatch/exception_wrapper.rb +2 -1
- data/lib/assets/javascripts/web-console.js +1 -0
- data/lib/assets/javascripts/web_console.js +41 -0
- data/lib/web_console.rb +14 -15
- data/lib/web_console/colors.rb +87 -0
- data/lib/web_console/colors/light.rb +24 -0
- data/lib/web_console/colors/monokai.rb +24 -0
- data/lib/web_console/colors/solarized.rb +47 -0
- data/lib/web_console/colors/tango.rb +24 -0
- data/lib/web_console/colors/xterm.rb +24 -0
- data/lib/web_console/engine.rb +95 -0
- data/lib/web_console/exception_extension.rb +10 -11
- data/lib/web_console/repl_session.rb +5 -3
- data/lib/web_console/slave.rb +139 -0
- data/lib/web_console/version.rb +1 -1
- data/lib/web_console/view_helpers.rb +3 -7
- data/test/controllers/web_console/console_sessions_controller_test.rb +95 -0
- data/test/dummy/app/controllers/exception_test_controller.rb +4 -0
- data/test/dummy/app/views/exception_test/xhr.html.erb +1 -0
- data/test/dummy/config/application.rb +37 -1
- data/test/dummy/config/environments/test.rb +2 -0
- data/test/dummy/config/routes.rb +1 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/log/development.log +61814 -0
- data/test/dummy/log/test.log +8621 -3
- 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/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/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/test/dummy/tmp/cache/assets/development/sprockets/7a50a9e605754e99783de95715b976b0 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/806b0e33a2fe8e1245534345fa27c30a +0 -0
- data/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/models/console_session_test.rb +58 -0
- data/test/web_console/colors_test.rb +58 -0
- data/test/web_console/engine_test.rb +136 -0
- data/test/web_console/slave_test.rb +71 -0
- data/vendor/assets/javascripts/term.js +5726 -0
- metadata +188 -7
- data/lib/web_console/railtie.rb +0 -15
|
@@ -0,0 +1,13 @@
|
|
|
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_self
|
|
12
|
+
*= require_tree .
|
|
13
|
+
*/
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<% WebConsole.config.style.instance_eval do %>
|
|
2
|
+
body { color: <%= colors.foreground %>; background: <%= colors.background %>; margin: 0; padding: 0; }
|
|
3
|
+
|
|
4
|
+
.terminal { float: left; overflow: hidden; font: <%= font %>; }
|
|
5
|
+
.terminal-cursor { color: <%= colors.background %>; background: <%= colors.foreground %>; }
|
|
6
|
+
<% end %>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module WebConsole
|
|
2
|
+
class ApplicationController < ActionController::Base
|
|
3
|
+
before_action :prevent_unauthorized_requests!
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def prevent_unauthorized_requests!
|
|
8
|
+
unless request.remote_ip.in?(WebConsole.config.whitelisted_ips)
|
|
9
|
+
render nothing: true, status: :unauthorized
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require_dependency 'web_console/application_controller'
|
|
2
|
+
|
|
3
|
+
module WebConsole
|
|
4
|
+
class ConsoleSessionsController < ApplicationController
|
|
5
|
+
rescue_from ConsoleSession::Unavailable do |exception|
|
|
6
|
+
render json: exception, status: :gone
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
rescue_from ConsoleSession::Invalid do |exception|
|
|
10
|
+
render json: exception, status: :unprocessable_entity
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def index
|
|
14
|
+
@console_session = ConsoleSession.create
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def input
|
|
18
|
+
@console_session = ConsoleSession.find(params[:id])
|
|
19
|
+
@console_session.send_input(console_session_params[:input])
|
|
20
|
+
|
|
21
|
+
render nothing: true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def configuration
|
|
25
|
+
@console_session = ConsoleSession.find(params[:id])
|
|
26
|
+
@console_session.configure(console_session_params)
|
|
27
|
+
|
|
28
|
+
render nothing: true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def pending_output
|
|
32
|
+
@console_session = ConsoleSession.find(params[:id])
|
|
33
|
+
|
|
34
|
+
render json: { output: @console_session.pending_output }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def console_session_params
|
|
40
|
+
params.permit(:id, :input, :width, :height)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
module WebConsole
|
|
2
|
+
# Manage and persist (in memory) WebConsole::Slave instances.
|
|
3
|
+
class ConsoleSession
|
|
4
|
+
include ActiveModel::Model
|
|
5
|
+
|
|
6
|
+
# In-memory storage for the console sessions. Session preservation is
|
|
7
|
+
# troubled on servers with multiple workers and threads.
|
|
8
|
+
INMEMORY_STORAGE = {}
|
|
9
|
+
|
|
10
|
+
# Base error class for ConsoleSession specific exceptions.
|
|
11
|
+
#
|
|
12
|
+
# Provides #to_json implementation, so all subclasses are JSON
|
|
13
|
+
# serializable.
|
|
14
|
+
class Error < StandardError
|
|
15
|
+
def to_json(*)
|
|
16
|
+
{ error: to_s }.to_json
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Raised when trying to find a session that is no longer in the in-memory
|
|
21
|
+
# session storage or when the slave process exited.
|
|
22
|
+
Unavailable = Class.new(Error)
|
|
23
|
+
|
|
24
|
+
# Raised when an operation transition to an invalid state.
|
|
25
|
+
Invalid = Class.new(Error)
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
# Finds a session by its pid.
|
|
29
|
+
#
|
|
30
|
+
# Raises WebConsole::ConsoleSession::Expired if there is no such session.
|
|
31
|
+
def find(pid)
|
|
32
|
+
INMEMORY_STORAGE[pid.to_i] or raise Unavailable, 'Session unavailable'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Creates an already persisted consolse session.
|
|
36
|
+
#
|
|
37
|
+
# Use this method if you need to persist a session, without providing it
|
|
38
|
+
# any input.
|
|
39
|
+
def create
|
|
40
|
+
new.persist
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def initialize
|
|
45
|
+
@slave = WebConsole::Slave.new
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Explicitly persist the model in the in-memory storage.
|
|
49
|
+
def persist
|
|
50
|
+
INMEMORY_STORAGE[pid] = self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Returns true if the current session is persisted in the in-memory storage.
|
|
54
|
+
def persisted?
|
|
55
|
+
self == INMEMORY_STORAGE[pid]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns an Enumerable of all key attributes if any is set, regardless if
|
|
59
|
+
# the object is persisted or not.
|
|
60
|
+
def to_key
|
|
61
|
+
[pid] if persisted?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def delegate_and_call_slave_method(name, *args, &block)
|
|
67
|
+
# Cache the delegated method, so we don't have to hit #method_missing
|
|
68
|
+
# on every call.
|
|
69
|
+
define_singleton_method(name) do |*inner_args, &inner_block|
|
|
70
|
+
begin
|
|
71
|
+
@slave.public_send(name, *inner_args, &inner_block)
|
|
72
|
+
rescue ArgumentError => exc
|
|
73
|
+
raise Invalid, exc
|
|
74
|
+
rescue Slave::Closed => exc
|
|
75
|
+
raise Unavailable, exc
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Now call the method, since that's our most common use case. Delegate
|
|
80
|
+
# the method and than call it.
|
|
81
|
+
public_send(name, *args, &block)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def method_missing(name, *args, &block)
|
|
85
|
+
if @slave.respond_to?(name)
|
|
86
|
+
delegate_and_call_slave_method(name, *args, &block)
|
|
87
|
+
else
|
|
88
|
+
super
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def respond_to_missing?(name, include_all = false)
|
|
93
|
+
@slave.respond_to?(name) or super
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>WebConsole</title>
|
|
5
|
+
<%= stylesheet_link_tag "web_console/application", media: "all" %>
|
|
6
|
+
<%= javascript_include_tag "web_console/application" %>
|
|
7
|
+
<%= csrf_meta_tags %>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
|
|
11
|
+
<%= yield %>
|
|
12
|
+
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
var config = {
|
|
3
|
+
terminal: {
|
|
4
|
+
colors: <%= raw WebConsole.config.style.colors.to_json %>
|
|
5
|
+
},
|
|
6
|
+
|
|
7
|
+
transport: {
|
|
8
|
+
url: {
|
|
9
|
+
input: "<%= web_console.input_console_session_path(@console_session) %>",
|
|
10
|
+
pendingOutput: "<%= web_console.pending_output_console_session_path(@console_session) %>",
|
|
11
|
+
configuration: "<%= web_console.configuration_console_session_path(@console_session) %>"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
</script>
|
data/config/routes.rb
ADDED
|
@@ -30,86 +30,86 @@ module ActionDispatch
|
|
|
30
30
|
|
|
31
31
|
private
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def change_stack_trace(id, frame_id)
|
|
40
|
-
console_session = WebConsole::REPLSession.find(id)
|
|
41
|
-
binding = console_session.binding_stack[frame_id.to_i]
|
|
42
|
-
console_session.binding = binding
|
|
43
|
-
[200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump("success")]]
|
|
44
|
-
end
|
|
33
|
+
def update_repl_session(id, input)
|
|
34
|
+
console_session = WebConsole::REPLSession.find(id)
|
|
35
|
+
response = console_session.save(input: input)
|
|
36
|
+
[ 200, { "Content-Type" => "text/plain; charset=utf-8" }, [ response.to_json ] ]
|
|
37
|
+
end
|
|
45
38
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
def change_stack_trace(id, frame_id)
|
|
40
|
+
console_session = WebConsole::REPLSession.find(id)
|
|
41
|
+
binding = console_session.binding_stack[frame_id.to_i]
|
|
42
|
+
console_session.binding = binding
|
|
43
|
+
[ 200, { "Content-Type" => "text/plain; charset=utf-8" }, [ JSON.dump("success") ] ]
|
|
49
44
|
end
|
|
50
45
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
binding: binding_from_exception(exception),
|
|
56
|
-
binding_stack: exception.__web_console_bindings_stack
|
|
57
|
-
)
|
|
58
|
-
log_error(env, wrapper)
|
|
59
|
-
|
|
60
|
-
if env['action_dispatch.show_detailed_exceptions']
|
|
61
|
-
request = Request.new(env)
|
|
62
|
-
template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
|
|
63
|
-
request: request,
|
|
64
|
-
exception: wrapper.exception,
|
|
65
|
-
application_trace: traces[:application_trace],
|
|
66
|
-
framework_trace: traces[:framework_trace],
|
|
67
|
-
full_trace: traces[:full_trace],
|
|
68
|
-
routes_inspector: routes_inspector(exception),
|
|
69
|
-
extract_sources: extract_sources,
|
|
70
|
-
console_session: console_session
|
|
71
|
-
)
|
|
72
|
-
file = "rescues/#{wrapper.rescue_template}"
|
|
46
|
+
def render_exception(env, exception)
|
|
47
|
+
if exception.respond_to?(:original_exception) && exception.original_exception
|
|
48
|
+
exception = exception.original_exception
|
|
49
|
+
end
|
|
73
50
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
51
|
+
wrapper = ExceptionWrapper.new(env, exception)
|
|
52
|
+
traces = traces_from_wrapper(wrapper)
|
|
53
|
+
extract_sources = wrapper.extract_sources
|
|
54
|
+
console_session = WebConsole::REPLSession.create(
|
|
55
|
+
binding: binding_from_exception(exception),
|
|
56
|
+
binding_stack: exception.__web_console_bindings_stack
|
|
57
|
+
)
|
|
58
|
+
log_error(env, wrapper)
|
|
59
|
+
|
|
60
|
+
if env['action_dispatch.show_detailed_exceptions']
|
|
61
|
+
request = Request.new(env)
|
|
62
|
+
template = ActionView::Base.new([ RESCUES_TEMPLATE_PATH ],
|
|
63
|
+
request: request,
|
|
64
|
+
exception: wrapper.exception,
|
|
65
|
+
application_trace: traces[:application_trace],
|
|
66
|
+
framework_trace: traces[:framework_trace],
|
|
67
|
+
full_trace: traces[:full_trace],
|
|
68
|
+
routes_inspector: routes_inspector(exception),
|
|
69
|
+
extract_sources: extract_sources,
|
|
70
|
+
console_session: console_session
|
|
71
|
+
)
|
|
72
|
+
file = "rescues/#{wrapper.rescue_template}"
|
|
73
|
+
|
|
74
|
+
if request.xhr?
|
|
75
|
+
body = template.render(template: file, layout: false, formats: [ :text ])
|
|
76
|
+
format = "text/plain"
|
|
77
|
+
else
|
|
78
|
+
body = template.render(template: file, layout: 'rescues/layout')
|
|
79
|
+
format = "text/html"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
[ wrapper.status_code, { 'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s }, [ body ] ]
|
|
77
83
|
else
|
|
78
|
-
|
|
79
|
-
format = "text/html"
|
|
84
|
+
raise exception
|
|
80
85
|
end
|
|
81
|
-
|
|
82
|
-
[wrapper.status_code, {'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
|
|
83
|
-
else
|
|
84
|
-
raise exception
|
|
85
86
|
end
|
|
86
|
-
end
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
# Augment the exception traces by providing ids for all unique stack frames.
|
|
89
|
+
def traces_from_wrapper(wrapper)
|
|
90
|
+
id_counter = 0
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
application_trace = wrapper.application_trace.map do |trace|
|
|
93
|
+
prev = id_counter
|
|
94
|
+
id_counter += 1
|
|
95
|
+
{ id: prev, trace: trace }
|
|
96
|
+
end
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
framework_trace = wrapper.framework_trace.map do |trace|
|
|
99
|
+
prev = id_counter
|
|
100
|
+
id_counter += 1
|
|
101
|
+
{ id: prev, trace: trace }
|
|
102
|
+
end
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
{
|
|
105
|
+
application_trace: application_trace,
|
|
106
|
+
framework_trace: framework_trace,
|
|
107
|
+
full_trace: application_trace + framework_trace
|
|
108
|
+
}
|
|
109
|
+
end
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
def binding_from_exception(exception)
|
|
112
|
+
exception.__web_console_bindings_stack[0]
|
|
113
|
+
end
|
|
114
114
|
end
|
|
115
115
|
end
|
|
@@ -2,8 +2,9 @@ module ActionDispatch
|
|
|
2
2
|
class ExceptionWrapper
|
|
3
3
|
def extract_sources
|
|
4
4
|
exception.backtrace.map do |trace|
|
|
5
|
-
file, line
|
|
5
|
+
file, line = trace.split(":")
|
|
6
6
|
line_number = line.to_i
|
|
7
|
+
|
|
7
8
|
{
|
|
8
9
|
code: source_fragment(file, line_number) || {},
|
|
9
10
|
file: file,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//= require ./web_console
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//= require term
|
|
2
|
+
|
|
3
|
+
;(function(BaseTerminal) {
|
|
4
|
+
|
|
5
|
+
// Expose the main WebConsole namespace.
|
|
6
|
+
var WebConsole = this.WebConsole = {};
|
|
7
|
+
|
|
8
|
+
// Follow term.js example and expose inherits and EventEmitter.
|
|
9
|
+
var inherits = WebConsole.inherits = BaseTerminal.inherits;
|
|
10
|
+
var EventEmitter = WebConsole.EventEmitter = BaseTerminal.EventEmitter;
|
|
11
|
+
|
|
12
|
+
var Terminal = WebConsole.Terminal = function(options) {
|
|
13
|
+
if (typeof options === 'number') {
|
|
14
|
+
return BaseTerminal.apply(this, arguments);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
BaseTerminal.call(this, options || (options = {}));
|
|
18
|
+
|
|
19
|
+
this.open();
|
|
20
|
+
|
|
21
|
+
if (!(options.rows || options.cols) || !options.geometry) {
|
|
22
|
+
this.fitScreen();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Make WebConsole.Terminal inherit from BaseTerminal (term.js).
|
|
27
|
+
inherits(Terminal, BaseTerminal);
|
|
28
|
+
|
|
29
|
+
Terminal.prototype.fitScreen = function() {
|
|
30
|
+
var width = Math.floor(this.element.clientWidth / this.cols);
|
|
31
|
+
var height = Math.floor(this.element.clientHeight / this.rows);
|
|
32
|
+
|
|
33
|
+
var rows = Math.floor(window.innerHeight / height);
|
|
34
|
+
var cols = Math.floor(this.parent.clientWidth / width);
|
|
35
|
+
|
|
36
|
+
this.resize(cols, rows);
|
|
37
|
+
|
|
38
|
+
return [cols, rows];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
}).call(this, Terminal);
|