diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 50c94e0dc..de4b094c6 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -19,7 +19,10 @@
"superagent": "^8.0.9",
"vue": "^3.3.4",
"vue-i18n": "^9.2.2",
- "vue-router": "^4.2.4"
+ "vue-router": "^4.2.4",
+ "xterm": "^5.2.1",
+ "xterm-addon-attach": "^0.8.0",
+ "xterm-addon-fit": "^0.7.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
@@ -1182,6 +1185,27 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
+ "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/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 9fd7ef86b..affce4cfe 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -20,7 +20,10 @@
"superagent": "^8.0.9",
"vue": "^3.3.4",
"vue-i18n": "^9.2.2",
- "vue-router": "^4.2.4"
+ "vue-router": "^4.2.4",
+ "xterm": "^5.2.1",
+ "xterm-addon-attach": "^0.8.0",
+ "xterm-addon-fit": "^0.7.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
diff --git a/frontend/src/components/LogsViewer.vue b/frontend/src/components/LogsViewer.vue
index d9ffd1968..233083dc9 100644
--- a/frontend/src/components/LogsViewer.vue
+++ b/frontend/src/components/LogsViewer.vue
@@ -11,7 +11,7 @@
-
+
@@ -35,7 +35,8 @@ import ProgressSpinner from 'primevue/progressspinner';
import { TopBar, MainLayout } from 'pankow';
-import { create } from '../models/LogsModel.js';
+import LogsModel from '../models/LogsModel.js';
+import AppModel from '../models/AppModel.js';
const API_ORIGIN = import.meta.env.VITE_API_ORIGIN ? 'https://' + import.meta.env.VITE_API_ORIGIN : '';
@@ -55,6 +56,7 @@ export default {
accessToken: localStorage.token,
apiOrigin: API_ORIGIN || '',
logsModel: null,
+ appModel: null,
busyRestart: false,
id: '',
name: '',
@@ -75,7 +77,7 @@ export default {
this.busyRestart = true;
- await this.logsModel.restartApp();
+ await this.appModel.restart();
this.busyRestart = false;
}
@@ -119,11 +121,12 @@ export default {
return;
}
- this.logsModel = create(this.apiOrigin, this.accessToken, this.type, this.id);
+ this.logsModel = LogsModel.create(this.apiOrigin, this.accessToken, this.type, this.id);
if (this.type === 'app') {
+ this.appModel = AppModel.create(this.apiOrigin, this.accessToken, this.id);
try {
- const app = await this.logsModel.getApp();
+ 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);
diff --git a/frontend/src/components/Terminal.vue b/frontend/src/components/Terminal.vue
new file mode 100644
index 000000000..04532a2b3
--- /dev/null
+++ b/frontend/src/components/Terminal.vue
@@ -0,0 +1,176 @@
+
+