web-console-rails3 0.2.0 → 0.3.0

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.
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);