diff --git a/dashboard/gulpfile.js b/dashboard/gulpfile.js index 37addba4b..afcc32232 100644 --- a/dashboard/gulpfile.js +++ b/dashboard/gulpfile.js @@ -63,23 +63,6 @@ gulp.task('moment', function () { .pipe(gulp.dest('dist/3rdparty/js')); }); -gulp.task('xterm-core', function () { - return gulp.src('node_modules/xterm/**/*') - .pipe(gulp.dest('dist/3rdparty/xterm')); -}); - -gulp.task('xterm-addon-attach', function () { - return gulp.src('node_modules/xterm-addon-attach/**/*') - .pipe(gulp.dest('dist/3rdparty/xterm-addon-attach')); -}); - -gulp.task('xterm-addon-fit', function () { - return gulp.src('node_modules/xterm-addon-fit/**/*') - .pipe(gulp.dest('dist/3rdparty/xterm-addon-fit')); -}); - -gulp.task('xterm', gulp.series(['xterm-core', 'xterm-addon-attach', 'xterm-addon-fit'])); - gulp.task('3rdparty-copy', function () { return gulp.src([ 'src/3rdparty/**/*.js', @@ -94,7 +77,7 @@ gulp.task('3rdparty-copy', function () { ]).pipe(gulp.dest('dist/3rdparty/')); }); -gulp.task('3rdparty', gulp.series(['3rdparty-copy', 'moment', 'monaco', 'xterm', 'bootstrap', 'fontawesome'])); +gulp.task('3rdparty', gulp.series(['3rdparty-copy', 'moment', 'monaco', 'bootstrap', 'fontawesome'])); // -------------- // JavaScript @@ -133,15 +116,6 @@ gulp.task('js-filemanager', function () { .pipe(gulp.dest('dist/js')); }); -gulp.task('js-terminal', function () { - return gulp.src(['src/js/terminal.js', 'src/js/client.js', 'src/js/utils.js']) - .pipe(ejs({ apiOrigin: apiOrigin, revision: revision, appstore: appstore }, {}, { ext: '.js' })) - .pipe(sourcemaps.init()) - .pipe(concat('terminal.js', { newLine: ';' })) - .pipe(sourcemaps.write()) - .pipe(gulp.dest('dist/js')); -}); - gulp.task('js-passwordreset', function () { return gulp.src(['src/js/passwordreset.js', 'src/js/utils.js']) .pipe(ejs({ apiOrigin: apiOrigin, revision: revision, appstore: appstore }, {}, { ext: '.js' })) @@ -187,7 +161,7 @@ gulp.task('js-restore', function () { .pipe(gulp.dest('dist/js')); }); -gulp.task('js', gulp.series([ 'js-index', 'js-logs', 'js-filemanager', 'js-terminal', 'js-passwordreset', 'js-setupaccount', 'js-setup', 'js-setupdns', 'js-restore' ])); +gulp.task('js', gulp.series([ 'js-index', 'js-logs', 'js-filemanager', 'js-passwordreset', 'js-setupaccount', 'js-setup', 'js-setupdns', 'js-restore' ])); // -------------- // HTML @@ -264,7 +238,6 @@ gulp.task('watch', function (done) { gulp.watch(['src/js/restore.js', 'src/js/client.js', 'src/js/utils.js'], gulp.series(['js-restore'])); gulp.watch(['src/js/logs.js', 'src/js/client.js', 'src/js/utils.js'], gulp.series(['js-logs'])); gulp.watch(['src/js/filemanager.js', 'src/js/client.js', 'src/js/utils.js', 'src/components/*.js'], gulp.series(['js-filemanager'])); - gulp.watch(['src/js/terminal.js', 'src/js/client.js', 'src/js/utils.js'], gulp.series(['js-terminal'])); gulp.watch(['src/js/passwordreset.js', 'src/js/utils.js'], gulp.series(['js-passwordreset'])); gulp.watch(['src/js/setupaccount.js', 'src/js/utils.js'], gulp.series(['js-setupaccount'])); gulp.watch(['src/js/index.js', 'src/js/client.js', 'src/js/main.js', 'src/views/*.js', 'src/js/utils.js'], gulp.series(['js-index'])); diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 763daa7f2..d247836e6 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -23,9 +23,6 @@ "moment": "^2.29.4", "monaco-editor": "^0.39.0", "sass": "^1.63.3", - "xterm": "^5.2.1", - "xterm-addon-attach": "^0.8.0", - "xterm-addon-fit": "^0.7.0", "yargs": "^17.7.2" } }, @@ -6855,27 +6852,6 @@ "node": ">=0.4" } }, - "node_modules/xterm": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/xterm/-/xterm-5.2.1.tgz", - "integrity": "sha512-cs5Y1fFevgcdoh2hJROMVIWwoBHD80P1fIP79gopLHJIE4kTzzblanoivxTiQ4+92YM9IxS36H1q0MxIJXQBcA==" - }, - "node_modules/xterm-addon-attach": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/xterm-addon-attach/-/xterm-addon-attach-0.8.0.tgz", - "integrity": "sha512-k8N5boSYn6rMJTTNCgFpiSTZ26qnYJf3v/nJJYexNO2sdAHDN3m1ivVQWVZ8CHJKKnZQw1rc44YP2NtgalWHfQ==", - "peerDependencies": { - "xterm": "^5.0.0" - } - }, - "node_modules/xterm-addon-fit": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.7.0.tgz", - "integrity": "sha512-tQgHGoHqRTgeROPnvmtEJywLKoC/V9eNs4bLLz7iyJr1aW/QFzRwfd3MGiJ6odJd9xEfxcW36/xRU47JkD5NKQ==", - "peerDependencies": { - "xterm": "^5.0.0" - } - }, "node_modules/y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", @@ -12561,23 +12537,6 @@ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" }, - "xterm": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/xterm/-/xterm-5.2.1.tgz", - "integrity": "sha512-cs5Y1fFevgcdoh2hJROMVIWwoBHD80P1fIP79gopLHJIE4kTzzblanoivxTiQ4+92YM9IxS36H1q0MxIJXQBcA==" - }, - "xterm-addon-attach": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/xterm-addon-attach/-/xterm-addon-attach-0.8.0.tgz", - "integrity": "sha512-k8N5boSYn6rMJTTNCgFpiSTZ26qnYJf3v/nJJYexNO2sdAHDN3m1ivVQWVZ8CHJKKnZQw1rc44YP2NtgalWHfQ==", - "requires": {} - }, - "xterm-addon-fit": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.7.0.tgz", - "integrity": "sha512-tQgHGoHqRTgeROPnvmtEJywLKoC/V9eNs4bLLz7iyJr1aW/QFzRwfd3MGiJ6odJd9xEfxcW36/xRU47JkD5NKQ==", - "requires": {} - }, "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", diff --git a/dashboard/package.json b/dashboard/package.json index 986417573..5c1fc9a59 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -27,9 +27,6 @@ "moment": "^2.29.4", "monaco-editor": "^0.39.0", "sass": "^1.63.3", - "xterm": "^5.2.1", - "xterm-addon-attach": "^0.8.0", - "xterm-addon-fit": "^0.7.0", "yargs": "^17.7.2" }, "eslintConfig": { diff --git a/dashboard/src/js/client.js b/dashboard/src/js/client.js index 4074be411..23f427c5b 100644 --- a/dashboard/src/js/client.js +++ b/dashboard/src/js/client.js @@ -961,15 +961,6 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout }); }; - Client.prototype.createExec = function (id, options, callback) { - post('/api/v1/apps/' + id + '/exec', options, null, function (error, data, status) { - if (error) return callback(error); - if (status !== 200) return callback(new ClientError(status, data)); - - callback(null, data.id); - }); - }; - Client.prototype.version = function (callback) { get('/api/v1/cloudron/status', null, function (error, data, status) { if (error) return callback(error); @@ -2766,40 +2757,6 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout }); }; - - Client.prototype.uploadFile = function (appId, file, progressCallback, callback) { - var fd = new FormData(); - fd.append('file', file); - - var config = { - headers: { 'Content-Type': undefined }, - transformRequest: angular.identity, - uploadEventHandlers: { - progress: progressCallback - } - }; - - post('/api/v1/apps/' + appId + '/upload?file=' + encodeURIComponent('/tmp/' + file.name), fd, config, function (error, data, status) { - if (error) return callback(error); - if (status !== 202) return callback(new ClientError(status, data)); - - callback(null); - }); - }; - - Client.prototype.checkDownloadableFile = function (appId, filePath, callback) { - var config = { - headers: { 'Content-Type': undefined } - }; - - head('/api/v1/apps/' + appId + '/download?file=' + encodeURIComponent(filePath), config, function (error, data, status) { - if (error) return callback(error); - if (status !== 200) return callback(new ClientError(status, data)); - - callback(null); - }); - }; - Client.prototype.sendTestMail = function (domain, to, callback) { var data = { to: to diff --git a/dashboard/src/js/terminal.js b/dashboard/src/js/terminal.js deleted file mode 100644 index d6e4d831a..000000000 --- a/dashboard/src/js/terminal.js +++ /dev/null @@ -1,383 +0,0 @@ -'use strict'; - -/* global angular, $, Terminal, AttachAddon, FitAddon, ISTATES */ - -// create main application module -angular.module('Application', ['pascalprecht.translate', 'ngCookies', 'angular-md5', 'ui-notification']); - -angular.module('Application').controller('TerminalController', ['$scope', '$translate', '$timeout', '$location', 'Client', function ($scope, $translate, $timeout, $location, Client) { - var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {}); - - $scope.config = Client.getConfig(); - $scope.user = Client.getUserInfo(); - - $scope.apps = []; - $scope.selected = ''; - $scope.terminal = null; - $scope.terminalSocket = null; - $scope.fitAddon = null; - $scope.restartAppBusy = false; - $scope.appBusy = false; - $scope.selectedAppInfo = null; - $scope.schedulerTasks = []; - - $scope.downloadFile = { - error: '', - filePath: '', - busy: false, - - downloadUrl: function () { - if (!$scope.downloadFile.filePath) return ''; - - var filePath = encodeURIComponent($scope.downloadFile.filePath); - - return Client.apiOrigin + '/api/v1/apps/' + $scope.selected.value + '/download?file=' + filePath + '&access_token=' + Client.getToken(); - }, - - show: function () { - $scope.downloadFile.busy = false; - $scope.downloadFile.error = ''; - $scope.downloadFile.filePath = ''; - $('#downloadFileModal').modal('show'); - }, - - submit: function () { - $scope.downloadFile.busy = true; - - Client.checkDownloadableFile($scope.selected.value, $scope.downloadFile.filePath, function (error) { - $scope.downloadFile.busy = false; - - if (error) { - $scope.downloadFile.error = 'The requested file does not exist.'; - return; - } - - // we have to click the link to make the browser do the download - // don't know how to prevent the browsers - $('#fileDownloadLink')[0].click(); - - $('#downloadFileModal').modal('hide'); - }); - } - }; - - $scope.uploadProgress = { - busy: false, - total: 0, - current: 0, - - show: function () { - $scope.uploadProgress.total = 0; - $scope.uploadProgress.current = 0; - - $('#uploadProgressModal').modal('show'); - }, - - hide: function () { - $('#uploadProgressModal').modal('hide'); - } - }; - - $scope.uploadFile = function () { - var fileUpload = document.querySelector('#fileUpload'); - - fileUpload.onchange = function (e) { - if (e.target.files.length === 0) return; - - $scope.uploadProgress.busy = true; - $scope.uploadProgress.show(); - - Client.uploadFile($scope.selected.value, e.target.files[0], function progress(e) { - $scope.uploadProgress.total = e.total; - $scope.uploadProgress.current = e.loaded; - }, function (error) { - if (error) console.error(error); - - $scope.uploadProgress.busy = false; - $scope.uploadProgress.hide(); - }); - }; - - fileUpload.click(); - }; - - $scope.usesAddon = function (addon) { - if (!$scope.selected || !$scope.selected.addons) return false; - return !!Object.keys($scope.selected.addons).find(function (a) { return a === addon; }); - }; - - function reset() { - if ($scope.terminal) { - $scope.terminal.dispose(); - $scope.terminal = null; - } - - if ($scope.terminalSocket) { - $scope.terminalSocket = null; - } - - $scope.selectedAppInfo = null; - } - - $scope.restartApp = function () { - $scope.restartAppBusy = true; - $scope.appBusy = true; - - var appId = $scope.selected.value; - - function waitUntilRestarted(callback) { - refreshApp(appId, function (error, result) { - if (error) return callback(error); - - if (result.installationState === ISTATES.INSTALLED) return callback(); - setTimeout(waitUntilRestarted.bind(null, callback), 2000); - }); - } - - Client.restartApp(appId, function (error) { - if (error) console.error('Failed to restart app.', error); - - waitUntilRestarted(function (error) { - if (error) console.error('Failed wait for restart.', error); - - $scope.restartAppBusy = false; - $scope.appBusy = false; - }); - }); - }; - - function createTerminalSocket(callback) { - var appId = $scope.selected.value; - - Client.createExec(appId, { cmd: [ '/bin/bash' ], tty: true, lang: 'C.UTF-8' }, function (error, execId) { - if (error) return callback(error); - - try { - // websocket cannot use relative urls - var url = Client.apiOrigin.replace('https', 'wss') + '/api/v1/apps/' + appId + '/exec/' + execId + '/startws?tty=true&rows=' + $scope.terminal.rows + '&columns=' + $scope.terminal.cols + '&access_token=' + Client.getToken(); - $scope.terminalSocket = new WebSocket(url); - $scope.terminal.loadAddon(new AttachAddon.AttachAddon($scope.terminalSocket)); - - $scope.terminalSocket.onclose = function () { - // retry in one second - $scope.terminalReconnectTimeout = setTimeout(function () { - showTerminal(true); - }, 1000); - }; - - callback(); - } catch (e) { - callback(e); - } - }); - } - - function refreshApp(id, callback) { - Client.getApp(id, function (error, result) { - if (error) return callback(error); - - $scope.selectedAppInfo = result; - - callback(null, result); - }); - } - - function showTerminal(retry) { - reset(); - - if (!$scope.selected) return; - - var appId = $scope.selected.value; - - refreshApp(appId, function (error) { - if (error) return console.error(error); - - var result = $scope.selectedAppInfo; - - $scope.schedulerTasks = result.manifest.addons.scheduler ? Object.keys(result.manifest.addons.scheduler).map(function (k) { return { name: k, command: result.manifest.addons.scheduler[k].command }; }) : []; - - $scope.terminal = new Terminal(); - - $scope.fitAddon = new FitAddon.FitAddon(); - $scope.terminal.loadAddon($scope.fitAddon); - - $scope.terminal.open(document.querySelector('#terminalContainer')); - - window.terminal = $scope.terminal; - - // Let the browser handle paste - $scope.terminal.attachCustomKeyEventHandler(function (e) { - if (e.key === 'v' && (e.ctrlKey || e.metaKey)) return false; - }); - - if (retry) $scope.terminal.writeln('Reconnecting...'); - else $scope.terminal.writeln('Connecting...'); - - // we have to give it some time to setup the terminal to make it fit, there is no event unfortunately - setTimeout(function () { - if (!$scope.terminal) return; - - // this is here so that the text wraps correctly after the fit! - // var YELLOW = '\u001b[33m'; // https://gist.github.com/dainkaplan/4651352 - // var NC = '\u001b[0m'; - // $scope.terminal.writeln(YELLOW + 'If you resize the browser window, press Ctrl+D to start a new session with the current size.' + NC); - - // we have to first write something on reconnect after app restart..not sure why - $scope.fitAddon.fit(); - - // create exec container after we fit() since we cannot resize exec container post-creation - createTerminalSocket(function (error) { if (error) console.error(error); }); - - $scope.terminal.focus(); - }, 1000); - }); - } - - $scope.terminalInject = function (addon, extra) { - if (!$scope.terminalSocket) return; - - var cmd, manifestVersion = $scope.selected.manifest.manifestVersion; - if (addon === 'mysql') { - if (manifestVersion === 1) { - cmd = 'mysql --user=${MYSQL_USERNAME} --password=${MYSQL_PASSWORD} --host=${MYSQL_HOST} ${MYSQL_DATABASE}'; - } else { - cmd = 'mysql --user=${CLOUDRON_MYSQL_USERNAME} --password=${CLOUDRON_MYSQL_PASSWORD} --host=${CLOUDRON_MYSQL_HOST} ${CLOUDRON_MYSQL_DATABASE}'; - } - } else if (addon === 'postgresql') { - if (manifestVersion === 1) { - cmd = 'PGPASSWORD=${POSTGRESQL_PASSWORD} psql -h ${POSTGRESQL_HOST} -p ${POSTGRESQL_PORT} -U ${POSTGRESQL_USERNAME} -d ${POSTGRESQL_DATABASE}'; - } else { - cmd = 'PGPASSWORD=${CLOUDRON_POSTGRESQL_PASSWORD} psql -h ${CLOUDRON_POSTGRESQL_HOST} -p ${CLOUDRON_POSTGRESQL_PORT} -U ${CLOUDRON_POSTGRESQL_USERNAME} -d ${CLOUDRON_POSTGRESQL_DATABASE}'; - } - } else if (addon === 'mongodb') { - if (manifestVersion === 1) { - cmd = 'mongo -u "${MONGODB_USERNAME}" -p "${MONGODB_PASSWORD}" ${MONGODB_HOST}:${MONGODB_PORT}/${MONGODB_DATABASE}'; - } else { - cmd = 'mongosh -u "${CLOUDRON_MONGODB_USERNAME}" -p "${CLOUDRON_MONGODB_PASSWORD}" ${CLOUDRON_MONGODB_HOST}:${CLOUDRON_MONGODB_PORT}/${CLOUDRON_MONGODB_DATABASE}'; - } - } else if (addon === 'redis') { - if (manifestVersion === 1) { - cmd = 'redis-cli -h "${REDIS_HOST}" -p "${REDIS_PORT}" -a "${REDIS_PASSWORD}"'; - } else { - cmd = 'redis-cli -h "${CLOUDRON_REDIS_HOST}" -p "${CLOUDRON_REDIS_PORT}" -a "${CLOUDRON_REDIS_PASSWORD}" --no-auth-warning'; - } - } else if (addon === 'scheduler' && extra) { - cmd = extra.command; - } - - if (!cmd) return; - - cmd += ' '; - - $scope.terminalSocket.send(cmd); - $scope.terminal.focus(); - }; - - // terminal right click handling - $scope.terminalClear = function () { - if (!$scope.terminal) return; - $scope.terminal.clear(); - $scope.terminal.focus(); - }; - - $scope.terminalCopyToClipboard = function () { - if (!$scope.terminal) return; - - // execCommand('copy') would copy any selection from the page, so do this only if terminal has a selection - if (!$scope.terminal.getSelection()) return; - - document.execCommand('copy'); - $scope.terminal.focus(); - }; - - $('.contextMenuBackdrop').on('click', function () { - $('#terminalContextMenu').hide(); - $('.contextMenuBackdrop').hide(); - - $scope.terminal.focus(); - }); - - $('#terminalContainer').on('contextmenu', function (e) { - if (!$scope.terminal) return true; - - e.preventDefault(); - - $('.contextMenuBackdrop').show(); - $('#terminalContextMenu').css({ - display: 'block', - left: e.pageX, - top: e.pageY - }); - - return false; - }); - - window.addEventListener('resize', function () { - if ($scope.fitAddon) $scope.fitAddon.fit(); - }); - - Client.getStatus(function (error, status) { - if (error) return $scope.error(error); - - if (!status.activated) { - console.log('Not activated yet, closing or redirecting', status); - window.close(); - window.location.href = '/'; - return; - } - - // check version and force reload if needed - if (!localStorage.version) { - localStorage.version = status.version; - } else if (localStorage.version !== status.version) { - localStorage.version = status.version; - window.location.reload(true); - } - - console.log('Running terminal version ', localStorage.version); - - // get user profile as the first thing. this populates the "scope" and affects subsequent API calls - Client.refreshUserInfo(function (error) { - if (error) return $scope.error(error); - - Client.refreshConfig(function (error) { - if (error) return $scope.error(error); - - refreshApp(search.id, function (error, app) { - $scope.selected = { - type: 'app', - value: app.id, - name: app.fqdn + ' (' + app.manifest.title + ')', - addons: app.manifest.addons, - manifest: app.manifest - }; - - // now mark the Client to be ready - Client.setReady(); - - $scope.initialized = true; - - showTerminal(); - }); - }); - }); - }); - - window.addEventListener('keydown', function (event) { - if (event.key === 'C' && event.ctrlKey) { // ctrl shift c - event.preventDefault(); - $scope.terminalCopyToClipboard(); - } - }); - - $translate([ 'terminal.title' ]).then(function (tr) { - if (tr['terminal.title'] !== 'terminal.title') window.document.title = tr['terminal.title']; - }); - - // setup all the dialog focus handling - ['downloadFileModal'].forEach(function (id) { - $('#' + id).on('shown.bs.modal', function () { - $(this).find('[autofocus]:first').focus(); - }); - }); -}]); diff --git a/dashboard/src/terminal.html b/dashboard/src/terminal.html deleted file mode 100644 index 81730fc73..000000000 --- a/dashboard/src/terminal.html +++ /dev/null @@ -1,172 +0,0 @@ - - -
- - - -