web-console-rails3 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.markdown +2 -2
  3. data/app/assets/javascripts/web_console/application.js +1 -3
  4. data/app/assets/javascripts/web_console/console_sessions.js +3 -24
  5. data/app/assets/stylesheets/web_console/application.css +1 -0
  6. data/app/assets/stylesheets/web_console/console_sessions.css +0 -8
  7. data/app/controllers/web_console/application_controller.rb +1 -0
  8. data/app/controllers/web_console/console_sessions_controller.rb +24 -4
  9. data/app/models/web_console/console_session.rb +32 -62
  10. data/app/views/web_console/console_sessions/index.html.erb +9 -4
  11. data/config/routes.rb +7 -1
  12. data/lib/assets/javascripts/web-console.js +1 -0
  13. data/lib/assets/javascripts/web_console.js +202 -0
  14. data/lib/web_console.rb +1 -1
  15. data/lib/web_console/engine.rb +17 -7
  16. data/lib/web_console/slave.rb +112 -0
  17. data/lib/web_console/version.rb +1 -1
  18. data/test/controllers/web_console/console_sessions_controller_test.rb +29 -11
  19. data/test/dummy/config/application.rb +17 -1
  20. data/test/dummy/config/application.rb.orig +58 -0
  21. data/test/dummy/log/development.log +28962 -0
  22. data/test/dummy/log/test.log +329 -0
  23. data/test/dummy/tmp/cache/assets/CBD/6E0/sprockets%2F3293dfc0984076f0e8371a9e7640f4a8 +0 -0
  24. data/test/dummy/tmp/cache/assets/CD0/FC0/sprockets%2Fbb2777627d42a216f3d9ced4322040b3 +0 -0
  25. data/test/dummy/tmp/cache/assets/CFA/E30/sprockets%2F35be1d26565dc0310c29f1a5e2f62f10 +0 -0
  26. data/test/dummy/tmp/cache/assets/D1D/FD0/sprockets%2Fa4a5ffe670666ce3d8d59179905201ef +0 -0
  27. data/test/dummy/tmp/cache/assets/D21/6B0/sprockets%2F9e242803fe56d6305274ff7e6487deda +0 -0
  28. data/test/dummy/tmp/cache/assets/D22/980/sprockets%2Fce6aa94ad2bc107104c0540f62c5128c +0 -0
  29. data/test/dummy/tmp/cache/assets/D3E/380/sprockets%2F434d98c8380bb9daf43810155aaf68ba +0 -0
  30. data/test/dummy/tmp/cache/assets/D66/940/sprockets%2F6151175b6ad4f9bab0c7e2b508e7b70f +0 -0
  31. data/test/dummy/tmp/cache/assets/D69/710/sprockets%2Ff67078d4b979a58c97feede196f6b385 +0 -0
  32. data/test/dummy/tmp/cache/assets/D95/C40/sprockets%2F09cb0a274209abf0391cbfce6ee67b82 +0 -0
  33. data/test/dummy/tmp/cache/assets/D9B/A30/sprockets%2Fc3436b3fe5da7c2456f26e2ae36da6c5 +0 -0
  34. data/test/dummy/tmp/cache/assets/D9F/400/sprockets%2F7f60332f86073dc8ed80b4c2a9dfcbe1 +0 -0
  35. data/test/dummy/tmp/cache/assets/DAE/8C0/sprockets%2Fa9b8f7bc5ca2efe658a13d7f4609c729 +0 -0
  36. data/test/dummy/tmp/cache/assets/DB3/0C0/sprockets%2F689fb998e2a7add3e00db88df254c87a +0 -0
  37. data/test/dummy/tmp/cache/assets/DD4/440/sprockets%2Fa33d646ac00d8bc87a4a496af6eed96f +0 -0
  38. data/test/dummy/tmp/cache/assets/DDB/890/sprockets%2F5ed566ca9fafd1b82373ffea2a8d8681 +0 -0
  39. data/test/models/console_session_test.rb +16 -75
  40. data/test/web_console/slave_test.rb +53 -0
  41. data/test/web_console_test.rb +51 -11
  42. data/vendor/assets/javascripts/vt100.js +4408 -0
  43. data/vendor/assets/stylesheets/vt100.css +272 -0
  44. metadata +23 -42
  45. data/lib/web_console/fiber.rb +0 -48
  46. data/lib/web_console/repl.rb +0 -59
  47. data/lib/web_console/repl/dummy.rb +0 -38
  48. data/lib/web_console/repl/irb.rb +0 -62
  49. data/lib/web_console/stream.rb +0 -30
  50. data/test/web_console/repl/dummy_test.rb +0 -54
  51. data/test/web_console/repl/irb_test.rb +0 -156
  52. data/test/web_console/repl_test.rb +0 -15
  53. data/vendor/assets/javascripts/jquery.console.js +0 -727
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: edcf6e9666067ebf464f12458a6d7ce664fbccd2
4
- data.tar.gz: f4873b4e0fdd81af7a96e81f31b4f503a880f045
3
+ metadata.gz: 4556090d4990261a3b9cbdfa3e9eb35bc50772ca
4
+ data.tar.gz: f73bd7ae863f2aa237be9160efd7dbb4bd33b4b5
5
5
  SHA512:
6
- metadata.gz: daf689f8d30f22afc92bee293fd585baeab0ab9b83ee1fa8699eb8fdc2e85873f8393179ed0e4f851654dd6c1d7bbcd1f1d7755e03428e1a494948f2c4231e74
7
- data.tar.gz: c4c5f25e7504541322abd30518c6e76c27bf5231bb8e9884c37e4ad978a34f31b99de4c344a485eff03d80adf7b0b885ba3a9ff7258f0d20e49f87fc92395f2c
6
+ metadata.gz: edd0352d444bf3e257e43c0943a6e551e37448b2ac3bd9f1b2d135db93763ee2df5ddf4840bfe3077d23045d70775bde0908e7faf2e2990e57fc1adc7922577d
7
+ data.tar.gz: 15d85727b97fd2b305faeb24e75002a0023807afdcb93455b43376b4b059c4314fab6fdc883e284a3da1a61ec7ff69d354bdb3bed5d37294e63af905bbda9828
data/README.markdown CHANGED
@@ -1,7 +1,7 @@
1
1
  Web Console for Rails 3
2
2
  =======================
3
3
 
4
- [![Travis](https://travis-ci.org/gsamokovarov/web-console-rails3.png)](https://travis-ci.org/gsamokovarov/web-console-rails3)
4
+ [![Travis](https://travis-ci.org/gsamokovarov/web-console-rails3.png)](https://travis-ci.org/gsamokovarov/web-console-rails3) [![Version](https://badge.fury.io/rb/web-console-rails3.png)](http://badge.fury.io/rb/web-console-rails3)
5
5
 
6
6
  [Web Console] is great and all, but by default, it only runs on _Rails 4_. If
7
7
  you can't easily upgrade your application to _Rails 4_, you can use this build
@@ -21,7 +21,7 @@ To install it in your current application, add the following to your `Gemfile`.
21
21
 
22
22
  ```ruby
23
23
  group :development do
24
- gem 'web-console-rails3', '~> 0.2.0'
24
+ gem 'web-console-rails3', '~> 0.3.0'
25
25
  end
26
26
  ```
27
27
 
@@ -1,4 +1,2 @@
1
- //= require jquery
2
- //= require jquery_ujs
3
- //= require jquery.console
1
+ //= require web_console
4
2
  //= require_tree .
@@ -1,24 +1,3 @@
1
- $(function() {
2
- var ERROR_CLASS = 'jquery-console-message-error';
3
-
4
- var $console = $('#console');
5
- var instance = $console.console({
6
- autofocus: true,
7
- promptLabel: $console.data('initial-prompt'),
8
- commandHandle: function(line, report) {
9
- $.ajax({
10
- url: $console.data('remote-path'),
11
- type: 'PUT',
12
- dataType: 'json',
13
- data: { input: line },
14
- success: function(response) {
15
- instance.promptLabel(response.prompt);
16
- report([{msg: response.output}]);
17
- },
18
- error: function(xhr) {
19
- report([{msg: xhr.responseJSON.error, className: ERROR_CLASS}]);
20
- }
21
- });
22
- }
23
- });
24
- });
1
+ window.onload = function() {
2
+ window.terminal = new WebConsole.Terminal(TerminalSettings);
3
+ };
@@ -8,6 +8,7 @@
8
8
  * You're free to add application-wide styles to this file and they'll appear at the top of the
9
9
  * compiled file, but it's generally better to create a new file per style scope.
10
10
  *
11
+ *= require vt100
11
12
  *= require_self
12
13
  *= require_tree .
13
14
  */
@@ -1,8 +0,0 @@
1
- body, div, textarea { margin: 0; padding 0 }
2
-
3
- #console { font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 13px; position: fixed; width: 98%; height: 98%; left: 1%; top: 1% }
4
- #console div.jquery-console-inner { height: 100%; background: #333; color: #fff; overflow: auto }
5
- #console div.jquery-console-focus span.jquery-console-cursor { font-weight: bold; background: #fff }
6
- #console div.jquery-console-message { white-space: pre-wrap }
7
- #console div.jquery-console-message-error { font-weight: bold; color: #ff530d }
8
- #console span.jquery-console-prompt-label { font-weight: bold }
@@ -8,6 +8,7 @@ module WebConsole
8
8
  before_action :prevent_unauthorized_requests!
9
9
 
10
10
  private
11
+
11
12
  def prevent_unauthorized_requests!
12
13
  unless request.remote_ip.in?(WebConsole.config.whitelisted_ips)
13
14
  render nothing: true, status: :unauthorized
@@ -1,4 +1,4 @@
1
- require_dependency "web_console/application_controller"
1
+ require_dependency 'web_console/application_controller'
2
2
 
3
3
  module WebConsole
4
4
  class ConsoleSessionsController < ApplicationController
@@ -6,18 +6,38 @@ module WebConsole
6
6
  render json: exception, status: :gone
7
7
  end
8
8
 
9
+ rescue_from ConsoleSession::Invalid do |exception|
10
+ render json: exception, status: :unprocessable_entity
11
+ end
12
+
9
13
  def index
10
14
  @console_session = ConsoleSession.create
11
15
  end
12
16
 
13
- def update
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
14
25
  @console_session = ConsoleSession.find(params[:id])
15
- render json: @console_session.save(console_session_params)
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 }
16
35
  end
17
36
 
18
37
  private
38
+
19
39
  def console_session_params
20
- params.permit(:input)
40
+ params.permit(:input, :width, :height)
21
41
  end
22
42
  end
23
43
  end
@@ -1,33 +1,35 @@
1
1
  module WebConsole
2
+ # Manage and persist (in memory) WebConsole::Slave instances.
2
3
  class ConsoleSession
3
- include Mutex_m
4
-
5
4
  include ActiveModel::Model
6
- include ActiveModel::Serializers::JSON
7
5
 
8
6
  # In-memory storage for the console sessions. Session preservation is
9
7
  # troubled on servers with multiple workers and threads.
10
8
  INMEMORY_STORAGE = {}
11
9
 
12
- # Store and define the available attributes.
13
- ATTRIBUTES = [ :id, :input, :output, :prompt ].each do |attr|
14
- attr_accessor attr
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
15
18
  end
16
19
 
17
20
  # Raised when trying to find a session that is no longer in the in-memory
18
21
  # session storage.
19
- class NotFound < Exception
20
- def to_json(*)
21
- {error: message}.to_json
22
- end
23
- end
22
+ NotFound = Class.new(Error)
23
+
24
+ # Raised when an operation transition to an invalid state.
25
+ Invalid = Class.new(Error)
24
26
 
25
27
  class << self
26
- # Finds a session by its id.
28
+ # Finds a session by its pid.
27
29
  #
28
30
  # Raises WebConsole::ConsoleSession::Expired if there is no such session.
29
- def find(id)
30
- INMEMORY_STORAGE[id.to_i] or raise NotFound.new('Session unavailable')
31
+ def find(pid)
32
+ INMEMORY_STORAGE[pid.to_i] or raise NotFound, 'Session unavailable'
31
33
  end
32
34
 
33
35
  # Creates an already persisted consolse session.
@@ -35,76 +37,44 @@ module WebConsole
35
37
  # Use this method if you need to persist a session, without providing it
36
38
  # any input.
37
39
  def create
38
- INMEMORY_STORAGE[(model = new).id] = model
40
+ new.persist
39
41
  end
40
42
  end
41
43
 
42
44
  def initialize(attributes = {})
43
- @repl = WebConsole::REPL.default.new
44
-
45
- super
46
- ensure_consequential_id!
47
- populate_repl_attributes!(initial: true)
45
+ @slave = WebConsole::Slave.new
48
46
  end
49
47
 
50
- # Saves the model into the in-memory storage.
51
- #
52
- # Returns false if the model is not valid (e.g. its missing input).
53
- def save(attributes = {})
54
- self.attributes = attributes if attributes.present?
55
- populate_repl_attributes!
56
- store!
48
+ # Explicitly persist the model in the in-memory storage.
49
+ def persist
50
+ INMEMORY_STORAGE[pid] = self
57
51
  end
58
52
 
59
53
  # Returns true if the current session is persisted in the in-memory storage.
60
54
  def persisted?
61
- self == INMEMORY_STORAGE[id]
55
+ self == INMEMORY_STORAGE[pid]
62
56
  end
63
57
 
64
58
  # Returns an Enumerable of all key attributes if any is set, regardless if
65
59
  # the object is persisted or not.
66
60
  def to_key
67
- super if persisted?
61
+ [pid] if persisted?
68
62
  end
69
63
 
70
- protected
71
- # Returns a hash of the attributes and their values.
72
- def attributes
73
- return Hash[ATTRIBUTES.zip([nil])]
74
- end
75
-
76
- # Sets model attributes from a hash.
77
- def attributes=(attributes)
78
- attributes.each do |attr, value|
79
- next unless ATTRIBUTES.include?(attr.to_sym)
80
- public_send(:"#{attr}=", value)
81
- end
82
- end
83
-
84
64
  private
85
- def ensure_consequential_id!
86
- synchronize do
87
- self.id = begin
88
- @@counter ||= 0
89
- @@counter += 1
90
- end
91
- end
92
- end
93
65
 
94
- def populate_repl_attributes!(options = {})
95
- synchronize do
96
- # Don't send any input on the initial population so we don't bump up
97
- # the numbers in the dynamic prompts.
98
- self.output = @repl.send_input(input) unless options[:initial]
99
- self.prompt = @repl.prompt
66
+ def method_missing(name, *args, &block)
67
+ if @slave.respond_to?(name)
68
+ @slave.public_send(name, *args, &block)
69
+ else
70
+ super
100
71
  end
72
+ rescue ArgumentError => exc
73
+ raise Invalid, exc
101
74
  end
102
75
 
103
- def store!
104
- synchronize { INMEMORY_STORAGE[id] = self }
76
+ def respond_to_missing?(name, include_all = false)
77
+ @slave.respond_to?(name) or super
105
78
  end
106
79
  end
107
-
108
- # Explicitly configue include_root_in_json for Rails 3 compatibility.
109
- ConsoleSession.include_root_in_json = false
110
80
  end
@@ -1,4 +1,9 @@
1
- <div id="console"
2
- data-remote-path="<%= web_console.console_session_path(@console_session) %>"
3
- data-initial-prompt="<%= @console_session.prompt %>">
4
- </div>
1
+ <script>
2
+ var TerminalSettings = {
3
+ url: {
4
+ input: "<%= web_console.input_console_session_path(@console_session) %>",
5
+ pendingOutput: "<%= web_console.pending_output_console_session_path(@console_session) %>",
6
+ configuration: "<%= web_console.configuration_console_session_path(@console_session) %>"
7
+ }
8
+ };
9
+ </script>
data/config/routes.rb CHANGED
@@ -1,5 +1,11 @@
1
1
  WebConsole::Engine.routes.draw do
2
2
  root to: 'console_sessions#index'
3
3
 
4
- resources :console_sessions
4
+ resources :console_sessions do
5
+ member do
6
+ put :input
7
+ get :pending_output
8
+ put :configuration
9
+ end
10
+ end
5
11
  end
@@ -0,0 +1 @@
1
+ //= require ./web_console
@@ -0,0 +1,202 @@
1
+ //= require vt100
2
+
3
+ (function() {
4
+
5
+ // Store utilities in an underscore-like object.
6
+ var _ = {
7
+ // Simple prototypical inheritence helper.
8
+ //
9
+ // Forces `cls` to inherit from `base` prototype.
10
+ inherits: function(cls, base) {
11
+ var constructor = _.noop();
12
+ constructor.prototype = base.prototype;
13
+ cls.prototype = new constructor();
14
+ cls.prototype.constructor = cls;
15
+ },
16
+
17
+ // Factory for empty functions.
18
+ noop: function() {
19
+ return function() {};
20
+ },
21
+
22
+ // Binds a function to an explicit context.
23
+ bind: Function.prototype.bind ? function(func, context) {
24
+ return func.bind(context);
25
+ } : function(func, context) {
26
+ return function() {
27
+ return func.apply(context, arguments);
28
+ };
29
+ }
30
+ };
31
+
32
+ // Expose the main WebConsole namespace.
33
+ var WebConsole = this.WebConsole = {};
34
+
35
+ // Use an IE friendly implementation of XMLHttpRequest.
36
+ WebConsole.XHR = (function() {
37
+ try {
38
+ return XMLHttpRequest;
39
+ } catch (e) {
40
+ var constructor = function() {
41
+ return new ActiveXObject('Microsoft.XMLHTTP');
42
+ };
43
+
44
+ // Attach the XHR ready states as constants.
45
+ constructor.UNSET = 0;
46
+ constructor.OPENED = 1;
47
+ constructor.HEADERS_RECEIVED = 2;
48
+ constructor.LOADING = 3;
49
+ constructor.DONE = 3;
50
+ return constructor;
51
+ }
52
+ }).call(this);
53
+
54
+ WebConsole.Terminal = function(options) {
55
+ options || (options = {});
56
+
57
+ if (typeof options.url === 'string') {
58
+ this.url = {
59
+ input: options.url,
60
+ pendingOutput: options.url,
61
+ configuration: options.url
62
+ };
63
+ } else {
64
+ this.url = options.url;
65
+ }
66
+
67
+ this.disableAudio = true;
68
+
69
+ VT100.call(this);
70
+
71
+ this.requestPendingOutput();
72
+ };
73
+
74
+ // Inherit from VT100.
75
+ _.inherits(WebConsole.Terminal, VT100);
76
+
77
+ // Try to be minimalistic and disable the context menu.
78
+ WebConsole.Terminal.prototype.showContextMenu = _.noop();
79
+
80
+ // Don't show the current size on resize.
81
+ WebConsole.Terminal.prototype.showCurrentSize = _.noop();
82
+
83
+ // Shorthand for creating XHR requests.
84
+ WebConsole.Terminal.prototype.createRequest = function(method, url, options) {
85
+ options || (options = {});
86
+
87
+ var request = new WebConsole.XHR();
88
+ request.open(method, url);
89
+
90
+ if (typeof options.form === 'object') {
91
+ var content = [], form = options.form;
92
+
93
+ for (var key in form) {
94
+ var value = form[key];
95
+ content.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
96
+ }
97
+
98
+ var formMimeType = 'application/x-www-form-urlencoded; charset=utf-8';
99
+ request.setRequestHeader('Content-Type', formMimeType);
100
+ request.data = content.join('&');
101
+ }
102
+
103
+ return request;
104
+ };
105
+
106
+ WebConsole.Terminal.prototype.clearPendingInput = function() {
107
+ this.pendingInput = '';
108
+ };
109
+
110
+ WebConsole.Terminal.prototype.feedPendingInput = function(input) {
111
+ (this.pendingInput === void 0) && (this.pendingInput = '');
112
+ this.pendingInput += input;
113
+ };
114
+
115
+ WebConsole.Terminal.prototype.requestPendingOutput = function() {
116
+ var request = this.createRequest('GET', this.url.pendingOutput);
117
+
118
+ request.onreadystatechange = _.bind(function() {
119
+ var response;
120
+
121
+ if (request.readyState === WebConsole.XHR.DONE) {
122
+ if (request.status === 200) {
123
+ response = JSON.parse(request.responseText);
124
+ if (response.output) {
125
+ this.vt100(unescape(encodeURIComponent(response.output)));
126
+ }
127
+
128
+ this.requestPendingOutput();
129
+ } else {
130
+ try {
131
+ // Try to parse the error if it is a JSON.
132
+ response = JSON.parse(request.responseText);
133
+ } catch(e) {
134
+ // No luck with that, but it's not a big deal.
135
+ }
136
+ this.disconnect(response && response.error);
137
+ }
138
+ }
139
+ }, this);
140
+
141
+ request.send(null);
142
+ };
143
+
144
+ // Send the input to the server.
145
+ //
146
+ // Each key press is encoded to an intermediate format, before it is sent to
147
+ // the server.
148
+ //
149
+ // WebConsole#keysPressed is an alias for WebConsole#sendInput.
150
+ WebConsole.Terminal.prototype.sendInput =
151
+ WebConsole.Terminal.prototype.keysPressed = function(input) {
152
+ input || (input = '');
153
+
154
+ if (this.disconnected) return;
155
+ if (this.sendingInput) return this.feedPendingInput(input);
156
+
157
+ // Indicate that we are starting to send input.
158
+ this.sendingInput = true;
159
+
160
+ var request = this.createRequest('PUT', this.url.input, {
161
+ form: { input: (this.pendingInput || '') + input }
162
+ });
163
+
164
+ // Clear the pending input.
165
+ this.clearPendingInput();
166
+
167
+ request.onreadystatechange = _.bind(function() {
168
+ if (request.readyState === WebConsole.XHR.DONE) {
169
+ this.sendingInput = false;
170
+ if (this.pendingInput) this.sendInput();
171
+ }
172
+ }, this);
173
+
174
+ request.send(request.data);
175
+ };
176
+
177
+ // Send the terminal configuration to the server.
178
+ //
179
+ // Right now by configuration, we understand the terminal widht and terminal
180
+ // height.
181
+ //
182
+ // WebConsole#resized is an alias for WebConsole#sendconfiguration.
183
+ WebConsole.Terminal.prototype.sendConfiguration =
184
+ WebConsole.Terminal.prototype.resized = function() {
185
+ if (this.disconnected) return;
186
+
187
+ var request = this.createRequest('PUT', this.url.configuration, {
188
+ form: { width: this.terminalWidth, height: this.terminalHeight }
189
+ });
190
+
191
+ // Just send the configuration and don't care about any output.
192
+ request.send(request.data);
193
+ };
194
+
195
+ // Don't send any more requests to the server.
196
+ WebConsole.Terminal.prototype.disconnect = function(message) {
197
+ this.disconnected = true;
198
+ if (this.cursorX > 0) this.vt100('\r\n');
199
+ this.vt100(message || 'Disconnected');
200
+ };
201
+
202
+ }).call(this);