initial version

This commit is contained in:
ryan.kuba 2022-06-28 12:08:43 -04:00
commit ec936a3e9f
15 changed files with 788 additions and 0 deletions

117
public/css/index.css Normal file
View file

@ -0,0 +1,117 @@
#terminal {
display: none;
position: fixed;
width: 100%;
height: calc(100% - 50px);
left: 0;
top: 50px;
}
#container {
position: fixed;
width: 100%;
height: calc(100% - 50px);
left: 0;
top: 50px;
overflow: auto;
}
#EULA {
width: 90%;
height: 80%;
white-space: pre-wrap;
overflow: auto;
position: absolute;
top: 10%;
left: 50%;
transform: translate(-50%, -10%);
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
}
#EULAButton {
position: absolute;
top: 90%;
left: 50%;
transform: translate(-50%, -90%);
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
}
#titleBar {
width: 100%;
height: 50px;
background: #595959;
color: white;
display: inline-block;
}
form {
max-width: 80%;
margin:20px auto;
display:block;
}
#title {
float: right;
height: 50px;
line-height: 50px;
text-align: center;
margin-right: 10px;
}
#logo {
width: 138px;
height: 40px;
margin: 5px;
}
.center {
margin:10px auto;
display:block;
}
#systeminfo {
margin: 10px;
width: calc(100% - 20px);
}
#dockerinfo {
margin: 10px;
width: calc(100% - 20px);
}
/* Image cards */
.card {
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
transition: 0.3s;
width: 150px;
height: 150px;
text-align: center;
margin: 5px;
padding-top: 5px;
padding-bottom: 5px;
cursor: pointer;
border-radius: 20px;
}
.card p {
margin: 1px;
}
.card h2 {
margin: 5px;
}
.card:hover {
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
}
.cardcontainer {
display: flex;
flex-flow: row wrap;
}
.thumb {
width: 100px;
height: 100px;
}

1
public/css/terminal.css Normal file

File diff suppressed because one or more lines are too long

180
public/css/xterm.css Normal file
View file

@ -0,0 +1,180 @@
/**
* 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 {
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: 5;
}
.xterm .xterm-helper-textarea {
padding: 0;
border: 0;
margin: 0;
/* 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: -5;
/** 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,
.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: 10;
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;
}
.xterm-strikethrough {
text-decoration: line-through;
}
.xterm-screen .xterm-decoration-container .xterm-decoration {
z-index: 6;
position: absolute;
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

0
public/img/thumbnails/.gitignore vendored Normal file
View file

24
public/index.html Normal file
View file

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>Kasm Wizard</title>
<meta charset="UTF-8" />
<link rel="stylesheet" href="public/css/xterm.css" />
<link rel="stylesheet" href="public/css/terminal.css" />
<link rel="stylesheet" href="public/css/index.css" />
<script src="public/js/xterm.js"></script>
<script src="public/js/xterm-addon-fit.js"></script>
<script src="public/js/jquery.js"></script>
<script src="socket.io/socket.io.js"></script>
</head>
<body>
<div id="titleBar">
<img src="public/img/logo.png" id="logo">
<div id="title"></div>
</div>
<div id="spacing"></div>
<div id="container"></div>
<div id="terminal"></div>
<script src="public/js/index.js"></script>
</body>
</html>

299
public/js/index.js Normal file
View file

@ -0,0 +1,299 @@
// Variables
var EULA;
var images;
var term;
var installImages = [];
var installSettings = {};
var selected = false;
// Socket.io connection
var host = window.location.hostname;
var port = window.location.port;
var protocol = window.location.protocol;
var path = window.location.pathname;
var socket = io(protocol + '//' + host + ':' + port, { path: path + 'socket.io'});
//// Page Functions ////
// Render term data
function renderTerm(data) {
term.write(data);
}
// Execute install
async function install() {
showTerminal()
titleChange('Installing');
// Create new object based on image selection
let selectedImages = {images: {}};
if (installImages.length == 0) {
socket.emit('install', [installSettings, false]);
} else {
for await (let image of installImages) {
if (images.images[image].hasOwnProperty('enabled')) {
images.images[image].enabled = true;
}
Object.assign(selectedImages.images, {[image]: images.images[image]});
}
socket.emit('install', [installSettings, selectedImages]);
}
}
// Show page container
function showContainer() {
$('#terminal').empty();
$('#terminal').css('display', 'none');
term = null;
$('#container').empty();
$('#container').css('display', 'block');
}
// Show terminal
function showTerminal() {
$('#container').empty();
$('#container').css('display', 'none');
$('#terminal').empty();
$('#terminal').css('display', 'block');
term = new Terminal();
fitaddon = new FitAddon.FitAddon();
term.loadAddon(fitaddon);
term.open($('#terminal')[0]);
fitaddon.fit();
}
// Change nav title
function titleChange(value) {
$('#title').empty();
$('#title').text(value);
}
// Render landing as installer page
function renderInstall(data) {
showContainer();
titleChange('EULA');
EULA = data[0];
images = data[1];
let EULADiv = $('<div>', {id: 'EULA'}).text(EULA);
$('#container').append(EULADiv);
let EULAButton = $('<button>', {id: 'EULAButton', onclick: 'pickSettings()', class: 'btn btn-default btn-ghost'}).text('Accept and continue');
$('#container').append(EULAButton);
}
// Render Dashboard
async function renderDash(info) {
showContainer();
titleChange('Dashboard');
// Kasm docker containers
containersTable = $('<tbody>');
containersTable.append(
$('<tr>').append([
$('<th>').text('Web URL'),
$('<td>').append($('<a>', {href: 'https://' + host + ':' + info.port, target: '_blank'}).text('https://' + host + ':' + info.port))
])
);
for await (let container of info.containers) {
containersTable.append(
$('<tr>').append([
$('<th>').text(container.Names[0]),
$('<td>').text(container.State + ' ' + container.Status)
])
);
}
dockerCard = $('<div>', {id: 'dockerinfo', class: 'terminal-card'}).append([
$('<header>').text('Kasm Docker containers'),
$('<table>').append(containersTable)
]);
// System Information
let usedmem = (info.mem.active/info.mem.total)*100;
let totalmem = parseFloat(info.mem.total/1000000000).toFixed(2);
let diskbuffer = parseFloat(info.mem.buffcache/1000000000).toFixed(2);
sysinfoTable = $('<tbody>').append([
$('<tr>').append([
$('<th>').text('CPU'),
$('<td>').text(info.cpu.vendor + ' ' + info.cpu.brand)
]),
$('<tr>').append([
$('<th>').text('CPU Cores'),
$('<td>').text(info.cpu.cores)
]),
$('<tr>').append([
$('<th>').text('CPU Usage'),
$('<td>').append(
$('<div>', {class: 'progress-bar progress-bar-no-arrow'}).append(
$('<div>', {
class: 'progress-bar-filled',
style: 'width: ' + info.cpuPercent.currentLoad + '%'
})
)
)
]),
$('<tr>').append([
$('<th>').text('Total Memory'),
$('<td>').text(totalmem + 'G')
]),
$('<tr>').append([
$('<th>').text('Disk Buffer'),
$('<td>').text(diskbuffer + 'G')
]),
$('<tr>').append([
$('<th>').text('Memory Usage'),
$('<td>').append(
$('<div>', {class: 'progress-bar progress-bar-no-arrow'}).append(
$('<div>', {
class: 'progress-bar-filled',
style: 'width: ' + usedmem + '%'
})
)
)
])
]);
systemCard = $('<div>', {id: 'systeminfo', class: 'terminal-card'}).append([
$('<header>').text('System Information'),
$('<table>').append(sysinfoTable)
]);
$('#container').append([systemCard, dockerCard]);
}
// Render primary form
async function pickSettings() {
showContainer();
titleChange('Install Settings');
let form = $('<form>', {id: 'settingsform'});
let fieldset = $('<fieldset>').append($('<legend>').text('Kasm Install Settings'));
let adminPass = $('<div>', {class: 'form-group'}).append([
$('<label>', {for: 'adminPass'}).text('admin@kasm.local Password: '),
$('<input>', {name: 'adminPass', id: 'adminPass', type: 'password', required: true, placeholder: 'required'})
]);
let userPass = $('<div>', {class: 'form-group'}).append([
$('<label>', {for: 'userPass'}).text('user@kasm.local Password: '),
$('<input>', {name: 'userPass', id: 'userPass', type: 'password', placeholder: 'required'}).prop('required',true)
]);
let useRolling = $('<div>', {class: 'form-group'}).append([
$('<label>', {for: 'useRolling'}).text('Use Rolling Images: '),
$('<input>', {name: 'useRolling', id: 'useRolling', type: 'checkbox'})
]);
let noDownload = $('<div>', {class: 'form-group'}).append([
$('<label>', {for: 'noDownload'}).text('Skip Image Download: '),
$('<input>', {name: 'noDownload', id: 'noDownload', type: 'checkbox'})
]);
let submit = $('<div>', {class: 'form-group'}).append([
$('<input>', {name: 'submit', type: 'submit', value: 'Next', class: 'btn btn-default btn-ghost'})
]);
fieldset.append([
adminPass,
userPass,
useRolling,
noDownload,
submit
]);
form.append(fieldset);
$('#container').append(form);
// Grab data and move to image selection
form.on('submit', function (e) {
e.preventDefault();
installSettings.adminPass = $('#adminPass').val();
installSettings.userPass = $('#userPass').val();
installSettings.useRolling = $('#useRolling').is(":checked");
installSettings.noDownload = $('#noDownload').is(":checked");
pickImages();
});
}
// Render image selection
async function pickImages() {
showContainer();
titleChange('Image Selection');
let imagesDiv = $('<div>', {class: 'cardcontainer', id: 'images'});
$('#container').append(imagesDiv);
for await (let image of Object.keys(images.images)) {
let imageName = $('<p>').text(image);
let imageDiv = $('<div>', {
class: 'card',
id: image.replace(new RegExp(' ', 'g'), '_'),
title: images.images[image].description,
onclick: 'selectImage(\'' + image.replace(new RegExp(' ', 'g'), '_') + '\')'
}).append(imageName).css('filter', 'grayscale(100%)')
let thumb = $('<img>', {class: 'thumb', src: 'public/' + images.images[image].image_src});
imageDiv.append(thumb);
$('#images').append(imageDiv);
}
let allButton = $('<button>', {class: 'btn btn-default btn-ghost center', onclick: 'selectAll()'}).text('Select All');
let installButton = $('<button>', {class: 'btn btn-default btn-ghost center', onclick: 'install()'}).text('Install');
$('#container').append([
allButton,
installButton
]);
}
// Select an individual image
function selectImage(image) {
let imageKey = image.replace(new RegExp('_', 'g'), ' ');
if (installImages.includes(imageKey)) {
installImages = installImages.filter(e => e !== imageKey)
$('#' + image).css({
filter: 'grayscale(100%)',
background: ''
});
} else {
installImages.push(imageKey);
$('#' + image).css({
filter: '',
background: '#89cff0'
});
}
}
// Select all images
function selectAll() {
installImages = [];
if (selected) {
selected = false;
for (let image of Object.keys(images.images)) {
let imageElem = image.replace(new RegExp(' ', 'g'), '_');
$('#' + imageElem).css({
filter: 'grayscale(100%)',
background: ''
});
}
} else {
selected = true;
for (let image of Object.keys(images.images)) {
let imageElem = image.replace(new RegExp(' ', 'g'), '_');
installImages.push(image);
$('#' + imageElem).css({
filter: '',
background: '#89cff0'
});
}
}
}
// Show finished page
function done(port) {
showContainer();
titleChange('Complete');
let titleBar = $('<div>');
titleBar.append($('<h2>', {class: 'center'}).text('Installation Complete'));
titleBar.append($('<h3>', {class: 'center'}).text('This page will reload in 5 seconds'));
titleBar.append($('<h3>', {class: 'center'}).text('Your installation is available on port ' + port));
$('#container').append(titleBar);
setTimeout(function(){
location.reload(true);
}, 5000);
}
//// Socket events ////
socket.on('renderinstall', renderInstall);
socket.on('renderdash', renderDash);
socket.on('term', renderTerm);
socket.on('done', done);
// Render landing on page load
window.onload = function() {
showContainer();
titleChange('Loading');
$('#container').append($('<div>').append($('<h2>', {class: 'center'}).text('Docker still loading, please refresh for installer')))
socket.emit('renderlanding');
}

2
public/js/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(self,(function(){return(()=>{"use strict";var e={775:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0;var r=function(){function e(){}return e.prototype.activate=function(e){this._terminal=e},e.prototype.dispose=function(){},e.prototype.fit=function(){var e=this.proposeDimensions();if(e&&this._terminal){var t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}},e.prototype.proposeDimensions=function(){if(this._terminal&&this._terminal.element&&this._terminal.element.parentElement){var e=this._terminal._core;if(0!==e._renderService.dimensions.actualCellWidth&&0!==e._renderService.dimensions.actualCellHeight){var t=window.getComputedStyle(this._terminal.element.parentElement),r=parseInt(t.getPropertyValue("height")),i=Math.max(0,parseInt(t.getPropertyValue("width"))),n=window.getComputedStyle(this._terminal.element),o=r-(parseInt(n.getPropertyValue("padding-top"))+parseInt(n.getPropertyValue("padding-bottom"))),a=i-(parseInt(n.getPropertyValue("padding-right"))+parseInt(n.getPropertyValue("padding-left")))-e.viewport.scrollBarWidth;return{cols:Math.max(2,Math.floor(a/e._renderService.dimensions.actualCellWidth)),rows:Math.max(1,Math.floor(o/e._renderService.dimensions.actualCellHeight))}}}},e}();t.FitAddon=r}},t={};return function r(i){if(t[i])return t[i].exports;var n=t[i]={exports:{}};return e[i](n,n.exports,r),n.exports}(775)})()}));

1
public/js/xterm.js Normal file

File diff suppressed because one or more lines are too long