├── .gitignore ├── .gitlab-ci.yml ├── README.md ├── index.js ├── package.json └── public ├── css ├── index.css ├── terminal.css └── xterm.css ├── favicon.ico ├── img ├── logo.png └── thumbnails │ └── .gitignore ├── index.html └── js ├── index.js ├── jquery.js ├── xterm-addon-fit.js └── xterm.js /.gitignore: -------------------------------------------------------------------------------- 1 | public/img/thumbnails/*.png 2 | node_modules 3 | *.swp 4 | *.swo 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: docker 2 | services: 3 | - docker:dind 4 | 5 | stages: 6 | - upload 7 | 8 | before_script: 9 | - export SANITIZED_BRANCH="$(echo $CI_COMMIT_REF_NAME | sed -r 's#^release/##' | sed 's/\//_/g')" 10 | 11 | push_to_s3: 12 | stage: upload 13 | script: 14 | - apk add aws-cli curl gzip tar 15 | - export S3_URL="https://${S3_BUCKET}.s3.amazonaws.com/wizard/${CI_COMMIT_SHA}.tar.gz" 16 | - tar -czf ${CI_COMMIT_SHA}.tar.gz * 17 | - aws s3 cp ${CI_COMMIT_SHA}.tar.gz s3://${S3_BUCKET}/wizard/ 18 | - aws s3 cp ${CI_COMMIT_SHA}.tar.gz s3://${S3_BUCKET}/wizard/${SANITIZED_BRANCH}.tar.gz 19 | - curl --request POST --header "PRIVATE-TOKEN:${GITLAB_API_TOKEN}" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/statuses/${CI_COMMIT_SHA}?state=success&name=build-url&target_url=${S3_URL}" 20 | tags: 21 | - aws-autoscale 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kasm Installation Wizard 2 | 3 | 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. 4 | 5 | # Usage 6 | 7 | Install deps: 8 | 9 | ``` 10 | npm install 11 | ``` 12 | 13 | Run: 14 | 15 | ``` 16 | sudo node index.js 17 | ``` 18 | 19 | # Requirements 20 | 21 | Linux host: 22 | [https://kasmweb.com/docs/develop/install/system_requirements.html](https://kasmweb.com/docs/develop/install/system_requirements.html) 23 | 24 | Alternatively Docker [DinD](https://hub.docker.com/_/docker) setup with Docker and Docker Compose installed. 25 | 26 | As configured files ingested from a current Kasm Workspaces installer are needed: 27 | 28 | ```bash 29 | /wizard/ 30 | ├── default_images_amd64.yaml 31 | ├── default_images_arm64.yaml 32 | └── LICENSE.txt 33 | /opt/kasm/certs/ 34 | ├── kasm_wizard.crt 35 | └── kasm_wizard.key 36 | /kasm_release/ 37 | └── Full Kasm workspaces installer 38 | ``` 39 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Imports 2 | var Docker = require('dockerode'); 3 | var socketIO = require('socket.io'); 4 | var pty = require("node-pty"); 5 | var fsw = require('fs').promises; 6 | var fs = require('fs'); 7 | var os = require('os'); 8 | var yaml = require('js-yaml'); 9 | var _ = require('lodash'); 10 | var si = require('systeminformation'); 11 | var express = require('express'); 12 | var app = require('express')(); 13 | var privateKey = fs.readFileSync('/opt/kasm/certs/kasm_wizard.key', 'utf8'); 14 | var certificate = fs.readFileSync('/opt/kasm/certs/kasm_wizard.crt', 'utf8'); 15 | var credentials = {key: privateKey, cert: certificate}; 16 | var https = require('https').Server(credentials, app); 17 | var baserouter = express.Router(); 18 | var docker = new Docker({socketPath: '/var/run/docker.sock'}); 19 | var arch = os.arch().replace('x64', 'amd64'); 20 | var baseUrl = process.env.SUBFOLDER || '/'; 21 | var version = process.env.VERSION || 'stable'; 22 | var port = process.env.KASM_PORT || '443'; 23 | const { spawn } = require('node:child_process'); 24 | var EULA; 25 | var images; 26 | var currentVersion; 27 | var gpuInfo; 28 | var installSettings = {}; 29 | var upgradeSettings = {}; 30 | // Grab installer variables 31 | async function installerBlobs() { 32 | EULA = await fsw.readFile('/kasm_release/licenses/LICENSE.txt', 'utf8'); 33 | let imagesText = await fsw.readFile('/wizard/default_images_' + arch + '.yaml', 'utf8'); 34 | images = yaml.load(imagesText); 35 | currentVersion = fs.readFileSync('/version.txt', 'utf8').replace(/(\r\n|\n|\r)/gm,''); 36 | let gpuData = []; 37 | let gpuCmd = spawn('/gpuinfo.sh'); 38 | gpuCmd.stdout.on('data', function(data) { 39 | gpuData.push(data); 40 | }); 41 | gpuCmd.on('close', function(code) { 42 | try { 43 | if (code == 0) { 44 | gpuInfo = JSON.parse(gpuData.join('')); 45 | } else { 46 | gpuInfo = {}; 47 | } 48 | } catch (err) { 49 | // Manually backfill GPU info if available 50 | gpuInfo = {}; 51 | for (let i = 0; i < 10; i++) { 52 | let num = i.toString(); 53 | if (fs.existsSync('/dev/dri/card' + num)) { 54 | gpuInfo['/dev/dri/card' + num] = "Unknown GPU"; 55 | } 56 | } 57 | } 58 | }); 59 | } 60 | installerBlobs(); 61 | 62 | // GPU image yaml merging 63 | async function setGpu(imagesI) { 64 | if (upgradeSettings['forceGpu'] !== undefined) { 65 | installSettings = upgradeSettings; 66 | } 67 | let gpu = installSettings.forceGpu.split('|')[0]; 68 | let gpuName = installSettings.forceGpu.split('|')[1]; 69 | let card = gpu.slice(-1); 70 | let render = (Number(card) + 128).toString(); 71 | // Handle NVIDIA Gpus 72 | var baseRun; 73 | if (gpuName.indexOf('NVIDIA') !== -1) { 74 | baseRun = JSON.parse('{"runtime":"nvidia","environment":{"NVIDIA_DRIVER_CAPABILITIES":"all","KASM_EGL_CARD":"/dev/dri/card' + card + '","KASM_RENDERD":"/dev/dri/renderD' + render + '"},"device_requests":[{"driver": "","count": -1,"device_ids": null,"capabilities":[["gpu"]],"options":{}}]}'); 75 | } else { 76 | baseRun = JSON.parse('{"environment":{"DRINODE":"/dev/dri/renderD' + render + '", "HW3D": true},"devices":["/dev/dri/card' + card + ':/dev/dri/card' + card + ':rwm","/dev/dri/renderD' + render + ':/dev/dri/renderD' + render + ':rwm"]}'); 77 | } 78 | let baseExec = JSON.parse('{"first_launch":{"user":"root","cmd": "bash -c \'chown -R kasm-user:kasm-user /dev/dri/*\'"}}'); 79 | for (var i=0; i\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>";white-space:pre;position:absolute;top:0;left:0;line-height:var(--global-line-height);color:#9ca2ab}code{font-weight:inherit;background-color:var(--code-bg-color);font-family:var(--mono-font-stack)}code::after,code::before{content:"`";display:inline}pre code::after,pre code::before{content:""}pre{display:block;word-break:break-all;word-wrap:break-word;color:var(--secondary-color);background-color:var(--background-color);border:1px solid var(--secondary-color);padding:var(--global-space);white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap}pre code{overflow-x:scroll;padding:0;margin:0;display:inline-block;min-width:100%;font-family:var(--mono-font-stack)}.terminal .logo,.terminal blockquote,.terminal code,.terminal h1,.terminal h2,.terminal h3,.terminal h4,.terminal h5,.terminal h6,.terminal strong{font-size:var(--global-font-size);font-style:normal;font-family:var(--font-stack);color:var(--font-color)}.terminal-prompt{position:relative;white-space:nowrap}.terminal-prompt::before{content:"> "}.terminal-prompt::after{content:"";-webkit-animation:cursor .8s infinite;animation:cursor .8s infinite;background:var(--primary-color);border-radius:0;display:inline-block;height:1em;margin-left:.2em;width:3px;bottom:-2px;position:relative}@-webkit-keyframes cursor{0%{opacity:0}50%{opacity:1}to{opacity:0}}@keyframes cursor{0%{opacity:0}50%{opacity:1}to{opacity:0}}li,li>ul>li{position:relative;display:block;padding-left:calc(var(--global-space) * 2)}nav>ul>li{padding-left:0}li::after{position:absolute;top:0;left:0}ul>li::after{content:"-"}nav ul>li::after{content:""}ol li::before{content:counters(item, ".") ". ";counter-increment:item}ol ol li::before{content:counters(item, ".") " ";counter-increment:item}.terminal-menu li::after,.terminal-menu li::before{display:none}ol{counter-reset:item}ol li:nth-child(n+10)::after{left:-7px}ol ol{margin-top:0;margin-bottom:0}.terminal-menu{width:100%}.terminal-nav{display:flex;flex-direction:column;align-items:flex-start}ul ul{margin-top:0;margin-bottom:0}.terminal-menu ul{list-style-type:none;padding:0!important;display:flex;flex-direction:column;width:100%;flex-grow:1;font-size:var(--global-font-size);margin-top:0}.terminal-menu li{display:flex;margin:0 0 .5em 0;padding:0}ol.terminal-toc li{border-bottom:1px dotted var(--secondary-color);padding:0;margin-bottom:15px}.terminal-menu li:last-child{margin-bottom:0}ol.terminal-toc li a{margin:4px 4px 4px 0;background:var(--background-color);position:relative;top:6px;text-align:left;padding-right:4px}.terminal-menu li a:not(.btn){text-decoration:none;display:block;width:100%;border:none;color:var(--secondary-color)}.terminal-menu li a.active{color:var(--font-color)}.terminal-menu li a:hover{background:0 0;color:inherit}ol.terminal-toc li::before{content:counters(item, ".") ". ";counter-increment:item;position:absolute;right:0;background:var(--background-color);padding:4px 0 4px 4px;bottom:-8px}ol.terminal-toc li a:hover{background:var(--primary-color);color:var(--invert-font-color)}hr{position:relative;overflow:hidden;margin:calc(var(--global-space) * 4) 0;border:0;border-bottom:1px dashed var(--secondary-color)}p{margin:0 0 var(--global-line-height);color:var(--global-font-color)}.container{max-width:var(--page-width)}.container,.container-fluid{margin:0 auto;padding:0 calc(var(--global-space) * 2)}img{width:100%}.progress-bar{height:8px;background-color:var(--progress-bar-background);margin:12px 0}.progress-bar.progress-bar-show-percent{margin-top:38px}.progress-bar-filled{background-color:var(--progress-bar-fill);height:100%;transition:width .3s ease;position:relative;width:0}.progress-bar-filled::before{content:"";border:6px solid transparent;border-top-color:var(--progress-bar-fill);position:absolute;top:-12px;right:-6px}.progress-bar-filled::after{color:var(--progress-bar-fill);content:attr(data-filled);display:block;font-size:12px;white-space:nowrap;position:absolute;border:6px solid transparent;top:-38px;right:0;transform:translateX(50%)}.progress-bar-no-arrow>.progress-bar-filled::after,.progress-bar-no-arrow>.progress-bar-filled::before{content:"";display:none;visibility:hidden;opacity:0}table{width:100%;border-collapse:collapse;color:var(--font-color);font-size:var(--global-font-size)}table td,table th{vertical-align:top;border:1px solid var(--font-color);line-height:var(--global-line-height);padding:calc(var(--global-space)/ 2);font-size:1em}table thead th{font-size:1em}table tfoot tr th{font-weight:500}table caption{font-size:1em;margin:0 0 1em 0}table tbody td:first-child{font-weight:700;color:var(--secondary-color)}.form{width:100%}fieldset{border:1px solid var(--font-color);padding:1em}label{font-size:1em;color:var(--font-color)}input[type=email],input[type=number],input[type=password],input[type=search],input[type=text]{border:1px var(--input-style) var(--font-color);width:100%;padding:.7em .5em;font-size:1em;font-family:var(--font-stack);-webkit-appearance:none;border-radius:0}input[type=email]:active,input[type=email]:focus,input[type=number]:active,input[type=number]:focus,input[type=password]:active,input[type=password]:focus,input[type=search]:active,input[type=search]:focus,input[type=text]:active,input[type=text]:focus{outline:0;-webkit-appearance:none;border:1px solid var(--font-color)}input[type=email]:not(:placeholder-shown):invalid,input[type=number]:not(:placeholder-shown):invalid,input[type=password]:not(:placeholder-shown):invalid,input[type=search]:not(:placeholder-shown):invalid,input[type=text]:not(:placeholder-shown):invalid{border-color:var(--error-color)}input,textarea{color:var(--font-color);background-color:var(--background-color)}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:var(--secondary-color)!important;opacity:1}input::-moz-placeholder,textarea::-moz-placeholder{color:var(--secondary-color)!important;opacity:1}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:var(--secondary-color)!important;opacity:1}input::-ms-input-placeholder,textarea::-ms-input-placeholder{color:var(--secondary-color)!important;opacity:1}input::placeholder,textarea::placeholder{color:var(--secondary-color)!important;opacity:1}textarea{height:auto;width:100%;resize:none;border:1px var(--input-style) var(--font-color);padding:.5em;font-size:1em;font-family:var(--font-stack);-webkit-appearance:none;border-radius:0}textarea:focus{outline:0;-webkit-appearance:none;border:1px solid var(--font-color)}textarea:not(:placeholder-shown):invalid{border-color:var(--error-color)}input:-webkit-autofill,input:-webkit-autofill:focus textarea:-webkit-autofill,input:-webkit-autofill:hover,select:-webkit-autofill,select:-webkit-autofill:focus,select:-webkit-autofill:hover,textarea:-webkit-autofill:hover textarea:-webkit-autofill:focus{border:1px solid var(--font-color);-webkit-text-fill-color:var(--font-color);box-shadow:0 0 0 1000px var(--invert-font-color) inset;-webkit-box-shadow:0 0 0 1000px var(--invert-font-color) inset;transition:background-color 5000s ease-in-out 0s}.form-group{margin-bottom:var(--global-line-height);overflow:auto}.btn{border-style:solid;border-width:1px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;outline:0;padding:.65em 2em;font-size:1em;font-family:inherit;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;z-index:1}.btn:active{box-shadow:none}.btn.btn-ghost{border-color:var(--font-color);color:var(--font-color);background-color:transparent}.btn.btn-ghost:focus,.btn.btn-ghost:hover{border-color:var(--tertiary-color);color:var(--tertiary-color);z-index:2}.btn.btn-ghost:hover{background-color:transparent}.btn-block{width:100%;display:flex}.btn-default{background-color:var(--font-color);border-color:var(--invert-font-color);color:var(--invert-font-color)}.btn-default:focus:not(.btn-ghost),.btn-default:hover{background-color:var(--secondary-color);color:var(--invert-font-color)}.btn-default.btn-ghost:focus,.btn-default.btn-ghost:hover{border-color:var(--secondary-color);color:var(--secondary-color);z-index:2}.btn-error{color:var(--invert-font-color);background-color:var(--error-color);border:1px solid var(--error-color)}.btn-error:focus:not(.btn-ghost),.btn-error:hover{background-color:var(--error-color);border-color:var(--error-color)}.btn-error.btn-ghost{border-color:var(--error-color);color:var(--error-color)}.btn-error.btn-ghost:focus,.btn-error.btn-ghost:hover{border-color:var(--error-color);color:var(--error-color);z-index:2}.btn-primary{color:var(--invert-font-color);background-color:var(--primary-color);border:1px solid var(--primary-color)}.btn-primary:focus:not(.btn-ghost),.btn-primary:hover{background-color:var(--primary-color);border-color:var(--primary-color)}.btn-primary.btn-ghost{border-color:var(--primary-color);color:var(--primary-color)}.btn-primary.btn-ghost:focus,.btn-primary.btn-ghost:hover{border-color:var(--primary-color);color:var(--primary-color);z-index:2}.btn-small{padding:.5em 1.3em!important;font-size:.9em!important}.btn-group{overflow:auto}.btn-group .btn{float:left}.btn-group .btn-ghost:not(:first-child){margin-left:-1px}.terminal-card{border:1px solid var(--secondary-color)}.terminal-card>header{color:var(--invert-font-color);text-align:center;background-color:var(--secondary-color);padding:.5em 0}.terminal-card>div:first-of-type{padding:var(--global-space)}.terminal-timeline{position:relative;padding-left:70px}.terminal-timeline::before{content:' ';background:var(--secondary-color);display:inline-block;position:absolute;left:35px;width:2px;height:100%;z-index:400}.terminal-timeline .terminal-card{margin-bottom:25px}.terminal-timeline .terminal-card::before{content:' ';background:var(--invert-font-color);border:2px solid var(--secondary-color);display:inline-block;position:absolute;margin-top:25px;left:26px;width:15px;height:15px;z-index:400}.terminal-alert{color:var(--font-color);padding:1em;border:1px solid var(--font-color);margin-bottom:var(--global-space)}.terminal-alert-error{color:var(--error-color);border-color:var(--error-color)}.terminal-alert-primary{color:var(--primary-color);border-color:var(--primary-color)}@media screen and (max-width:960px){label{display:block;width:100%}pre::-webkit-scrollbar{height:3px}}@media screen and (max-width:480px){form{width:100%}}@media only screen and (min-width:30em){.terminal-nav{flex-direction:row;align-items:center}.terminal-menu ul{flex-direction:row;justify-items:flex-end;align-items:center;justify-content:flex-end;margin-top:calc(var(--global-space) * 2)}.terminal-menu li{margin:0;margin-right:2em}.terminal-menu li:last-child{margin-right:0}}.terminal-media:not(:last-child){margin-bottom:1.25rem}.terminal-media-left{padding-right:var(--global-space)}.terminal-media-left,.terminal-media-right{display:table-cell;vertical-align:top}.terminal-media-right{padding-left:var(--global-space)}.terminal-media-body{display:table-cell;vertical-align:top}.terminal-media-heading{font-size:1em;font-weight:700}.terminal-media-content{margin-top:.3rem}.terminal-placeholder{background-color:var(--secondary-color);text-align:center;color:var(--font-color);font-size:1rem;border:1px solid var(--secondary-color)}figure>img{padding:0}.terminal-avatarholder{width:calc(var(--global-space) * 5);height:calc(var(--global-space) * 5)}.terminal-avatarholder img{padding:0}figure{margin:0}figure>figcaption{color:var(--secondary-color);text-align:center}.hljs{display:block;overflow-x:auto;padding:.5em;background:var(--block-background-color);color:var(--font-color)}.hljs-comment,.hljs-quote{color:var(--secondary-color)}.hljs-variable{color:var(--font-color)}.hljs-built_in,.hljs-keyword,.hljs-name,.hljs-selector-tag,.hljs-tag{color:var(--primary-color)}.hljs-addition,.hljs-attribute,.hljs-literal,.hljs-section,.hljs-string,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type{color:var(--secondary-color)}.hljs-string{color:var(--secondary-color)}.hljs-deletion,.hljs-meta,.hljs-selector-attr,.hljs-selector-pseudo{color:var(--primary-color)}.hljs-doctag{color:var(--secondary-color)}.hljs-attr{color:var(--primary-color)}.hljs-bullet,.hljs-link,.hljs-symbol{color:var(--primary-color)}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700} 2 | -------------------------------------------------------------------------------- /public/css/xterm.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 The xterm.js authors. All rights reserved. 3 | * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) 4 | * https://github.com/chjj/term.js 5 | * @license MIT 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | * Originally forked from (with the author's permission): 26 | * Fabrice Bellard's javascript vt100 for jslinux: 27 | * http://bellard.org/jslinux/ 28 | * Copyright (c) 2011 Fabrice Bellard 29 | * The original design remains. The terminal itself 30 | * has been extended to include xterm CSI codes, among 31 | * other features. 32 | */ 33 | 34 | /** 35 | * Default styles for xterm.js 36 | */ 37 | 38 | .xterm { 39 | position: relative; 40 | user-select: none; 41 | -ms-user-select: none; 42 | -webkit-user-select: none; 43 | } 44 | 45 | .xterm.focus, 46 | .xterm:focus { 47 | outline: none; 48 | } 49 | 50 | .xterm .xterm-helpers { 51 | position: absolute; 52 | top: 0; 53 | /** 54 | * The z-index of the helpers must be higher than the canvases in order for 55 | * IMEs to appear on top. 56 | */ 57 | z-index: 5; 58 | } 59 | 60 | .xterm .xterm-helper-textarea { 61 | padding: 0; 62 | border: 0; 63 | margin: 0; 64 | /* Move textarea out of the screen to the far left, so that the cursor is not visible */ 65 | position: absolute; 66 | opacity: 0; 67 | left: -9999em; 68 | top: 0; 69 | width: 0; 70 | height: 0; 71 | z-index: -5; 72 | /** Prevent wrapping so the IME appears against the textarea at the correct position */ 73 | white-space: nowrap; 74 | overflow: hidden; 75 | resize: none; 76 | } 77 | 78 | .xterm .composition-view { 79 | /* TODO: Composition position got messed up somewhere */ 80 | background: #000; 81 | color: #FFF; 82 | display: none; 83 | position: absolute; 84 | white-space: nowrap; 85 | z-index: 1; 86 | } 87 | 88 | .xterm .composition-view.active { 89 | display: block; 90 | } 91 | 92 | .xterm .xterm-viewport { 93 | /* On OS X this is required in order for the scroll bar to appear fully opaque */ 94 | background-color: #000; 95 | overflow-y: scroll; 96 | cursor: default; 97 | position: absolute; 98 | right: 0; 99 | left: 0; 100 | top: 0; 101 | bottom: 0; 102 | } 103 | 104 | .xterm .xterm-screen { 105 | position: relative; 106 | } 107 | 108 | .xterm .xterm-screen canvas { 109 | position: absolute; 110 | left: 0; 111 | top: 0; 112 | } 113 | 114 | .xterm .xterm-scroll-area { 115 | visibility: hidden; 116 | } 117 | 118 | .xterm-char-measure-element { 119 | display: inline-block; 120 | visibility: hidden; 121 | position: absolute; 122 | top: 0; 123 | left: -9999em; 124 | line-height: normal; 125 | } 126 | 127 | .xterm { 128 | cursor: text; 129 | } 130 | 131 | .xterm.enable-mouse-events { 132 | /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ 133 | cursor: default; 134 | } 135 | 136 | .xterm.xterm-cursor-pointer, 137 | .xterm .xterm-cursor-pointer { 138 | cursor: pointer; 139 | } 140 | 141 | .xterm.column-select.focus { 142 | /* Column selection mode */ 143 | cursor: crosshair; 144 | } 145 | 146 | .xterm .xterm-accessibility, 147 | .xterm .xterm-message { 148 | position: absolute; 149 | left: 0; 150 | top: 0; 151 | bottom: 0; 152 | right: 0; 153 | z-index: 10; 154 | color: transparent; 155 | } 156 | 157 | .xterm .live-region { 158 | position: absolute; 159 | left: -9999px; 160 | width: 1px; 161 | height: 1px; 162 | overflow: hidden; 163 | } 164 | 165 | .xterm-dim { 166 | opacity: 0.5; 167 | } 168 | 169 | .xterm-underline { 170 | text-decoration: underline; 171 | } 172 | 173 | .xterm-strikethrough { 174 | text-decoration: line-through; 175 | } 176 | 177 | .xterm-screen .xterm-decoration-container .xterm-decoration { 178 | z-index: 6; 179 | position: absolute; 180 | } 181 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasmtech/kasm-install-wizard/9b173217d15eb77ed57e330a6ca5b8accea748b5/public/favicon.ico -------------------------------------------------------------------------------- /public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasmtech/kasm-install-wizard/9b173217d15eb77ed57e330a6ca5b8accea748b5/public/img/logo.png -------------------------------------------------------------------------------- /public/img/thumbnails/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kasmtech/kasm-install-wizard/9b173217d15eb77ed57e330a6ca5b8accea748b5/public/img/thumbnails/.gitignore -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Kasm Wizard 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 |
19 |
20 |
21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /public/js/index.js: -------------------------------------------------------------------------------- 1 | // Variables 2 | var EULA; 3 | var images; 4 | var gpus; 5 | var term; 6 | var installImages = []; 7 | var installSettings = {}; 8 | var upgradeSettings = {}; 9 | var selected = false; 10 | 11 | // Socket.io connection 12 | var host = window.location.hostname; 13 | var port = window.location.port; 14 | var protocol = window.location.protocol; 15 | var path = window.location.pathname; 16 | var socket = io(protocol + '//' + host + ':' + port, { path: path + 'socket.io'}); 17 | 18 | //// Page Functions //// 19 | 20 | // Render term data 21 | function renderTerm(data) { 22 | term.write(data); 23 | } 24 | 25 | // Execute install 26 | async function install() { 27 | showTerminal() 28 | titleChange('Installing'); 29 | // Create new object based on image selection 30 | let selectedImages = {alembic_version: images.alembic_version, images: [], group_images: []}; 31 | if (installImages.length == 0) { 32 | socket.emit('install', [installSettings, false]); 33 | } else { 34 | for await (let image of installImages) { 35 | let srcImage = images.images.find(x => x.friendly_name === image); 36 | srcImage['enabled'] = true; 37 | selectedImages.images.push(srcImage); 38 | selectedImages.group_images.push({image_id: srcImage.image_id, group_id: "68d557ac-4cac-42cc-a9f3-1c7c853de0f3"}); 39 | } 40 | socket.emit('install', [installSettings, selectedImages]); 41 | } 42 | } 43 | 44 | // Execute upgrade 45 | async function upgrade() { 46 | showTerminal() 47 | titleChange('Upgrading'); 48 | // Create new object based on image selection 49 | let selectedImages = {alembic_version: images.alembic_version, images: [], group_images: []}; 50 | if (installImages.length == 0) { 51 | socket.emit('upgrade', [upgradeSettings, false]); 52 | } else { 53 | for await (let image of installImages) { 54 | let srcImage = images.images.find(x => x.friendly_name === image); 55 | srcImage['enabled'] = true; 56 | selectedImages.images.push(srcImage); 57 | selectedImages.group_images.push({image_id: srcImage.image_id, group_id: "68d557ac-4cac-42cc-a9f3-1c7c853de0f3"}); 58 | } 59 | socket.emit('upgrade', [upgradeSettings, selectedImages]); 60 | } 61 | } 62 | 63 | // Show page container 64 | function showContainer() { 65 | $('#terminal').empty(); 66 | $('#terminal').css('display', 'none'); 67 | term = null; 68 | $('#container').empty(); 69 | $('#container').css('display', 'block'); 70 | } 71 | 72 | // Show terminal 73 | function showTerminal() { 74 | $('#container').empty(); 75 | $('#container').css('display', 'none'); 76 | $('#terminal').empty(); 77 | $('#terminal').css('display', 'block'); 78 | term = new Terminal(); 79 | fitaddon = new FitAddon.FitAddon(); 80 | term.loadAddon(fitaddon); 81 | term.open($('#terminal')[0]); 82 | fitaddon.fit(); 83 | } 84 | 85 | // Change nav title 86 | function titleChange(value) { 87 | $('#title').empty(); 88 | $('#title').text(value); 89 | } 90 | 91 | // Render landing as installer page 92 | function renderInstall(data) { 93 | showContainer(); 94 | titleChange('EULA'); 95 | EULA = data[0]; 96 | images = data[1]; 97 | gpus = data[2]; 98 | let EULADiv = $('
', {id: 'EULA'}).text(EULA); 99 | $('#container').append(EULADiv); 100 | let EULAButton = $('