forked from gsamokovarov/web-console
-
Notifications
You must be signed in to change notification settings - Fork 178
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #25 from rails/web-terminal-is-back
Bring back 1.0 features without automount
- Loading branch information
Showing
34 changed files
with
7,092 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
//= require_tree . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
//= require web-console | ||
|
||
var AJAXTransport = (function(WebConsole) { | ||
|
||
var inherits = WebConsole.inherits; | ||
var EventEmitter = WebConsole.EventEmitter; | ||
|
||
var FORM_MIME_TYPE = 'application/x-www-form-urlencoded; charset=utf-8'; | ||
|
||
var AJAXTransport = function(options) { | ||
EventEmitter.call(this); | ||
options || (options = {}); | ||
|
||
this.url = (typeof options.url === 'string') ? { | ||
input: options.url, | ||
pendingOutput: options.url, | ||
configuration: options.url | ||
} : options.url; | ||
|
||
this.pendingInput = ''; | ||
|
||
this.initializeEventHandlers(); | ||
}; | ||
|
||
inherits(AJAXTransport, EventEmitter); | ||
|
||
// Initializes the default event handlers. | ||
AJAXTransport.prototype.initializeEventHandlers = function() { | ||
this.on('input', this.sendInput); | ||
this.on('configuration', this.sendConfiguration); | ||
this.once('initialization', function(cols, rows) { | ||
this.emit('configuration', cols, rows); | ||
this.pollForPendingOutput(); | ||
}); | ||
}; | ||
|
||
// Shorthand for creating XHR requests. | ||
AJAXTransport.prototype.createRequest = function(method, url, options) { | ||
options || (options = {}); | ||
|
||
var request = new XMLHttpRequest; | ||
request.open(method, url); | ||
|
||
if (typeof options.form === 'object') { | ||
var content = [], form = options.form; | ||
|
||
for (var key in form) { | ||
var value = form[key]; | ||
content.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); | ||
} | ||
|
||
request.setRequestHeader('Content-Type', FORM_MIME_TYPE); | ||
request.data = content.join('&'); | ||
} | ||
|
||
return request; | ||
}; | ||
|
||
AJAXTransport.prototype.pollForPendingOutput = function() { | ||
var request = this.createRequest('GET', this.url.pendingOutput); | ||
|
||
var self = this; | ||
request.onreadystatechange = function() { | ||
if (request.readyState === XMLHttpRequest.DONE) { | ||
if (request.status === 200) { | ||
self.emit('pendingOutput', request.responseText); | ||
self.pollForPendingOutput(); | ||
} else { | ||
self.emit('disconnect', request); | ||
} | ||
} | ||
}; | ||
|
||
request.send(null); | ||
}; | ||
|
||
// Send the input to the server. | ||
// | ||
// Each key press is encoded to an intermediate format, before it is sent to | ||
// the server. | ||
// | ||
// WebConsole#keysPressed is an alias for WebConsole#sendInput. | ||
AJAXTransport.prototype.sendInput = function(input) { | ||
input || (input = ''); | ||
|
||
if (this.disconnected) return; | ||
if (this.sendingInput) return this.pendingInput += input; | ||
|
||
// Indicate that we are starting to send input. | ||
this.sendingInput = true; | ||
|
||
var request = this.createRequest('PUT', this.url.input, { | ||
form: { input: this.pendingInput + input } | ||
}); | ||
|
||
// Clear the pending input. | ||
this.pendingInput = ''; | ||
|
||
var self = this; | ||
request.onreadystatechange = function() { | ||
if (request.readyState === XMLHttpRequest.DONE) { | ||
self.sendingInput = false; | ||
if (self.pendingInput) self.sendInput(); | ||
} | ||
}; | ||
|
||
request.send(request.data); | ||
}; | ||
|
||
// Send the terminal configuration to the server. | ||
// | ||
// Right now by configuration, we understand the terminal widht and terminal | ||
// height. | ||
// | ||
// WebConsole#resized is an alias for WebConsole#sendconfiguration. | ||
AJAXTransport.prototype.sendConfiguration = function(cols, rows) { | ||
if (this.disconnected) return; | ||
|
||
var request = this.createRequest('PUT', this.url.configuration, { | ||
form: { width: cols, height: rows } | ||
}); | ||
|
||
// Just send the configuration and don't care about any output. | ||
request.send(request.data); | ||
}; | ||
|
||
return AJAXTransport; | ||
|
||
}).call(this, WebConsole); | ||
|
||
window.addEventListener('load', function() { | ||
var geometry = calculateFitScreenGeometry(); | ||
config.terminal.cols = geometry[0]; | ||
config.terminal.rows = geometry[1]; | ||
|
||
var terminal = window.terminal = new WebConsole.Terminal(config.terminal); | ||
|
||
terminal.on('data', function(data) { | ||
transport.emit('input', data); | ||
}); | ||
|
||
var transport = new AJAXTransport(config.transport); | ||
|
||
transport.on('pendingOutput', function(response) { | ||
var json = JSON.parse(response); | ||
if (json.output) terminal.write(json.output); | ||
}); | ||
|
||
transport.on('disconnect', function() { | ||
terminal.destroy(); | ||
}); | ||
|
||
transport.emit('initialization', terminal.cols, terminal.rows); | ||
|
||
// Utilities | ||
// --------- | ||
|
||
function calculateFitScreenGeometry() { | ||
// Currently, resizing term.js is broken. E.g. opening vi causes it to go | ||
// back to 80x24 and fail with off-by-one error. Other stuff, like chip8 | ||
// are rendered incorrectly and so on. | ||
// | ||
// To work around it, create a temporary terminal, just so we can get the | ||
// best dimensions for the screen. | ||
var temporary = new WebConsole.Terminal; | ||
try { | ||
return temporary.fitScreen(); | ||
} finally { | ||
temporary.destroy(); | ||
} | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/* | ||
* This is a manifest file that'll be compiled into application.css, which will include all the files | ||
* listed below. | ||
* | ||
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, | ||
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. | ||
* | ||
* You're free to add application-wide styles to this file and they'll appear at the top of the | ||
* compiled file, but it's generally better to create a new file per style scope. | ||
* | ||
*= require_self | ||
*= require_tree . | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<% WebConsole.config.style.instance_eval do %> | ||
body { color: <%= colors.foreground %>; background: <%= colors.background %>; margin: 0; padding: 0; } | ||
|
||
.terminal { float: left; overflow: hidden; font: <%= font %>; } | ||
.terminal-cursor { color: <%= colors.background %>; background: <%= colors.foreground %>; } | ||
<% end %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module WebConsole | ||
class ApplicationController < ActionController::Base | ||
before_action :prevent_unauthorized_requests! | ||
|
||
private | ||
|
||
def prevent_unauthorized_requests! | ||
unless request.remote_ip.in?(WebConsole.config.whitelisted_ips) | ||
render nothing: true, status: :unauthorized | ||
end | ||
end | ||
end | ||
end |
43 changes: 43 additions & 0 deletions
43
app/controllers/web_console/console_sessions_controller.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
require_dependency 'web_console/application_controller' | ||
|
||
module WebConsole | ||
class ConsoleSessionsController < ApplicationController | ||
rescue_from ConsoleSession::Unavailable do |exception| | ||
render json: exception, status: :gone | ||
end | ||
|
||
rescue_from ConsoleSession::Invalid do |exception| | ||
render json: exception, status: :unprocessable_entity | ||
end | ||
|
||
def index | ||
@console_session = ConsoleSession.create | ||
end | ||
|
||
def input | ||
@console_session = ConsoleSession.find(params[:id]) | ||
@console_session.send_input(console_session_params[:input]) | ||
|
||
render nothing: true | ||
end | ||
|
||
def configuration | ||
@console_session = ConsoleSession.find(params[:id]) | ||
@console_session.configure(console_session_params) | ||
|
||
render nothing: true | ||
end | ||
|
||
def pending_output | ||
@console_session = ConsoleSession.find(params[:id]) | ||
|
||
render json: { output: @console_session.pending_output } | ||
end | ||
|
||
private | ||
|
||
def console_session_params | ||
params.permit(:id, :input, :width, :height) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module WebConsole | ||
module ApplicationHelper | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module WebConsole | ||
module ConsoleSessionHelper | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
module WebConsole | ||
# Manage and persist (in memory) WebConsole::Slave instances. | ||
class ConsoleSession | ||
include ActiveModel::Model | ||
|
||
# In-memory storage for the console sessions. Session preservation is | ||
# troubled on servers with multiple workers and threads. | ||
INMEMORY_STORAGE = {} | ||
|
||
# Base error class for ConsoleSession specific exceptions. | ||
# | ||
# Provides #to_json implementation, so all subclasses are JSON | ||
# serializable. | ||
class Error < StandardError | ||
def to_json(*) | ||
{ error: to_s }.to_json | ||
end | ||
end | ||
|
||
# Raised when trying to find a session that is no longer in the in-memory | ||
# session storage or when the slave process exited. | ||
Unavailable = Class.new(Error) | ||
|
||
# Raised when an operation transition to an invalid state. | ||
Invalid = Class.new(Error) | ||
|
||
class << self | ||
# Finds a session by its pid. | ||
# | ||
# Raises WebConsole::ConsoleSession::Expired if there is no such session. | ||
def find(pid) | ||
INMEMORY_STORAGE[pid.to_i] or raise Unavailable, 'Session unavailable' | ||
end | ||
|
||
# Creates an already persisted consolse session. | ||
# | ||
# Use this method if you need to persist a session, without providing it | ||
# any input. | ||
def create | ||
new.persist | ||
end | ||
end | ||
|
||
def initialize | ||
@slave = WebConsole::Slave.new | ||
end | ||
|
||
# Explicitly persist the model in the in-memory storage. | ||
def persist | ||
INMEMORY_STORAGE[pid] = self | ||
end | ||
|
||
# Returns true if the current session is persisted in the in-memory storage. | ||
def persisted? | ||
self == INMEMORY_STORAGE[pid] | ||
end | ||
|
||
# Returns an Enumerable of all key attributes if any is set, regardless if | ||
# the object is persisted or not. | ||
def to_key | ||
[pid] if persisted? | ||
end | ||
|
||
private | ||
|
||
def delegate_and_call_slave_method(name, *args, &block) | ||
# Cache the delegated method, so we don't have to hit #method_missing | ||
# on every call. | ||
define_singleton_method(name) do |*inner_args, &inner_block| | ||
begin | ||
@slave.public_send(name, *inner_args, &inner_block) | ||
rescue ArgumentError => exc | ||
raise Invalid, exc | ||
rescue Slave::Closed => exc | ||
raise Unavailable, exc | ||
end | ||
end | ||
|
||
# Now call the method, since that's our most common use case. Delegate | ||
# the method and than call it. | ||
public_send(name, *args, &block) | ||
end | ||
|
||
def method_missing(name, *args, &block) | ||
if @slave.respond_to?(name) | ||
delegate_and_call_slave_method(name, *args, &block) | ||
else | ||
super | ||
end | ||
end | ||
|
||
def respond_to_missing?(name, include_all = false) | ||
@slave.respond_to?(name) or super | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>WebConsole</title> | ||
<%= stylesheet_link_tag "web_console/application", media: "all" %> | ||
<%= javascript_include_tag "web_console/application" %> | ||
<%= csrf_meta_tags %> | ||
</head> | ||
<body> | ||
|
||
<%= yield %> | ||
|
||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<script> | ||
var config = { | ||
terminal: { | ||
colors: <%= raw WebConsole.config.style.colors.to_json %> | ||
}, | ||
|
||
transport: { | ||
url: { | ||
input: "<%= web_console.input_console_session_path(@console_session) %>", | ||
pendingOutput: "<%= web_console.pending_output_console_session_path(@console_session) %>", | ||
configuration: "<%= web_console.configuration_console_session_path(@console_session) %>" | ||
} | ||
} | ||
}; | ||
</script> |
Oops, something went wrong.