web-console 3.6.2 → 4.0.3
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/CHANGELOG.markdown +35 -0
- data/README.markdown +32 -25
- data/lib/web_console.rb +3 -1
- data/lib/web_console/evaluator.rb +5 -3
- data/lib/web_console/exception_mapper.rb +23 -2
- data/lib/web_console/extensions.rb +10 -23
- data/lib/web_console/injector.rb +5 -3
- data/lib/web_console/interceptor.rb +18 -0
- data/lib/web_console/middleware.rb +6 -9
- data/lib/web_console/{whitelist.rb → permissions.rb} +5 -9
- data/lib/web_console/railtie.rb +19 -4
- data/lib/web_console/request.rb +4 -20
- data/lib/web_console/session.rb +11 -9
- data/lib/web_console/source_location.rb +15 -0
- data/lib/web_console/template.rb +2 -3
- data/lib/web_console/templates/console.js.erb +116 -28
- data/lib/web_console/templates/error_page.js.erb +7 -8
- data/lib/web_console/templates/index.html.erb +4 -0
- data/lib/web_console/templates/regular_page.js.erb +24 -0
- data/lib/web_console/templates/style.css.erb +182 -33
- data/lib/web_console/testing/fake_middleware.rb +0 -1
- data/lib/web_console/version.rb +1 -1
- data/lib/web_console/view.rb +5 -0
- data/lib/web_console/whiny_request.rb +4 -4
- metadata +14 -12
data/lib/web_console/railtie.rb
CHANGED
@@ -5,11 +5,12 @@ require "rails/railtie"
|
|
5
5
|
module WebConsole
|
6
6
|
class Railtie < ::Rails::Railtie
|
7
7
|
config.web_console = ActiveSupport::OrderedOptions.new
|
8
|
-
config.web_console.whitelisted_ips = %w( 127.0.0.1 ::1 )
|
9
8
|
|
10
9
|
initializer "web_console.initialize" do
|
11
10
|
require "bindex"
|
12
11
|
require "web_console/extensions"
|
12
|
+
|
13
|
+
ActionDispatch::DebugExceptions.register_interceptor(Interceptor)
|
13
14
|
end
|
14
15
|
|
15
16
|
initializer "web_console.development_only" do
|
@@ -50,9 +51,23 @@ module WebConsole
|
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
53
|
-
initializer "web_console.
|
54
|
-
|
55
|
-
|
54
|
+
initializer "web_console.permissions" do
|
55
|
+
permissions = web_console_permissions
|
56
|
+
Request.permissions = Permissions.new(permissions)
|
57
|
+
end
|
58
|
+
|
59
|
+
def web_console_permissions
|
60
|
+
case
|
61
|
+
when config.web_console.permissions
|
62
|
+
config.web_console.permissions
|
63
|
+
when config.web_console.allowed_ips
|
64
|
+
config.web_console.allowed_ips
|
65
|
+
when config.web_console.whitelisted_ips
|
66
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
67
|
+
The config.web_console.whitelisted_ips is deprecated and will be ignored in future release of web_console.
|
68
|
+
Please use config.web_console.allowed_ips instead.
|
69
|
+
MSG
|
70
|
+
config.web_console.whitelisted_ips
|
56
71
|
end
|
57
72
|
end
|
58
73
|
|
data/lib/web_console/request.rb
CHANGED
@@ -1,35 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module WebConsole
|
4
|
-
# Web Console tailored request object.
|
5
4
|
class Request < ActionDispatch::Request
|
6
|
-
|
7
|
-
cattr_accessor :whitelisted_ips
|
8
|
-
@@whitelisted_ips = Whitelist.new
|
5
|
+
cattr_accessor :permissions, default: Permissions.new
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
# Returns whether a request came from a whitelisted IP.
|
14
|
-
#
|
15
|
-
# For a request to hit Web Console features, it needs to come from a white
|
16
|
-
# listed IP.
|
17
|
-
def from_whitelisted_ip?
|
18
|
-
whitelisted_ips.include?(strict_remote_ip)
|
7
|
+
def permitted?
|
8
|
+
permissions.include?(strict_remote_ip)
|
19
9
|
end
|
20
10
|
|
21
|
-
# Determines the remote IP using our much stricter whitelist.
|
22
11
|
def strict_remote_ip
|
23
|
-
GetSecureIp.new(self,
|
12
|
+
GetSecureIp.new(self, permissions).to_s
|
24
13
|
rescue ActionDispatch::RemoteIp::IpSpoofAttackError
|
25
14
|
"[Spoofed]"
|
26
15
|
end
|
27
16
|
|
28
|
-
# Returns whether the request is acceptable.
|
29
|
-
def acceptable?
|
30
|
-
xhr? && accepts.any? { |mime| Mime[:web_console_v2] == mime }
|
31
|
-
end
|
32
|
-
|
33
17
|
private
|
34
18
|
|
35
19
|
class GetSecureIp < ActionDispatch::RemoteIp::GetIp
|
data/lib/web_console/session.rb
CHANGED
@@ -11,8 +11,7 @@ module WebConsole
|
|
11
11
|
# error pages only, as currently, this is the only client that needs to do
|
12
12
|
# that.
|
13
13
|
class Session
|
14
|
-
cattr_reader :inmemory_storage
|
15
|
-
@@inmemory_storage = {}
|
14
|
+
cattr_reader :inmemory_storage, default: {}
|
16
15
|
|
17
16
|
class << self
|
18
17
|
# Finds a persisted session in memory by its id.
|
@@ -32,9 +31,9 @@ module WebConsole
|
|
32
31
|
# storage.
|
33
32
|
def from(storage)
|
34
33
|
if exc = storage[:__web_console_exception]
|
35
|
-
new(ExceptionMapper.
|
34
|
+
new(ExceptionMapper.follow(exc))
|
36
35
|
elsif binding = storage[:__web_console_binding]
|
37
|
-
new([binding])
|
36
|
+
new([[binding]])
|
38
37
|
end
|
39
38
|
end
|
40
39
|
end
|
@@ -42,10 +41,11 @@ module WebConsole
|
|
42
41
|
# An unique identifier for every REPL.
|
43
42
|
attr_reader :id
|
44
43
|
|
45
|
-
def initialize(
|
44
|
+
def initialize(exception_mappers)
|
46
45
|
@id = SecureRandom.hex(16)
|
47
|
-
|
48
|
-
@
|
46
|
+
|
47
|
+
@exception_mappers = exception_mappers
|
48
|
+
@evaluator = Evaluator.new(@current_binding = exception_mappers.first.first)
|
49
49
|
|
50
50
|
store_into_memory
|
51
51
|
end
|
@@ -60,8 +60,10 @@ module WebConsole
|
|
60
60
|
# Switches the current binding to the one at specified +index+.
|
61
61
|
#
|
62
62
|
# Returns nothing.
|
63
|
-
def switch_binding_to(index)
|
64
|
-
|
63
|
+
def switch_binding_to(index, exception_object_id)
|
64
|
+
bindings = ExceptionMapper.find_binding(@exception_mappers, exception_object_id)
|
65
|
+
|
66
|
+
@evaluator = Evaluator.new(@current_binding = bindings[index.to_i])
|
65
67
|
end
|
66
68
|
|
67
69
|
# Returns context of the current binding
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class SourceLocation
|
4
|
+
def initialize(binding)
|
5
|
+
@binding = binding
|
6
|
+
end
|
7
|
+
|
8
|
+
if RUBY_VERSION >= "2.6"
|
9
|
+
def path() @binding.source_location.first end
|
10
|
+
def lineno() @binding.source_location.last end
|
11
|
+
else
|
12
|
+
def path() @binding.eval("__FILE__") end
|
13
|
+
def lineno() @binding.eval("__LINE__") end
|
14
|
+
end
|
15
|
+
end
|
data/lib/web_console/template.rb
CHANGED
@@ -7,8 +7,7 @@ module WebConsole
|
|
7
7
|
# Rails error pages.
|
8
8
|
class Template
|
9
9
|
# Lets you customize the default templates folder location.
|
10
|
-
cattr_accessor :template_paths
|
11
|
-
@@template_paths = [ File.expand_path("../templates", __FILE__) ]
|
10
|
+
cattr_accessor :template_paths, default: [ File.expand_path("../templates", __FILE__) ]
|
12
11
|
|
13
12
|
def initialize(env, session)
|
14
13
|
@env = env
|
@@ -18,7 +17,7 @@ module WebConsole
|
|
18
17
|
|
19
18
|
# Render a template (inferred from +template_paths+) as a plain string.
|
20
19
|
def render(template)
|
21
|
-
view = View.new(template_paths, instance_values)
|
20
|
+
view = View.new(ActionView::LookupContext.new(template_paths), instance_values)
|
22
21
|
view.render(template: template, layout: false)
|
23
22
|
end
|
24
23
|
end
|
@@ -380,8 +380,13 @@ REPLConsole.prototype.install = function(container) {
|
|
380
380
|
var clientHeightStart = consoleOuter.clientHeight;
|
381
381
|
|
382
382
|
var doDrag = function(e) {
|
383
|
-
|
383
|
+
var height = startHeight + startY - e.clientY;
|
384
384
|
consoleOuter.scrollTop = scrollTopStart + (clientHeightStart - consoleOuter.clientHeight);
|
385
|
+
if (height > document.documentElement.clientHeight) {
|
386
|
+
container.style.height = document.documentElement.clientHeight;
|
387
|
+
} else {
|
388
|
+
container.style.height = height + 'px';
|
389
|
+
}
|
385
390
|
shiftConsoleActions();
|
386
391
|
};
|
387
392
|
|
@@ -603,6 +608,12 @@ REPLConsole.prototype.writeError = function(output) {
|
|
603
608
|
return consoleMessage;
|
604
609
|
};
|
605
610
|
|
611
|
+
REPLConsole.prototype.writeNotification = function(output) {
|
612
|
+
var consoleMessage = this.writeOutput(output);
|
613
|
+
addClass(consoleMessage, "notification-message");
|
614
|
+
return consoleMessage;
|
615
|
+
};
|
616
|
+
|
606
617
|
REPLConsole.prototype.onEnterKey = function() {
|
607
618
|
var input = this._input;
|
608
619
|
|
@@ -654,57 +665,85 @@ REPLConsole.prototype.onKeyDown = function(ev) {
|
|
654
665
|
}
|
655
666
|
|
656
667
|
switch (ev.keyCode) {
|
657
|
-
case
|
658
|
-
|
668
|
+
case 65: // Ctrl-A
|
669
|
+
if (ev.ctrlKey) {
|
670
|
+
this.setInput(this._input, 0);
|
671
|
+
ev.preventDefault();
|
672
|
+
}
|
673
|
+
break;
|
674
|
+
|
675
|
+
case 69: // Ctrl-E
|
659
676
|
if (ev.ctrlKey) {
|
660
677
|
this.onTabKey();
|
661
678
|
ev.preventDefault();
|
662
679
|
}
|
663
680
|
break;
|
664
|
-
|
665
|
-
|
681
|
+
|
682
|
+
case 87: // Ctrl-W
|
683
|
+
if (ev.ctrlKey) {
|
684
|
+
this.deleteWord();
|
685
|
+
ev.preventDefault();
|
686
|
+
}
|
687
|
+
break;
|
688
|
+
|
689
|
+
case 85: // Ctrl-U
|
690
|
+
if (ev.ctrlKey) {
|
691
|
+
this.deleteLine();
|
692
|
+
ev.preventDefault();
|
693
|
+
}
|
694
|
+
break;
|
695
|
+
|
696
|
+
case 69: // Ctrl-E
|
697
|
+
if (ev.ctrlKey) {
|
698
|
+
this.onTabKey();
|
699
|
+
ev.preventDefault();
|
700
|
+
}
|
701
|
+
break;
|
702
|
+
|
703
|
+
case 80: // Ctrl-P
|
704
|
+
if (! ev.ctrlKey) break;
|
705
|
+
|
706
|
+
case 78: // Ctrl-N
|
707
|
+
if (! ev.ctrlKey) break;
|
708
|
+
|
709
|
+
case 9: // Tab
|
666
710
|
this.onTabKey();
|
667
711
|
ev.preventDefault();
|
668
712
|
break;
|
669
|
-
|
670
|
-
|
713
|
+
|
714
|
+
case 13: // Enter key
|
671
715
|
this.onEnterKey();
|
672
716
|
ev.preventDefault();
|
673
717
|
break;
|
674
|
-
|
675
|
-
|
676
|
-
if (! ev.ctrlKey) break;
|
677
|
-
case 38:
|
678
|
-
// Up arrow
|
718
|
+
|
719
|
+
case 38: // Up arrow
|
679
720
|
this.onNavigateHistory(-1);
|
680
721
|
ev.preventDefault();
|
681
722
|
break;
|
682
|
-
|
683
|
-
|
684
|
-
if (! ev.ctrlKey) break;
|
685
|
-
case 40:
|
686
|
-
// Down arrow
|
723
|
+
|
724
|
+
case 40: // Down arrow
|
687
725
|
this.onNavigateHistory(1);
|
688
726
|
ev.preventDefault();
|
689
727
|
break;
|
690
|
-
|
691
|
-
|
728
|
+
|
729
|
+
case 37: // Left arrow
|
692
730
|
var caretPos = this._caretPos > 0 ? this._caretPos - 1 : this._caretPos;
|
693
731
|
this.setInput(this._input, caretPos);
|
694
732
|
ev.preventDefault();
|
695
733
|
break;
|
696
|
-
|
697
|
-
|
734
|
+
|
735
|
+
case 39: // Right arrow
|
698
736
|
var length = this._input.length;
|
699
737
|
var caretPos = this._caretPos < length ? this._caretPos + 1 : this._caretPos;
|
700
738
|
this.setInput(this._input, caretPos);
|
701
739
|
ev.preventDefault();
|
702
740
|
break;
|
703
|
-
|
704
|
-
|
741
|
+
|
742
|
+
case 8: // Delete
|
705
743
|
this.deleteAtCurrent();
|
706
744
|
ev.preventDefault();
|
707
745
|
break;
|
746
|
+
|
708
747
|
default:
|
709
748
|
break;
|
710
749
|
}
|
@@ -721,7 +760,7 @@ REPLConsole.prototype.onKeyDown = function(ev) {
|
|
721
760
|
_this.addToInput(_this.clipboard.value);
|
722
761
|
_this.clipboard.value = "";
|
723
762
|
_this.clipboard.blur();
|
724
|
-
},
|
763
|
+
}, 100);
|
725
764
|
}
|
726
765
|
}
|
727
766
|
|
@@ -757,6 +796,47 @@ REPLConsole.prototype.deleteAtCurrent = function() {
|
|
757
796
|
}
|
758
797
|
};
|
759
798
|
|
799
|
+
/**
|
800
|
+
* Deletes the current line.
|
801
|
+
*/
|
802
|
+
REPLConsole.prototype.deleteLine = function() {
|
803
|
+
if (this._caretPos > 0) {
|
804
|
+
this.setInput("", 0);
|
805
|
+
|
806
|
+
if (!this._input) {
|
807
|
+
this.autocomplete && this.autocomplete.cancel();
|
808
|
+
this.autocomplete = false;
|
809
|
+
}
|
810
|
+
}
|
811
|
+
};
|
812
|
+
|
813
|
+
/**
|
814
|
+
* Deletes the current word.
|
815
|
+
*/
|
816
|
+
REPLConsole.prototype.deleteWord = function() {
|
817
|
+
if (this._caretPos > 0) {
|
818
|
+
var i = 1, current = this._caretPos;
|
819
|
+
while (this._input[current - i++] == " ");
|
820
|
+
|
821
|
+
var deleteIndex = 0;
|
822
|
+
for (; current - i > 0; i++) {
|
823
|
+
if (this._input[current - i] == " ") {
|
824
|
+
deleteIndex = current - i;
|
825
|
+
break;
|
826
|
+
}
|
827
|
+
}
|
828
|
+
|
829
|
+
var before = this._input.substring(0, deleteIndex);
|
830
|
+
var after = this._input.substring(current, this._input.length);
|
831
|
+
this.setInput(before + after, deleteIndex);
|
832
|
+
|
833
|
+
if (!this._input) {
|
834
|
+
this.autocomplete && this.autocomplete.cancel();
|
835
|
+
this.autocomplete = false;
|
836
|
+
}
|
837
|
+
}
|
838
|
+
};
|
839
|
+
|
760
840
|
/**
|
761
841
|
* Insert a character at the current position.
|
762
842
|
*/
|
@@ -795,11 +875,20 @@ REPLConsole.prototype.scrollToBottom = function() {
|
|
795
875
|
this.outer.scrollTop = this.outer.scrollHeight;
|
796
876
|
};
|
797
877
|
|
798
|
-
// Change the binding of the console
|
799
|
-
REPLConsole.prototype.switchBindingTo = function(frameId, callback) {
|
878
|
+
// Change the binding of the console.
|
879
|
+
REPLConsole.prototype.switchBindingTo = function(frameId, exceptionObjectId, callback) {
|
800
880
|
var url = this.getSessionUrl('trace');
|
801
881
|
var params = "frame_id=" + encodeURIComponent(frameId);
|
802
|
-
|
882
|
+
|
883
|
+
if (exceptionObjectId) {
|
884
|
+
params = params + "&exception_object_id=" + encodeURIComponent(exceptionObjectId);
|
885
|
+
}
|
886
|
+
|
887
|
+
var _this = this;
|
888
|
+
postRequest(url, params, function() {
|
889
|
+
var text = "Context has changed to: " + callback();
|
890
|
+
_this.writeNotification(text);
|
891
|
+
});
|
803
892
|
};
|
804
893
|
|
805
894
|
/**
|
@@ -835,7 +924,6 @@ REPLConsole.request = function request(method, url, params, callback) {
|
|
835
924
|
xhr.open(method, url, true);
|
836
925
|
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
837
926
|
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
838
|
-
xhr.setRequestHeader("Accept", "<%= Mime[:web_console_v2] %>");
|
839
927
|
xhr.send(params);
|
840
928
|
|
841
929
|
xhr.onreadystatechange = function() {
|
@@ -8,15 +8,13 @@ for (var i = 0; i < traceFrames.length; i++) {
|
|
8
8
|
e.preventDefault();
|
9
9
|
var target = e.target;
|
10
10
|
var frameId = target.dataset.frameId;
|
11
|
+
var exceptionObjectId = target.dataset.exceptionObjectId;
|
11
12
|
|
12
13
|
// Change the binding of the console.
|
13
|
-
changeBinding(frameId, function() {
|
14
|
-
|
15
|
-
selectedFrame.className = selectedFrame.className.replace("selected", "");
|
16
|
-
}
|
17
|
-
|
18
|
-
target.className += " selected";
|
14
|
+
changeBinding(frameId, exceptionObjectId, function() {
|
15
|
+
// Rails already handles toggling the select class
|
19
16
|
selectedFrame = target;
|
17
|
+
return target.innerHTML;
|
20
18
|
});
|
21
19
|
|
22
20
|
// Change the extracted source code
|
@@ -24,8 +22,9 @@ for (var i = 0; i < traceFrames.length; i++) {
|
|
24
22
|
});
|
25
23
|
}
|
26
24
|
|
27
|
-
|
28
|
-
|
25
|
+
// Change the binding of the current session and prompt the user.
|
26
|
+
function changeBinding(frameId, exceptionObjectId, callback) {
|
27
|
+
REPLConsole.currentSession.switchBindingTo(frameId, exceptionObjectId, callback);
|
29
28
|
}
|
30
29
|
|
31
30
|
function changeSourceExtract(frameId) {
|
@@ -0,0 +1,24 @@
|
|
1
|
+
// Push the error page body upwards the size of the console.
|
2
|
+
document.addEventListener('DOMContentLoaded', function() {
|
3
|
+
var consoleElement = document.getElementById('console');
|
4
|
+
var resizerElement = consoleElement.getElementsByClassName('resizer')[0];
|
5
|
+
var bodyElement = document.body;
|
6
|
+
|
7
|
+
function setBodyElementBottomMargin(pixels) {
|
8
|
+
bodyElement.style.marginBottom = pixels + 'px';
|
9
|
+
}
|
10
|
+
|
11
|
+
var currentConsoleElementHeight = consoleElement.offsetHeight;
|
12
|
+
setBodyElementBottomMargin(currentConsoleElementHeight);
|
13
|
+
|
14
|
+
resizerElement.addEventListener('mousedown', function(event) {
|
15
|
+
function recordConsoleElementHeight(event) {
|
16
|
+
resizerElement.removeEventListener('mouseup', recordConsoleElementHeight);
|
17
|
+
|
18
|
+
var currentConsoleElementHeight = consoleElement.offsetHeight;
|
19
|
+
setBodyElementBottomMargin(currentConsoleElementHeight);
|
20
|
+
}
|
21
|
+
|
22
|
+
resizerElement.addEventListener('mouseup', recordConsoleElementHeight);
|
23
|
+
});
|
24
|
+
});
|