Commit 9fdeee7b authored by Ryan Kuba's avatar Ryan Kuba

#65 decided on going with xterm, consoles can be accessed from the taisun...

#65 decided on going with xterm, consoles can be accessed from the taisun interface so stacks with tools can be launched if needed
parent e1b89e19
Pipeline #23086301 passed with stage
in 5 minutes and 49 seconds
......@@ -19,6 +19,7 @@ var express = require('express');
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var pty = require('node-pty');
var Docker = require('dockerode');
var docker = new Docker({socketPath: '/var/run/docker.sock'});
var exec = require('child_process').exec;
......@@ -116,6 +117,7 @@ app.use('/public', express.static(__dirname + '/public'));
//// Embedded guac ////
app.get("/desktop/:containerid", function (req, res) {
var container = docker.getContainer(req.params.containerid);
// Make sure this is a container
container.inspect(function (err, data) {
if (data == null){
res.send('container does not exist');
......@@ -126,6 +128,41 @@ app.get("/desktop/:containerid", function (req, res) {
}
});
});
//// Terminal Emulator ////
app.get("/terminal/:containerid", function (req, res) {
var container = docker.getContainer(req.params.containerid);
// Make sure this is a container
container.inspect(function (err, data) {
if (data == null){
res.send('container does not exist');
}
else{
// Shell check
var options = {
Cmd: ['/bin/sh', '-c', 'test -e /bin/bash12'],
AttachStdout: true,
AttachStderr: true
};
container.exec(options, function(err, exec) {
if (err) return;
exec.start(function(err, stream) {
if (err) return;
container.modem.demuxStream(stream, process.stdout, process.stderr);
stream.on('end', function(output){
exec.inspect(function(err, data) {
if (data.ExitCode == 0){
res.render(__dirname + '/views/terminal.ejs', {containerid : req.params.containerid,shell : '/bin/bash'});
}
else{
res.render(__dirname + '/views/terminal.ejs', {containerid : req.params.containerid,shell : '/bin/sh'});
}
});
});
});
});
}
});
});
//// Api ////
// Container data
app.get("/containers", function (req, res) {
......@@ -204,12 +241,12 @@ io.on('connection', function(socket){
io.sockets.in(socket.id).emit('modal_update','Starting Launch Process for Guacd');
// Check if the guacd image exists on this server
images.list(function (err, res) {
if (JSON.stringify(res).indexOf('glyptodon/guacd:latest') > -1 ){
if (JSON.stringify(res).indexOf('guacamole/guacd:latest') > -1 ){
deployguac();
}
else {
io.sockets.in(socket.id).emit('modal_update','Guacd image not present on server downloading now');
docker.pull('glyptodon/guacd:latest', function(err, stream) {
docker.pull('guacamole/guacd:latest', function(err, stream) {
stream.pipe(process.stdout);
stream.once('end', deployguac);
});
......@@ -460,6 +497,49 @@ io.on('connection', function(socket){
socket.on('getmanage', function(){
containerinfo('manageinfo');
});
// Container Terminal access
socket.on('spawnterm', function(containerid, w, h, shell){
console.log('Spawning terminal on ' + containerid);
var container = docker.getContainer(containerid);
var cmd = {
"AttachStdout": true,
"AttachStderr": true,
"AttachStdin": true,
"Tty": true,
Cmd: [shell]
};
container.exec(cmd, (err, exec) => {
if (err) return;
var options = {
'Tty': true,
stream: true,
stdin: true,
stdout: true,
stderr: true,
hijack: true
};
exec.start(options, (err, stream) => {
if (err) return;
var dimensions = { h, w };
exec.resize(dimensions, () => { });
stream.on('data', (chunk) => {
io.sockets.in(socket.id).emit('termdata', chunk.toString());
});
socket.on('termdata', (data) => {
stream.write(data);
});
socket.on('resizeterm', (w, h) => {
var dimensions = { h, w };
exec.resize(dimensions, () => { });
});
// Close Terminal
socket.on('killterm', function(){
console.log('Killing Terminal on ' + containerid);
stream.end();
});
});
});
});
///////////////////
//// Functions ////
///////////////////
......@@ -479,6 +559,7 @@ io.on('connection', function(socket){
function getres(id){
var xcmd = 'docker exec ' + id + ' xrandr';
exec(xcmd, function (err, stdout) {
if (err) return;
var resolutions = xparse(stdout);
io.sockets.in(socket.id).emit('sendres', resolutions);
});
......@@ -529,7 +610,7 @@ io.on('connection', function(socket){
}
else{
var guacoptions ={
Image: 'glyptodon/guacd',
Image: 'guacamole/guacd',
name: 'guacd'
};
docker.createContainer(guacoptions, function (err, container){
......@@ -559,11 +640,13 @@ io.on('connection', function(socket){
function upgradetaisun(){
// Check if the upgrade image exists on this server
images.list(function (err, res) {
if (err) return;
if (JSON.stringify(res).indexOf('taisun/updater:latest') > -1 ){
runupgrade();
}
else {
docker.pull('taisun/updater:latest', function(err, stream) {
if (err) return;
stream.pipe(process.stdout);
stream.once('end', runupgrade);
});
......@@ -595,17 +678,20 @@ io.on('connection', function(socket){
function upgradestack(stackname){
// Check if the upgrade image exists on this server
images.list(function (err, res) {
if (err) return;
if (JSON.stringify(res).indexOf('taisun/updater:latest') > -1 ){
stackupgrade(stackname);
}
else {
io.sockets.in(socket.id).emit('senddockerodeoutstart','Need to pull the updater image');
docker.pull('taisun/updater:latest', function(err, stream) {
docker.modem.followProgress(stream, onFinished, onProgress);
if (err) return;
docker.modem.followProgress(stream, onFinished, onProgress);
function onProgress(event) {
io.sockets.in(socket.id).emit('senddockerodeout', event);
io.sockets.in(socket.id).emit('senddockerodeout', event);
}
function onFinished(err) {
if (err) return;
io.sockets.in(socket.id).emit('senddockerodeoutstart', 'Finished Pull process for updater');
stackupgrade(stackname);
console.log('Finished Pulling updater');
......@@ -680,6 +766,7 @@ io.on('connection', function(socket){
io.sockets.in(socket.id).emit('senddockerodeout', event);
}
function onFinished(err, output) {
if (err) return;
io.sockets.in(socket.id).emit('senddockerodeoutdone', 'Finished Build process for ' + repo + ' at ' + checkout);
console.log('Finished building ' + repo + ' at ' + checkout);
rmdir(tempfolder);
......@@ -798,6 +885,8 @@ io.on('connection', function(socket){
});
// Spin up application on port 80
http.listen(80, function(){
console.log('listening on *:80');
......
......@@ -1755,14 +1755,21 @@
"nan": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==",
"optional": true
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
},
"negotiator": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
},
"node-pty": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-0.7.4.tgz",
"integrity": "sha512-WxMY1BsGcHJ2Z2qWpYL7QbfOSnkkCzV0H/9+dJ7uQEIJyz0A4fVBLymswBCTc7RoweY5ingib2gNvf87KvJxuA==",
"requires": {
"nan": "2.10.0"
}
},
"node.extend": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/node.extend/-/node.extend-1.0.8.tgz",
......
......@@ -1061,7 +1061,7 @@ socket.on('manageinfo', function(containers) {
<div class="card mb-3">\
<div class="card-header">\
<i class="fab fa-docker"></i>\
Container ' + name + ' <button type="button" style="cursor:pointer;" data-toggle="modal" data-target="#modal" class="btn btn-sm btn-primary containerlogsbutton float-right" value="' + id + '">Logs <i class="fa fa-fw fa-terminal"></i></button>\
Container ' + name + ' <button type="button" style="cursor:pointer;" data-toggle="modal" data-target="#modal" class="btn btn-sm btn-primary containerlogsbutton float-right" value="' + id + '">Logs <i class="fas fa-file-alt"></i></button> <button type="button" style="cursor:pointer;margin-right:10px;" class="btn btn-sm btn-primary float-right" onclick="window.open(\'/terminal/' + id + '\',\'_blank\');">Terminal <i class="fa fa-fw fa-terminal"></i></button>\
</div>\
<div style="overflow-x:auto" class="card-body" id="' + id + '">\
<div class="card mb-3">\
......
// Initiate a websocket connection to the server
//import * as fit from './fit.js';
var host = window.location.hostname;
var port = window.location.port;
var protocol = window.location.protocol;
var socket = io.connect(protocol + '//' + host + ':' + port, {});
var containerid = $('#containerid').val();
var shell = $('#shell').val();
$(document).ready(function(){
var terminalContainer = document.getElementById('terminal-container');
var term = new Terminal({cursorBlink: true});
term.open(terminalContainer);
var geometry = proposeGeometry(term);
socket.emit('spawnterm', containerid, geometry.cols, geometry.rows, shell );
// Browser -> Backend
term.on('data', function (data) {
fit(term);
socket.emit('termdata', data);
});
// Backend -> Browser
socket.on('termdata', function (data) {
term.write(data);
});
// When window is closed terminate the shell
window.onbeforeunload = closepid;
function closepid(){
socket.emit('killterm');
return null;
}
// Resize TTY when window stops resizing
$(window).resize(function() {
if(this.resizeTO) clearTimeout(this.resizeTO);
this.resizeTO = setTimeout(function() {
$(this).trigger('resizeEnd');
}, 500);
});
$(window).bind('resizeEnd', function() {
resizeterm();
});
function resizeterm(){
fit(term);
var geometry = proposeGeometry(term);
socket.emit('resizeterm', geometry.cols, geometry.rows);
}
});
// Fit logic pulled xterm fit addon and modified to send geometry to docker for tty
function proposeGeometry(term) {
if (!term.element.parentElement) {
return null;
}
var parentElementStyle = window.getComputedStyle(term.element.parentElement);
var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17);
var elementStyle = window.getComputedStyle(term.element);
var elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom'));
var elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left'));
var availableHeight = parentElementHeight - elementPaddingVer;
var availableWidth = parentElementWidth - elementPaddingHor;
var geometry = {
cols: Math.floor(availableWidth / term.renderer.dimensions.actualCellWidth),
rows: Math.floor(availableHeight / term.renderer.dimensions.actualCellHeight)
};
return geometry;
}
function fit(term) {
var geometry = proposeGeometry(term);
if (geometry) {
// Force a full render
if (term.rows !== geometry.rows || term.cols !== geometry.cols) {
term.renderer.clear();
term.resize(geometry.cols, geometry.rows);
}
}
}
\ No newline at end of file
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* https://github.com/chjj/term.js
* @license MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Originally forked from (with the author's permission):
* Fabrice Bellard's javascript vt100 for jslinux:
* http://bellard.org/jslinux/
* Copyright (c) 2011 Fabrice Bellard
* The original design remains. The terminal itself
* has been extended to include xterm CSI codes, among
* other features.
*/
/**
* Default styles for xterm.js
*/
.xterm {
font-family: courier-new, courier, monospace;
font-feature-settings: "liga" 0;
position: relative;
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
.xterm.focus,
.xterm:focus {
outline: none;
}
.xterm .xterm-helpers {
position: absolute;
top: 0;
/**
* The z-index of the helpers must be higher than the canvases in order for
* IMEs to appear on top.
*/
z-index: 10;
}
.xterm .xterm-helper-textarea {
/*
* HACK: to fix IE's blinking cursor
* Move textarea out of the screen to the far left, so that the cursor is not visible.
*/
position: absolute;
opacity: 0;
left: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -10;
/** Prevent wrapping so the IME appears against the textarea at the correct position */
white-space: nowrap;
overflow: hidden;
resize: none;
}
.xterm .composition-view {
/* TODO: Composition position got messed up somewhere */
background: #000;
color: #FFF;
display: none;
position: absolute;
white-space: nowrap;
z-index: 1;
}
.xterm .composition-view.active {
display: block;
}
.xterm .xterm-viewport {
/* On OS X this is required in order for the scroll bar to appear fully opaque */
background-color: #000;
overflow-y: scroll;
}
.xterm canvas {
position: absolute;
left: 0;
top: 0;
}
.xterm .xterm-scroll-area {
visibility: hidden;
}
.xterm .xterm-char-measure-element {
display: inline-block;
visibility: hidden;
position: absolute;
left: -9999em;
}
.xterm.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default;
}
.xterm:not(.enable-mouse-events) {
cursor: text;
}
This diff is collapsed.
<html>
<head>
<title>Terminal</title>
<link href="/public/vendor/xterm/css/xterm.css" rel="stylesheet" type="text/css">
<script src="/public/vendor/jquery/jquery.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="/public/vendor/xterm/js/xterm.js"></script>
</head>
<body>
<div id="terminal-container" style="position:fixed; top:0px; left:0px; bottom:0px; right:0px; width:100%; height:100%; border:none; margin:0; padding:0; overflow:hidden; z-index:999999;"></div>
<!-- Storage for variables -->
<input type="hidden" id="containerid" value="<%- containerid -%>" />
<input type="hidden" id="shell" value="<%- shell -%>" />
<!-- Main Logic -->
<script src="/public/js/term.js" ></script>
</body>
</html>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment