mirror of
https://github.com/louislam/dockge.git
synced 2026-05-22 14:32:16 +00:00
Init (#1)
This commit is contained in:
228
frontend/src/components/Terminal.vue
Normal file
228
frontend/src/components/Terminal.vue
Normal file
@@ -0,0 +1,228 @@
|
||||
<template>
|
||||
<div class="shadow-box">
|
||||
<div v-pre ref="terminal" class="main-terminal"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Terminal } from "xterm";
|
||||
import { WebLinksAddon } from "xterm-addon-web-links";
|
||||
import { TERMINAL_COLS, TERMINAL_ROWS } from "../../../backend/util-common";
|
||||
|
||||
export default {
|
||||
/**
|
||||
* @type {Terminal}
|
||||
*/
|
||||
terminal: null,
|
||||
components: {
|
||||
|
||||
},
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
require: true,
|
||||
},
|
||||
|
||||
// Require if mode is interactive
|
||||
stackName: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
// Require if mode is interactive
|
||||
serviceName: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
// Require if mode is interactive
|
||||
shell: {
|
||||
type: String,
|
||||
default: "bash",
|
||||
},
|
||||
|
||||
rows: {
|
||||
type: Number,
|
||||
default: TERMINAL_ROWS,
|
||||
},
|
||||
|
||||
cols: {
|
||||
type: Number,
|
||||
default: TERMINAL_COLS,
|
||||
},
|
||||
|
||||
// Mode
|
||||
// displayOnly: Only display terminal output
|
||||
// mainTerminal: Allow input limited commands and output
|
||||
// interactive: Free input and output
|
||||
mode: {
|
||||
type: String,
|
||||
default: "displayOnly",
|
||||
}
|
||||
},
|
||||
emits: [ "has-data" ],
|
||||
data() {
|
||||
return {
|
||||
first: true,
|
||||
terminalInputBuffer: "",
|
||||
cursorPosition: 0,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
let cursorBlink = true;
|
||||
|
||||
if (this.mode === "displayOnly") {
|
||||
cursorBlink = false;
|
||||
}
|
||||
|
||||
this.terminal = new Terminal({
|
||||
fontSize: 16,
|
||||
fontFamily: "monospace",
|
||||
cursorBlink,
|
||||
cols: this.cols,
|
||||
rows: this.rows,
|
||||
});
|
||||
|
||||
if (this.mode === "mainTerminal") {
|
||||
this.mainTerminalConfig();
|
||||
} else if (this.mode === "interactive") {
|
||||
this.interactiveTerminalConfig();
|
||||
}
|
||||
|
||||
//this.terminal.loadAddon(new WebLinksAddon());
|
||||
|
||||
// Bind to a div
|
||||
this.terminal.open(this.$refs.terminal);
|
||||
this.terminal.focus();
|
||||
|
||||
// Notify parent component when data is received
|
||||
this.terminal.onCursorMove(() => {
|
||||
console.debug("onData triggered");
|
||||
if (this.first) {
|
||||
this.$emit("has-data");
|
||||
this.first = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.bind();
|
||||
|
||||
// Create a new Terminal
|
||||
if (this.mode === "mainTerminal") {
|
||||
this.$root.getSocket().emit("mainTerminal", this.name, (res) => {
|
||||
if (!res.ok) {
|
||||
this.$root.toastRes(res);
|
||||
}
|
||||
});
|
||||
} else if (this.mode === "interactive") {
|
||||
console.debug("Create Interactive terminal:", this.name);
|
||||
this.$root.getSocket().emit("interactiveTerminal", this.stackName, this.serviceName, this.shell, (res) => {
|
||||
if (!res.ok) {
|
||||
this.$root.toastRes(res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
unmounted() {
|
||||
this.$root.unbindTerminal(this.name);
|
||||
this.terminal.dispose();
|
||||
},
|
||||
|
||||
methods: {
|
||||
bind(name) {
|
||||
// Workaround: normally this.name should be set, but it is not sometimes, so we use the parameter, but eventually this.name and name must be the same name
|
||||
if (name) {
|
||||
this.$root.unbindTerminal(name);
|
||||
this.$root.bindTerminal(name, this.terminal);
|
||||
console.debug("Terminal bound via parameter: " + name);
|
||||
} else if (this.name) {
|
||||
this.$root.unbindTerminal(this.name);
|
||||
this.$root.bindTerminal(this.name, this.terminal);
|
||||
console.debug("Terminal bound: " + this.name);
|
||||
} else {
|
||||
console.debug("Terminal name not set");
|
||||
}
|
||||
},
|
||||
|
||||
removeInput() {
|
||||
const backspaceCount = this.terminalInputBuffer.length;
|
||||
const backspaces = "\b \b".repeat(backspaceCount);
|
||||
this.cursorPosition = 0;
|
||||
this.terminal.write(backspaces);
|
||||
this.terminalInputBuffer = "";
|
||||
},
|
||||
|
||||
mainTerminalConfig() {
|
||||
this.terminal.onKey(e => {
|
||||
const code = e.key.charCodeAt(0);
|
||||
console.debug("Encode: " + JSON.stringify(e.key));
|
||||
|
||||
if (e.key === "\r") {
|
||||
// Return if no input
|
||||
if (this.terminalInputBuffer.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const buffer = this.terminalInputBuffer;
|
||||
|
||||
// Remove the input from the terminal
|
||||
this.removeInput();
|
||||
|
||||
this.$root.getSocket().emit("terminalInput", this.name, buffer + e.key, (err) => {
|
||||
this.$root.toastError(err.msg);
|
||||
});
|
||||
|
||||
} else if (code === 127) { // Backspace
|
||||
if (this.cursorPosition > 0) {
|
||||
this.terminal.write("\b \b");
|
||||
this.cursorPosition--;
|
||||
this.terminalInputBuffer = this.terminalInputBuffer.slice(0, -1);
|
||||
}
|
||||
} else if (e.key === "\u001B\u005B\u0041" || e.key === "\u001B\u005B\u0042") { // UP OR DOWN
|
||||
// Do nothing
|
||||
|
||||
} else if (e.key === "\u001B\u005B\u0043") { // RIGHT
|
||||
// TODO
|
||||
} else if (e.key === "\u001B\u005B\u0044") { // LEFT
|
||||
// TODO
|
||||
} else if (e.key === "\u0003") { // Ctrl + C
|
||||
console.debug("Ctrl + C");
|
||||
this.$root.getSocket().emit("terminalInput", this.name, e.key);
|
||||
this.removeInput();
|
||||
} else {
|
||||
this.cursorPosition++;
|
||||
this.terminalInputBuffer += e.key;
|
||||
console.log(this.terminalInputBuffer);
|
||||
this.terminal.write(e.key);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
interactiveTerminalConfig() {
|
||||
this.terminal.onKey(e => {
|
||||
this.$root.getSocket().emit("terminalInput", this.name, e.key, (res) => {
|
||||
if (!res.ok) {
|
||||
this.$root.toastRes(res);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.main-terminal {
|
||||
height: 100%;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.terminal {
|
||||
padding: 10px 15px;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user