mirror of
https://github.com/xcat2/confluent.git
synced 2024-11-23 10:02:04 +00:00
728 lines
15 KiB
JavaScript
728 lines
15 KiB
JavaScript
/**
|
|
* tty.js
|
|
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
|
* Copyright 2014, IBM Corporation
|
|
*/
|
|
|
|
;(function() {
|
|
|
|
'use strict';
|
|
/**
|
|
* Elements
|
|
*/
|
|
|
|
var document = this.document
|
|
, window = this
|
|
, root
|
|
, body
|
|
, h1
|
|
, open
|
|
, lights;
|
|
|
|
/**
|
|
* Helpers
|
|
*/
|
|
|
|
var EventEmitter = Terminal.EventEmitter
|
|
, inherits = Terminal.inherits
|
|
, on = Terminal.on
|
|
, off = Terminal.off
|
|
, cancel = Terminal.cancel;
|
|
|
|
/**
|
|
* Console
|
|
*/
|
|
|
|
function ConsoleWindow(consoleurl, nodename) {
|
|
var self = this;
|
|
|
|
if (!(this instanceof ConsoleWindow)) {
|
|
return new ConsoleWindow(consoleurl, nodename);
|
|
}
|
|
|
|
EventEmitter.call(this);
|
|
|
|
var el
|
|
, grip
|
|
, bar
|
|
, button
|
|
, title;
|
|
|
|
el = document.createElement('div');
|
|
el.className = 'window';
|
|
|
|
grip = document.createElement('div');
|
|
grip.className = 'grip';
|
|
|
|
bar = document.createElement('div');
|
|
bar.className = 'bar';
|
|
|
|
button = document.createElement('div');
|
|
button.innerHTML = 'x';
|
|
button.title = 'close';
|
|
button.className = 'tab';
|
|
|
|
title = document.createElement('div');
|
|
title.className = 'title';
|
|
title.innerHTML = nodename;
|
|
|
|
this.nodename = nodename;
|
|
this.element = el;
|
|
this.grip = grip;
|
|
this.bar = bar;
|
|
this.button = button;
|
|
this.title = title;
|
|
this.consoleurl = consoleurl;
|
|
|
|
this.tabs = [];
|
|
this.focused = null;
|
|
|
|
this.cols = 100; //Terminal.geometry[0];
|
|
this.rows = 30; //Terminal.geometry[1];
|
|
|
|
el.appendChild(grip);
|
|
el.appendChild(bar);
|
|
bar.appendChild(title);
|
|
bar.appendChild(button);
|
|
document.body.appendChild(el);
|
|
|
|
//tty.windows.push(this);
|
|
|
|
this.createTab();
|
|
this.focus();
|
|
this.bind();
|
|
|
|
this.tabs[0].once('open', function() {
|
|
//tty.emit('open window', self);
|
|
self.emit('open');
|
|
});
|
|
}
|
|
|
|
inherits(ConsoleWindow, EventEmitter);
|
|
|
|
ConsoleWindow.prototype.bind = function() {
|
|
var self = this
|
|
, el = this.element
|
|
, bar = this.bar
|
|
, grip = this.grip
|
|
, button = this.button
|
|
, last = 0;
|
|
|
|
on(button, 'click', function(ev) {
|
|
self.destroy();
|
|
return cancel(ev);
|
|
});
|
|
|
|
on(grip, 'mousedown', function(ev) {
|
|
self.focus();
|
|
self.resizing(ev);
|
|
return cancel(ev);
|
|
});
|
|
|
|
on(el, 'mousedown', function(ev) {
|
|
if (ev.target !== el && ev.target !== bar) return;
|
|
|
|
self.focus();
|
|
|
|
cancel(ev);
|
|
|
|
if (new Date - last < 600) {
|
|
return self.maximize();
|
|
}
|
|
last = new Date;
|
|
|
|
self.drag(ev);
|
|
|
|
return cancel(ev);
|
|
});
|
|
};
|
|
|
|
ConsoleWindow.prototype.focus = function() {
|
|
// Restack
|
|
var parent = this.element.parentNode;
|
|
if (parent) {
|
|
parent.removeChild(this.element);
|
|
parent.appendChild(this.element);
|
|
}
|
|
|
|
// Focus Foreground Tab
|
|
this.focused.focus();
|
|
|
|
//tty.emit('focus window', this);
|
|
this.emit('focus');
|
|
};
|
|
|
|
ConsoleWindow.prototype.destroy = function() {
|
|
if (this.destroyed) return;
|
|
this.destroyed = true;
|
|
|
|
if (this.minimize) this.minimize();
|
|
|
|
//splice(tty.windows, this);
|
|
//if (tty.windows.length) tty.windows[0].focus();
|
|
|
|
this.element.parentNode.removeChild(this.element);
|
|
|
|
this.each(function(term) {
|
|
term.destroy();
|
|
});
|
|
|
|
//tty.emit('close window', this);
|
|
this.emit('close');
|
|
};
|
|
|
|
ConsoleWindow.prototype.drag = function(ev) {
|
|
var self = this
|
|
, el = this.element;
|
|
|
|
if (this.minimize) return;
|
|
|
|
var drag = {
|
|
left: el.offsetLeft,
|
|
top: el.offsetTop,
|
|
pageX: ev.pageX,
|
|
pageY: ev.pageY
|
|
};
|
|
|
|
el.style.opacity = '0.60';
|
|
el.style.cursor = 'move';
|
|
document.documentElement.style.cursor = 'move';
|
|
|
|
function move(ev) {
|
|
el.style.left =
|
|
(drag.left + ev.pageX - drag.pageX) + 'px';
|
|
el.style.top =
|
|
(drag.top + ev.pageY - drag.pageY) + 'px';
|
|
}
|
|
|
|
function up() {
|
|
el.style.opacity = '';
|
|
el.style.cursor = '';
|
|
document.documentElement.style.cursor = '';
|
|
|
|
off(document, 'mousemove', move);
|
|
off(document, 'mouseup', up);
|
|
|
|
var ev = {
|
|
left: el.style.left.replace(/\w+/g, ''),
|
|
top: el.style.top.replace(/\w+/g, '')
|
|
};
|
|
|
|
//tty.emit('drag window', self, ev);
|
|
self.emit('drag', ev);
|
|
}
|
|
|
|
on(document, 'mousemove', move);
|
|
on(document, 'mouseup', up);
|
|
};
|
|
|
|
ConsoleWindow.prototype.resizing = function(ev) {
|
|
var self = this
|
|
, el = this.element
|
|
, term = this.focused;
|
|
|
|
if (this.minimize) delete this.minimize;
|
|
|
|
var resize = {
|
|
w: el.clientWidth,
|
|
h: el.clientHeight
|
|
};
|
|
|
|
el.style.overflow = 'hidden';
|
|
el.style.opacity = '0.70';
|
|
el.style.cursor = 'se-resize';
|
|
document.documentElement.style.cursor = 'se-resize';
|
|
term.element.style.height = '100%';
|
|
|
|
function move(ev) {
|
|
var x, y;
|
|
y = el.offsetHeight - term.element.clientHeight;
|
|
x = ev.pageX - el.offsetLeft;
|
|
y = (ev.pageY - el.offsetTop) - y;
|
|
el.style.width = x + 'px';
|
|
el.style.height = y + 'px';
|
|
}
|
|
|
|
function up() {
|
|
var x, y;
|
|
|
|
x = el.clientWidth / resize.w;
|
|
y = el.clientHeight / resize.h;
|
|
x = (x * term.cols) | 0;
|
|
y = (y * term.rows) | 0;
|
|
|
|
self.resize(x, y);
|
|
|
|
el.style.width = '';
|
|
el.style.height = '';
|
|
|
|
el.style.overflow = '';
|
|
el.style.opacity = '';
|
|
el.style.cursor = '';
|
|
document.documentElement.style.cursor = '';
|
|
term.element.style.height = '';
|
|
|
|
off(document, 'mousemove', move);
|
|
off(document, 'mouseup', up);
|
|
}
|
|
|
|
on(document, 'mousemove', move);
|
|
on(document, 'mouseup', up);
|
|
};
|
|
|
|
ConsoleWindow.prototype.maximize = function() {
|
|
if (this.minimize) return this.minimize();
|
|
|
|
var self = this
|
|
, el = this.element
|
|
, term = this.focused
|
|
, x
|
|
, y;
|
|
|
|
var m = {
|
|
cols: term.cols,
|
|
rows: term.rows,
|
|
left: el.offsetLeft,
|
|
top: el.offsetTop,
|
|
root: root.className
|
|
};
|
|
|
|
this.minimize = function() {
|
|
delete this.minimize;
|
|
|
|
el.style.left = m.left + 'px';
|
|
el.style.top = m.top + 'px';
|
|
el.style.width = '';
|
|
el.style.height = '';
|
|
term.element.style.width = '';
|
|
term.element.style.height = '';
|
|
el.style.boxSizing = '';
|
|
self.grip.style.display = '';
|
|
root.className = m.root;
|
|
|
|
self.resize(m.cols, m.rows);
|
|
|
|
//tty.emit('minimize window', self);
|
|
self.emit('minimize');
|
|
};
|
|
|
|
window.scrollTo(0, 0);
|
|
|
|
x = root.clientWidth / term.element.offsetWidth;
|
|
y = root.clientHeight / term.element.offsetHeight;
|
|
x = (x * term.cols) | 0;
|
|
y = (y * term.rows) | 0;
|
|
|
|
el.style.left = '0px';
|
|
el.style.top = '0px';
|
|
el.style.width = '100%';
|
|
el.style.height = '100%';
|
|
term.element.style.width = '100%';
|
|
term.element.style.height = '100%';
|
|
el.style.boxSizing = 'border-box';
|
|
this.grip.style.display = 'none';
|
|
root.className = 'maximized';
|
|
|
|
this.resize(x, y);
|
|
|
|
//tty.emit('maximize window', this);
|
|
this.emit('maximize');
|
|
};
|
|
|
|
ConsoleWindow.prototype.resize = function(cols, rows) {
|
|
this.cols = cols;
|
|
this.rows = rows;
|
|
|
|
this.each(function(term) {
|
|
term.resize(cols, rows);
|
|
});
|
|
|
|
//tty.emit('resize window', this, cols, rows);
|
|
this.emit('resize', cols, rows);
|
|
};
|
|
|
|
ConsoleWindow.prototype.each = function(func) {
|
|
var i = this.tabs.length;
|
|
while (i--) {
|
|
func(this.tabs[i], i);
|
|
}
|
|
};
|
|
|
|
ConsoleWindow.prototype.createTab = function() {
|
|
return new Tab(this, this.consoleurl);
|
|
};
|
|
|
|
ConsoleWindow.prototype.highlight = function() {
|
|
var self = this;
|
|
|
|
this.element.style.borderColor = 'orange';
|
|
setTimeout(function() {
|
|
self.element.style.borderColor = '';
|
|
}, 200);
|
|
|
|
this.focus();
|
|
};
|
|
|
|
ConsoleWindow.prototype.focusTab = function(next) {
|
|
var tabs = this.tabs
|
|
, i = indexOf(tabs, this.focused)
|
|
, l = tabs.length;
|
|
|
|
if (!next) {
|
|
if (tabs[--i]) return tabs[i].focus();
|
|
if (tabs[--l]) return tabs[l].focus();
|
|
} else {
|
|
if (tabs[++i]) return tabs[i].focus();
|
|
if (tabs[0]) return tabs[0].focus();
|
|
}
|
|
|
|
return this.focused && this.focused.focus();
|
|
};
|
|
|
|
ConsoleWindow.prototype.nextTab = function() {
|
|
return this.focusTab(true);
|
|
};
|
|
|
|
ConsoleWindow.prototype.previousTab = function() {
|
|
return this.focusTab(false);
|
|
};
|
|
|
|
/**
|
|
* Tab
|
|
*/
|
|
|
|
function Tab(win, consoleurl) {
|
|
var self = this;
|
|
|
|
var cols = win.cols
|
|
, rows = win.rows;
|
|
|
|
Terminal.call(this, {
|
|
cols: cols,
|
|
rows: rows
|
|
});
|
|
|
|
var button = document.createElement('div');
|
|
button.className = 'tab';
|
|
button.innerHTML = '\u2022';
|
|
//win.bar.appendChild(button);
|
|
|
|
on(button, 'click', function(ev) {
|
|
if (ev.ctrlKey || ev.altKey || ev.metaKey || ev.shiftKey) {
|
|
self.destroy();
|
|
} else {
|
|
self.focus();
|
|
}
|
|
return cancel(ev);
|
|
});
|
|
|
|
this.id = '';
|
|
this.consoleurl = consoleurl;
|
|
this.clientcount = 0;
|
|
this.connectstate = 'unconnected';
|
|
this.lasterror = ''
|
|
this.window = win;
|
|
this.button = button;
|
|
this.element = null;
|
|
this.process = '';
|
|
this.open();
|
|
this.hookKeys();
|
|
// Now begins the code that will embarass me when I actually know my way
|
|
// around javascript -jbjohnso
|
|
this.sessid = '';
|
|
this.datapending = false;
|
|
this.waitingdata = false;
|
|
this.sentdata = function(data, textStatus, jqXHR) {
|
|
if (this.waitingdata) {
|
|
$.ajax({type: 'POST', url: consoleurl,
|
|
data: { session: this.sessid, bytes: this.waitingdata },
|
|
success: this.sentdata, dataType: 'json'});
|
|
this.waitingdata = false;
|
|
} else {
|
|
this.datapending = false;
|
|
}
|
|
}.bind(this);
|
|
this.on('data', function(data) {
|
|
// Send data to console from terminal
|
|
if (this.datapending) {
|
|
if (!this.waitingdata) {
|
|
this.waitingdata = data;
|
|
} else {
|
|
this.waitingdata = this.waitingdata + data;
|
|
}
|
|
return;
|
|
}
|
|
this.datapending = true;
|
|
$.ajax({type: 'POST', url: consoleurl, data: { session: this.sessid, bytes: data },
|
|
success: this.sentdata, dataType: 'json'});
|
|
}.bind(this));
|
|
this.gotdata = function(data, textStatus, jqXHR) {
|
|
if ("data" in data) {
|
|
this.write(data.data);
|
|
}
|
|
var updatetitle = false;
|
|
var updateinfo = [];
|
|
if ("connectstate" in data) {
|
|
updatetitle = true;
|
|
this.connectstate = data.connectstate;
|
|
}
|
|
if (this.connectstate != "connected") {
|
|
updateinfo.push(this.connectstate);
|
|
} else {
|
|
self.lasterror = '';
|
|
}
|
|
if ("error" in data) {
|
|
updatetitle = true;
|
|
this.lasterror = data.error
|
|
}
|
|
if (this.lasterror != '') {
|
|
updateinfo.push(this.lasterror);
|
|
}
|
|
if ("clientcount" in data) {
|
|
updatetitle = true;
|
|
this.clientcount = data.clientcount;
|
|
}
|
|
if (this.clientcount > 1) {
|
|
updateinfo.push("clients: " + this.clientcount.toString());
|
|
}
|
|
if (updatetitle == true) {
|
|
if (updateinfo.length > 0) {
|
|
this.window.title.innerHTML = this.window.nodename + " [" + updateinfo.join() + "]";
|
|
} else {
|
|
this.window.title.innerHTML = this.window.nodename;
|
|
}
|
|
}
|
|
$.ajax({type: 'POST', url: consoleurl, data: { session: this.sessid },
|
|
success: this.gotdata, dataType: 'json'});
|
|
}.bind(this);
|
|
this.gotsession = function(data, textStatus, jqXHR) {
|
|
this.sessid = data.session
|
|
$.ajax({type: 'POST', url: consoleurl, data: { session: this.sessid },
|
|
success: this.gotdata, dataType: 'json'});
|
|
}.bind(this);
|
|
$.ajax({type: "POST", url: consoleurl, success: this.gotsession,
|
|
dataType: 'json'});
|
|
|
|
win.tabs.push(this);
|
|
};
|
|
|
|
inherits(Tab, Terminal);
|
|
|
|
Tab.prototype._write = Tab.prototype.write;
|
|
|
|
Tab.prototype.write = function(data) {
|
|
if (this.window.focused !== this) this.button.style.color = 'red';
|
|
return this._write(data);
|
|
};
|
|
|
|
Tab.prototype._focus = Tab.prototype.focus;
|
|
|
|
Tab.prototype.focus = function() {
|
|
if (Terminal.focus === this) return;
|
|
|
|
var win = this.window;
|
|
|
|
// maybe move to Tab.prototype.switch
|
|
if (win.focused !== this) {
|
|
if (win.focused) {
|
|
if (win.focused.element.parentNode) {
|
|
win.focused.element.parentNode.removeChild(win.focused.element);
|
|
}
|
|
win.focused.button.style.fontWeight = '';
|
|
}
|
|
|
|
win.element.appendChild(this.element);
|
|
win.focused = this;
|
|
|
|
//win.title.innerHTML = this.process;
|
|
this.button.style.fontWeight = 'bold';
|
|
this.button.style.color = '';
|
|
}
|
|
|
|
this._focus();
|
|
|
|
win.focus();
|
|
|
|
//tty.emit('focus tab', this);
|
|
this.emit('focus');
|
|
};
|
|
|
|
Tab.prototype._resize = Tab.prototype.resize;
|
|
|
|
Tab.prototype.resize = function(cols, rows) {
|
|
//this.socket.emit('resize', this.id, cols, rows);
|
|
this._resize(cols, rows);
|
|
//tty.emit('resize tab', this, cols, rows);
|
|
this.emit('resize', cols, rows);
|
|
};
|
|
|
|
Tab.prototype.__destroy = Tab.prototype.destroy;
|
|
|
|
Tab.prototype._destroy = function() {
|
|
if (this.destroyed) return;
|
|
this.destroyed = true;
|
|
|
|
var win = this.window;
|
|
|
|
this.button.parentNode.removeChild(this.button);
|
|
if (this.element.parentNode) {
|
|
this.element.parentNode.removeChild(this.element);
|
|
}
|
|
|
|
if (tty.terms[this.id]) delete tty.terms[this.id];
|
|
splice(win.tabs, this);
|
|
|
|
if (win.focused === this) {
|
|
win.previousTab();
|
|
}
|
|
|
|
if (!win.tabs.length) {
|
|
win.destroy();
|
|
}
|
|
|
|
this.__destroy();
|
|
};
|
|
|
|
Tab.prototype.destroy = function() {
|
|
if (this.destroyed) return;
|
|
//TODO: politely let server know of client closure
|
|
this._destroy();
|
|
//tty.emit('close tab', this);
|
|
this.emit('close');
|
|
};
|
|
|
|
Tab.prototype.hookKeys = function() {
|
|
var self = this;
|
|
|
|
// Alt-[jk] to quickly swap between windows.
|
|
this.on('key', function(key, ev) {
|
|
if (Terminal.focusKeys === false) {
|
|
return;
|
|
}
|
|
|
|
var offset
|
|
, i;
|
|
|
|
if (key === '\x1bj') {
|
|
offset = -1;
|
|
} else if (key === '\x1bk') {
|
|
offset = +1;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
i = indexOf(tty.windows, this.window) + offset;
|
|
|
|
this._ignoreNext();
|
|
|
|
if (tty.windows[i]) return tty.windows[i].highlight();
|
|
|
|
if (offset > 0) {
|
|
if (tty.windows[0]) return tty.windows[0].highlight();
|
|
} else {
|
|
i = tty.windows.length - 1;
|
|
if (tty.windows[i]) return tty.windows[i].highlight();
|
|
}
|
|
|
|
return this.window.highlight();
|
|
});
|
|
|
|
this.on('request paste', function(key) {
|
|
this.socket.emit('request paste', function(err, text) {
|
|
if (err) return;
|
|
self.send(text);
|
|
});
|
|
});
|
|
|
|
this.on('request create', function() {
|
|
this.window.createTab();
|
|
});
|
|
|
|
this.on('request term', function(key) {
|
|
if (this.window.tabs[key]) {
|
|
this.window.tabs[key].focus();
|
|
}
|
|
});
|
|
|
|
this.on('request term next', function(key) {
|
|
this.window.nextTab();
|
|
});
|
|
|
|
this.on('request term previous', function(key) {
|
|
this.window.previousTab();
|
|
});
|
|
};
|
|
|
|
Tab.prototype._ignoreNext = function() {
|
|
// Don't send the next key.
|
|
var handler = this.handler;
|
|
this.handler = function() {
|
|
this.handler = handler;
|
|
};
|
|
var showCursor = this.showCursor;
|
|
this.showCursor = function() {
|
|
this.showCursor = showCursor;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Program-specific Features
|
|
*/
|
|
|
|
Tab.prototype._bindMouse = Tab.prototype.bindMouse;
|
|
|
|
Tab.prototype.bindMouse = function() {
|
|
if (!Terminal.programFeatures) return this._bindMouse();
|
|
|
|
var self = this;
|
|
|
|
var wheelEvent = 'onmousewheel' in window
|
|
? 'mousewheel'
|
|
: 'DOMMouseScroll';
|
|
|
|
on(self.element, wheelEvent, function(ev) {
|
|
if (self.mouseEvents) return;
|
|
|
|
if ((ev.type === 'mousewheel' && ev.wheelDeltaY > 0)
|
|
|| (ev.type === 'DOMMouseScroll' && ev.detail < 0)) {
|
|
// page up
|
|
self.keyDown({keyCode: 33});
|
|
} else {
|
|
// page down
|
|
self.keyDown({keyCode: 34});
|
|
}
|
|
|
|
return cancel(ev);
|
|
});
|
|
|
|
return this._bindMouse();
|
|
};
|
|
|
|
/**
|
|
* Helpers
|
|
*/
|
|
|
|
function indexOf(obj, el) {
|
|
var i = obj.length;
|
|
while (i--) {
|
|
if (obj[i] === el) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
function splice(obj, el) {
|
|
var i = indexOf(obj, el);
|
|
if (~i) obj.splice(i, 1);
|
|
}
|
|
|
|
function sanitize(text) {
|
|
if (!text) return '';
|
|
return (text + '').replace(/[&<>]/g, '')
|
|
}
|
|
|
|
this.ConsoleWindow = ConsoleWindow;
|
|
|
|
}).call(function() {
|
|
return this;
|
|
}());
|