mirror of
https://github.com/kasmtech/kasm-install-wizard.git
synced 2026-07-17 16:35:08 +00:00
initial version
This commit is contained in:
commit
ec936a3e9f
15 changed files with 788 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
public/img/thumbnails/*.png
|
||||||
|
node_modules
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
package-lock.json
|
||||||
35
README.md
Normal file
35
README.md
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Kasm Installation Wizard
|
||||||
|
|
||||||
|
The purpose of this application is to wrap the existing Bash based installation logic for Kasm Workspaces into a web based wizard. Post install the wizard should display the installation's information.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
Install deps:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo node index.js
|
||||||
|
```
|
||||||
|
|
||||||
|
# Requirements
|
||||||
|
|
||||||
|
Linux host:
|
||||||
|
[https://kasmweb.com/docs/develop/install/system_requirements.html](https://kasmweb.com/docs/develop/install/system_requirements.html)
|
||||||
|
|
||||||
|
Alternatively Docker [DinD](https://hub.docker.com/_/docker) setup with Docker and Docker Compose installed.
|
||||||
|
|
||||||
|
As configured files ingested from a current Kasm Workspaces installer are needed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/wizard/
|
||||||
|
├── default_images_amd64.yaml
|
||||||
|
├── default_images_arm64.yaml
|
||||||
|
└── LICENSE.txt
|
||||||
|
/kasm_release/
|
||||||
|
└── Full Kasm workspaces installer
|
||||||
|
```
|
||||||
104
index.js
Normal file
104
index.js
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
// Imports
|
||||||
|
var Docker = require('dockerode');
|
||||||
|
var socketIO = require('socket.io');
|
||||||
|
var express = require('express');
|
||||||
|
var app = require('express')();
|
||||||
|
var http = require('http').Server(app);
|
||||||
|
var baserouter = express.Router();
|
||||||
|
var pty = require("node-pty");
|
||||||
|
var fsw = require('fs').promises;
|
||||||
|
var fs = require('fs');
|
||||||
|
var os = require('os');
|
||||||
|
var yaml = require('js-yaml');
|
||||||
|
var si = require('systeminformation');
|
||||||
|
var docker = new Docker({socketPath: '/var/run/docker.sock'});
|
||||||
|
var arch = os.arch().replace('x64', 'amd64');
|
||||||
|
var baseUrl = process.env.SUBFOLDER || '/';
|
||||||
|
var version = process.env.VERSION || 'stable';
|
||||||
|
var port = process.env.KASM_PORT || '443';
|
||||||
|
var EULA;
|
||||||
|
var images;
|
||||||
|
// Grab installer variables
|
||||||
|
async function installerBlobs() {
|
||||||
|
EULA = await fsw.readFile('/kasm_release/licenses/LICENSE.txt', 'utf8');
|
||||||
|
let imagesText = await fsw.readFile('/wizard/default_images_' + arch + '.yaml', 'utf8');
|
||||||
|
images = yaml.load(imagesText);
|
||||||
|
}
|
||||||
|
installerBlobs();
|
||||||
|
|
||||||
|
//// Http server ////
|
||||||
|
baserouter.use('/public', express.static(__dirname + '/public'));
|
||||||
|
baserouter.get("/", function (req, res) {
|
||||||
|
res.sendFile(__dirname + '/public/index.html');
|
||||||
|
});
|
||||||
|
baserouter.get("/favicon.ico", function (req, res) {
|
||||||
|
res.sendFile(__dirname + '/public/favicon.ico');
|
||||||
|
});
|
||||||
|
app.use(baseUrl, baserouter);
|
||||||
|
http.listen(3000);
|
||||||
|
|
||||||
|
//// socketIO comms ////
|
||||||
|
io = socketIO(http, {path: baseUrl + 'socket.io'});
|
||||||
|
io.on('connection', async function (socket) {
|
||||||
|
// Run bash install with our custom flags
|
||||||
|
async function install(data) {
|
||||||
|
installSettings = data[0];
|
||||||
|
let imagesI = data[1];
|
||||||
|
let imagesD = images;
|
||||||
|
installFlags = ['/kasm_release/install.sh', '-B' ,'-H', '-e', '-L', port, '-P', installSettings.adminPass, '-U', installSettings.userPass];
|
||||||
|
if (installSettings.useRolling == true) {
|
||||||
|
installFlags.push('-O');
|
||||||
|
}
|
||||||
|
if (installSettings.noDownload == true) {
|
||||||
|
installFlags.push('-u');
|
||||||
|
}
|
||||||
|
if (imagesI == false) {
|
||||||
|
installFlags.push('-I');
|
||||||
|
installFlags.push('-b');
|
||||||
|
} else {
|
||||||
|
// If installing less than 10 images skip disk check
|
||||||
|
if (Object.keys(imagesI.images).length < 10) {
|
||||||
|
installFlags.push('-b');
|
||||||
|
}
|
||||||
|
// Flag the images properly based on selection
|
||||||
|
for await (let image of Object.keys(images.images)) {
|
||||||
|
if (imagesI.images.hasOwnProperty(image)) {
|
||||||
|
imagesD.images[image].enabled = true;
|
||||||
|
imagesD.images[image].hidden = false;
|
||||||
|
} else {
|
||||||
|
imagesD.images[image].enabled = false;
|
||||||
|
imagesD.images[image].hidden = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let yamlStr = yaml.dump(imagesD);
|
||||||
|
await fsw.writeFile('/kasm_release/conf/database/seed_data/default_images_' + arch + '.yaml', yamlStr);
|
||||||
|
}
|
||||||
|
let cmd = pty.spawn('/bin/bash', installFlags);
|
||||||
|
cmd.on('data', function(data) {
|
||||||
|
socket.emit('term', data);
|
||||||
|
});
|
||||||
|
cmd.on('exit', function(code, signal) {
|
||||||
|
if (code == 0) {
|
||||||
|
socket.emit('done', port);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Render landing page depending on installed status
|
||||||
|
async function renderLanding() {
|
||||||
|
let containers = await docker.listContainers();
|
||||||
|
if (containers.length !== 0) {
|
||||||
|
let dashinfo = {};
|
||||||
|
dashinfo['containers'] = containers;
|
||||||
|
dashinfo['cpu'] = await si.cpu();
|
||||||
|
dashinfo['mem'] = await si.mem();
|
||||||
|
dashinfo['cpuPercent'] = await si.currentLoad();
|
||||||
|
dashinfo['port'] = port;
|
||||||
|
socket.emit('renderdash', dashinfo);
|
||||||
|
} else {
|
||||||
|
socket.emit('renderinstall', [EULA, images]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//// Incoming requests ////
|
||||||
|
socket.on('renderlanding', renderLanding);
|
||||||
|
socket.on('install', install);
|
||||||
|
});
|
||||||
19
package.json
Normal file
19
package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "kasm-wizard",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Kasm web based installation wizard",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dockerode": "^3.3.2",
|
||||||
|
"express": "^4.18.1",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
|
"node-pty": "^0.10.1",
|
||||||
|
"socket.io": "^4.5.1",
|
||||||
|
"systeminformation": "^5.11.16"
|
||||||
|
}
|
||||||
|
}
|
||||||
117
public/css/index.css
Normal file
117
public/css/index.css
Normal 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
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
180
public/css/xterm.css
Normal 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
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
public/img/logo.png
Normal file
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
0
public/img/thumbnails/.gitignore
vendored
Normal file
24
public/index.html
Normal file
24
public/index.html
Normal 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
299
public/js/index.js
Normal 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
2
public/js/jquery.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/js/xterm-addon-fit.js
Normal file
1
public/js/xterm-addon-fit.js
Normal 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
1
public/js/xterm.js
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue