Replace go-bindata with go 1.16 embed (#452)
This commit is contained in:
@@ -4,8 +4,10 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"embed"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -31,13 +33,15 @@ import (
|
||||
"google.golang.org/api/people/v1"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/jteeuwen/go-bindata/go-bindata -pkg handlers -o gen_bindata.go -prefix ../www/ ../www/...
|
||||
//go:generate gofmt -w -s gen_bindata.go
|
||||
|
||||
var core pwd.PWDApi
|
||||
var e event.EventApi
|
||||
var landings = map[string][]byte{}
|
||||
|
||||
//go:embed www/*
|
||||
var embeddedFiles embed.FS
|
||||
|
||||
var staticFiles fs.FS
|
||||
|
||||
var latencyHistogramVec = prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "pwd_handlers_duration_ms",
|
||||
Help: "How long it took to process a specific handler, in a specific host",
|
||||
@@ -48,6 +52,7 @@ type HandlerExtender func(h *mux.Router)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(latencyHistogramVec)
|
||||
staticFiles, _ = fs.Sub(embeddedFiles, "www")
|
||||
|
||||
}
|
||||
|
||||
@@ -193,7 +198,7 @@ func Register(extend HandlerExtender) {
|
||||
}
|
||||
|
||||
func serveAsset(w http.ResponseWriter, r *http.Request, name string) {
|
||||
a, err := Asset(name)
|
||||
a, err := fs.ReadFile(staticFiles, name)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
@@ -219,7 +224,7 @@ func initAssets(p *types.Playground) {
|
||||
}
|
||||
|
||||
lpath := path.Join(p.AssetsDir, "landing.html")
|
||||
landing, err := Asset(lpath)
|
||||
landing, err := fs.ReadFile(staticFiles, lpath)
|
||||
if err != nil {
|
||||
log.Printf("Could not load %v: %v", lpath, err)
|
||||
return
|
||||
|
||||
@@ -20,8 +20,8 @@ func (c *CookieID) SetCookie(rw http.ResponseWriter, host string) error {
|
||||
Value: encoded,
|
||||
Domain: host,
|
||||
Path: "/",
|
||||
SameSite: http.SameSiteNoneMode,
|
||||
Secure: true,
|
||||
SameSite: http.SameSiteDefaultMode,
|
||||
Secure: false,
|
||||
HttpOnly: true,
|
||||
}
|
||||
http.SetCookie(rw, cookie)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,7 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
@@ -33,9 +34,9 @@ func Home(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
index, err := Asset(filepath.Join(playground.AssetsDir, "/index.html"))
|
||||
index, err := fs.ReadFile(staticFiles, filepath.Join(playground.AssetsDir, "/index.html"))
|
||||
if err != nil {
|
||||
index, err = Asset("default/index.html")
|
||||
index, err = fs.ReadFile(staticFiles, "default/index.html")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -25,6 +25,7 @@ func LoggedInUser(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := core.UserGet(cookie.Id)
|
||||
if err != nil {
|
||||
log.Printf("Couldn't get user with id %s. Got: %v\n", cookie.Id, err)
|
||||
@@ -207,6 +208,8 @@ func LoginCallback(rw http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Lalalala %#v\n", user)
|
||||
|
||||
cookieData := CookieID{Id: user.Id, UserName: user.Name, UserAvatar: user.Avatar, ProviderId: user.ProviderUserId}
|
||||
|
||||
host := "localhost"
|
||||
|
||||
22
handlers/www/503.html
Normal file
22
handlers/www/503.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Docker Playground</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic|Material+Icons" />
|
||||
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/angular_material/1.1.0/angular-material.min.css">
|
||||
<link rel="stylesheet" href="/assets/style.css" />
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', 'UA-89019737-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div layout="column" style="height:100%;">
|
||||
An error has occurred. If you have some time, please report it. Thanks!
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
928
handlers/www/assets/app.js
Normal file
928
handlers/www/assets/app.js
Normal file
@@ -0,0 +1,928 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var app = angular.module('DockerPlay', ['ngMaterial', 'ngFileUpload', 'ngclipboard']);
|
||||
|
||||
// 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);
|
||||
}]);
|
||||
|
||||
function SessionBuilderModalController($mdDialog, $scope) {
|
||||
$scope.createBuilderTerminal();
|
||||
|
||||
$scope.closeSessionBuilder = function() {
|
||||
$mdDialog.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
app.controller('PlayController', ['$scope', '$rootScope', '$log', '$http', '$location', '$timeout', '$mdDialog', '$window', 'TerminalService', 'KeyboardShortcutService', 'InstanceService', 'SessionService', 'Upload', function($scope, $rootScope, $log, $http, $location, $timeout, $mdDialog, $window, TerminalService, KeyboardShortcutService, InstanceService, SessionService, Upload) {
|
||||
$scope.sessionId = SessionService.getCurrentSessionId();
|
||||
$rootScope.instances = [];
|
||||
$scope.idx = {};
|
||||
$scope.host = window.location.host;
|
||||
$scope.idxByHostname = {};
|
||||
$rootScope.selectedInstance = null;
|
||||
$scope.isAlive = true;
|
||||
$scope.ttl = '--:--:--';
|
||||
$scope.connected = false;
|
||||
$scope.type = {windows: false};
|
||||
$scope.isInstanceBeingCreated = false;
|
||||
$scope.newInstanceBtnText = '+ Add new instance';
|
||||
$scope.deleteInstanceBtnText = 'Delete';
|
||||
$scope.isInstanceBeingDeleted = false;
|
||||
$scope.uploadProgress = 0;
|
||||
|
||||
$scope.uploadFiles = function (files, invalidFiles) {
|
||||
let total = files.length;
|
||||
let uploadFile = function() {
|
||||
let file = files.shift();
|
||||
if (!file){
|
||||
$scope.uploadMessage = "";
|
||||
$scope.uploadProgress = 0;
|
||||
return
|
||||
}
|
||||
$scope.uploadMessage = "Uploading file(s) " + (total - files.length) + "/"+ total + " : " + file.name;
|
||||
let upload = Upload.upload({url: '/sessions/' + $scope.sessionId + '/instances/' + $rootScope.selectedInstance.name + '/uploads', data: {file: file}, method: 'POST'})
|
||||
.then(function(){}, function(){}, function(evt) {
|
||||
$scope.uploadProgress = parseInt(100.0 * evt.loaded / evt.total);
|
||||
});
|
||||
|
||||
// process next file
|
||||
upload.finally(uploadFile);
|
||||
}
|
||||
|
||||
uploadFile();
|
||||
}
|
||||
|
||||
var selectedKeyboardShortcuts = KeyboardShortcutService.getCurrentShortcuts();
|
||||
|
||||
$scope.resizeHandler = null;
|
||||
|
||||
angular.element($window).bind('resize', function() {
|
||||
if ($rootScope.selectedInstance) {
|
||||
if (!$scope.resizeHandler) {
|
||||
$scope.resizeHandler = setTimeout(function() {
|
||||
$scope.resizeHandler = null
|
||||
$scope.resize($scope.selectedInstance.term.proposeGeometry());
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on("settings:shortcutsSelected", function(e, preset) {
|
||||
selectedKeyboardShortcuts = preset;
|
||||
});
|
||||
|
||||
|
||||
$scope.showAlert = function(title, content, parent, cb) {
|
||||
$mdDialog.show(
|
||||
$mdDialog.alert()
|
||||
.parent(angular.element(document.querySelector(parent || '#popupContainer')))
|
||||
.clickOutsideToClose(true)
|
||||
.title(title)
|
||||
.textContent(content)
|
||||
.ok('Got it!')
|
||||
).finally(function() {
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.resize = function(geometry) {
|
||||
$scope.socket.emit('instance viewport resize', geometry.cols, geometry.rows);
|
||||
}
|
||||
|
||||
KeyboardShortcutService.setResizeFunc($scope.resize);
|
||||
|
||||
$scope.closeSession = function() {
|
||||
// Remove alert before closing browser tab
|
||||
window.onbeforeunload = null;
|
||||
$scope.socket.emit('session close');
|
||||
}
|
||||
|
||||
$scope.upsertInstance = function(info) {
|
||||
var i = info;
|
||||
if (!$scope.idx[i.name]) {
|
||||
$rootScope.instances.push(i);
|
||||
i.buffer = '';
|
||||
$scope.idx[i.name] = i;
|
||||
$scope.idxByHostname[i.hostname] = i;
|
||||
} else {
|
||||
$scope.idx[i.name] = Object.assign($scope.idx[i.name], info);
|
||||
}
|
||||
|
||||
return $scope.idx[i.name];
|
||||
}
|
||||
|
||||
$scope.newInstance = function() {
|
||||
updateNewInstanceBtnState(true);
|
||||
var instanceType = $scope.type.windows ? 'windows': 'linux';
|
||||
$http({
|
||||
method: 'POST',
|
||||
url: '/sessions/' + $scope.sessionId + '/instances',
|
||||
data : { ImageName : InstanceService.getDesiredImage(), type: instanceType }
|
||||
}).then(function(response) {
|
||||
$scope.upsertInstance(response.data);
|
||||
}, function(response) {
|
||||
if (response.status == 409) {
|
||||
$scope.showAlert('Max instances reached', 'Maximum number of instances reached')
|
||||
} else if (response.status == 503 && response.data.error == 'out_of_capacity') {
|
||||
$scope.showAlert('Out Of Capacity', 'We are really sorry. But we are currently out of capacity and cannot create new instances. Please try again later.')
|
||||
}
|
||||
}).finally(function() {
|
||||
updateNewInstanceBtnState(false);
|
||||
});
|
||||
}
|
||||
|
||||
$scope.setSessionState = function(state) {
|
||||
$scope.ready = state;
|
||||
|
||||
if (!state) {
|
||||
$mdDialog.show({
|
||||
onComplete: function(){SessionBuilderModalController($mdDialog, $scope)},
|
||||
contentElement: '#builderDialog',
|
||||
parent: angular.element(document.body),
|
||||
clickOutsideToClose: false,
|
||||
scope: $scope,
|
||||
preserveScope: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.loadPlaygroundConf = function() {
|
||||
$http({
|
||||
method: 'GET',
|
||||
url: '/my/playground',
|
||||
}).then(function(response) {
|
||||
$scope.playground = response.data;
|
||||
});
|
||||
|
||||
}
|
||||
$scope.getSession = function(sessionId) {
|
||||
$http({
|
||||
method: 'GET',
|
||||
url: '/sessions/' + $scope.sessionId,
|
||||
}).then(function(response) {
|
||||
$scope.setSessionState(response.data.ready);
|
||||
|
||||
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 i = response.data;
|
||||
for (var k in i.instances) {
|
||||
var instance = i.instances[k];
|
||||
$rootScope.instances.push(instance);
|
||||
$scope.idx[instance.name] = instance;
|
||||
$scope.idxByHostname[instance.hostname] = instance;
|
||||
}
|
||||
|
||||
var base = '';
|
||||
if (window.location.protocol == 'http:') {
|
||||
base = 'ws://';
|
||||
} else {
|
||||
base = 'wss://';
|
||||
}
|
||||
base += window.location.host;
|
||||
if (window.location.port) {
|
||||
base += ':' + window.location.port;
|
||||
}
|
||||
|
||||
var socket = new ReconnectingWebSocket(base + '/sessions/' + sessionId + '/ws/', null, {reconnectInterval: 1000});
|
||||
socket.listeners = {};
|
||||
|
||||
socket.on = function(name, cb) {
|
||||
if (!socket.listeners[name]) {
|
||||
socket.listeners[name] = [];
|
||||
}
|
||||
socket.listeners[name].push(cb);
|
||||
}
|
||||
|
||||
socket.emit = function() {
|
||||
var name = arguments[0]
|
||||
var args = [];
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
args.push(arguments[i]);
|
||||
}
|
||||
socket.send(JSON.stringify({name: name, args: args}));
|
||||
}
|
||||
|
||||
socket.addEventListener('open', function (event) {
|
||||
$scope.connected = true;
|
||||
for (var i in $rootScope.instances) {
|
||||
var instance = $rootScope.instances[i];
|
||||
if (instance.term) {
|
||||
instance.term.setOption('disableStdin', false);
|
||||
}
|
||||
}
|
||||
});
|
||||
socket.addEventListener('close', function (event) {
|
||||
$scope.connected = false;
|
||||
for (var i in $rootScope.instances) {
|
||||
var instance = $rootScope.instances[i];
|
||||
if (instance.term) {
|
||||
instance.term.setOption('disableStdin', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
socket.addEventListener('message', function (event) {
|
||||
var m = JSON.parse(event.data);
|
||||
var ls = socket.listeners[m.name];
|
||||
if (ls) {
|
||||
for (var i=0; i<ls.length; i++) {
|
||||
var l = ls[i];
|
||||
l.apply(l, m.args);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
socket.on('instance terminal status', function(name, status) {
|
||||
var instance = $scope.idx[name];
|
||||
if (instance) {
|
||||
instance.status = status;
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('session ready', function(ready) {
|
||||
$scope.setSessionState(ready);
|
||||
});
|
||||
|
||||
socket.on('session builder out', function(data) {
|
||||
$scope.builderTerminal.write(data);
|
||||
});
|
||||
|
||||
socket.on('instance terminal out', function(name, data) {
|
||||
var instance = $scope.idx[name];
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
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', function() {
|
||||
window.location.href = '/';
|
||||
});
|
||||
$scope.isAlive = false;
|
||||
socket.close();
|
||||
});
|
||||
|
||||
socket.on('instance new', function(name, ip, hostname, proxyHost) {
|
||||
var instance = $scope.upsertInstance({ name: name, ip: ip, hostname: hostname, proxy_host: proxyHost, session_id: $scope.sessionId});
|
||||
$scope.$apply(function() {
|
||||
$scope.showInstance(instance);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('instance delete', function(name) {
|
||||
$scope.removeInstance(name);
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
socket.on('instance viewport resize', function(cols, rows) {
|
||||
if (cols == 0 || rows == 0) {
|
||||
return
|
||||
}
|
||||
// viewport has changed, we need to resize all terminals
|
||||
$rootScope.instances.forEach(function(instance) {
|
||||
if (instance.term) {
|
||||
instance.term.resize(cols, rows);
|
||||
if (instance.buffer) {
|
||||
instance.term.write(instance.buffer);
|
||||
instance.buffer = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('instance stats', function(stats) {
|
||||
if (! $scope.idx[stats.instance]) {
|
||||
return
|
||||
}
|
||||
$scope.idx[stats.instance].mem = stats.mem;
|
||||
$scope.idx[stats.instance].cpu = stats.cpu;
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
socket.on('instance docker swarm status', function(status) {
|
||||
if (!$scope.idx[status.instance]) {
|
||||
return
|
||||
}
|
||||
if (status.is_manager) {
|
||||
$scope.idx[status.instance].isManager = true
|
||||
} else if (status.is_worker) {
|
||||
$scope.idx[status.instance].isManager = false
|
||||
} else {
|
||||
$scope.idx[status.instance].isManager = null
|
||||
}
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
socket.on('instance k8s status', function(status) {
|
||||
if (!$scope.idx[status.instance]) {
|
||||
return
|
||||
}
|
||||
if (status.is_manager) {
|
||||
$scope.idx[status.instance].isK8sManager = true
|
||||
} else if (status.is_worker) {
|
||||
$scope.idx[status.instance].isK8sManager = false
|
||||
} else {
|
||||
$scope.idx[status.instance].isK8sManager = null
|
||||
}
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
socket.on('instance docker ports', function(status) {
|
||||
if (!$scope.idx[status.instance]) {
|
||||
return
|
||||
}
|
||||
$scope.idx[status.instance].ports = status.ports;
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
socket.on('instance docker swarm ports', function(status) {
|
||||
for(var i in status.instances) {
|
||||
var instance = status.instances[i];
|
||||
if ($scope.idxByHostname[instance]) {
|
||||
$scope.idxByHostname[instance].swarmPorts = status.ports;
|
||||
}
|
||||
}
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
$scope.socket = socket;
|
||||
|
||||
|
||||
// If instance is passed in URL, select it
|
||||
let inst = $scope.idx[$location.hash()];
|
||||
if (inst) {
|
||||
$scope.showInstance(inst);
|
||||
} else if($rootScope.instances.length > 0) {
|
||||
// if no instance has been passed, select the first.
|
||||
$scope.showInstance($rootScope.instances[0]);
|
||||
}
|
||||
}, function(response) {
|
||||
if (response.status == 404) {
|
||||
document.write('session not found');
|
||||
return
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.openPort = function(instance) {
|
||||
var port = prompt('What port would you like to open?');
|
||||
if (!port) return;
|
||||
|
||||
var url = $scope.getProxyUrl(instance, port);
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
$scope.getProxyUrl = function(instance, port) {
|
||||
var url = 'http://' + instance.proxy_host + '-' + port + '.direct.' + $scope.host;
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
$scope.showInstance = function(instance) {
|
||||
$rootScope.selectedInstance = instance;
|
||||
$location.hash(instance.name);
|
||||
if (!instance.term) {
|
||||
$timeout(function() {
|
||||
createTerminal(instance);
|
||||
TerminalService.setFontSize(TerminalService.getFontSize());
|
||||
instance.term.focus();
|
||||
$timeout(function() {
|
||||
}, 0, false);
|
||||
}, 0, false);
|
||||
return
|
||||
}
|
||||
instance.term.focus();
|
||||
}
|
||||
|
||||
$scope.removeInstance = function(name) {
|
||||
if ($scope.idx[name]) {
|
||||
var handler = $scope.idx[name].terminalBufferInterval;
|
||||
clearInterval(handler);
|
||||
}
|
||||
if ($scope.idx[name]) {
|
||||
delete $scope.idx[name];
|
||||
$rootScope.instances = $rootScope.instances.filter(function(i) {
|
||||
return i.name != name;
|
||||
});
|
||||
if ($rootScope.instances.length) {
|
||||
$scope.showInstance($rootScope.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.openEditor = function(instance) {
|
||||
var w = window.screen.availWidth * 45 / 100;
|
||||
var h = window.screen.availHeight * 45 / 100;
|
||||
$window.open('/sessions/' + instance.session_id + '/instances/'+instance.name+'/editor', 'editor',
|
||||
'width='+w+',height='+h+',resizable,scrollbars=yes,status=1');
|
||||
};
|
||||
|
||||
$scope.loadPlaygroundConf();
|
||||
$scope.getSession($scope.sessionId);
|
||||
|
||||
$scope.createBuilderTerminal = function() {
|
||||
var builderTerminalContainer = document.getElementById('builder-terminal');
|
||||
let term = new Terminal({
|
||||
cursorBlink: false
|
||||
});
|
||||
|
||||
term.open(builderTerminalContainer);
|
||||
$scope.builderTerminal = term;
|
||||
}
|
||||
function createTerminal(instance, cb) {
|
||||
if (instance.term) {
|
||||
return instance.term;
|
||||
}
|
||||
|
||||
var terminalContainer = document.getElementById('terminal-' + instance.name);
|
||||
|
||||
var term = new Terminal({
|
||||
cursorBlink: false,
|
||||
screenReaderMode: true
|
||||
});
|
||||
|
||||
term.open(terminalContainer);
|
||||
|
||||
|
||||
const handleCopy = (e) => {
|
||||
// Ctrl + Alt + C
|
||||
if (e.ctrlKey && e.altKey && (e.keyCode == 67)) {
|
||||
document.execCommand('copy');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
term.attachCustomKeyEventHandler(function(e) {
|
||||
// handleCopy(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, e })});
|
||||
});
|
||||
|
||||
// Set geometry during the next tick, to avoid race conditions.
|
||||
|
||||
setTimeout(function() {
|
||||
$scope.resize(term.proposeGeometry());
|
||||
}, 0);
|
||||
|
||||
instance.terminalBuffer = '';
|
||||
instance.terminalBufferInterval = setInterval(function() {
|
||||
if (instance.terminalBuffer.length > 0) {
|
||||
$scope.socket.emit('instance terminal in', instance.name, instance.terminalBuffer);
|
||||
instance.terminalBuffer = '';
|
||||
}
|
||||
}, 70);
|
||||
term.on('data', function(d) {
|
||||
instance.terminalBuffer += d;
|
||||
});
|
||||
|
||||
instance.term = term;
|
||||
|
||||
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', '$mdThemingProvider', function($mdIconProvider, $locationProvider, $mdThemingProvider) {
|
||||
$locationProvider.html5Mode({enabled: true, requireBase: false});
|
||||
$mdIconProvider.defaultIconSet('../assets/social-icons.svg', 24);
|
||||
$mdThemingProvider.theme('kube')
|
||||
.primaryPalette('grey')
|
||||
.accentPalette('grey');
|
||||
}])
|
||||
.component('settingsIcon', {
|
||||
template : "<md-button class='md-mini' 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('templatesIcon', {
|
||||
template : "<md-button class='md-mini' ng-click='$ctrl.onClick()'><md-icon class='material-icons'>build</md-icon></md-button>",
|
||||
controller : function($mdDialog) {
|
||||
var $ctrl = this;
|
||||
$ctrl.onClick = function() {
|
||||
$mdDialog.show({
|
||||
controller : function() {},
|
||||
template : "<templates-dialog></templates-dialog>",
|
||||
parent: angular.element(document.body),
|
||||
clickOutsideToClose : true
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
.component("templatesDialog", {
|
||||
templateUrl : "templates-modal.html",
|
||||
controller : function($mdDialog, $scope, SessionService) {
|
||||
var $ctrl = this;
|
||||
$scope.building = false;
|
||||
$scope.templates = SessionService.getAvailableTemplates();
|
||||
$ctrl.close = function() {
|
||||
$mdDialog.cancel();
|
||||
}
|
||||
$ctrl.setupSession = function(setup) {
|
||||
$scope.building = true;
|
||||
SessionService.setup(setup, function(err) {
|
||||
$scope.building = false;
|
||||
if (err) {
|
||||
$scope.errorMessage = err;
|
||||
return;
|
||||
}
|
||||
$ctrl.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.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("SessionService", function($http) {
|
||||
var templates = [
|
||||
{
|
||||
title: '3 Managers and 2 Workers',
|
||||
icon: '/assets/swarm.png',
|
||||
setup: {
|
||||
instances: [
|
||||
{hostname: 'manager1', is_swarm_manager: true},
|
||||
{hostname: 'manager2', is_swarm_manager: true},
|
||||
{hostname: 'manager3', is_swarm_manager: true},
|
||||
{hostname: 'worker1', is_swarm_worker: true},
|
||||
{hostname: 'worker2', is_swarm_worker: true}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '5 Managers and no workers',
|
||||
icon: '/assets/swarm.png',
|
||||
setup: {
|
||||
instances: [
|
||||
{hostname: 'manager1', is_swarm_manager: true},
|
||||
{hostname: 'manager2', is_swarm_manager: true},
|
||||
{hostname: 'manager3', is_swarm_manager: true},
|
||||
{hostname: 'manager4', is_swarm_manager: true},
|
||||
{hostname: 'manager5', is_swarm_manager: true}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '1 Manager and 1 Worker',
|
||||
icon: '/assets/swarm.png',
|
||||
setup: {
|
||||
instances: [
|
||||
{hostname: 'manager1', is_swarm_manager: true},
|
||||
{hostname: 'worker1', is_swarm_worker: true}
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return {
|
||||
getAvailableTemplates: getAvailableTemplates,
|
||||
getCurrentSessionId: getCurrentSessionId,
|
||||
setup: setup,
|
||||
};
|
||||
|
||||
function getCurrentSessionId() {
|
||||
return window.location.pathname.replace('/p/', '');
|
||||
}
|
||||
function getAvailableTemplates() {
|
||||
return templates;
|
||||
}
|
||||
function setup(plan, cb) {
|
||||
return $http
|
||||
.post("/sessions/" + getCurrentSessionId() + "/setup", plan)
|
||||
.then(function(response) {
|
||||
if (cb) cb();
|
||||
}, function(response) {
|
||||
if (cb) cb(response.data);
|
||||
});
|
||||
}
|
||||
})
|
||||
.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); }
|
||||
},
|
||||
{
|
||||
description: "Increase Font Size",
|
||||
command: "Ctrl++",
|
||||
ctrlKey : true,
|
||||
keyCode: 187,
|
||||
action: function(context) {
|
||||
TerminalService.increaseFontSize();
|
||||
context.e.preventDefault()
|
||||
}
|
||||
},
|
||||
{
|
||||
description: "Decrease Font Size",
|
||||
command: "Ctrl+-",
|
||||
ctrlKey: true,
|
||||
keyCode: 189,
|
||||
action: function(context) {
|
||||
context.e.preventDefault()
|
||||
TerminalService.decreaseFontSize();
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
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); }},
|
||||
{
|
||||
description: "Increase Font Size",
|
||||
command: "Cmd++",
|
||||
metaKey : true,
|
||||
keyCode: 187,
|
||||
action: function(context) {
|
||||
TerminalService.increaseFontSize();
|
||||
context.e.preventDefault()
|
||||
}
|
||||
},
|
||||
{
|
||||
description: "Decrease Font Size",
|
||||
command: "Cmd+-",
|
||||
metaKey: true,
|
||||
keyCode: 189,
|
||||
action: function(context) {
|
||||
context.e.preventDefault()
|
||||
TerminalService.decreaseFontSize();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
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', '$rootScope', function($window, $rootScope) {
|
||||
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($rootScope.selectedInstance){
|
||||
return $rootScope.selectedInstance.term.getOption("fontSize") + "px"
|
||||
}else{
|
||||
return $(".terminal").css("font-size")
|
||||
}
|
||||
}
|
||||
function setFontSize(value) {
|
||||
const { term }= $rootScope.selectedInstance;
|
||||
fontSize = value;
|
||||
var size = parseInt(value);
|
||||
term.setOption("fontSize", size)
|
||||
term.resize(1,1)
|
||||
term.fit()
|
||||
}
|
||||
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();
|
||||
terminal.containerElement.append(terminal.element)
|
||||
setTimeout(()=>{
|
||||
terminal.resize(1,1);
|
||||
terminal.fit();
|
||||
terminal.focus();
|
||||
},100)
|
||||
fullscreen = null;
|
||||
} else {
|
||||
// save the current parent
|
||||
terminal.containerElement = $(terminal.element).parent()
|
||||
$("body").append(terminal.element)
|
||||
fullscreen = terminal.proposeGeometry();
|
||||
terminal.toggleFullScreen();
|
||||
terminal.fit();
|
||||
terminal.focus();
|
||||
}
|
||||
}
|
||||
}]);
|
||||
})();
|
||||
134
handlers/www/assets/attach.js
Normal file
134
handlers/www/assets/attach.js
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Implements the attach method, that
|
||||
* attaches the terminal to a WebSocket stream.
|
||||
*
|
||||
* The bidirectional argument indicates, whether the terminal should
|
||||
* send data to the socket as well and is true, by default.
|
||||
*/
|
||||
|
||||
(function (attach) {
|
||||
if (typeof exports === 'object' && typeof module === 'object') {
|
||||
/*
|
||||
* CommonJS environment
|
||||
*/
|
||||
module.exports = attach(require('../../src/xterm'));
|
||||
} else if (typeof define == 'function') {
|
||||
/*
|
||||
* Require.js is available
|
||||
*/
|
||||
define(['../../src/xterm'], attach);
|
||||
} else {
|
||||
/*
|
||||
* Plain browser environment
|
||||
*/
|
||||
attach(window.Terminal);
|
||||
}
|
||||
})(function (Xterm) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This module provides methods for attaching a terminal to a WebSocket
|
||||
* stream.
|
||||
*
|
||||
* @module xterm/addons/attach/attach
|
||||
*/
|
||||
var exports = {};
|
||||
|
||||
/**
|
||||
* Attaches the given terminal to the given socket.
|
||||
*
|
||||
* @param {Xterm} term - The terminal to be attached to the given socket.
|
||||
* @param {WebSocket} socket - The socket to attach the current terminal.
|
||||
* @param {boolean} bidirectional - Whether the terminal should send data
|
||||
* to the socket as well.
|
||||
* @param {boolean} buffered - Whether the rendering of incoming data
|
||||
* should happen instantly or at a maximum
|
||||
* frequency of 1 rendering per 10ms.
|
||||
*/
|
||||
exports.attach = function (term, socket, bidirectional, buffered) {
|
||||
bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional;
|
||||
term.socket = socket;
|
||||
|
||||
term._flushBuffer = function () {
|
||||
term.write(term._attachSocketBuffer);
|
||||
term._attachSocketBuffer = null;
|
||||
clearTimeout(term._attachSocketBufferTimer);
|
||||
term._attachSocketBufferTimer = null;
|
||||
};
|
||||
|
||||
term._pushToBuffer = function (data) {
|
||||
if (term._attachSocketBuffer) {
|
||||
term._attachSocketBuffer += data;
|
||||
} else {
|
||||
term._attachSocketBuffer = data;
|
||||
setTimeout(term._flushBuffer, 10);
|
||||
}
|
||||
};
|
||||
|
||||
term._getMessage = function (ev) {
|
||||
if (buffered) {
|
||||
term._pushToBuffer(ev.data);
|
||||
} else {
|
||||
term.write(ev.data);
|
||||
}
|
||||
};
|
||||
|
||||
term._sendData = function (data) {
|
||||
socket.send(data);
|
||||
};
|
||||
|
||||
socket.addEventListener('message', term._getMessage);
|
||||
|
||||
if (bidirectional) {
|
||||
term.on('data', term._sendData);
|
||||
}
|
||||
|
||||
socket.addEventListener('close', term.detach.bind(term, socket));
|
||||
socket.addEventListener('error', term.detach.bind(term, socket));
|
||||
};
|
||||
|
||||
/**
|
||||
* Detaches the given terminal from the given socket
|
||||
*
|
||||
* @param {Xterm} term - The terminal to be detached from the given socket.
|
||||
* @param {WebSocket} socket - The socket from which to detach the current
|
||||
* terminal.
|
||||
*/
|
||||
exports.detach = function (term, socket) {
|
||||
term.off('data', term._sendData);
|
||||
|
||||
socket = (typeof socket == 'undefined') ? term.socket : socket;
|
||||
|
||||
if (socket) {
|
||||
socket.removeEventListener('message', term._getMessage);
|
||||
}
|
||||
|
||||
delete term.socket;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attaches the current terminal to the given socket
|
||||
*
|
||||
* @param {WebSocket} socket - The socket to attach the current terminal.
|
||||
* @param {boolean} bidirectional - Whether the terminal should send data
|
||||
* to the socket as well.
|
||||
* @param {boolean} buffered - Whether the rendering of incoming data
|
||||
* should happen instantly or at a maximum
|
||||
* frequency of 1 rendering per 10ms.
|
||||
*/
|
||||
Xterm.prototype.attach = function (socket, bidirectional, buffered) {
|
||||
return exports.attach(this, socket, bidirectional, buffered);
|
||||
};
|
||||
|
||||
/**
|
||||
* Detaches the current terminal from the given socket.
|
||||
*
|
||||
* @param {WebSocket} socket - The socket from which to detach the current
|
||||
* terminal.
|
||||
*/
|
||||
Xterm.prototype.detach = function (socket) {
|
||||
return exports.detach(this, socket);
|
||||
};
|
||||
|
||||
return exports;
|
||||
});
|
||||
BIN
handlers/www/assets/button.png
Normal file
BIN
handlers/www/assets/button.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
21
handlers/www/assets/editor.css
Normal file
21
handlers/www/assets/editor.css
Normal file
@@ -0,0 +1,21 @@
|
||||
.alert-top {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width:100px;
|
||||
display:none;
|
||||
text-align: center;
|
||||
padding: 3px;
|
||||
height: 30px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.alert-newfile {
|
||||
text-align: center;
|
||||
padding: 3px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.col-md-3 {
|
||||
overflow-x: auto;
|
||||
}
|
||||
72
handlers/www/assets/full_horizontal.svg
Normal file
72
handlers/www/assets/full_horizontal.svg
Normal file
@@ -0,0 +1,72 @@
|
||||
<svg viewBox="0 0 340 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<style type="text/css">.st0{fill:#099CEC;}
|
||||
.st1{fill:#066DA5;}
|
||||
.st2{fill:#089CEC;}
|
||||
.st3{fill:#FFFFFF;}
|
||||
.st4{fill:#056BA2;}
|
||||
.st5{opacity:0.6;fill:#101E26;enable-background:new ;}
|
||||
.st6{opacity:0.1;fill:#101E26;enable-background:new ;}</style>
|
||||
|
||||
<title>full_horizontal</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g>
|
||||
<title>background</title>
|
||||
<rect fill="none" id="canvas_background" height="102" width="342" y="-1" x="-1"/>
|
||||
</g>
|
||||
<g>
|
||||
<title>Layer 1</title>
|
||||
<polygon points="58.62161469459534,32 69.6216299533844,32 69.6216299533844,42 58.62161469459534,42 " class="st0" id="Rectangle"/>
|
||||
<polygon points="71.6216299533844,32 82.6216299533844,32 82.6216299533844,42 71.6216299533844,42 " class="st0" id="Rectangle_1_"/>
|
||||
<polygon points="84.6216299533844,32 95.6216299533844,32 95.6216299533844,42 84.6216299533844,42 " class="st0" id="Rectangle_2_"/>
|
||||
<polygon points="97.6216299533844,32 108.6216299533844,32 108.6216299533844,42 97.6216299533844,42 " class="st0" id="Rectangle_3_"/>
|
||||
<polygon points="110.6216299533844,32 121.6216299533844,32 121.6216299533844,42 110.6216299533844,42 " class="st0" id="Rectangle_4_"/>
|
||||
<polygon points="71.6216299533844,20 82.6216299533844,20 82.6216299533844,30 71.6216299533844,30 " class="st0" id="Rectangle_5_"/>
|
||||
<polygon points="84.6216299533844,20 95.6216299533844,20 95.6216299533844,30 84.6216299533844,30 " class="st0" id="Rectangle_6_"/>
|
||||
<polygon points="97.6216299533844,20 108.6216299533844,20 108.6216299533844,30 97.6216299533844,30 " class="st0" id="Rectangle_7_"/>
|
||||
<polygon points="97.6216299533844,8 108.6216299533844,8 108.6216299533844,18 97.6216299533844,18 " class="st0" id="Rectangle_8_"/>
|
||||
<path d="m296.221621,53.4c-0.4,-0.4 -0.9,-0.7 -1.5,-0.9c-0.6,-0.2 -1.3,-0.4 -2,-0.5c-0.7,-0.1 -1.4,-0.1 -2,-0.1c-1.4,0 -2.8,0.2 -4,0.7c-1.3,0.5 -2.4,1.1 -3.5,2l0,-0.4c0,-0.6 -0.2,-1.1 -0.7,-1.6c-0.4,-0.4 -1,-0.7 -1.6,-0.7c-0.6,0 -1.2,0.2 -1.6,0.7c-0.4,0.4 -0.7,1 -0.7,1.6l0,19.6c0,0.6 0.2,1.1 0.7,1.6c0.4,0.4 1,0.7 1.6,0.7c0.6,0 1.1,-0.2 1.6,-0.7c0.4,-0.4 0.7,-1 0.7,-1.6l0,-9.8c0,-1 0.2,-2 0.6,-2.9c0.4,-0.9 0.9,-1.7 1.6,-2.4c0.7,-0.7 1.5,-1.2 2.4,-1.6c0.9,-0.4 1.9,-0.6 2.9,-0.6c1.1,0 2,0.2 2.9,0.5c0.4,0.2 0.7,0.2 0.9,0.2c0.3,0 0.6,-0.1 0.9,-0.2c0.3,-0.1 0.5,-0.3 0.7,-0.5c0.2,-0.2 0.4,-0.4 0.5,-0.7c0.1,-0.3 0.2,-0.6 0.2,-0.9c0,-0.6 -0.2,-1.1 -0.6,-1.5zm-38.8,8.3c0.2,-0.8 0.6,-1.5 1.1,-2.2c0.5,-0.7 1,-1.2 1.7,-1.7c0.6,-0.5 1.4,-0.8 2.1,-1.1c0.8,-0.3 1.6,-0.4 2.4,-0.4c0.8,0 1.6,0.1 2.4,0.4c0.8,0.3 1.5,0.6 2.1,1.1c0.6,0.5 1.2,1 1.7,1.7c0.5,0.7 0.8,1.4 1.1,2.2l-14.6,0zm15.8,-6.2c-2.4,-2.3 -5.2,-3.5 -8.5,-3.5c-3.3,0 -6.2,1.2 -8.5,3.5c-2.3,2.3 -3.5,5.2 -3.5,8.5s1.2,6.2 3.5,8.5c2.3,2.3 5.2,3.5 8.5,3.5c3,0 5.6,-1 7.9,-2.9c0.4,-0.4 0.6,-1 0.6,-1.6c0,-0.6 -0.2,-1.2 -0.6,-1.6c-0.4,-0.4 -1,-0.6 -1.6,-0.6c-0.6,0 -1.1,0.2 -1.5,0.6c-0.7,0.6 -1.4,1 -2.2,1.3c-0.8,0.3 -1.7,0.4 -2.6,0.4c-0.8,0 -1.6,-0.1 -2.4,-0.4c-0.8,-0.3 -1.5,-0.6 -2.1,-1.1c-0.6,-0.5 -1.2,-1 -1.7,-1.7c-0.5,-0.7 -0.8,-1.4 -1.1,-2.2l17,0c0.6,0 1.2,-0.2 1.6,-0.6c0.4,-0.4 0.7,-1 0.7,-1.6c0,-1.7 -0.3,-3.2 -0.9,-4.6c-0.6,-1.5 -1.5,-2.8 -2.6,-3.9zm-22.6,-1.3c0,-0.3 -0.1,-0.6 -0.2,-0.9c-0.1,-0.3 -0.3,-0.5 -0.5,-0.7c-0.2,-0.2 -0.4,-0.4 -0.7,-0.5c-0.3,-0.1 -0.6,-0.2 -0.9,-0.2c-0.4,0 -0.8,0.1 -1.2,0.3l-12.8,8.4l0,-16.7c0,-0.6 -0.2,-1.2 -0.7,-1.6c-0.4,-0.4 -1,-0.7 -1.6,-0.7c-0.6,0 -1.2,0.2 -1.6,0.7c-0.4,0.4 -0.7,1 -0.7,1.6l0,29.9c0,0.6 0.2,1.1 0.7,1.6c0.4,0.4 1,0.7 1.6,0.7c0.6,0 1.1,-0.2 1.6,-0.7c0.4,-0.4 0.7,-1 0.7,-1.6l0,-7.8l2.6,-1.7l9.9,11.2c0.4,0.4 0.9,0.6 1.5,0.6c0.3,0 0.6,-0.1 0.9,-0.2c0.3,-0.1 0.5,-0.3 0.7,-0.5c0.2,-0.2 0.4,-0.4 0.5,-0.7c0.1,-0.3 0.2,-0.6 0.2,-0.9c0,-0.6 -0.2,-1.1 -0.6,-1.6l-9.2,-10.4l9,-5.8c0.5,-0.4 0.8,-1 0.8,-1.8zm-36.9,4.4c0.7,-0.7 1.5,-1.2 2.4,-1.6c0.9,-0.4 1.9,-0.6 2.9,-0.6c0.9,0 1.8,0.2 2.6,0.5c0.8,0.3 1.6,0.8 2.3,1.4c0.4,0.3 0.9,0.5 1.5,0.5c0.6,0 1.2,-0.2 1.6,-0.6c0.4,-0.4 0.6,-1 0.6,-1.6c0,-0.7 -0.3,-1.2 -0.8,-1.7c-2.2,-1.9 -4.8,-2.9 -7.8,-2.9c-3.3,0 -6.2,1.2 -8.5,3.5c-2.3,2.3 -3.5,5.2 -3.5,8.5s1.2,6.2 3.5,8.5c2.3,2.3 5.2,3.5 8.5,3.5c3,0 5.6,-1 7.8,-2.9c0.5,-0.5 0.7,-1 0.7,-1.7c0,-0.6 -0.2,-1.2 -0.6,-1.6c-0.4,-0.4 -1,-0.6 -1.6,-0.6c-0.5,0 -1,0.2 -1.4,0.5c-0.7,0.6 -1.5,1.1 -2.3,1.4c-0.8,0.3 -1.7,0.5 -2.6,0.5c-1,0 -2,-0.2 -2.9,-0.6c-0.9,-0.4 -1.7,-0.9 -2.4,-1.6c-0.7,-0.7 -1.2,-1.5 -1.6,-2.4c-0.4,-0.9 -0.6,-1.9 -0.6,-2.9c0,-1 0.2,-2 0.6,-2.9c0.4,-1.1 0.9,-1.9 1.6,-2.6zm-13.9,8.3c-0.4,0.9 -0.9,1.7 -1.6,2.4c-0.7,0.7 -1.5,1.2 -2.4,1.6c-0.9,0.4 -1.9,0.6 -2.9,0.6c-1.1,0 -2,-0.2 -3,-0.6c-0.9,-0.4 -1.7,-0.9 -2.4,-1.6c-0.7,-0.7 -1.2,-1.5 -1.6,-2.4c-0.4,-0.9 -0.6,-1.9 -0.6,-2.9s0.2,-2 0.6,-2.9c0.4,-0.9 0.9,-1.7 1.6,-2.4c0.7,-0.7 1.5,-1.2 2.4,-1.6c0.9,-0.4 1.9,-0.6 3,-0.6c1,0 2,0.2 2.9,0.6c0.9,0.4 1.7,0.9 2.4,1.6c0.7,0.7 1.2,1.5 1.6,2.4c0.4,0.9 0.6,1.9 0.6,2.9s-0.2,2 -0.6,2.9zm1.6,-11.4c-2.4,-2.3 -5.2,-3.5 -8.5,-3.5c-3.3,0 -6.2,1.2 -8.5,3.5c-2.4,2.3 -3.5,5.2 -3.5,8.5s1.2,6.2 3.5,8.5c2.3,2.3 5.2,3.5 8.5,3.5c3.3,0 6.1,-1.2 8.5,-3.5c2.3,-2.3 3.5,-5.2 3.5,-8.5c0,-1.7 -0.3,-3.2 -0.9,-4.6s-1.5,-2.8 -2.6,-3.9zm-27.8,11.4c-0.4,0.9 -0.9,1.7 -1.6,2.4c-0.7,0.7 -1.5,1.2 -2.4,1.6c-0.9,0.4 -1.9,0.6 -2.9,0.6c-1.1,0 -2,-0.2 -3,-0.6c-0.9,-0.4 -1.7,-0.9 -2.4,-1.6c-0.7,-0.7 -1.2,-1.5 -1.6,-2.4c-0.4,-0.9 -0.6,-1.9 -0.6,-2.9s0.2,-2 0.6,-2.9c0.4,-0.9 0.9,-1.7 1.6,-2.4c0.7,-0.7 1.5,-1.2 2.4,-1.6c0.9,-0.4 1.9,-0.6 3,-0.6c1,0 2,0.2 2.9,0.6c0.9,0.4 1.7,0.9 2.4,1.6c0.7,0.7 1.2,1.5 1.6,2.4c0.4,0.9 0.6,1.9 0.6,2.9s-0.2,2 -0.6,2.9zm2.8,-25.3c-0.6,0 -1.2,0.2 -1.6,0.6c-0.4,0.4 -0.6,1 -0.6,1.6l0,10.7c-2.2,-1.8 -4.7,-2.6 -7.5,-2.6c-3.3,0 -6.2,1.2 -8.5,3.5c-2.3,2.3 -3.5,5.2 -3.5,8.5s1.2,6.2 3.5,8.5s5.2,3.5 8.5,3.5c3.3,0 6.1,-1.2 8.5,-3.5c2.3,-2.3 3.5,-5.2 3.5,-8.5l0,-20c0,-0.6 -0.2,-1.2 -0.7,-1.6c-0.4,-0.5 -1,-0.7 -1.6,-0.7z" class="st1" id="Fill-1"/>
|
||||
<path d="m141.521621,34c-0.2,-4.3 -2.4,-7.9 -6.5,-11.1l-1.5,-1l-1,1.5c-2,3 -2.8,7 -2.5,10.6c0.2,2.2 1,4.7 2.5,6.5c-1.1,0.9 -4.7,2.6 -9.5,2.5l-71.5,0c-1.3,7.6 0.9,36.2 34,36.2c24.6,0 44.8,-11 54,-34.2c3,0 11.1,0.5 15,-7c0.1,-0.1 1,-2 1,-2l-1.5,-1c-2.2,-1.5 -8.2,-2.1 -12.5,-1z" class="st2" id="Shape"/>
|
||||
<path d="m58.621621,32l0,10l11,0l0,-10l-11,0zm-2,-2l15,0l0,14l-15,0l0,-14z" class="st1" id="Rectangle-38-Copy"/>
|
||||
<path d="m78.321621,65.8c-8.2,2.8 -16.9,3.2 -18.8,3.2c6.1,5.1 17,14.7 35,9.2c-3.3,-0.9 -11,-3.9 -16.2,-12.4z" class="st3" id="Fill-21"/>
|
||||
<path d="m77.421621,64c-3.9,2.2 -8.5,3.5 -13.3,3.5c-2.4,0 -4.8,-0.3 -7.1,-0.9c1.4,2.4 1.4,2.4 1.4,2.4c1.8,0.4 3.7,0.5 5.6,0.5c5.2,0 10,-1.4 14.3,-3.8c-0.3,-0.5 -0.6,-1.1 -0.9,-1.7z" class="st4" id="Shape_1_"/>
|
||||
<ellipse ry="3.5" rx="3.5" cy="59.2" cx="85.021621" class="st3" id="Oval"/>
|
||||
<path id="svg_1" d="m87.021621,59.1c-0.1,0.1 -0.3,0.1 -0.5,0.1c-0.6,0 -1,-0.5 -1,-1c0,-0.3 0.2,-0.6 0.4,-0.8c-0.3,-0.1 -0.6,-0.2 -0.9,-0.2c-1.1,0 -2,0.9 -2,2s0.9,2 2,2c1.1,0 2,-0.9 2,-2c0,0 0,-0.1 0,-0.1z" class="st5"/>
|
||||
<path d="m154.121621,34.9c-2.2,-1.5 -8.4,-2.2 -12.5,-1l-0.9,1.3c-13.4,27.7 -30.2,39.5 -53.1,40c-25,-0.1 -31.2,-13.2 -33,-31.2l-4,0c-0.5,7.6 1.4,35.3 34.8,35.3c24.6,0 44.8,-11 54.2,-34.2c3.1,0.1 11.6,0.1 15.3,-7.6l0.8,-1.6l-1.6,-1z" class="st6" id="Shape_2_"/>
|
||||
<path d="m154.621621,34.1l2.2,1.5l-0.4,0.8c-0.1,0.2 -0.1,0.2 -0.2,0.3c-0.1,0.2 -0.2,0.5 -0.3,0.7c-0.3,0.6 -0.4,0.9 -0.5,1.1c-1.7,3.3 -4.3,5.4 -7.5,6.6c-2.4,0.9 -4.6,1.1 -7.7,1c-9.2,22.3 -28.9,34.2 -54.7,34.2c-15.1,0 -25.2,-5.8 -30.8,-15.8c-2.1,-3.8 -3.5,-8.1 -4.1,-12.5c-0.5,-3.5 -0.5,-6.8 -0.1,-9.2l0.1,-0.8l0.8,0l71.5,0c3.2,0.1 6.2,-0.8 8,-1.8c-1.1,-1.7 -1.8,-3.9 -2.1,-6.2c-0.3,-4 0.6,-8.1 2.7,-11.2l1.6,-2.3l2.5,1.5c4,3.1 6.2,6.6 6.8,10.7c4.3,-0.8 9.8,-0.2 12.2,1.4zm-1.1,1.7c-2.1,-1.4 -7.8,-1.9 -11.7,-0.9l-1.2,0.3l-0.1,-1.2c-0.2,-3.9 -2.2,-7.3 -6.1,-10.3l-0.6,-0.4l-0.4,0.7c-1.8,2.7 -2.6,6.4 -2.3,9.9c0.2,2.3 1,4.5 2.3,6l0.7,0.8l-0.8,0.6c-2,1.5 -5.9,2.8 -10.1,2.7l-70.8,0c-0.2,2.1 -0.2,4.8 0.2,7.7c0.6,4.1 1.9,8.2 3.9,11.8c5.2,9.3 14.6,14.8 29,14.8c25.2,0 44.3,-11.7 53.1,-33.6l0.3,-0.6l0.7,0c0.3,0 0.5,0 0.5,0c3,0.1 5,-0.1 7.1,-0.9c2.8,-1 4.9,-2.8 6.4,-5.6c0.1,-0.2 0.1,-0.2 0.2,-0.3c0.1,-0.2 0.2,-0.4 0.3,-0.6c0,-0.1 0.1,-0.2 0.1,-0.2l-0.7,-0.7z" class="st4" id="Shape_3_"/>
|
||||
<rect height="6" width="1" class="st1" y="34" x="60.621621" id="Rectangle-5"/>
|
||||
<rect height="6" width="1" class="st1" y="34" x="63.621621" id="Rectangle-5_1_"/>
|
||||
<rect height="6" width="1" class="st1" y="34" x="66.621621" id="Rectangle-5_2_"/>
|
||||
<path d="m71.621621,32l0,10l11,0l0,-10l-11,0zm-2,-2l15,0l0,14l-15,0l0,-14z" class="st1" id="Rectangle-38-Copy_1_"/>
|
||||
<rect height="6" width="1" class="st1" y="34" x="73.621621" id="Rectangle-5_3_"/>
|
||||
<rect height="6" width="1" class="st1" y="34" x="76.621621" id="Rectangle-5_4_"/>
|
||||
<rect height="6" width="1" class="st1" y="34" x="79.621621" id="Rectangle-5_5_"/>
|
||||
<path d="m84.621621,32l0,10l11,0l0,-10l-11,0zm-2,-2l15,0l0,14l-15,0l0,-14z" class="st1" id="Rectangle-38-Copy_2_"/>
|
||||
<rect height="6" width="1" class="st1" y="34" x="86.621621" id="Rectangle-5_6_"/>
|
||||
<rect height="6" width="1" class="st1" y="34" x="89.621621" id="Rectangle-5_7_"/>
|
||||
<rect height="6" width="1" class="st1" y="34" x="92.621621" id="Rectangle-5_8_"/>
|
||||
<path d="m97.621621,32l0,10l11,0l0,-10l-11,0zm-2,-2l15,0l0,14l-15,0l0,-14z" class="st1" id="Rectangle-38-Copy_3_"/>
|
||||
<rect height="6" width="1" class="st1" y="34" x="99.621621" id="Rectangle-5_9_"/>
|
||||
<rect height="6" width="1" class="st1" y="34" x="102.621621" id="Rectangle-5_10_"/>
|
||||
<rect height="6" width="1" class="st1" y="34" x="105.621621" id="Rectangle-5_11_"/>
|
||||
<path d="m110.621621,32l0,10l11,0l0,-10l-11,0zm-2,-2l15,0l0,14l-15,0l0,-14z" class="st1" id="Rectangle-38-Copy_4_"/>
|
||||
<rect height="6" width="1" class="st1" y="34" x="112.621621" id="Rectangle-5_12_"/>
|
||||
<rect height="6" width="1" class="st1" y="34" x="115.621621" id="Rectangle-5_13_"/>
|
||||
<rect height="6" width="1" class="st1" y="34" x="118.621621" id="Rectangle-5_14_"/>
|
||||
<path d="m71.621621,20l0,10l11,0l0,-10l-11,0zm-2,-2l15,0l0,14l-15,0l0,-14z" class="st1" id="Rectangle-38-Copy_5_"/>
|
||||
<rect height="6" width="1" class="st1" y="22" x="76.621621" id="Rectangle-5_15_"/>
|
||||
<rect height="6" width="1" class="st1" y="22" x="79.621621" id="Rectangle-5_16_"/>
|
||||
<path d="m84.621621,20l0,10l11,0l0,-10l-11,0zm-2,-2l15,0l0,14l-15,0l0,-14z" class="st1" id="Rectangle-38-Copy_6_"/>
|
||||
<rect height="6" width="1" class="st1" y="22" x="86.621621" id="Rectangle-5_17_"/>
|
||||
<rect height="6" width="1" class="st1" y="22" x="89.621621" id="Rectangle-5_18_"/>
|
||||
<rect height="6" width="1" class="st1" y="22" x="92.621621" id="Rectangle-5_19_"/>
|
||||
<path d="m97.621621,20l0,10l11,0l0,-10l-11,0zm-2,-2l15,0l0,14l-15,0l0,-14z" class="st1" id="Rectangle-38-Copy_7_"/>
|
||||
<rect height="6" width="1" class="st1" y="22" x="99.621621" id="Rectangle-5_20_"/>
|
||||
<rect height="6" width="1" class="st1" y="22" x="102.621621" id="Rectangle-5_21_"/>
|
||||
<rect height="6" width="1" class="st1" y="22" x="105.621621" id="Rectangle-5_22_"/>
|
||||
<path d="m97.621621,8l0,10l11,0l0,-10l-11,0zm-2,-2l15,0l0,14l-15,0l0,-14z" class="st1" id="Rectangle-38-Copy_8_"/>
|
||||
<rect height="6" width="1" class="st1" y="10" x="102.621621" id="Rectangle-5_23_"/>
|
||||
<rect height="6" width="1" class="st1" y="10" x="99.621621" id="Rectangle-5_24_"/>
|
||||
<rect height="6" width="1" class="st1" y="10" x="105.621621" id="Rectangle-5_25_"/>
|
||||
<rect height="6" width="1" class="st1" y="22" x="73.621621" id="Rectangle-5_26_"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
82
handlers/www/assets/landing.css
Normal file
82
handlers/www/assets/landing.css
Normal file
@@ -0,0 +1,82 @@
|
||||
/* Space out content a bit */
|
||||
body {
|
||||
padding-top: 1.5rem;
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
/* Everything but the jumbotron gets side spacing for mobile first views */
|
||||
.header,
|
||||
.marketing,
|
||||
.footer {
|
||||
padding-right: 1rem;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
/* Custom page header */
|
||||
.header {
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: .05rem solid #e5e5e5;
|
||||
}
|
||||
/* Make the masthead heading the same height as the navigation */
|
||||
.header h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
line-height: 3rem;
|
||||
}
|
||||
|
||||
/* Custom page footer */
|
||||
.footer {
|
||||
padding-top: 1.5rem;
|
||||
color: #777;
|
||||
border-top: .05rem solid #e5e5e5;
|
||||
}
|
||||
|
||||
/* Customize container */
|
||||
@media (min-width: 48em) {
|
||||
.container {
|
||||
max-width: 46rem;
|
||||
}
|
||||
}
|
||||
.container-narrow > hr {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
/* Main marketing message and sign up button */
|
||||
.jumbotron {
|
||||
text-align: center;
|
||||
border-bottom: .05rem solid #e5e5e5;
|
||||
}
|
||||
.jumbotron .btn {
|
||||
padding: .75rem 1.5rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.btn.dropdown-toggle, .dropdown-menu a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Supporting marketing content */
|
||||
.marketing {
|
||||
margin: 3rem 0;
|
||||
}
|
||||
.marketing p + h4 {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
/* Responsive: Portrait tablets and up */
|
||||
@media screen and (min-width: 48em) {
|
||||
/* Remove the padding we set earlier */
|
||||
.header,
|
||||
.marketing,
|
||||
.footer {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
/* Space out the masthead */
|
||||
.header {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
/* Remove the bottom border on the jumbotron for visual effect */
|
||||
.jumbotron {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
11
handlers/www/assets/package-lock.json
generated
Normal file
11
handlers/www/assets/package-lock.json
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"xterm": {
|
||||
"version": "3.14.5",
|
||||
"resolved": "https://registry.npmjs.org/xterm/-/xterm-3.14.5.tgz",
|
||||
"integrity": "sha512-DVmQ8jlEtL+WbBKUZuMxHMBgK/yeIZwkXB81bH+MGaKKnJGYwA+770hzhXPfwEIokK9On9YIFPRleVp/5G7z9g=="
|
||||
}
|
||||
}
|
||||
}
|
||||
3
handlers/www/assets/setup-xterm.js
Normal file
3
handlers/www/assets/setup-xterm.js
Normal file
@@ -0,0 +1,3 @@
|
||||
Terminal.applyAddon(fit);
|
||||
Terminal.applyAddon(fullscreen);
|
||||
|
||||
26
handlers/www/assets/social-icons.svg
Normal file
26
handlers/www/assets/social-icons.svg
Normal file
@@ -0,0 +1,26 @@
|
||||
<svg><defs>
|
||||
<g id="cake"><path d="M12 6c1.11 0 2-.9 2-2 0-.38-.1-.73-.29-1.03L12 0l-1.71 2.97c-.19.3-.29.65-.29 1.03 0 1.1.9 2 2 2zm4.6 9.99l-1.07-1.07-1.08 1.07c-1.3 1.3-3.58 1.31-4.89 0l-1.07-1.07-1.09 1.07C6.75 16.64 5.88 17 4.96 17c-.73 0-1.4-.23-1.96-.61V21c0 .55.45 1 1 1h16c.55 0 1-.45 1-1v-4.61c-.56.38-1.23.61-1.96.61-.92 0-1.79-.36-2.44-1.01zM18 9h-5V7h-2v2H6c-1.66 0-3 1.34-3 3v1.54c0 1.08.88 1.96 1.96 1.96.52 0 1.02-.2 1.38-.57l2.14-2.13 2.13 2.13c.74.74 2.03.74 2.77 0l2.14-2.13 2.13 2.13c.37.37.86.57 1.38.57 1.08 0 1.96-.88 1.96-1.96V12C21 10.34 19.66 9 18 9z"/></g>
|
||||
<g id="domain"><path d="M12 7V3H2v18h20V7H12zM6 19H4v-2h2v2zm0-4H4v-2h2v2zm0-4H4V9h2v2zm0-4H4V5h2v2zm4 12H8v-2h2v2zm0-4H8v-2h2v2zm0-4H8V9h2v2zm0-4H8V5h2v2zm10 12h-8v-2h2v-2h-2v-2h2v-2h-2V9h8v10zm-2-8h-2v2h2v-2zm0 4h-2v2h2v-2z"/></g>
|
||||
<g id="group"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></g>
|
||||
<g id="group-add"><path d="M8 10H5V7H3v3H0v2h3v3h2v-3h3v-2zm10 1c1.66 0 2.99-1.34 2.99-3S19.66 5 18 5c-.32 0-.63.05-.91.14.57.81.9 1.79.9 2.86s-.34 2.04-.9 2.86c.28.09.59.14.91.14zm-5 0c1.66 0 2.99-1.34 2.99-3S14.66 5 13 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm6.62 2.16c.83.73 1.38 1.66 1.38 2.84v2h3v-2c0-1.54-2.37-2.49-4.38-2.84zM13 13c-2 0-6 1-6 3v2h12v-2c0-2-4-3-6-3z"/></g>
|
||||
<g id="location-city"><path d="M15 11V5l-3-3-3 3v2H3v14h18V11h-6zm-8 8H5v-2h2v2zm0-4H5v-2h2v2zm0-4H5V9h2v2zm6 8h-2v-2h2v2zm0-4h-2v-2h2v2zm0-4h-2V9h2v2zm0-4h-2V5h2v2zm6 12h-2v-2h2v2zm0-4h-2v-2h2v2z"/></g>
|
||||
<g id="mood"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5s.67 1.5 1.5 1.5zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5 7.67 11 8.5 11zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z"/></g>
|
||||
<g id="notifications"><path d="M11.5 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6.5-6v-5.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-2.87.68-5 3.25-5 6.32V16l-2 2v1h17v-1l-2-2z"/></g>
|
||||
<g id="notifications-none"><path d="M11.5 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6.5-6v-5.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-2.87.68-5 3.25-5 6.32V16l-2 2v1h17v-1l-2-2zm-2 1H7v-6.5C7 8.01 9.01 6 11.5 6S16 8.01 16 10.5V17z"/></g>
|
||||
<g id="notifications-off"><path d="M11.5 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zM18 10.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-.51.12-.99.32-1.45.56L18 14.18V10.5zm-.27 8.5l2 2L21 19.73 4.27 3 3 4.27l2.92 2.92C5.34 8.16 5 9.29 5 10.5V16l-2 2v1h14.73z"/></g>
|
||||
<g id="notifications-on"><path d="M6.58 3.58L5.15 2.15C2.76 3.97 1.18 6.8 1.03 10h2c.15-2.65 1.51-4.97 3.55-6.42zM19.97 10h2c-.15-3.2-1.73-6.03-4.13-7.85l-1.43 1.43c2.05 1.45 3.41 3.77 3.56 6.42zm-1.97.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-2.87.68-5 3.25-5 6.32V16l-2 2v1h17v-1l-2-2v-5.5zM11.5 22c.14 0 .27-.01.4-.04.65-.13 1.19-.58 1.44-1.18.1-.24.16-.5.16-.78h-4c0 1.1.9 2 2 2z"/></g>
|
||||
<g id="notifications-paused"><path d="M11.5 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6.5-6v-5.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-2.87.68-5 3.25-5 6.32V16l-2 2v1h17v-1l-2-2zm-4-6.2l-2.8 3.4H14V15H9v-1.8l2.8-3.4H9V8h5v1.8z"/></g>
|
||||
<g id="pages"><path d="M3 5v6h5L7 7l4 1V3H5c-1.1 0-2 .9-2 2zm5 8H3v6c0 1.1.9 2 2 2h6v-5l-4 1 1-4zm9 4l-4-1v5h6c1.1 0 2-.9 2-2v-6h-5l1 4zm2-14h-6v5l4-1-1 4h5V5c0-1.1-.9-2-2-2z"/></g>
|
||||
<g id="party-mode"><path d="M20 4h-3.17L15 2H9L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 3c1.63 0 3.06.79 3.98 2H12c-1.66 0-3 1.34-3 3 0 .35.07.69.18 1H7.1c-.06-.32-.1-.66-.1-1 0-2.76 2.24-5 5-5zm0 10c-1.63 0-3.06-.79-3.98-2H12c1.66 0 3-1.34 3-3 0-.35-.07-.69-.18-1h2.08c.07.32.1.66.1 1 0 2.76-2.24 5-5 5z"/></g>
|
||||
<g id="people"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></g>
|
||||
<g id="people-outline"><path d="M16.5 13c-1.2 0-3.07.34-4.5 1-1.43-.67-3.3-1-4.5-1C5.33 13 1 14.08 1 16.25V19h22v-2.75c0-2.17-4.33-3.25-6.5-3.25zm-4 4.5h-10v-1.25c0-.54 2.56-1.75 5-1.75s5 1.21 5 1.75v1.25zm9 0H14v-1.25c0-.46-.2-.86-.52-1.22.88-.3 1.96-.53 3.02-.53 2.44 0 5 1.21 5 1.75v1.25zM7.5 12c1.93 0 3.5-1.57 3.5-3.5S9.43 5 7.5 5 4 6.57 4 8.5 5.57 12 7.5 12zm0-5.5c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm9 5.5c1.93 0 3.5-1.57 3.5-3.5S18.43 5 16.5 5 13 6.57 13 8.5s1.57 3.5 3.5 3.5zm0-5.5c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2z"/></g>
|
||||
<g id="person"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></g>
|
||||
<g id="person-add"><path d="M15 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm-9-2V7H4v3H1v2h3v3h2v-3h3v-2H6zm9 4c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></g>
|
||||
<g id="person-outline"><path d="M12 5.9c1.16 0 2.1.94 2.1 2.1s-.94 2.1-2.1 2.1S9.9 9.16 9.9 8s.94-2.1 2.1-2.1m0 9c2.97 0 6.1 1.46 6.1 2.1v1.1H5.9V17c0-.64 3.13-2.1 6.1-2.1M12 4C9.79 4 8 5.79 8 8s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 9c-2.67 0-8 1.34-8 4v3h16v-3c0-2.66-5.33-4-8-4z"/></g>
|
||||
<g id="plus-one"><path d="M10 8H8v4H4v2h4v4h2v-4h4v-2h-4zm4.5-1.92V7.9l2.5-.5V18h2V5z"/></g>
|
||||
<g id="poll"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z"/></g>
|
||||
<g id="public"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></g>
|
||||
<g id="school"><path d="M5 13.18v4L12 21l7-3.82v-4L12 17l-7-3.82zM12 3L1 9l11 6 9-4.91V17h2V9L12 3z"/></g>
|
||||
<g id="share"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z"/></g>
|
||||
<g id="whatshot"><path d="M13.5.67s.74 2.65.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l.03-.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8C20 8.61 17.41 3.8 13.5.67zM11.71 19c-1.78 0-3.22-1.4-3.22-3.14 0-1.62 1.05-2.76 2.81-3.12 1.77-.36 3.6-1.21 4.62-2.58.39 1.29.59 2.65.59 4.04 0 2.65-2.15 4.8-4.8 4.8z"/></g>
|
||||
</defs></svg>
|
||||
|
After Width: | Height: | Size: 6.8 KiB |
99
handlers/www/assets/style.css
Normal file
99
handlers/www/assets/style.css
Normal file
@@ -0,0 +1,99 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Rationale');
|
||||
|
||||
.selected button {
|
||||
background-color: rgba(158,158,158,0.2);
|
||||
}
|
||||
|
||||
.terminal-container {
|
||||
background-color: #000;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.terminal-instance{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.clock {
|
||||
font-family: 'Rationale', sans-serif;
|
||||
font-size: 3.0em;
|
||||
color: #1da4eb;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.welcome {
|
||||
background-color: #e7e7e7;
|
||||
}
|
||||
|
||||
.welcome > div {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.welcome > div > img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.g-recaptcha div {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: auto;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.uploadStatus .bottom-block {
|
||||
display: block;
|
||||
position: relative;
|
||||
background-color: rgba(255, 235, 169, 0.25);
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.uploadStatus .bottom-block > span {
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.uploadStatus {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border: 2px solid #aad1f9;
|
||||
transition: opacity 0.1s linear;
|
||||
border-top: 0px;
|
||||
}
|
||||
|
||||
.disconnected {
|
||||
background-color: #FDF4B6;
|
||||
}
|
||||
md-input-container {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
md-input-container .md-errors-spacer {
|
||||
height: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.stats {
|
||||
min-height: 230px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
-webkit-appearance: none;
|
||||
width: 7px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 4px;
|
||||
background-color: rgba(0,0,0,.5);
|
||||
-webkit-box-shadow: 0 0 1px rgba(255,255,255,.5);
|
||||
}
|
||||
.md-mini {
|
||||
min-width: 24px;
|
||||
}
|
||||
|
||||
.dragover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
BIN
handlers/www/assets/swarm.png
Normal file
BIN
handlers/www/assets/swarm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
106
handlers/www/assets/xterm/addons/attach/attach.js
Normal file
106
handlers/www/assets/xterm/addons/attach/attach.js
Normal file
@@ -0,0 +1,106 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.attach = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
function attach(term, socket, bidirectional, buffered) {
|
||||
var addonTerminal = term;
|
||||
bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
|
||||
addonTerminal.__socket = socket;
|
||||
addonTerminal.__flushBuffer = function () {
|
||||
addonTerminal.write(addonTerminal.__attachSocketBuffer);
|
||||
addonTerminal.__attachSocketBuffer = null;
|
||||
};
|
||||
addonTerminal.__pushToBuffer = function (data) {
|
||||
if (addonTerminal.__attachSocketBuffer) {
|
||||
addonTerminal.__attachSocketBuffer += data;
|
||||
}
|
||||
else {
|
||||
addonTerminal.__attachSocketBuffer = data;
|
||||
setTimeout(addonTerminal.__flushBuffer, 10);
|
||||
}
|
||||
};
|
||||
var myTextDecoder;
|
||||
addonTerminal.__getMessage = function (ev) {
|
||||
var str;
|
||||
if (typeof ev.data === 'object') {
|
||||
if (!myTextDecoder) {
|
||||
myTextDecoder = new TextDecoder();
|
||||
}
|
||||
if (ev.data instanceof ArrayBuffer) {
|
||||
str = myTextDecoder.decode(ev.data);
|
||||
displayData(str);
|
||||
}
|
||||
else {
|
||||
var fileReader_1 = new FileReader();
|
||||
fileReader_1.addEventListener('load', function () {
|
||||
str = myTextDecoder.decode(fileReader_1.result);
|
||||
displayData(str);
|
||||
});
|
||||
fileReader_1.readAsArrayBuffer(ev.data);
|
||||
}
|
||||
}
|
||||
else if (typeof ev.data === 'string') {
|
||||
displayData(ev.data);
|
||||
}
|
||||
else {
|
||||
throw Error("Cannot handle \"" + typeof ev.data + "\" websocket message.");
|
||||
}
|
||||
};
|
||||
function displayData(str, data) {
|
||||
if (buffered) {
|
||||
addonTerminal.__pushToBuffer(str || data);
|
||||
}
|
||||
else {
|
||||
addonTerminal.write(str || data);
|
||||
}
|
||||
}
|
||||
addonTerminal.__sendData = function (data) {
|
||||
if (socket.readyState !== 1) {
|
||||
return;
|
||||
}
|
||||
socket.send(data);
|
||||
};
|
||||
addonTerminal._core.register(addSocketListener(socket, 'message', addonTerminal.__getMessage));
|
||||
if (bidirectional) {
|
||||
addonTerminal.__dataListener = addonTerminal.onData(addonTerminal.__sendData);
|
||||
addonTerminal._core.register(addonTerminal.__dataListener);
|
||||
}
|
||||
addonTerminal._core.register(addSocketListener(socket, 'close', function () { return detach(addonTerminal, socket); }));
|
||||
addonTerminal._core.register(addSocketListener(socket, 'error', function () { return detach(addonTerminal, socket); }));
|
||||
}
|
||||
exports.attach = attach;
|
||||
function addSocketListener(socket, type, handler) {
|
||||
socket.addEventListener(type, handler);
|
||||
return {
|
||||
dispose: function () {
|
||||
if (!handler) {
|
||||
return;
|
||||
}
|
||||
socket.removeEventListener(type, handler);
|
||||
handler = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
function detach(term, socket) {
|
||||
var addonTerminal = term;
|
||||
addonTerminal.__dataListener.dispose();
|
||||
addonTerminal.__dataListener = undefined;
|
||||
socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
|
||||
if (socket) {
|
||||
socket.removeEventListener('message', addonTerminal.__getMessage);
|
||||
}
|
||||
delete addonTerminal.__socket;
|
||||
}
|
||||
exports.detach = detach;
|
||||
function apply(terminalConstructor) {
|
||||
terminalConstructor.prototype.attach = function (socket, bidirectional, buffered) {
|
||||
attach(this, socket, bidirectional, buffered);
|
||||
};
|
||||
terminalConstructor.prototype.detach = function (socket) {
|
||||
detach(this, socket);
|
||||
};
|
||||
}
|
||||
exports.apply = apply;
|
||||
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
//# sourceMappingURL=attach.js.map
|
||||
1
handlers/www/assets/xterm/addons/attach/attach.js.map
Normal file
1
handlers/www/assets/xterm/addons/attach/attach.js.map
Normal file
File diff suppressed because one or more lines are too long
51
handlers/www/assets/xterm/addons/fit/fit.js
Normal file
51
handlers/www/assets/xterm/addons/fit/fit.js
Normal file
@@ -0,0 +1,51 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.fit = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
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')));
|
||||
var elementStyle = window.getComputedStyle(term.element);
|
||||
var elementPadding = {
|
||||
top: parseInt(elementStyle.getPropertyValue('padding-top')),
|
||||
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
||||
right: parseInt(elementStyle.getPropertyValue('padding-right')),
|
||||
left: parseInt(elementStyle.getPropertyValue('padding-left'))
|
||||
};
|
||||
var elementPaddingVer = elementPadding.top + elementPadding.bottom;
|
||||
var elementPaddingHor = elementPadding.right + elementPadding.left;
|
||||
var availableHeight = parentElementHeight - elementPaddingVer;
|
||||
var availableWidth = parentElementWidth - elementPaddingHor - term._core.viewport.scrollBarWidth;
|
||||
var geometry = {
|
||||
cols: Math.floor(availableWidth / term._core._renderCoordinator.dimensions.actualCellWidth),
|
||||
rows: Math.floor(availableHeight / term._core._renderCoordinator.dimensions.actualCellHeight)
|
||||
};
|
||||
return geometry;
|
||||
}
|
||||
exports.proposeGeometry = proposeGeometry;
|
||||
function fit(term) {
|
||||
var geometry = proposeGeometry(term);
|
||||
if (geometry) {
|
||||
if (term.rows !== geometry.rows || term.cols !== geometry.cols) {
|
||||
term._core._renderCoordinator.clear();
|
||||
term.resize(geometry.cols, geometry.rows);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.fit = fit;
|
||||
function apply(terminalConstructor) {
|
||||
terminalConstructor.prototype.proposeGeometry = function () {
|
||||
return proposeGeometry(this);
|
||||
};
|
||||
terminalConstructor.prototype.fit = function () {
|
||||
fit(this);
|
||||
};
|
||||
}
|
||||
exports.apply = apply;
|
||||
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
//# sourceMappingURL=fit.js.map
|
||||
1
handlers/www/assets/xterm/addons/fit/fit.js.map
Normal file
1
handlers/www/assets/xterm/addons/fit/fit.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"fit.js","sources":["../../../src/addons/fit/fit.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * Fit terminal columns and rows to the dimensions of its DOM element.\n *\n * ## Approach\n *\n * Rows: Truncate the division of the terminal parent element height by the\n * terminal row height.\n * Columns: Truncate the division of the terminal parent element width by the\n * terminal character width (apply display: inline at the terminal\n * row and truncate its width with the current number of columns).\n */\n\nimport { Terminal } from 'xterm';\n\nexport interface IGeometry {\n rows: number;\n cols: number;\n}\n\nexport function proposeGeometry(term: Terminal): IGeometry {\n if (!term.element.parentElement) {\n return null;\n }\n const parentElementStyle = window.getComputedStyle(term.element.parentElement);\n const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));\n const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));\n const elementStyle = window.getComputedStyle(term.element);\n const elementPadding = {\n top: parseInt(elementStyle.getPropertyValue('padding-top')),\n bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),\n right: parseInt(elementStyle.getPropertyValue('padding-right')),\n left: parseInt(elementStyle.getPropertyValue('padding-left'))\n };\n const elementPaddingVer = elementPadding.top + elementPadding.bottom;\n const elementPaddingHor = elementPadding.right + elementPadding.left;\n const availableHeight = parentElementHeight - elementPaddingVer;\n const availableWidth = parentElementWidth - elementPaddingHor - (<any>term)._core.viewport.scrollBarWidth;\n const geometry = {\n cols: Math.floor(availableWidth / (<any>term)._core._renderCoordinator.dimensions.actualCellWidth),\n rows: Math.floor(availableHeight / (<any>term)._core._renderCoordinator.dimensions.actualCellHeight)\n };\n return geometry;\n}\n\nexport function fit(term: Terminal): void {\n const geometry = proposeGeometry(term);\n if (geometry) {\n // Force a full render\n if (term.rows !== geometry.rows || term.cols !== geometry.cols) {\n (<any>term)._core._renderCoordinator.clear();\n term.resize(geometry.cols, geometry.rows);\n }\n }\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (<any>terminalConstructor.prototype).proposeGeometry = function (): IGeometry {\n return proposeGeometry(this);\n };\n\n (<any>terminalConstructor.prototype).fit = function (): void {\n fit(this);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADsBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAvBA;AAyBA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AATA;AAWA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AARA;"}
|
||||
10
handlers/www/assets/xterm/addons/fullscreen/fullscreen.css
Normal file
10
handlers/www/assets/xterm/addons/fullscreen/fullscreen.css
Normal file
@@ -0,0 +1,10 @@
|
||||
.xterm.fullscreen {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
z-index: 255;
|
||||
}
|
||||
29
handlers/www/assets/xterm/addons/fullscreen/fullscreen.js
Normal file
29
handlers/www/assets/xterm/addons/fullscreen/fullscreen.js
Normal file
@@ -0,0 +1,29 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.fullscreen = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
function toggleFullScreen(term, fullscreen) {
|
||||
var fn;
|
||||
if (typeof fullscreen === 'undefined') {
|
||||
fn = (term.element.classList.contains('fullscreen')) ?
|
||||
term.element.classList.remove : term.element.classList.add;
|
||||
}
|
||||
else if (!fullscreen) {
|
||||
fn = term.element.classList.remove;
|
||||
}
|
||||
else {
|
||||
fn = term.element.classList.add;
|
||||
}
|
||||
fn = fn.bind(term.element.classList);
|
||||
fn('fullscreen');
|
||||
}
|
||||
exports.toggleFullScreen = toggleFullScreen;
|
||||
function apply(terminalConstructor) {
|
||||
terminalConstructor.prototype.toggleFullScreen = function (fullscreen) {
|
||||
toggleFullScreen(this, fullscreen);
|
||||
};
|
||||
}
|
||||
exports.apply = apply;
|
||||
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
//# sourceMappingURL=fullscreen.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"fullscreen.js","sources":["../../../src/addons/fullscreen/fullscreen.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal } from 'xterm';\n\n/**\n * Toggle the given terminal's fullscreen mode.\n * @param term The terminal to toggle full screen mode\n * @param fullscreen Toggle fullscreen on (true) or off (false)\n */\nexport function toggleFullScreen(term: Terminal, fullscreen: boolean): void {\n let fn: (...tokens: string[]) => void;\n\n if (typeof fullscreen === 'undefined') {\n fn = (term.element.classList.contains('fullscreen')) ?\n term.element.classList.remove : term.element.classList.add;\n } else if (!fullscreen) {\n fn = term.element.classList.remove;\n } else {\n fn = term.element.classList.add;\n }\n\n fn = fn.bind(term.element.classList);\n fn('fullscreen');\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (<any>terminalConstructor.prototype).toggleFullScreen = function (fullscreen: boolean): void {\n toggleFullScreen(this, fullscreen);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADYA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAdA;AAgBA;AACA;AACA;AACA;AACA;AAJA;"}
|
||||
262
handlers/www/assets/xterm/addons/search/search.js
Normal file
262
handlers/www/assets/xterm/addons/search/search.js
Normal file
@@ -0,0 +1,262 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.search = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?';
|
||||
var LINES_CACHE_TIME_TO_LIVE = 15 * 1000;
|
||||
var SearchHelper = (function () {
|
||||
function SearchHelper(_terminal) {
|
||||
this._terminal = _terminal;
|
||||
this._linesCache = null;
|
||||
this._linesCacheTimeoutId = 0;
|
||||
this._destroyLinesCache = this._destroyLinesCache.bind(this);
|
||||
}
|
||||
SearchHelper.prototype.findNext = function (term, searchOptions) {
|
||||
var incremental = searchOptions.incremental;
|
||||
var result;
|
||||
if (!term || term.length === 0) {
|
||||
this._terminal.clearSelection();
|
||||
return false;
|
||||
}
|
||||
var startCol = 0;
|
||||
var startRow = this._terminal.buffer.viewportY;
|
||||
if (this._terminal.hasSelection()) {
|
||||
var currentSelection = this._terminal.getSelectionPosition();
|
||||
startRow = incremental ? currentSelection.startRow : currentSelection.endRow;
|
||||
startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn;
|
||||
}
|
||||
this._initLinesCache();
|
||||
var findingRow = startRow;
|
||||
var cumulativeCols = startCol;
|
||||
while (this._terminal.buffer.getLine(findingRow).isWrapped) {
|
||||
findingRow--;
|
||||
cumulativeCols += this._terminal.cols;
|
||||
}
|
||||
result = this._findInLine(term, findingRow, cumulativeCols, searchOptions);
|
||||
if (!result) {
|
||||
for (var y = startRow + 1; y < this._terminal.buffer.baseY + this._terminal.rows; y++) {
|
||||
result = this._findInLine(term, y, 0, searchOptions);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
for (var y = 0; y < findingRow; y++) {
|
||||
result = this._findInLine(term, y, 0, searchOptions);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._selectResult(result);
|
||||
};
|
||||
SearchHelper.prototype.findPrevious = function (term, searchOptions) {
|
||||
var result;
|
||||
if (!term || term.length === 0) {
|
||||
this._terminal.clearSelection();
|
||||
return false;
|
||||
}
|
||||
var isReverseSearch = true;
|
||||
var startRow = this._terminal.buffer.viewportY + this._terminal.rows - 1;
|
||||
var startCol = this._terminal.cols;
|
||||
if (this._terminal.hasSelection()) {
|
||||
var currentSelection = this._terminal.getSelectionPosition();
|
||||
startRow = currentSelection.startRow;
|
||||
startCol = currentSelection.startColumn;
|
||||
}
|
||||
this._initLinesCache();
|
||||
result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch);
|
||||
if (!result) {
|
||||
var cumulativeCols = this._terminal.cols;
|
||||
if (this._terminal.buffer.getLine(startRow).isWrapped) {
|
||||
cumulativeCols += startCol;
|
||||
}
|
||||
for (var y = startRow - 1; y >= 0; y--) {
|
||||
result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
if (this._terminal.buffer.getLine(y).isWrapped) {
|
||||
cumulativeCols += this._terminal.cols;
|
||||
}
|
||||
else {
|
||||
cumulativeCols = this._terminal.cols;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
var searchFrom = this._terminal.buffer.baseY + this._terminal.rows - 1;
|
||||
var cumulativeCols = this._terminal.cols;
|
||||
for (var y = searchFrom; y >= startRow; y--) {
|
||||
result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
if (this._terminal.buffer.getLine(y).isWrapped) {
|
||||
cumulativeCols += this._terminal.cols;
|
||||
}
|
||||
else {
|
||||
cumulativeCols = this._terminal.cols;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._selectResult(result);
|
||||
};
|
||||
SearchHelper.prototype._initLinesCache = function () {
|
||||
var _this = this;
|
||||
if (!this._linesCache) {
|
||||
this._linesCache = new Array(this._terminal.buffer.length);
|
||||
this._cursorMoveListener = this._terminal.onCursorMove(function () { return _this._destroyLinesCache(); });
|
||||
this._resizeListener = this._terminal.onResize(function () { return _this._destroyLinesCache(); });
|
||||
}
|
||||
window.clearTimeout(this._linesCacheTimeoutId);
|
||||
this._linesCacheTimeoutId = window.setTimeout(function () { return _this._destroyLinesCache(); }, LINES_CACHE_TIME_TO_LIVE);
|
||||
};
|
||||
SearchHelper.prototype._destroyLinesCache = function () {
|
||||
this._linesCache = null;
|
||||
if (this._cursorMoveListener) {
|
||||
this._cursorMoveListener.dispose();
|
||||
this._cursorMoveListener = undefined;
|
||||
}
|
||||
if (this._resizeListener) {
|
||||
this._resizeListener.dispose();
|
||||
this._resizeListener = undefined;
|
||||
}
|
||||
if (this._linesCacheTimeoutId) {
|
||||
window.clearTimeout(this._linesCacheTimeoutId);
|
||||
this._linesCacheTimeoutId = 0;
|
||||
}
|
||||
};
|
||||
SearchHelper.prototype._isWholeWord = function (searchIndex, line, term) {
|
||||
return (((searchIndex === 0) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex - 1]) !== -1)) &&
|
||||
(((searchIndex + term.length) === line.length) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex + term.length]) !== -1)));
|
||||
};
|
||||
SearchHelper.prototype._findInLine = function (term, row, col, searchOptions, isReverseSearch) {
|
||||
if (searchOptions === void 0) { searchOptions = {}; }
|
||||
if (isReverseSearch === void 0) { isReverseSearch = false; }
|
||||
if (this._terminal.buffer.getLine(row).isWrapped) {
|
||||
return;
|
||||
}
|
||||
var stringLine = this._linesCache ? this._linesCache[row] : void 0;
|
||||
if (stringLine === void 0) {
|
||||
stringLine = this.translateBufferLineToStringWithWrap(row, true);
|
||||
if (this._linesCache) {
|
||||
this._linesCache[row] = stringLine;
|
||||
}
|
||||
}
|
||||
var searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase();
|
||||
var searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase();
|
||||
var resultIndex = -1;
|
||||
if (searchOptions.regex) {
|
||||
var searchRegex = RegExp(searchTerm, 'g');
|
||||
var foundTerm = void 0;
|
||||
if (isReverseSearch) {
|
||||
while (foundTerm = searchRegex.exec(searchStringLine.slice(0, col))) {
|
||||
resultIndex = searchRegex.lastIndex - foundTerm[0].length;
|
||||
term = foundTerm[0];
|
||||
searchRegex.lastIndex -= (term.length - 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
foundTerm = searchRegex.exec(searchStringLine.slice(col));
|
||||
if (foundTerm && foundTerm[0].length > 0) {
|
||||
resultIndex = col + (searchRegex.lastIndex - foundTerm[0].length);
|
||||
term = foundTerm[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isReverseSearch) {
|
||||
if (col - searchTerm.length >= 0) {
|
||||
resultIndex = searchStringLine.lastIndexOf(searchTerm, col - searchTerm.length);
|
||||
}
|
||||
}
|
||||
else {
|
||||
resultIndex = searchStringLine.indexOf(searchTerm, col);
|
||||
}
|
||||
}
|
||||
if (resultIndex >= 0) {
|
||||
if (resultIndex >= this._terminal.cols) {
|
||||
row += Math.floor(resultIndex / this._terminal.cols);
|
||||
resultIndex = resultIndex % this._terminal.cols;
|
||||
}
|
||||
if (searchOptions.wholeWord && !this._isWholeWord(resultIndex, searchStringLine, term)) {
|
||||
return;
|
||||
}
|
||||
var line = this._terminal.buffer.getLine(row);
|
||||
for (var i = 0; i < resultIndex; i++) {
|
||||
var cell = line.getCell(i);
|
||||
var char = cell.char;
|
||||
if (char.length > 1) {
|
||||
resultIndex -= char.length - 1;
|
||||
}
|
||||
var charWidth = cell.width;
|
||||
if (charWidth === 0) {
|
||||
resultIndex++;
|
||||
}
|
||||
}
|
||||
return {
|
||||
term: term,
|
||||
col: resultIndex,
|
||||
row: row
|
||||
};
|
||||
}
|
||||
};
|
||||
SearchHelper.prototype.translateBufferLineToStringWithWrap = function (lineIndex, trimRight) {
|
||||
var lineString = '';
|
||||
var lineWrapsToNext;
|
||||
do {
|
||||
var nextLine = this._terminal.buffer.getLine(lineIndex + 1);
|
||||
lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
|
||||
lineString += this._terminal.buffer.getLine(lineIndex).translateToString(!lineWrapsToNext && trimRight).substring(0, this._terminal.cols);
|
||||
lineIndex++;
|
||||
} while (lineWrapsToNext);
|
||||
return lineString;
|
||||
};
|
||||
SearchHelper.prototype._selectResult = function (result) {
|
||||
if (!result) {
|
||||
this._terminal.clearSelection();
|
||||
return false;
|
||||
}
|
||||
this._terminal.select(result.col, result.row, result.term.length);
|
||||
this._terminal.scrollLines(result.row - this._terminal.buffer.viewportY);
|
||||
return true;
|
||||
};
|
||||
return SearchHelper;
|
||||
}());
|
||||
exports.SearchHelper = SearchHelper;
|
||||
|
||||
},{}],2:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var SearchHelper_1 = require("./SearchHelper");
|
||||
function findNext(terminal, term, searchOptions) {
|
||||
if (searchOptions === void 0) { searchOptions = {}; }
|
||||
var addonTerminal = terminal;
|
||||
if (!addonTerminal.__searchHelper) {
|
||||
addonTerminal.__searchHelper = new SearchHelper_1.SearchHelper(addonTerminal);
|
||||
}
|
||||
return addonTerminal.__searchHelper.findNext(term, searchOptions);
|
||||
}
|
||||
exports.findNext = findNext;
|
||||
function findPrevious(terminal, term, searchOptions) {
|
||||
var addonTerminal = terminal;
|
||||
if (!addonTerminal.__searchHelper) {
|
||||
addonTerminal.__searchHelper = new SearchHelper_1.SearchHelper(addonTerminal);
|
||||
}
|
||||
return addonTerminal.__searchHelper.findPrevious(term, searchOptions);
|
||||
}
|
||||
exports.findPrevious = findPrevious;
|
||||
function apply(terminalConstructor) {
|
||||
terminalConstructor.prototype.findNext = function (term, searchOptions) {
|
||||
return findNext(this, term, searchOptions);
|
||||
};
|
||||
terminalConstructor.prototype.findPrevious = function (term, searchOptions) {
|
||||
return findPrevious(this, term, searchOptions);
|
||||
};
|
||||
}
|
||||
exports.apply = apply;
|
||||
|
||||
},{"./SearchHelper":1}]},{},[2])(2)
|
||||
});
|
||||
//# sourceMappingURL=search.js.map
|
||||
1
handlers/www/assets/xterm/addons/search/search.js.map
Normal file
1
handlers/www/assets/xterm/addons/search/search.js.map
Normal file
File diff suppressed because one or more lines are too long
70
handlers/www/assets/xterm/addons/terminado/terminado.js
Normal file
70
handlers/www/assets/xterm/addons/terminado/terminado.js
Normal file
@@ -0,0 +1,70 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.terminado = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
function terminadoAttach(term, socket, bidirectional, buffered) {
|
||||
var addonTerminal = term;
|
||||
bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
|
||||
addonTerminal.__socket = socket;
|
||||
addonTerminal.__flushBuffer = function () {
|
||||
addonTerminal.write(addonTerminal.__attachSocketBuffer);
|
||||
addonTerminal.__attachSocketBuffer = null;
|
||||
};
|
||||
addonTerminal.__pushToBuffer = function (data) {
|
||||
if (addonTerminal.__attachSocketBuffer) {
|
||||
addonTerminal.__attachSocketBuffer += data;
|
||||
}
|
||||
else {
|
||||
addonTerminal.__attachSocketBuffer = data;
|
||||
setTimeout(addonTerminal.__flushBuffer, 10);
|
||||
}
|
||||
};
|
||||
addonTerminal.__getMessage = function (ev) {
|
||||
var data = JSON.parse(ev.data);
|
||||
if (data[0] === 'stdout') {
|
||||
if (buffered) {
|
||||
addonTerminal.__pushToBuffer(data[1]);
|
||||
}
|
||||
else {
|
||||
addonTerminal.write(data[1]);
|
||||
}
|
||||
}
|
||||
};
|
||||
addonTerminal.__sendData = function (data) {
|
||||
socket.send(JSON.stringify(['stdin', data]));
|
||||
};
|
||||
addonTerminal.__setSize = function (size) {
|
||||
socket.send(JSON.stringify(['set_size', size.rows, size.cols]));
|
||||
};
|
||||
socket.addEventListener('message', addonTerminal.__getMessage);
|
||||
if (bidirectional) {
|
||||
addonTerminal._core.register(addonTerminal.onData(addonTerminal.__sendData));
|
||||
}
|
||||
addonTerminal._core.register(addonTerminal.onResize(addonTerminal.__setSize));
|
||||
socket.addEventListener('close', function () { return terminadoDetach(addonTerminal, socket); });
|
||||
socket.addEventListener('error', function () { return terminadoDetach(addonTerminal, socket); });
|
||||
}
|
||||
exports.terminadoAttach = terminadoAttach;
|
||||
function terminadoDetach(term, socket) {
|
||||
var addonTerminal = term;
|
||||
addonTerminal.__dataListener.dispose();
|
||||
addonTerminal.__dataListener = undefined;
|
||||
socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
|
||||
if (socket) {
|
||||
socket.removeEventListener('message', addonTerminal.__getMessage);
|
||||
}
|
||||
delete addonTerminal.__socket;
|
||||
}
|
||||
exports.terminadoDetach = terminadoDetach;
|
||||
function apply(terminalConstructor) {
|
||||
terminalConstructor.prototype.terminadoAttach = function (socket, bidirectional, buffered) {
|
||||
return terminadoAttach(this, socket, bidirectional, buffered);
|
||||
};
|
||||
terminalConstructor.prototype.terminadoDetach = function (socket) {
|
||||
return terminadoDetach(this, socket);
|
||||
};
|
||||
}
|
||||
exports.apply = apply;
|
||||
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
//# sourceMappingURL=terminado.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"terminado.js","sources":["../../../src/addons/terminado/terminado.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * This module provides methods for attaching a terminal to a terminado\n * WebSocket stream.\n */\n\nimport { Terminal } from 'xterm';\nimport { ITerminadoAddonTerminal } from './Interfaces';\n\n/**\n * Attaches the given terminal to the given socket.\n *\n * @param term The terminal to be attached to the given socket.\n * @param socket The socket to attach the current terminal.\n * @param bidirectional Whether the terminal should send data to the socket as well.\n * @param buffered Whether the rendering of incoming data should happen instantly or at a maximum\n * frequency of 1 rendering per 10ms.\n */\nexport function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n const addonTerminal = <ITerminadoAddonTerminal>term;\n bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;\n addonTerminal.__socket = socket;\n\n addonTerminal.__flushBuffer = () => {\n addonTerminal.write(addonTerminal.__attachSocketBuffer);\n addonTerminal.__attachSocketBuffer = null;\n };\n\n addonTerminal.__pushToBuffer = (data: string) => {\n if (addonTerminal.__attachSocketBuffer) {\n addonTerminal.__attachSocketBuffer += data;\n } else {\n addonTerminal.__attachSocketBuffer = data;\n setTimeout(addonTerminal.__flushBuffer, 10);\n }\n };\n\n addonTerminal.__getMessage = (ev: MessageEvent) => {\n const data = JSON.parse(ev.data);\n if (data[0] === 'stdout') {\n if (buffered) {\n addonTerminal.__pushToBuffer(data[1]);\n } else {\n addonTerminal.write(data[1]);\n }\n }\n };\n\n addonTerminal.__sendData = (data: string) => {\n socket.send(JSON.stringify(['stdin', data]));\n };\n\n addonTerminal.__setSize = (size: {rows: number, cols: number}) => {\n socket.send(JSON.stringify(['set_size', size.rows, size.cols]));\n };\n\n socket.addEventListener('message', addonTerminal.__getMessage);\n\n if (bidirectional) {\n addonTerminal._core.register(addonTerminal.onData(addonTerminal.__sendData));\n }\n addonTerminal._core.register(addonTerminal.onResize(addonTerminal.__setSize));\n\n socket.addEventListener('close', () => terminadoDetach(addonTerminal, socket));\n socket.addEventListener('error', () => terminadoDetach(addonTerminal, socket));\n}\n\n/**\n * Detaches the given terminal from the given socket\n *\n * @param term The terminal to be detached from the given socket.\n * @param socket The socket from which to detach the current terminal.\n */\nexport function terminadoDetach(term: Terminal, socket: WebSocket): void {\n const addonTerminal = <ITerminadoAddonTerminal>term;\n addonTerminal.__dataListener.dispose();\n addonTerminal.__dataListener = undefined;\n\n socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;\n\n if (socket) {\n socket.removeEventListener('message', addonTerminal.__getMessage);\n }\n\n delete addonTerminal.__socket;\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n /**\n * Attaches the current terminal to the given socket\n *\n * @param socket - The socket to attach the current terminal.\n * @param bidirectional - Whether the terminal should send data to the socket as well.\n * @param buffered - Whether the rendering of incoming data should happen instantly or at a\n * maximum frequency of 1 rendering per 10ms.\n */\n (<any>terminalConstructor.prototype).terminadoAttach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n return terminadoAttach(this, socket, bidirectional, buffered);\n };\n\n /**\n * Detaches the current terminal from the given socket.\n *\n * @param socket The socket from which to detach the current terminal.\n */\n (<any>terminalConstructor.prototype).terminadoDetach = function (socket: WebSocket): void {\n return terminadoDetach(this, socket);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADoBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AA/CA;AAuDA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAZA;AAcA;AASA;AACA;AACA;AAOA;AACA;AACA;AACA;AArBA;"}
|
||||
42
handlers/www/assets/xterm/addons/webLinks/webLinks.js
Normal file
42
handlers/www/assets/xterm/addons/webLinks/webLinks.js
Normal file
@@ -0,0 +1,42 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.webLinks = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var protocolClause = '(https?:\\/\\/)';
|
||||
var domainCharacterSet = '[\\da-z\\.-]+';
|
||||
var negatedDomainCharacterSet = '[^\\da-z\\.-]+';
|
||||
var domainBodyClause = '(' + domainCharacterSet + ')';
|
||||
var tldClause = '([a-z\\.]{2,6})';
|
||||
var ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
|
||||
var localHostClause = '(localhost)';
|
||||
var portClause = '(:\\d{1,5})';
|
||||
var hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
|
||||
var pathCharacterSet = '(\\/[\\/\\w\\.\\-%~:+]*)*([^:"\'\\s])';
|
||||
var pathClause = '(' + pathCharacterSet + ')?';
|
||||
var queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
|
||||
var queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
|
||||
var hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
|
||||
var negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+';
|
||||
var bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;
|
||||
var start = '(?:^|' + negatedDomainCharacterSet + ')(';
|
||||
var end = ')($|' + negatedPathCharacterSet + ')';
|
||||
var strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);
|
||||
function handleLink(event, uri) {
|
||||
window.open(uri, '_blank');
|
||||
}
|
||||
function webLinksInit(term, handler, options) {
|
||||
if (handler === void 0) { handler = handleLink; }
|
||||
if (options === void 0) { options = {}; }
|
||||
options.matchIndex = 1;
|
||||
term.registerLinkMatcher(strictUrlRegex, handler, options);
|
||||
}
|
||||
exports.webLinksInit = webLinksInit;
|
||||
function apply(terminalConstructor) {
|
||||
terminalConstructor.prototype.webLinksInit = function (handler, options) {
|
||||
webLinksInit(this, handler, options);
|
||||
};
|
||||
}
|
||||
exports.apply = apply;
|
||||
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
//# sourceMappingURL=webLinks.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"webLinks.js","sources":["../../../src/addons/webLinks/webLinks.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal, ILinkMatcherOptions } from 'xterm';\n\nconst protocolClause = '(https?:\\\\/\\\\/)';\nconst domainCharacterSet = '[\\\\da-z\\\\.-]+';\nconst negatedDomainCharacterSet = '[^\\\\da-z\\\\.-]+';\nconst domainBodyClause = '(' + domainCharacterSet + ')';\nconst tldClause = '([a-z\\\\.]{2,6})';\nconst ipClause = '((\\\\d{1,3}\\\\.){3}\\\\d{1,3})';\nconst localHostClause = '(localhost)';\nconst portClause = '(:\\\\d{1,5})';\nconst hostClause = '((' + domainBodyClause + '\\\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';\nconst pathCharacterSet = '(\\\\/[\\\\/\\\\w\\\\.\\\\-%~:+]*)*([^:\"\\'\\\\s])';\nconst pathClause = '(' + pathCharacterSet + ')?';\nconst queryStringHashFragmentCharacterSet = '[0-9\\\\w\\\\[\\\\]\\\\(\\\\)\\\\/\\\\?\\\\!#@$%&\\'*+,:;~\\\\=\\\\.\\\\-]*';\nconst queryStringClause = '(\\\\?' + queryStringHashFragmentCharacterSet + ')?';\nconst hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';\nconst negatedPathCharacterSet = '[^\\\\/\\\\w\\\\.\\\\-%]+';\nconst bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;\nconst start = '(?:^|' + negatedDomainCharacterSet + ')(';\nconst end = ')($|' + negatedPathCharacterSet + ')';\nconst strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);\n\nfunction handleLink(event: MouseEvent, uri: string): void {\n window.open(uri, '_blank');\n}\n\n/**\n * Initialize the web links addon, registering the link matcher.\n * @param term The terminal to use web links within.\n * @param handler A custom handler to use.\n * @param options Custom options to use, matchIndex will always be ignored.\n */\nexport function webLinksInit(term: Terminal, handler: (event: MouseEvent, uri: string) => void = handleLink, options: ILinkMatcherOptions = {}): void {\n options.matchIndex = 1;\n term.registerLinkMatcher(strictUrlRegex, handler, options);\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (<any>terminalConstructor.prototype).webLinksInit = function (handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void {\n webLinksInit(this, handler, options);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAQA;AAAA;AAAA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AACA;AAJA;"}
|
||||
45
handlers/www/assets/xterm/addons/zmodem/zmodem.js
Normal file
45
handlers/www/assets/xterm/addons/zmodem/zmodem.js
Normal file
@@ -0,0 +1,45 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.zmodem = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var zmodem;
|
||||
function zmodemAttach(ws, opts) {
|
||||
if (opts === void 0) { opts = {}; }
|
||||
var term = this;
|
||||
var senderFunc = function (octets) { return ws.send(new Uint8Array(octets)); };
|
||||
var zsentry;
|
||||
function shouldWrite() {
|
||||
return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;
|
||||
}
|
||||
zsentry = new zmodem.Sentry({
|
||||
to_terminal: function (octets) {
|
||||
if (shouldWrite()) {
|
||||
term.write(String.fromCharCode.apply(String, octets));
|
||||
}
|
||||
},
|
||||
sender: senderFunc,
|
||||
on_retract: function () { return term.emit('zmodemRetract'); },
|
||||
on_detect: function (detection) { return term.emit('zmodemDetect', detection); }
|
||||
});
|
||||
function handleWSMessage(evt) {
|
||||
if (typeof evt.data === 'string') {
|
||||
if (shouldWrite()) {
|
||||
term.write(evt.data);
|
||||
}
|
||||
}
|
||||
else {
|
||||
zsentry.consume(evt.data);
|
||||
}
|
||||
}
|
||||
ws.binaryType = 'arraybuffer';
|
||||
ws.addEventListener('message', handleWSMessage);
|
||||
}
|
||||
function apply(terminalConstructor) {
|
||||
zmodem = (typeof window === 'object') ? window.Zmodem : { Browser: null };
|
||||
terminalConstructor.prototype.zmodemAttach = zmodemAttach;
|
||||
terminalConstructor.prototype.zmodemBrowser = zmodem.Browser;
|
||||
}
|
||||
exports.apply = apply;
|
||||
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
//# sourceMappingURL=zmodem.js.map
|
||||
1
handlers/www/assets/xterm/addons/zmodem/zmodem.js.map
Normal file
1
handlers/www/assets/xterm/addons/zmodem/zmodem.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"zmodem.js","sources":["../../../src/addons/zmodem/zmodem.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal } from 'xterm';\n\n/**\n *\n * Allow xterm.js to handle ZMODEM uploads and downloads.\n *\n * This addon is a wrapper around zmodem.js. It adds the following to the\n * Terminal class:\n *\n * - function `zmodemAttach(<WebSocket>, <Object>)` - creates a Zmodem.Sentry\n * on the passed WebSocket object. The Object passed is optional and\n * can contain:\n * - noTerminalWriteOutsideSession: Suppress writes from the Sentry\n * object to the Terminal while there is no active Session. This\n * is necessary for compatibility with, for example, the\n * `attach.js` addon.\n *\n * - event `zmodemDetect` - fired on Zmodem.Sentry’s `on_detect` callback.\n * Passes the zmodem.js Detection object.\n *\n * - event `zmodemRetract` - fired on Zmodem.Sentry’s `on_retract` callback.\n *\n * You’ll need to provide logic to handle uploads and downloads.\n * See zmodem.js’s documentation for more details.\n *\n * **IMPORTANT:** After you confirm() a zmodem.js Detection, if you have\n * used the `attach` or `terminado` addons, you’ll need to suspend their\n * operation for the duration of the ZMODEM session. (The demo does this\n * via `detach()` and a re-`attach()`.)\n */\n\nlet zmodem: any;\n\nexport interface IZmodemOptions {\n noTerminalWriteOutsideSession?: boolean;\n}\n\nfunction zmodemAttach(ws: WebSocket, opts: IZmodemOptions = {}): void {\n const term = this;\n const senderFunc = (octets: ArrayLike<number>) => ws.send(new Uint8Array(octets));\n\n let zsentry: any;\n\n function shouldWrite(): boolean {\n return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;\n }\n\n zsentry = new zmodem.Sentry({\n to_terminal: (octets: ArrayLike<number>) => {\n if (shouldWrite()) {\n term.write(\n String.fromCharCode.apply(String, octets)\n );\n }\n },\n sender: senderFunc,\n on_retract: () => (<any>term).emit('zmodemRetract'),\n on_detect: (detection: any) => (<any>term).emit('zmodemDetect', detection)\n });\n\n function handleWSMessage(evt: MessageEvent): void {\n\n // In testing with xterm.js’s demo the first message was\n // always text even if the rest were binary. While that\n // may be specific to xterm.js’s demo, ultimately we\n // should reject anything that isn’t binary.\n if (typeof evt.data === 'string') {\n if (shouldWrite()) {\n term.write(evt.data);\n }\n }\n else {\n zsentry.consume(evt.data);\n }\n }\n\n ws.binaryType = 'arraybuffer';\n ws.addEventListener('message', handleWSMessage);\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n zmodem = (typeof window === 'object') ? (<any>window).Zmodem : {Browser: null}; // Nullify browser for tests\n\n (<any>terminalConstructor.prototype).zmodemAttach = zmodemAttach;\n (<any>terminalConstructor.prototype).zmodemBrowser = zmodem.Browser;\n}\n",null],"names":[],"mappings":"ACAA;;;ADoCA;AAMA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AALA;"}
|
||||
171
handlers/www/assets/xterm/xterm.css
Normal file
171
handlers/www/assets/xterm/xterm.css
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* 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-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;
|
||||
cursor: default;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.xterm .xterm-screen {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.xterm .xterm-screen canvas {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.xterm .xterm-scroll-area {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.xterm-char-measure-element {
|
||||
display: inline-block;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -9999em;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.xterm {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.xterm.enable-mouse-events {
|
||||
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.xterm.xterm-cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.xterm.column-select.focus {
|
||||
/* Column selection mode */
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.xterm .xterm-accessibility,
|
||||
.xterm .xterm-message {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.xterm .live-region {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.xterm-dim {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.xterm-underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
10707
handlers/www/assets/xterm/xterm.js
Normal file
10707
handlers/www/assets/xterm/xterm.js
Normal file
File diff suppressed because it is too large
Load Diff
1
handlers/www/assets/xterm/xterm.js.map
Normal file
1
handlers/www/assets/xterm/xterm.js.map
Normal file
File diff suppressed because one or more lines are too long
51
handlers/www/bypass.html
Normal file
51
handlers/www/bypass.html
Normal file
@@ -0,0 +1,51 @@
|
||||
<!doctype html>
|
||||
<html ng-app="DockerPlay" ng-controller="BypassController">
|
||||
<head>
|
||||
<title>Docker Playground</title>
|
||||
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/angular_material/1.1.0/angular-material.min.css">
|
||||
<link rel="stylesheet" href="/assets/style.css" />
|
||||
</head>
|
||||
<body class="welcome">
|
||||
<div>
|
||||
<h1>Welcome!</h1>
|
||||
<h2>We're bypassing the Captcha and redirecting you now..</h2>
|
||||
<form id="welcomeFormBypass" method="POST" action="/">
|
||||
<button id="start" type="submit">Start Session</button>
|
||||
<input id="stack" type="hidden" name="stack" value=""/>
|
||||
<input id="stack_name" type="hidden" name="stack_name" value=""/>
|
||||
<input id="image_name" type="hidden" name="image_name" value=""/>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-animate.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-aria.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angular_material/1.1.0/angular-material.min.js"></script>
|
||||
|
||||
<script src="/assets/app.js"></script>
|
||||
<script>
|
||||
function getParameterByName(name, url) {
|
||||
if (!url) url = window.location.href;
|
||||
name = name.replace(/[\[\]]/g, "\\$&");
|
||||
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
|
||||
results = regex.exec(url);
|
||||
if (!results) return null;
|
||||
if (!results[2]) return '';
|
||||
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
var stack = getParameterByName('stack');
|
||||
if (stack) {
|
||||
document.getElementById('stack').value = stack;
|
||||
}
|
||||
var stackName = getParameterByName('stack_name');
|
||||
if (stack) {
|
||||
document.getElementById('stack_name').value = stackName;
|
||||
}
|
||||
var imageName = getParameterByName('image_name');
|
||||
if (stack) {
|
||||
document.getElementById('image_name').value = imageName;
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
341
handlers/www/default/index.html
Normal file
341
handlers/www/default/index.html
Normal file
@@ -0,0 +1,341 @@
|
||||
<!doctype html>
|
||||
<html ng-app="DockerPlay" ng-controller="PlayController">
|
||||
<head>
|
||||
<title>Docker Playground</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic|Material+Icons" />
|
||||
<link rel="stylesheet" href="https://unpkg.com/angular-material@1.1.10/angular-material.min.css">
|
||||
<link rel="stylesheet" href="/assets/xterm/xterm.css" />
|
||||
<link rel="stylesheet" href="/assets/xterm/addons/fullscreen/fullscreen.css" />
|
||||
<link rel="stylesheet" href="/assets/style.css" />
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', 'UA-89019737-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
<script type="text/javascript" src="//cdn.bizible.com/scripts/bizible.js"async=""></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div layout="column" style="height:100%;" ng-cloak>
|
||||
<section id="sessionEnd" layout="row" flex ng-if="!isAlive">
|
||||
<md-content flex layout-padding ng-if="!instances.length">
|
||||
<div layout="column" layout-align="top center">
|
||||
<p>
|
||||
<strong>Your session has expired.</strong>
|
||||
</p>
|
||||
</div>
|
||||
<div flex></div>
|
||||
</md-content>
|
||||
</section>
|
||||
|
||||
<section ng-if="!connected" class="disconnected" layout="row" layout-align="center center">
|
||||
<h1 class="md-headline">No connection to server. Reconnecting...</h1>
|
||||
<md-progress-circular class="md-hue-2" md-diameter="20px"></md-progress-circular>
|
||||
</section>
|
||||
|
||||
<section id="popupContainer" layout="row" flex ng-if="isAlive">
|
||||
<md-sidenav
|
||||
class="md-sidenav-left"
|
||||
md-component-id="left"
|
||||
md-is-locked-open="$mdMedia('gt-sm')"
|
||||
md-whiteframe="4" layout="column">
|
||||
|
||||
<md-toolbar class="md-theme-indigo">
|
||||
<span class="clock">{{ttl}}</span>
|
||||
<md-button class="md-warn md-raised" ng-click="closeSession()">Close session</md-button>
|
||||
<div class="md-toolbar-tools">
|
||||
<h1 class="md-toolbar-tools">Instances</h1>
|
||||
<templates-icon></templates-icon>
|
||||
<settings-icon></settings-icon><br/>
|
||||
</div>
|
||||
<div class="md-toolbar-tools" ng-if="playground.allow_windows_instances">
|
||||
<md-switch ng-model="type.windows">
|
||||
Windows containers {{windows}}
|
||||
</md-switch>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
<md-content layout-padding>
|
||||
<md-button ng-click="newInstance()" ng-disabled="isInstanceBeingCreated" class="md-primary">{{newInstanceBtnText}}</md-button>
|
||||
<md-list class="md-dense" flex>
|
||||
<md-list-item ng-switch on="instance.isManager || instance.isK8sManager" class="md-2-line" ng-repeat="instance in instances | orderBy:'hostname'" ng-click="showInstance(instance)" ng-class="instance.name == selectedInstance.name ? 'selected' : false">
|
||||
<md-icon ng-switch-when="true" style="color: blue" md-svg-icon="person"></md-icon>
|
||||
<md-icon ng-switch-when="false" md-svg-icon="person-outline"></md-icon>
|
||||
<div class="md-list-item-text" layout="column">
|
||||
<h3>{{instance.ip}}</h3>
|
||||
<h4>{{instance.hostname}}</h4>
|
||||
</div>
|
||||
<md-divider ng-if="!$last"></md-divider>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
</md-content>
|
||||
</md-sidenav>
|
||||
<md-content flex layout-padding ng-if="!instances.length">
|
||||
<div layout="column" layout-align="top center">
|
||||
<p>Add instances to your playground.</p>
|
||||
<p><strong>Sessions and all their instances are deleted after {{ttl}} hours.</strong></p>
|
||||
</div>
|
||||
|
||||
<div flex></div>
|
||||
</md-content>
|
||||
<md-content flex layout="column" ng-repeat="instance in instances" ng-show="instance.name == selectedInstance.name" ngf-drop class="drop-box" ngf-drag-over-class="'dragover'" ngf-max-size="100000000" ngf-change="uploadFiles($files, $invalidFiles)" ngf-multiple="true">
|
||||
<md-card class="stats" md-theme="default" md-theme-watch>
|
||||
<md-card-title>
|
||||
<md-card-title-text>
|
||||
<span class="md-headline">{{instance.name}}</span>
|
||||
</md-card-title-text>
|
||||
</md-card-title>
|
||||
<md-card-content>
|
||||
<div layout-gt-sm="row">
|
||||
<md-input-container class="md-icon-float md-block">
|
||||
<label>IP</label>
|
||||
<input ng-model="instance.ip" type="text" readonly="readonly">
|
||||
</md-input-container>
|
||||
<md-button class="md-raised" ng-click="openPort(instance)">
|
||||
Open Port
|
||||
</md-button>
|
||||
<md-chips ng-model="instance.ports" name="port" readonly="true" md-removable="false">
|
||||
<md-chip-template>
|
||||
<strong><a href="{{getProxyUrl(instance, $chip)}}" title="{{getProxyUrl(instance, $chip)}}" target="_blank">{{$chip}}</a></strong>
|
||||
</md-chip-template>
|
||||
</md-chips>
|
||||
<md-chips ng-model="instance.swarmPorts" name="port" readonly="true" md-removable="false">
|
||||
<md-chip-template>
|
||||
<strong><a href="{{getProxyUrl(instance, $chip)}}" title="{{getProxyUrl(instance, $chip)}}" target="_blank">{{$chip}}</a></strong>
|
||||
</md-chip-template>
|
||||
</md-chips>
|
||||
</div>
|
||||
<div layout-gt-sm="row">
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<label>Memory</label>
|
||||
<input ng-model="instance.mem" type="text" readonly="readonly">
|
||||
</md-input-container>
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<label>CPU</label>
|
||||
<input ng-model="instance.cpu" type="text" readonly="readonly">
|
||||
</md-input-container>
|
||||
</div>
|
||||
<div layout-gt-sm="row">
|
||||
<md-input-container>
|
||||
<label>SSH</label>
|
||||
<input value="ssh {{instance.proxy_host}}@direct.{{host}}" type="text" readonly="readonly" size="50">
|
||||
<md-icon ngclipboard data-clipboard-text="ssh {{instance.proxy_host}}@direct.{{host}} "class="material-icons">content_copy
|
||||
<md-tooltip md-direction="top">Copy!</md-tooltip>
|
||||
</md-icon>
|
||||
</md-input-container>
|
||||
<div class="md-block" glex-gt-sm></div>
|
||||
</div>
|
||||
</md-card-content>
|
||||
<md-card-actions>
|
||||
<md-button class="md-warn md-raised" ng-click="deleteInstance(instance)" ng-disabled="isInstanceBeingDeleted">{{deleteInstanceBtnText}}</md-button>
|
||||
<md-button class="md-raised" ng-click="openEditor(instance)">
|
||||
<md-icon class="material-icons">insert_drive_file</md-icon> Editor
|
||||
</md-button>
|
||||
</md-card-actions>
|
||||
</md-card>
|
||||
<md-card flex md-theme="default" md-theme-watch >
|
||||
<div ng-show="uploadMessage" class="uploadStatus">
|
||||
<md-progress-linear md-mode="determinate" value="{{uploadProgress}}"></md-progress-linear>
|
||||
<div class="bottom-block">
|
||||
<span>{{uploadMessage}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="instance.status=='reconnect'" class="uploadStatus">Connection has been lost. Sometimes this happens when a windows instance is joining a swarm. Trying to reconnect terminal...</div>
|
||||
<id class="terminal-container container-{{instance.name}}">
|
||||
<div class="terminal-instance" id="terminal-{{instance.name}}"></div>
|
||||
</id>
|
||||
</md-card>
|
||||
</md-content>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div style="visibility: hidden;">
|
||||
<div class="md-dialog-container" id="builderDialog">
|
||||
<md-dialog>
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
<h2>Session stack builder</h2>
|
||||
<span flex></span>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
<md-dialog-content layout-padding>
|
||||
<div flex="100" style="margin: 20px 0px;">
|
||||
We are building your stack. This might take a while.<br/>
|
||||
</div>
|
||||
<div id="builder-terminal" style="height: 450px; width: 800px">
|
||||
</div>
|
||||
<div layout="row" ng-if="ready">
|
||||
<div flex="100" style="margin-top: 20px; text-align:center; font-weight: bold; color: green;">
|
||||
Your session is ready!
|
||||
</div>
|
||||
</div>
|
||||
</md-dialog-content>
|
||||
<md-dialog-actions layout="row" ng-if="ready">
|
||||
<span flex></span>
|
||||
<md-button ng-click="closeSessionBuilder()">
|
||||
Close
|
||||
</md-button>
|
||||
</md-dialog-actions>
|
||||
</md-dialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script type="text/ng-template" id="templates-modal.html">
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
<h2>Templates</h2>
|
||||
<span flex></span>
|
||||
<md-button class="md-icon-button" ng-click="$ctrl.close()">
|
||||
<md-icon class="material-icon" aria-label="Close dialog">close</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
<md-dialog-content>
|
||||
<div class="md-dialog-content" style="width:600px;">
|
||||
<div layout="row" layout-sm="column" layout-align="space-around" ng-if="building">
|
||||
<md-progress-circular md-mode="indeterminate"></md-progress-circular>
|
||||
</div>
|
||||
<div layout="row" ng-if="errorMessage">
|
||||
<div flex="100" style="margin-top: 20px; text-align:center; font-weight: bold; color: red;">
|
||||
{{errorMessage}}
|
||||
</div>
|
||||
</div>
|
||||
<md-list flex ng-if="!building">
|
||||
<md-list-item class="md-3-line" ng-repeat="template in templates" ng-click="$ctrl.setupSession(template.setup)">
|
||||
<md-card md-theme="default" md-theme-watch>
|
||||
<md-card-title>
|
||||
<md-card-title-text>
|
||||
<span class="md-headline">{{template.title}}</span>
|
||||
</md-card-title-text>
|
||||
<md-card-title-media>
|
||||
<div class="md-media-sm card-media"><img ng-src="{{template.icon}}" style="height: 75px;" class="md-card-image"></div>
|
||||
</md-card-title-media>
|
||||
</md-card-title>
|
||||
</md-card>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
</div>
|
||||
</md-dialog-content>
|
||||
<md-dialog-actions layout="row">
|
||||
<span flex></span>
|
||||
<md-button ng-click="$ctrl.close()">
|
||||
Close
|
||||
</md-button>
|
||||
</md-dialog-actions>
|
||||
</script>
|
||||
<script type="text/ng-template" id="settings-modal.html">
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
<h2>Settings</h2>
|
||||
<span flex></span>
|
||||
<md-button class="md-icon-button" ng-click="$ctrl.close()">
|
||||
<md-icon class="material-icon" aria-label="Close dialog">close</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
<md-dialog-content>
|
||||
<div class="md-dialog-content" style="width:600px;">
|
||||
<div layout="row">
|
||||
<div flex="50">
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<label>Keyboard Shortcut Preset</label>
|
||||
<md-select ng-model="$ctrl.currentShortcutConfig" ng-model-options="{getterSetter: true}" placeholder="Keyboard shortcut prefix">
|
||||
<md-option ng-repeat="preset in $ctrl.keyboardShortcutPresets" value="{{preset}}">
|
||||
{{preset.name}}
|
||||
</md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</div>
|
||||
<div flex="10"></div>
|
||||
<div flex="40">
|
||||
<div ng-if="$ctrl.selectedShortcutPreset">
|
||||
Preset details:
|
||||
<ul>
|
||||
<li ng-if="$ctrl.selectedShortcutPreset.presets.length == 0">No presets defined</li>
|
||||
<li ng-repeat="preset in $ctrl.selectedShortcutPreset.presets">
|
||||
<code>{{preset.command}}</code> - {{preset.description}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div layout="row">
|
||||
<div flex="50">
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<label>Instance Image</label>
|
||||
<md-select ng-model="$ctrl.currentDesiredInstanceImage" ng-model-options="{getterSetter: true}" placeholder="New Instance Image">
|
||||
<md-option ng-repeat="image in $ctrl.instanceImages" value="{{image}}">
|
||||
{{ image }}
|
||||
</md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</div>
|
||||
</div>
|
||||
<div layout="row">
|
||||
<div flex="50">
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<label>Terminal Font Size</label>
|
||||
<md-select ng-model="$ctrl.currentTerminalFontSize" ng-model-options="{getterSetter: true}">
|
||||
<md-option ng-repeat="size in $ctrl.terminalFontSizes" value="{{size}}">
|
||||
{{ size }}
|
||||
</md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</md-dialog-content>
|
||||
|
||||
<md-dialog-actions layout="row">
|
||||
<span flex></span>
|
||||
<md-button ng-click="$ctrl.close()">
|
||||
Close
|
||||
</md-button>
|
||||
</md-dialog-actions>
|
||||
</script>
|
||||
|
||||
<script src="https://unpkg.com/reconnectingwebsocket@1.0.0/reconnecting-websocket.min.js" integrity="sha384-FtJyC+/3fgtPbqlacLHdGwBrmPjKoYBsiqNF5/BEprsnIXB4xtXLCJRx7Xx+TWKP" crossorigin="anonymous"></script>
|
||||
<script
|
||||
src="https://code.jquery.com/jquery-3.2.1.min.js"
|
||||
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<script src="https://unpkg.com/angular@1.5.5/angular.min.js"></script>
|
||||
<script src="https://unpkg.com/angular-animate@1.5.5/angular-animate.min.js"></script>
|
||||
<script src="https://unpkg.com/angular-aria@1.5.5/angular-aria.min.js"></script>
|
||||
<script src="https://unpkg.com/angular-messages@1.5.5/angular-messages.min.js"></script>
|
||||
<script src="https://unpkg.com/angular-material@1.1.0/angular-material.min.js"></script>
|
||||
<script src="https://cdn.rawgit.com/zenorocha/clipboard.js/master/dist/clipboard.min.js"></script>
|
||||
<script src="https://unpkg.com/ngclipboard@2.0.0/dist/ngclipboard.min.js"></script>
|
||||
|
||||
|
||||
<script src="https://unpkg.com/ng-file-upload@12.2.13/dist/ng-file-upload-all.min.js" integrity="sha384-NbBOS/QuqJqwWOtYg/L3ZDhgl/6GFyvkRMypJQLgoisMPtJiHj5uQ+3bj8V8Muwm" crossorigin="anonymous"></script>
|
||||
|
||||
<script src="/assets/xterm/xterm.js"></script>
|
||||
<script src="/assets/xterm/addons/fit/fit.js"></script>
|
||||
<script src="/assets/xterm/addons/fullscreen/fullscreen.js"></script>
|
||||
<script src="/assets/setup-xterm.js"></script>
|
||||
<script src="/assets/attach.js"></script>
|
||||
<script src="https://unpkg.com/moment@2.16.0/min/moment.min.js"></script>
|
||||
<script src="/assets/app.js"></script>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
window.onbeforeunload = function (e) {
|
||||
e = e || window.event;
|
||||
|
||||
// For IE and Firefox prior to version 4
|
||||
if (e) {
|
||||
e.returnValue = 'Make sure you saved your session URL';
|
||||
}
|
||||
|
||||
// For Safari
|
||||
return 'Make sure you saved your session URL';
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
172
handlers/www/default/landing.html
Normal file
172
handlers/www/default/landing.html
Normal file
@@ -0,0 +1,172 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" ng-app="PWDLanding" ng-controller="LoginController">
|
||||
<head>
|
||||
<script src="https://unpkg.com/angular@1.6.6/angular.min.js"></script>
|
||||
<script src="https://unpkg.com/angular-cookies@1.6.6/angular-cookies.min.js"></script>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>Play with Docker</title>
|
||||
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/bootstrap@4.0.0-beta/dist/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
|
||||
|
||||
<!-- Custom styles for this template -->
|
||||
<link href="/assets/landing.css" rel="stylesheet">
|
||||
|
||||
<script type="text/javascript" src="//cdn.bizible.com/scripts/bizible.js"async=""></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<div class="header clearfix">
|
||||
<nav>
|
||||
<ul class="nav nav-pills float-right">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://github.com/play-with-docker/play-with-docker">Contribute</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="jumbotron" ng-cloak>
|
||||
<img src="https://www.docker.com/sites/default/files/Whale%20Logo332_5.png" />
|
||||
<h1 class="display-3">Play with Docker</h1>
|
||||
<p class="lead">A simple, interactive and fun playground to learn Docker</p>
|
||||
<div ng-hide="loggedIn" class="btn-group" role="group">
|
||||
<button id="btnGroupDrop1" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Login
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="btnGroupDrop1">
|
||||
<a ng-repeat="provider in providers" class="dropdown-item" ng-click="login(provider)">{{provider}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<form id="landingForm" method="POST" action="/">
|
||||
<p ng-show="loggedIn"><a class="btn btn-lg btn-success" href="#" ng-click="start()" role="button">Start</a></p>
|
||||
<input id="stack" type="hidden" name="stack" value=""/>
|
||||
<input id="stack_name" type="hidden" name="stack_name" value=""/>
|
||||
<input id="image_name" type="hidden" name="image_name" value=""/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="row marketing">
|
||||
<div class="col-lg-12">
|
||||
<p>Play with Docker (PWD) is a project hacked by <a href="https://www.twitter.com/marcosnils">Marcos Liljedhal</a> and <a href="https://www.twitter.com/xetorthio">Jonathan Leibiusky</a> and sponsored by Docker Inc.</p>
|
||||
<p>PWD is a Docker playground which allows users to run Docker commands in a matter of seconds. It gives the experience of having a free Alpine Linux Virtual Machine in browser, where you can build and run Docker containers and even create clusters in <a href="https://docs.docker.com/engine/swarm/">Docker Swarm Mode</a>. Under the hood Docker-in-Docker (DinD) is used to give the effect of multiple VMs/PCs. In addition to the playground, PWD also includes a training site composed of a large set of Docker labs and quizzes from beginner to advanced level available at <a href="http://training.play-with-docker.com/">training.play-with-docker.com</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<p>Your use of Play With Docker is subject to the Docker Terms of Service which can be accessed <a target="_blank" href="https://www.docker.com/legal/docker-terms-service">here</a></p>
|
||||
<p>© Play with Docker 2017 - 2020</p>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
|
||||
[[ if .SegmentId ]]
|
||||
<script>
|
||||
!function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement("script");e.type="text/javascript";e.async=!0;e.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION="4.0.0";
|
||||
analytics.load('[[ .SegmentId ]]');
|
||||
analytics.page();
|
||||
}}();
|
||||
</script>
|
||||
[[ end ]]
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/popper.js@1.11.0/dist/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/bootstrap@4.0.0-beta/dist/js/bootstrap.min.js" integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
angular.module('PWDLanding', [])
|
||||
.controller('LoginController', ['$scope', '$http', '$window', function($scope, $http, $window) {
|
||||
$scope.providers = [];
|
||||
$scope.loggedIn = false;
|
||||
$scope.user = null;
|
||||
|
||||
function checkLoggedIn() {
|
||||
$http({
|
||||
method: 'GET',
|
||||
url: '/users/me'
|
||||
}).then(function(response) {
|
||||
[[ if .SegmentId ]]
|
||||
analytics.identify(response.data.provider_user_id, {"email": response.data.email});
|
||||
[[ end ]]
|
||||
$scope.user = response.data;
|
||||
$scope.loggedIn = true;
|
||||
}, function(response) {
|
||||
[[ if .SegmentId ]]
|
||||
analytics.identify();
|
||||
[[ end ]]
|
||||
console.log('ERROR', response);
|
||||
$scope.user = null;
|
||||
$scope.loggedIn = false;
|
||||
});
|
||||
}
|
||||
|
||||
checkLoggedIn();
|
||||
|
||||
$http({
|
||||
method: 'GET',
|
||||
url: '/oauth/providers'
|
||||
}).then(function(response) {
|
||||
$scope.providers = response.data;
|
||||
if ($scope.providers.length == 0) {
|
||||
$scope.loggedIn = true;
|
||||
}
|
||||
}, function(response) {
|
||||
console.log('ERROR', response);
|
||||
});
|
||||
|
||||
|
||||
$scope.login = function(provider) {
|
||||
var width = screen.width*0.6;
|
||||
// fixed height as the login window is not responsive
|
||||
var height = 620;
|
||||
var x = screen.width/2 - width/2;
|
||||
var y = screen.height/2 - height/2;
|
||||
$window.open('/oauth/providers/' + provider + '/login', 'PWDLogin', 'width='+width+',height='+height+',left='+x+',top='+y);
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";
|
||||
// Listen to message from child window
|
||||
eventer(messageEvent,function(e) {
|
||||
if (e.data === 'done') {
|
||||
checkLoggedIn();
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
$scope.start = function() {
|
||||
function getParameterByName(name, url) {
|
||||
if (!url) url = window.location.href;
|
||||
name = name.replace(/[\[\]]/g, "\\$&");
|
||||
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
|
||||
results = regex.exec(url);
|
||||
if (!results) return null;
|
||||
if (!results[2]) return '';
|
||||
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
var stack = getParameterByName('stack');
|
||||
if (stack) {
|
||||
document.getElementById('stack').value = stack;
|
||||
}
|
||||
var stackName = getParameterByName('stack_name');
|
||||
if (stackName) {
|
||||
document.getElementById('stack_name').value = stackName;
|
||||
}
|
||||
var imageName = getParameterByName('image_name');
|
||||
if (imageName) {
|
||||
document.getElementById('image_name').value = imageName;
|
||||
}
|
||||
document.getElementById('landingForm').submit();
|
||||
}
|
||||
}]);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
177
handlers/www/editor.html
Normal file
177
handlers/www/editor.html
Normal file
@@ -0,0 +1,177 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>Editor</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/zTree.v3/3.5.29/css/metroStyle/metroStyle.min.css" integrity="sha256-9gdJ9sAxTV5MghBon+jq1V85ef8ALkS632yIIjAvAxc=" crossorigin="anonymous" />
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/open-iconic/1.1.1/font/css/open-iconic-bootstrap.min.css" integrity="sha256-BJ/G+e+y7bQdrYkS2RBTyNfBHpA9IuGaPmf9htub5MQ=" crossorigin="anonymous" />
|
||||
<link rel="stylesheet" href="/assets/editor.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid ">
|
||||
<div id="alert-info" class="alert alert-info alert-top" role="alert">
|
||||
<span class="alert-msg"></span>
|
||||
</div>
|
||||
<div class="row" style="height: 100%">
|
||||
<div class="col-md-3">
|
||||
<div>
|
||||
<div class="alert alert-info alert-newfile" role="alert">
|
||||
<span><strong>Create or upload</strong> files in the session terminal and then refresh </span>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" id='treeReloadBtn' class="btn btn-sm">
|
||||
<span class="oi oi-reload" title="Refresh" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="fileTree" class="ztree"></div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" id="tabs" role="tablist">
|
||||
</ul>
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" src="https://cdn.rawgit.com/zTree/zTree_v3/4f2717d4/js/jquery.ztree.core.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.9/ace.js" integrity="sha256-U//RSeH3TR3773Rk+1lAufJnRjEaG5LcdbvGV72kHEM=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.rawgit.com/beatgammit/base64-js/be644dec/base64js.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
$(document).ready(function() {
|
||||
var alertShow = function(msg) {
|
||||
$div = $('#alert-info');
|
||||
$div.find('.alert-msg').text(msg);
|
||||
if ($div.css('display') === 'none') {
|
||||
// fadein, fadeout.
|
||||
$div.fadeIn(1000).delay(1000).fadeOut(1000);
|
||||
}
|
||||
};
|
||||
//regiters events for newly created tab elements
|
||||
var registerEvents = function(tabId) {
|
||||
//this method will register event on close icon on the tab..
|
||||
$(".closeTab").click(function () {
|
||||
//there are multiple elements which has .closeTab icon so close the tab whose close icon is clicked
|
||||
var tabContentId = $(this).parent().attr("href");
|
||||
$(this).parent().parent().remove(); //remove li of tab
|
||||
$('#tabs a:last').tab('show'); // Select first tab
|
||||
$(tabContentId).remove(); //remove respective tab content
|
||||
});
|
||||
|
||||
$('#fileReload_'+tabId+'Btn').click(function() {
|
||||
var path = $(this).attr('data-file-path');
|
||||
loadFile(path, tabId);
|
||||
});
|
||||
|
||||
$('#fileSave_'+tabId+'Btn').click(function() {
|
||||
var path = $(this).attr('data-file-path');
|
||||
saveFile(path, tabId);
|
||||
});
|
||||
};
|
||||
|
||||
var getFilePath = function(treeNode) {
|
||||
var parent = treeNode.getParentNode();
|
||||
var path = '';
|
||||
if (parent) {
|
||||
return getFilePath(parent) + '/' + treeNode.name;
|
||||
} else {
|
||||
return treeNode.name;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var treeClick = function(event, treeId, treeNode, clickFlag) {
|
||||
if (!treeNode.isParent) {
|
||||
var tabId = treeNode.tId;
|
||||
if ($('#tab_' + tabId + '').length == 0) {
|
||||
var filePath = getFilePath(treeNode);
|
||||
$('.nav-tabs').append('<li class="nav-item"><a class="nav-link" data-toggle="tab" role="tab" href="#tab_' + tabId + '"><button class="close closeTab" type="button" >×</button>'+treeNode.name+'</a></li>');
|
||||
$('.tab-content').append(' \
|
||||
<div class="tab-pane" id="tab_' + tabId + '"> \
|
||||
<div style="height: 40px; width: 100%; padding-top: 5px; background-color: #F0F0F0"> \
|
||||
<button type="button" id="fileSave_'+tabId+'Btn" data-file-path="'+filePath+'" class="btn btn-info btn-sm"> \
|
||||
<span class="oi oi-data-transfer-upload" title="Save" aria-hidden="true"></span> Save \
|
||||
</button> \
|
||||
<button type="button" id="fileReload_'+tabId+'Btn" data-file-path="'+filePath+'" class="btn btn-info btn-sm"> \
|
||||
<span class="oi oi-reload" title="Save" aria-hidden="true"></span> Reload \
|
||||
</button> \
|
||||
</div> \
|
||||
<div id="editor_'+ tabId +'" style="height: calc(100vh - 82px); width: 100%;"></div> \
|
||||
</div> \
|
||||
');
|
||||
loadFile(filePath, tabId);
|
||||
registerEvents(tabId);
|
||||
}
|
||||
$('#tabs a[href="#tab_'+ tabId +'"]').tab('show');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var loadFile = function(filePath, tabId) {
|
||||
var editor = ace.edit('editor_'+tabId);
|
||||
$.get('./file?path='+filePath)
|
||||
.done(function( fileBase64 ) {
|
||||
var bytes = base64js.toByteArray(fileBase64);
|
||||
editor.setValue((new TextDecoder("utf-8")).decode(bytes), -1);
|
||||
editor.focus();
|
||||
alertShow('file loaded')
|
||||
});
|
||||
}
|
||||
|
||||
var saveFile = function(filePath, tabId) {
|
||||
var editor = ace.edit('editor_'+tabId);
|
||||
var fileData = new Blob([editor.getValue()], { type: 'text/plain' });
|
||||
var data = new FormData();
|
||||
var fileName = filePath.substr(filePath.lastIndexOf('/'))
|
||||
data.append(fileName,fileData, fileName);
|
||||
$.ajax({
|
||||
url: 'uploads?path='+filePath.substr(0, filePath.lastIndexOf('/')),
|
||||
data: data,
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
method: 'POST',
|
||||
type: 'POST', // For jQuery < 1.9
|
||||
success: function(data) {
|
||||
alertShow('file saved')
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var setting = {
|
||||
data: {
|
||||
key: {
|
||||
children: "contents"
|
||||
}
|
||||
},
|
||||
callback: {
|
||||
onClick: treeClick
|
||||
}
|
||||
};
|
||||
|
||||
var populateTree = function() {
|
||||
$.getJSON('./fstree')
|
||||
.done(function( treeData ) {
|
||||
treeData[0].open = true;
|
||||
$.fn.zTree.init($("#fileTree"), setting, treeData);
|
||||
});
|
||||
}
|
||||
|
||||
// Attach handlers to tree reload btn
|
||||
$('#treeReloadBtn').click(populateTree);
|
||||
|
||||
// populate tree whenever the page starts
|
||||
populateTree();
|
||||
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
263
handlers/www/k8s/index.html
Normal file
263
handlers/www/k8s/index.html
Normal file
@@ -0,0 +1,263 @@
|
||||
<!doctype html>
|
||||
<html ng-app="DockerPlay" ng-controller="PlayController">
|
||||
<head>
|
||||
<title>Docker Playground</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic|Material+Icons" />
|
||||
<link rel="stylesheet" href="https://unpkg.com/angular-material@1.1.10/angular-material.min.css">
|
||||
<link rel="stylesheet" href="/assets/xterm/xterm.css" />
|
||||
<link rel="stylesheet" href="/assets/xterm/addons/fullscreen/fullscreen.css" />
|
||||
<link rel="stylesheet" href="/assets/style.css" />
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', 'UA-89019737-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
<script type="text/javascript" src="//cdn.bizible.com/scripts/bizible.js"async=""></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div layout="column" style="height:100%;" ng-cloak>
|
||||
<section id="sessionEnd" layout="row" flex ng-if="!isAlive">
|
||||
<md-content flex layout-padding ng-if="!instances.length">
|
||||
<div layout="column" layout-align="top center">
|
||||
<p>
|
||||
<strong>Your session has expired.</strong>
|
||||
</p>
|
||||
</div>
|
||||
<div flex></div>
|
||||
</md-content>
|
||||
</section>
|
||||
|
||||
<section ng-if="!connected" class="disconnected" layout="row" layout-align="center center">
|
||||
<h1 class="md-headline">No connection to server. Reconnecting...</h1>
|
||||
<md-progress-circular class="md-hue-2" md-diameter="20px"></md-progress-circular>
|
||||
</section>
|
||||
|
||||
<section id="popupContainer" layout="row" flex ng-if="isAlive">
|
||||
<md-sidenav
|
||||
class="md-sidenav-left"
|
||||
md-component-id="left"
|
||||
md-theme="kube"
|
||||
md-is-locked-open="$mdMedia('gt-sm')"
|
||||
md-whiteframe="4" layout="column">
|
||||
|
||||
<md-toolbar class="md-accent md-hue-3">
|
||||
<span class="clock">{{ttl}}</span>
|
||||
<md-button class="md-warn md-raised" ng-click="closeSession()">Close session</md-button>
|
||||
<div class="md-toolbar-tools">
|
||||
<h1 class="md-toolbar-tools">Instances</h1>
|
||||
<settings-icon></settings-icon><br/>
|
||||
</div>
|
||||
<div class="md-toolbar-tools" ng-if="playground.allow_windows_instances">
|
||||
<md-switch ng-model="type.windows">
|
||||
Windows containers {{windows}}
|
||||
</md-switch>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
<md-content layout-padding>
|
||||
<md-button ng-click="newInstance()" ng-disabled="isInstanceBeingCreated" class="md-primary">{{newInstanceBtnText}}</md-button>
|
||||
<md-list class="md-dense" flex>
|
||||
<md-list-item ng-switch on="instance.isManager || instance.isK8sManager" class="md-2-line" ng-repeat="instance in instances | orderBy:'hostname'" ng-click="showInstance(instance)" ng-class="instance.name == selectedInstance.name ? 'selected' : false">
|
||||
<md-icon ng-switch-when="true" style="color: blue" md-svg-icon="person"></md-icon>
|
||||
<md-icon ng-switch-when="false" md-svg-icon="person-outline"></md-icon>
|
||||
<div class="md-list-item-text" layout="column">
|
||||
<h3>{{instance.ip}}</h3>
|
||||
<h4>{{instance.hostname}}</h4>
|
||||
</div>
|
||||
<md-divider ng-if="!$last"></md-divider>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
</md-content>
|
||||
</md-sidenav>
|
||||
<md-content flex layout-padding ng-if="!instances.length">
|
||||
<div layout="column" layout-align="top center">
|
||||
<p>Add instances to your playground.</p>
|
||||
<p><strong>Sessions and all their instances are deleted after {{ttl}} hours.</strong></p>
|
||||
</div>
|
||||
|
||||
<div flex></div>
|
||||
</md-content>
|
||||
<md-content flex layout="column" ng-repeat="instance in instances" ng-show="instance.name == selectedInstance.name" ngf-drop class="drop-box" ngf-drag-over-class="'dragover'" ngf-max-size="100000000" ngf-change="uploadFiles($files, $invalidFiles)" ngf-multiple="true">
|
||||
<md-card class="stats" md-theme="default" md-theme-watch>
|
||||
<md-card-title>
|
||||
<md-card-title-text>
|
||||
<span class="md-headline">{{instance.name}}</span>
|
||||
</md-card-title-text>
|
||||
</md-card-title>
|
||||
<md-card-content>
|
||||
<div layout-gt-sm="row">
|
||||
<md-input-container class="md-icon-float md-block">
|
||||
<label>IP</label>
|
||||
<input ng-model="instance.ip" type="text" readonly="readonly">
|
||||
</md-input-container>
|
||||
<md-chips ng-model="instance.ports" name="port" readonly="true" md-removable="false">
|
||||
<md-chip-template>
|
||||
<strong><a href="{{getProxyUrl(instance, $chip)}}" title="{{getProxyUrl(instance, $chip)}}" target="_blank">{{$chip}}</a></strong>
|
||||
</md-chip-template>
|
||||
</md-chips>
|
||||
<md-chips ng-model="instance.swarmPorts" name="port" readonly="true" md-removable="false">
|
||||
<md-chip-template>
|
||||
<strong><a href="{{getProxyUrl(instance, $chip)}}" title="{{getProxyUrl(instance, $chip)}}" target="_blank">{{$chip}}</a></strong>
|
||||
</md-chip-template>
|
||||
</md-chips>
|
||||
</div>
|
||||
<div layout-gt-sm="row">
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<label>Memory</label>
|
||||
<input ng-model="instance.mem" type="text" readonly="readonly">
|
||||
</md-input-container>
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<label>CPU</label>
|
||||
<input ng-model="instance.cpu" type="text" readonly="readonly">
|
||||
</md-input-container>
|
||||
</div>
|
||||
<div layout-gt-sm="row">
|
||||
<md-input-container>
|
||||
<label>URL</label>
|
||||
<input value="{{instance.proxy_host}}.direct.{{host}}" type="text" readonly="readonly" size="50">
|
||||
</md-input-container>
|
||||
<div class="md-block" glex-gt-sm></div>
|
||||
</div>
|
||||
</md-card-content>
|
||||
<md-card-actions>
|
||||
<md-button class="md-warn md-raised" ng-click="deleteInstance(instance)" ng-disabled="isInstanceBeingDeleted">{{deleteInstanceBtnText}}</md-button>
|
||||
</md-card-actions>
|
||||
</md-card>
|
||||
<md-card flex md-theme="default" md-theme-watch >
|
||||
<div ng-show="uploadMessage" class="uploadStatus">
|
||||
<md-progress-linear md-mode="determinate" value="{{uploadProgress}}"></md-progress-linear>
|
||||
<div class="bottom-block">
|
||||
<span>{{uploadMessage}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="instance.status=='reconnect'" class="uploadStatus">Connection has been lost. Sometimes this happens when a windows instance is joining a swarm. Trying to reconnect terminal...</div>
|
||||
<id class="terminal-container container-{{instance.name}}">
|
||||
<div class="terminal-instance" id="terminal-{{instance.name}}"></div>
|
||||
</id>
|
||||
</md-card>
|
||||
</md-content>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script type="text/ng-template" id="settings-modal.html">
|
||||
<md-toolbar md-theme="kube">
|
||||
<div class="md-toolbar-tools">
|
||||
<h2>Settings</h2>
|
||||
<span flex></span>
|
||||
<md-button class="md-icon-button" ng-click="$ctrl.close()">
|
||||
<md-icon class="material-icon" aria-label="Close dialog">close</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
<md-dialog-content>
|
||||
<div class="md-dialog-content" style="width:600px;">
|
||||
<div layout="row">
|
||||
<div flex="50">
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<label>Keyboard Shortcut Preset</label>
|
||||
<md-select ng-model="$ctrl.currentShortcutConfig" ng-model-options="{getterSetter: true}" placeholder="Keyboard shortcut prefix">
|
||||
<md-option ng-repeat="preset in $ctrl.keyboardShortcutPresets" value="{{preset}}">
|
||||
{{preset.name}}
|
||||
</md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</div>
|
||||
<div flex="10"></div>
|
||||
<div flex="40">
|
||||
<div ng-if="$ctrl.selectedShortcutPreset">
|
||||
Preset details:
|
||||
<ul>
|
||||
<li ng-if="$ctrl.selectedShortcutPreset.presets.length == 0">No presets defined</li>
|
||||
<li ng-repeat="preset in $ctrl.selectedShortcutPreset.presets">
|
||||
<code>{{preset.command}}</code> - {{preset.description}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div layout="row">
|
||||
<div flex="50">
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<label>Instance Image</label>
|
||||
<md-select ng-model="$ctrl.currentDesiredInstanceImage" ng-model-options="{getterSetter: true}" placeholder="New Instance Image">
|
||||
<md-option ng-repeat="image in $ctrl.instanceImages" value="{{image}}">
|
||||
{{ image }}
|
||||
</md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</div>
|
||||
</div>
|
||||
<div layout="row">
|
||||
<div flex="50">
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<label>Terminal Font Size</label>
|
||||
<md-select ng-model="$ctrl.currentTerminalFontSize" ng-model-options="{getterSetter: true}">
|
||||
<md-option ng-repeat="size in $ctrl.terminalFontSizes" value="{{size}}">
|
||||
{{ size }}
|
||||
</md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</md-dialog-content>
|
||||
|
||||
<md-dialog-actions layout="row">
|
||||
<span flex></span>
|
||||
<md-button ng-click="$ctrl.close()">
|
||||
Close
|
||||
</md-button>
|
||||
</md-dialog-actions>
|
||||
</script>
|
||||
|
||||
<script src="https://unpkg.com/reconnectingwebsocket@1.0.0/reconnecting-websocket.min.js" integrity="sha384-FtJyC+/3fgtPbqlacLHdGwBrmPjKoYBsiqNF5/BEprsnIXB4xtXLCJRx7Xx+TWKP" crossorigin="anonymous"></script>
|
||||
<script
|
||||
src="https://code.jquery.com/jquery-3.2.1.min.js"
|
||||
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<script src="https://unpkg.com/angular@1.5.5/angular.min.js"></script>
|
||||
<script src="https://unpkg.com/angular-animate@1.5.5/angular-animate.min.js"></script>
|
||||
<script src="https://unpkg.com/angular-aria@1.5.5/angular-aria.min.js"></script>
|
||||
<script src="https://unpkg.com/angular-messages@1.5.5/angular-messages.min.js"></script>
|
||||
<script src="https://unpkg.com/angular-material@1.1.0/angular-material.min.js"></script>
|
||||
<script src="https://cdn.rawgit.com/zenorocha/clipboard.js/master/dist/clipboard.min.js"></script>
|
||||
<script src="https://unpkg.com/ngclipboard@2.0.0/dist/ngclipboard.min.js"></script>
|
||||
|
||||
|
||||
<script src="https://unpkg.com/ng-file-upload@12.2.13/dist/ng-file-upload-all.min.js" integrity="sha384-NbBOS/QuqJqwWOtYg/L3ZDhgl/6GFyvkRMypJQLgoisMPtJiHj5uQ+3bj8V8Muwm" crossorigin="anonymous"></script>
|
||||
|
||||
<script src="/assets/xterm/xterm.js"></script>
|
||||
<script src="/assets/xterm/addons/fit/fit.js"></script>
|
||||
<script src="/assets/xterm/addons/fullscreen/fullscreen.js"></script>
|
||||
<script src="/assets/setup-xterm.js"></script>
|
||||
<script src="/assets/attach.js"></script>
|
||||
<script src="https://unpkg.com/moment@2.16.0/min/moment.min.js"></script>
|
||||
<script src="/assets/app.js"></script>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
window.onbeforeunload = function (e) {
|
||||
e = e || window.event;
|
||||
|
||||
// For IE and Firefox prior to version 4
|
||||
if (e) {
|
||||
e.returnValue = 'Make sure you saved your session URL';
|
||||
}
|
||||
|
||||
// For Safari
|
||||
return 'Make sure you saved your session URL';
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
171
handlers/www/k8s/landing.html
Normal file
171
handlers/www/k8s/landing.html
Normal file
@@ -0,0 +1,171 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" ng-app="PWDLanding" ng-controller="LoginController">
|
||||
<head>
|
||||
<script src="https://unpkg.com/angular@1.6.6/angular.min.js"></script>
|
||||
<script src="https://unpkg.com/angular-cookies@1.6.6/angular-cookies.min.js"></script>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>Play with Kubernetes</title>
|
||||
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/bootstrap@4.0.0-beta/dist/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
|
||||
|
||||
<!-- Custom styles for this template -->
|
||||
<link href="/assets/landing.css" rel="stylesheet">
|
||||
<script type="text/javascript" src="//cdn.bizible.com/scripts/bizible.js"async=""></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<div class="header clearfix">
|
||||
<nav>
|
||||
<ul class="nav nav-pills float-right">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://github.com/play-with-docker/play-with-docker">Contribute</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="jumbotron" ng-cloak>
|
||||
<img src="https://zdnet2.cbsistatic.com/hub/i/r/2015/07/21/bb0de0fc-5d9c-47c3-96dd-42ed50858fdb/resize/370xauto/8999227b80cc063f94a76f2b628b0499/kubernetes-logo.png" />
|
||||
<h1 class="display-3">Play with Kubernetes</h1>
|
||||
<p class="lead">A simple, interactive and fun playground to learn Kubernetes</p>
|
||||
<div ng-hide="loggedIn" class="btn-group" role="group">
|
||||
<button id="btnGroupDrop1" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Login
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="btnGroupDrop1">
|
||||
<a ng-repeat="provider in providers" class="dropdown-item" ng-click="login(provider)">{{provider}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<form id="landingForm" method="POST" action="/">
|
||||
<p ng-show="loggedIn"><a class="btn btn-lg btn-success" href="#" ng-click="start()" role="button">Start</a></p>
|
||||
<input id="stack" type="hidden" name="stack" value=""/>
|
||||
<input id="stack_name" type="hidden" name="stack_name" value=""/>
|
||||
<input id="image_name" type="hidden" name="image_name" value=""/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="row marketing">
|
||||
<div class="col-lg-12">
|
||||
<p>Play with Kubernetes is a labs site provided by <a href="https://docker.com">Docker</a> and created by Tutorius. Play with Kubernetes is a playground which allows users to run K8s clusters in a matter of seconds. It gives the experience of having a free Alpine Linux Virtual Machine in browser. Under the hood Docker-in-Docker (DinD) is used to give the effect of multiple VMs/PCs.</p>
|
||||
<p>If you want to learn more about Kubernetes, consider the <a href="https://training.play-with-kubernetes.com/">Play with Kubernetes Classroom</a> which provides more directed learning using an integrated Play with Kubernetes commandline.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<p>Your use of Play With Docker is subject to the Docker Terms of Service which can be accessed <a target="_blank" href="https://www.docker.com/legal/docker-terms-service">here</a></p>
|
||||
<p>Site provided by <a href="https://docker.com">Docker, Inc.</a>, created by Tutorius</p>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
|
||||
[[ if .SegmentId ]]
|
||||
<script>
|
||||
!function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t){var e=document.createElement("script");e.type="text/javascript";e.async=!0;e.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};analytics.SNIPPET_VERSION="4.0.0";
|
||||
analytics.load('[[ .SegmentId ]]');
|
||||
analytics.page();
|
||||
}}();
|
||||
</script>
|
||||
[[ end ]]
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/popper.js@1.11.0/dist/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/bootstrap@4.0.0-beta/dist/js/bootstrap.min.js" integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
angular.module('PWDLanding', [])
|
||||
.controller('LoginController', ['$scope', '$http', '$window', function($scope, $http, $window) {
|
||||
$scope.providers = [];
|
||||
$scope.loggedIn = false;
|
||||
$scope.user = null;
|
||||
|
||||
function checkLoggedIn() {
|
||||
$http({
|
||||
method: 'GET',
|
||||
url: '/users/me'
|
||||
}).then(function(response) {
|
||||
[[ if .SegmentId ]]
|
||||
analytics.identify(response.data.provider_user_id, {"email": response.data.email});
|
||||
[[ end ]]
|
||||
$scope.user = response.data;
|
||||
$scope.loggedIn = true;
|
||||
}, function(response) {
|
||||
[[ if .SegmentId ]]
|
||||
analytics.identify();
|
||||
[[ end ]]
|
||||
console.log('ERROR', response);
|
||||
$scope.user = null;
|
||||
$scope.loggedIn = false;
|
||||
});
|
||||
}
|
||||
|
||||
checkLoggedIn();
|
||||
|
||||
$http({
|
||||
method: 'GET',
|
||||
url: '/oauth/providers'
|
||||
}).then(function(response) {
|
||||
$scope.providers = response.data;
|
||||
if ($scope.providers.length == 0) {
|
||||
$scope.loggedIn = true;
|
||||
}
|
||||
}, function(response) {
|
||||
console.log('ERROR', response);
|
||||
});
|
||||
|
||||
|
||||
$scope.login = function(provider) {
|
||||
var width = screen.width*0.6;
|
||||
// fixed height as the login window is not responsive
|
||||
var height = 620;
|
||||
var x = screen.width/2 - width/2;
|
||||
var y = screen.height/2 - height/2;
|
||||
$window.open('/oauth/providers/' + provider + '/login', 'PWDLogin', 'width='+width+',height='+height+',left='+x+',top='+y);
|
||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";
|
||||
// Listen to message from child window
|
||||
eventer(messageEvent,function(e) {
|
||||
if (e.data === 'done') {
|
||||
checkLoggedIn();
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
$scope.start = function() {
|
||||
function getParameterByName(name, url) {
|
||||
if (!url) url = window.location.href;
|
||||
name = name.replace(/[\[\]]/g, "\\$&");
|
||||
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
|
||||
results = regex.exec(url);
|
||||
if (!results) return null;
|
||||
if (!results[2]) return '';
|
||||
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
var stack = getParameterByName('stack');
|
||||
if (stack) {
|
||||
document.getElementById('stack').value = stack;
|
||||
}
|
||||
var stackName = getParameterByName('stack_name');
|
||||
if (stackName) {
|
||||
document.getElementById('stack_name').value = stackName;
|
||||
}
|
||||
var imageName = getParameterByName('image_name');
|
||||
if (imageName) {
|
||||
document.getElementById('image_name').value = imageName;
|
||||
}
|
||||
document.getElementById('landingForm').submit();
|
||||
}
|
||||
}]);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
22
handlers/www/ooc.html
Normal file
22
handlers/www/ooc.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Docker Playground</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic|Material+Icons" />
|
||||
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/angular_material/1.1.0/angular-material.min.css">
|
||||
<link rel="stylesheet" href="/assets/style.css" />
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', 'UA-89019737-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div layout="column" style="height:100%;">
|
||||
We are really sorry but we are out of capacity and cannot create your session at the moment. Please try again later.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
2
handlers/www/robots.txt
Normal file
2
handlers/www/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
Reference in New Issue
Block a user