1
0
mirror of https://github.com/bingohuang/docker-labs.git synced 2025-07-14 01:57:32 +08:00
2017-05-05 03:12:45 +02:00

534 lines
19 KiB
JavaScript

(function() {
'use strict';
var app = angular.module('DockerPlay', ['ngMaterial']);
// Automatically redirects user to a new session when bypassing captcha.
// Controller keeps code/logic separate from the HTML
app.controller("BypassController", ['$scope', '$log', '$http', '$location', '$timeout', function($scope, $log, $http, $location, $timeout) {
setTimeout(function() {
document.getElementById("welcomeFormBypass").submit();
}, 500);
}]);
app.controller('PlayController', ['$scope', '$log', '$http', '$location', '$timeout', '$mdDialog', '$window', 'TerminalService', 'KeyboardShortcutService', 'InstanceService', function($scope, $log, $http, $location, $timeout, $mdDialog, $window, TerminalService, KeyboardShortcutService, InstanceService) {
$scope.sessionId = window.location.pathname.replace('/p/', '');
$scope.instances = [];
$scope.idx = {};
$scope.selectedInstance = null;
$scope.isAlive = true;
$scope.ttl = '--:--:--';
$scope.connected = true;
$scope.isInstanceBeingCreated = false;
$scope.newInstanceBtnText = '+ Add new instance';
$scope.deleteInstanceBtnText = 'Delete';
$scope.isInstanceBeingDeleted = false;
var selectedKeyboardShortcuts = KeyboardShortcutService.getCurrentShortcuts();
angular.element($window).bind('resize', function() {
if ($scope.selectedInstance) {
$scope.resize($scope.selectedInstance.term.proposeGeometry());
}
});
$scope.$on("settings:shortcutsSelected", function(e, preset) {
selectedKeyboardShortcuts = preset;
});
$scope.showAlert = function(title, content, parent) {
$mdDialog.show(
$mdDialog.alert()
.parent(angular.element(document.querySelector(parent || '#popupContainer')))
.clickOutsideToClose(true)
.title(title)
.textContent(content)
.ok('Got it!')
);
}
$scope.resize = function(geometry) {
$scope.socket.emit('viewport resize', geometry.cols, geometry.rows);
}
KeyboardShortcutService.setResizeFunc($scope.resize);
$scope.closeSession = function() {
$scope.socket.emit('session close');
}
$scope.upsertInstance = function(info) {
var i = info;
if (!$scope.idx[i.name]) {
$scope.instances.push(i);
i.buffer = '';
$scope.idx[i.name] = i;
} else {
$scope.idx[i.name].ip = i.ip;
$scope.idx[i.name].hostname = i.hostname;
}
return $scope.idx[i.name];
}
$scope.newInstance = function() {
updateNewInstanceBtnState(true);
$http({
method: 'POST',
url: '/sessions/' + $scope.sessionId + '/instances',
data : { ImageName : InstanceService.getDesiredImage() }
}).then(function(response) {
var i = $scope.upsertInstance(response.data);
$scope.showInstance(i);
}, function(response) {
if (response.status == 409) {
$scope.showAlert('Max instances reached', 'Maximum number of instances reached')
}
}).finally(function() {
updateNewInstanceBtnState(false);
});
}
$scope.getSession = function(sessionId) {
$http({
method: 'GET',
url: '/sessions/' + $scope.sessionId,
}).then(function(response) {
if (response.data.created_at) {
$scope.expiresAt = moment(response.data.expires_at);
setInterval(function() {
$scope.ttl = moment.utc($scope.expiresAt.diff(moment())).format('HH:mm:ss');
$scope.$apply();
}, 1000);
}
var socket = io({ path: '/sessions/' + sessionId + '/ws' });
socket.on('terminal out', function(name, data) {
var instance = $scope.idx[name];
if (!instance) {
// instance is new and was created from another client, we should add it
$scope.upsertInstance({ name: name });
instance = $scope.idx[name];
}
if (!instance.term) {
instance.buffer += data;
} else {
instance.term.write(data);
}
});
socket.on('session end', function() {
$scope.showAlert('Session timed out!', 'Your session has expired and all of your instances have been deleted.', '#sessionEnd')
$scope.isAlive = false;
});
socket.on('viewport', function(rows, cols) {
});
socket.on('new instance', function(name, ip, hostname) {
$scope.upsertInstance({ name: name, ip: ip, hostname: hostname });
$scope.$apply(function() {
if ($scope.instances.length == 1) {
$scope.showInstance($scope.instances[0]);
}
});
});
socket.on('delete instance', function(name) {
$scope.removeInstance(name);
$scope.$apply();
});
socket.on('viewport resize', function(cols, rows) {
// viewport has changed, we need to resize all terminals
$scope.instances.forEach(function(instance) {
instance.term.resize(cols, rows);
});
});
socket.on('connect_error', function() {
$scope.connected = false;
});
socket.on('connect', function() {
$scope.connected = true;
});
socket.on('instance stats', function(name, mem, cpu, isManager, ports) {
$scope.idx[name].mem = mem;
$scope.idx[name].cpu = cpu;
$scope.idx[name].isManager = isManager;
$scope.idx[name].ports = ports;
$scope.$apply();
});
$scope.socket = socket;
var i = response.data;
for (var k in i.instances) {
var instance = i.instances[k];
$scope.instances.push(instance);
$scope.idx[instance.name] = instance;
}
// If instance is passed in URL, select it
let inst = $scope.idx[$location.hash()];
if (inst) $scope.showInstance(inst);
}, function(response) {
if (response.status == 404) {
document.write('session not found');
return
}
});
}
$scope.getProxyUrl = function(instance, port) {
var url = window.location.protocol + '//pwd' + instance.ip.replace(/\./g, '_') + '-' + port + '.' + window.location.host;
return url;
}
$scope.showInstance = function(instance) {
$scope.selectedInstance = instance;
$location.hash(instance.name);
if (!instance.creatingTerminal) {
if (!instance.term) {
$timeout(function() {
createTerminal(instance);
TerminalService.setFontSize(TerminalService.getFontSize());
instance.term.focus();
}, 0, false);
return
}
}
$timeout(function() {
instance.term.focus();
}, 0, false);
}
$scope.removeInstance = function(name) {
if ($scope.idx[name]) {
delete $scope.idx[name];
$scope.instances = $scope.instances.filter(function(i) {
return i.name != name;
});
if ($scope.instances.length) {
$scope.showInstance($scope.instances[0]);
}
}
}
$scope.deleteInstance = function(instance) {
updateDeleteInstanceBtnState(true);
$http({
method: 'DELETE',
url: '/sessions/' + $scope.sessionId + '/instances/' + instance.name,
}).then(function(response) {
$scope.removeInstance(instance.name);
}, function(response) {
console.log('error', response);
}).finally(function() {
updateDeleteInstanceBtnState(false);
});
}
$scope.getSession($scope.sessionId);
function createTerminal(instance, cb) {
if (instance.term) {
return instance.term;
}
var terminalContainer = document.getElementById('terminal-' + instance.name);
var term = new Terminal({
cursorBlink: false
});
term.attachCustomKeydownHandler(function(e) {
// Ctrl + Alt + C
if (e.ctrlKey && e.altKey && (e.keyCode == 67)) {
document.execCommand('copy');
return false;
}
});
term.attachCustomKeydownHandler(function(e) {
if (selectedKeyboardShortcuts == null)
return;
var presets = selectedKeyboardShortcuts.presets
.filter(function(preset) { return preset.keyCode == e.keyCode })
.filter(function(preset) { return (preset.metaKey == undefined && !e.metaKey) || preset.metaKey == e.metaKey })
.filter(function(preset) { return (preset.ctrlKey == undefined && !e.ctrlKey) || preset.ctrlKey == e.ctrlKey })
.filter(function(preset) { return (preset.altKey == undefined && !e.altKey) || preset.altKey == e.altKey })
.forEach(function(preset) { preset.action({ terminal : term })});
});
term.open(terminalContainer);
// Set geometry during the next tick, to avoid race conditions.
setTimeout(function() {
$scope.resize(term.proposeGeometry());
}, 4);
term.on('data', function(d) {
$scope.socket.emit('terminal in', instance.name, d);
});
instance.term = term;
if (instance.buffer) {
term.write(instance.buffer);
instance.buffer = '';
}
if (cb) {
cb();
}
}
function updateNewInstanceBtnState(isInstanceBeingCreated) {
if (isInstanceBeingCreated === true) {
$scope.newInstanceBtnText = '+ Creating...';
$scope.isInstanceBeingCreated = true;
} else {
$scope.newInstanceBtnText = '+ Add new instance';
$scope.isInstanceBeingCreated = false;
}
}
function updateDeleteInstanceBtnState(isInstanceBeingDeleted) {
if (isInstanceBeingDeleted === true) {
$scope.deleteInstanceBtnText = 'Deleting...';
$scope.isInstanceBeingDeleted = true;
} else {
$scope.deleteInstanceBtnText = 'Delete';
$scope.isInstanceBeingDeleted = false;
}
}
}])
.config(['$mdIconProvider', '$locationProvider', function($mdIconProvider, $locationProvider) {
$locationProvider.html5Mode({enabled: true, requireBase: false});
$mdIconProvider.defaultIconSet('../assets/social-icons.svg', 24);
}])
.component('settingsIcon', {
template : "<md-button ng-click='$ctrl.onClick()'><md-icon class='material-icons'>settings</md-icon></md-button>",
controller : function($mdDialog) {
var $ctrl = this;
$ctrl.onClick = function() {
$mdDialog.show({
controller : function() {},
template : "<settings-dialog></settings-dialog>",
parent: angular.element(document.body),
clickOutsideToClose : true
})
}
}
})
.component("settingsDialog", {
templateUrl : "settings-modal.html",
controller : function($mdDialog, KeyboardShortcutService, $rootScope, InstanceService, TerminalService) {
var $ctrl = this;
$ctrl.$onInit = function() {
$ctrl.keyboardShortcutPresets = KeyboardShortcutService.getAvailablePresets();
$ctrl.selectedShortcutPreset = KeyboardShortcutService.getCurrentShortcuts();
$ctrl.instanceImages = InstanceService.getAvailableImages();
$ctrl.selectedInstanceImage = InstanceService.getDesiredImage();
$ctrl.terminalFontSizes = TerminalService.getFontSizes();
};
$ctrl.currentShortcutConfig = function(value) {
if (value !== undefined) {
value = JSON.parse(value);
KeyboardShortcutService.setCurrentShortcuts(value);
$ctrl.selectedShortcutPreset = angular.copy(KeyboardShortcutService.getCurrentShortcuts());
$rootScope.$broadcast('settings:shortcutsSelected', $ctrl.selectedShortcutPreset);
}
return JSON.stringify(KeyboardShortcutService.getCurrentShortcuts());
};
$ctrl.currentDesiredInstanceImage = function(value) {
if (value !== undefined) {
InstanceService.setDesiredImage(value);
}
return InstanceService.getDesiredImage(value);
};
$ctrl.currentTerminalFontSize = function(value) {
if (value !== undefined) {
// set font size
TerminalService.setFontSize(value);
return;
}
return TerminalService.getFontSize();
}
$ctrl.close = function() {
$mdDialog.cancel();
}
}
})
.service("InstanceService", function($http) {
var instanceImages = [];
_prepopulateAvailableImages();
return {
getAvailableImages : getAvailableImages,
setDesiredImage : setDesiredImage,
getDesiredImage : getDesiredImage,
};
function getAvailableImages() {
return instanceImages;
}
function getDesiredImage() {
var image = localStorage.getItem("settings.desiredImage");
if (image == null)
return instanceImages[0];
return image;
}
function setDesiredImage(image) {
if (image === null)
localStorage.removeItem("settings.desiredImage");
else
localStorage.setItem("settings.desiredImage", image);
}
function _prepopulateAvailableImages() {
return $http
.get("/instances/images")
.then(function(response) {
instanceImages = response.data;
});
}
})
.run(function(InstanceService) { /* forcing pre-populating for now */ })
.service("KeyboardShortcutService", ['TerminalService', function(TerminalService) {
var resizeFunc;
return {
getAvailablePresets : getAvailablePresets,
getCurrentShortcuts : getCurrentShortcuts,
setCurrentShortcuts : setCurrentShortcuts,
setResizeFunc : setResizeFunc
};
function setResizeFunc(f) {
resizeFunc = f;
}
function getAvailablePresets() {
return [
{ name : "None", presets : [
{ description : "Toggle terminal fullscreen", command : "Alt+enter", altKey : true, keyCode : 13, action : function(context) { TerminalService.toggleFullscreen(context.terminal, resizeFunc); }}
] },
{
name : "Mac OSX",
presets : [
{ description : "Clear terminal", command : "Cmd+K", metaKey : true, keyCode : 75, action : function(context) { context.terminal.clear(); }},
{ description : "Toggle terminal fullscreen", command : "Alt+enter", altKey : true, keyCode : 13, action : function(context) { TerminalService.toggleFullscreen(context.terminal, resizeFunc); }}
]
}
]
}
function getCurrentShortcuts() {
var shortcuts = localStorage.getItem("shortcut-preset-name");
if (shortcuts == null) {
shortcuts = getDefaultShortcutPrefixName();
if (shortcuts == null)
return null;
}
var preset = getAvailablePresets()
.filter(function(preset) { return preset.name == shortcuts; });
if (preset.length == 0)
console.error("Unable to find preset with name '" + shortcuts + "'");
return preset[0];
return (shortcuts == null) ? null : JSON.parse(shortcuts);
}
function setCurrentShortcuts(config) {
localStorage.setItem("shortcut-preset-name", config.name);
}
function getDefaultShortcutPrefixName() {
if (window.navigator.platform.toUpperCase().indexOf('MAC') >= 0)
return "Mac OSX";
return "None";
}
}])
.service('TerminalService', ['$window', function($window) {
var fullscreen;
var fontSize = getFontSize();
return {
getFontSizes : getFontSizes,
setFontSize : setFontSize,
getFontSize : getFontSize,
increaseFontSize : increaseFontSize,
decreaseFontSize : decreaseFontSize,
toggleFullscreen : toggleFullscreen
};
function getFontSizes() {
var terminalFontSizes = [];
for (var i=3; i<40; i++) {
terminalFontSizes.push(i+'px');
}
return terminalFontSizes;
};
function getFontSize() {
if (!fontSize) {
return $('.terminal').css('font-size');
}
return fontSize;
}
function setFontSize(value) {
fontSize = value;
var size = parseInt(value);
$('.terminal').css('font-size', value).css('line-height', (size + 2)+'px');
//.css('line-height', value).css('height', value);
angular.element($window).trigger('resize');
}
function increaseFontSize() {
var sizes = getFontSizes();
var size = getFontSize();
var i = sizes.indexOf(size);
if (i == -1) {
return;
}
if (i+1 > sizes.length) {
return;
}
setFontSize(sizes[i+1]);
}
function decreaseFontSize() {
var sizes = getFontSizes();
var size = getFontSize();
var i = sizes.indexOf(size);
if (i == -1) {
return;
}
if (i-1 < 0) {
return;
}
setFontSize(sizes[i-1]);
}
function toggleFullscreen(terminal, resize) {
if(fullscreen) {
terminal.toggleFullscreen();
resize(fullscreen);
fullscreen = null;
} else {
fullscreen = terminal.proposeGeometry();
terminal.toggleFullscreen();
angular.element($window).trigger('resize');
}
}
}]);
})();