First version of vuejs terminal

This commit is contained in:
Johannes Zellner
2023-07-14 14:48:43 +02:00
parent 6bf7a1a2d8
commit d162ffe508
10 changed files with 347 additions and 9 deletions
+176
View File
@@ -0,0 +1,176 @@
<template>
<MainLayout :gap="false">
<template #dialogs>
</template>
<template #header>
<TopBar class="navbar">
<template #left>
<span class="title">{{ name }}</span>
</template>
<template #right>
<a style="margin-left: 20px; margin-right: 5px;" :href="'/frontend/logs.html?appId=' + id" target="_blank"><Button severity="secondary" icon="pi pi-align-left" :label="$t('logs.title')" /></a>
<a style="margin-right: 5px;" :href="'/frontend/filemanager.html#/home/app/' + id" target="_blank"><Button type="button" severity="secondary" icon="pi pi-folder" :label="$t('filemanager.title')" /></a>
<Button type="button" :label="$t('filemanager.toolbar.restartApp')" severity="secondary" icon="pi pi-sync" @click="onRestartApp" :loading="busyRestart"/>
</template>
</TopBar>
</template>
<template #body>
<div id="terminal"></div>
</template>
</MainLayout>
</template>
<script>
import superagent from 'superagent';
import Button from 'primevue/button';
import Dialog from 'primevue/dialog';
import InputText from 'primevue/inputtext';
import Menu from 'primevue/menu';
import ProgressSpinner from 'primevue/progressspinner';
import { TopBar, MainLayout } from 'pankow';
import 'xterm/css/xterm.css';
import { Terminal } from 'xterm';
import { AttachAddon } from 'xterm-addon-attach';
import { FitAddon } from 'xterm-addon-fit';
import { create } from '../models/AppModel.js';
const API_ORIGIN = import.meta.env.VITE_API_ORIGIN ? 'https://' + import.meta.env.VITE_API_ORIGIN : '';
export default {
name: 'Terminal',
components: {
Button,
Dialog,
InputText,
MainLayout,
Menu,
ProgressSpinner,
TopBar
},
data() {
return {
accessToken: localStorage.token,
apiOrigin: API_ORIGIN || '',
appModel: null,
busyRestart: false,
id: '',
name: '',
terminal: null
};
},
methods: {
onClear() {
},
async onRestartApp() {
if (this.type !== 'app') return;
this.busyRestart = true;
await this.appModel.restart();
this.busyRestart = false;
},
async connect(retry = false) {
document.getElementsByClassName('cloudron-layout-body')[0].innerHTML = '';
let execId;
try {
const result = await superagent.post(`${this.apiOrigin}/api/v1/apps/${this.id}/exec`).query({ access_token: this.accessToken }).send({ cmd: [ '/bin/bash' ], tty: true, lang: 'C.UTF-8' });
execId = result.body.id;
} catch (error) {
console.error('Cannot create socket.', error);
return setTimeout(() => this.connect(true), 1000);
}
this.terminal = new Terminal();
this.terminal.open(document.getElementsByClassName('cloudron-layout-body')[0]);
if (retry) this.terminal.writeln('Reconnecting...');
else this.terminal.writeln('Connecting...');
const fitAddon = new FitAddon();
this.terminal.loadAddon(fitAddon);
fitAddon.fit();
// Let the browser handle paste
this.terminal.attachCustomKeyEventHandler(function (e) {
if (e.key === 'v' && (e.ctrlKey || e.metaKey)) return false;
});
// websocket cannot use relative urls
var url = `${this.apiOrigin.replace('https', 'wss')}/api/v1/apps/${this.id}/exec/${execId}/startws?tty=true&rows=${this.terminal.rows}&columns=${this.terminal.cols}&access_token=${this.accessToken}`;
const socket = new WebSocket(url);
this.terminal.loadAddon(new AttachAddon(socket));
socket.addEventListener('close', (event) => {
console.log('Socket closed. Reconnecting...');
return setTimeout(() => this.connect(true), 1000);
});
socket.addEventListener('error', (error) => {
console.log('Socket error. Reconnecting...', error);
return setTimeout(() => this.connect(true), 1000);
});
this.terminal.focus();
}
},
async mounted() {
if (!localStorage.token) {
console.error('Set localStorage.token');
return;
}
const urlParams = new URLSearchParams(window.location.search);
const id = urlParams.get('id');
if (!id) {
console.error('No app id specified');
return;
}
this.id = id;
this.name = id;
this.appModel = create(this.apiOrigin, this.accessToken, this.id);
try {
const app = await this.appModel.get();
this.name = `${app.label || app.fqdn} (${app.manifest.title})`;
} catch (e) {
console.error(`Failed to get app info for ${this.id}:`, e);
}
window.document.title = `Terminal - ${this.name}`;
this.connect();
}
};
</script>
<style>
body {
background-color: black;
}
.title {
font-size: 20px;
}
.cloudron-top {
background-color: black;
color: white;
margin-bottom: 0 !important;
padding: 5px 10px;
}
</style>