vue-ssh-terminal
Version:
SSH Terminal component based on xterm.js for multiple frontend frameworks
298 lines (297 loc) • 8.14 kB
JavaScript
import { Terminal as v } from "xterm";
import { FitAddon as T } from "xterm-addon-fit";
import { WebLinksAddon as z } from "xterm-addon-web-links";
import { SearchAddon as E } from "xterm-addon-search";
import { openBlock as S, createElementBlock as y, ref as H, onMounted as k, onBeforeUnmount as A } from "vue";
const w = {
fontSize: 14,
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
theme: {
background: "#000000",
foreground: "#ffffff",
cursor: "#ffffff",
cursorAccent: "#000000",
selection: "rgba(255, 255, 255, 0.3)"
},
cursorBlink: !0,
cursorStyle: "block",
scrollback: 1e3,
allowTransparency: !1,
tabStopWidth: 8,
screenReaderMode: !1,
convertEol: !0,
disableStdin: !1
};
function p(i, t, o = {}) {
if (!i)
throw new Error("Container element is required");
if (!t || !t.host || !t.username || !t.password)
throw new Error("SSH configuration (host, username, password) is required");
const s = {
...w,
...o.theme ? { theme: { ...w.theme, ...o.theme } } : {},
...o
}, e = new v(s), l = new T(), c = new z(), d = new E();
e.loadAddon(l), e.loadAddon(c), e.loadAddon(d), e.open(i), setTimeout(() => {
l.fit();
}, 0);
const m = new ResizeObserver(() => {
l.fit();
});
m.observe(i);
const u = t.wsUrl || "ws://localhost:8080";
let n = null, h = !1;
const f = () => {
n && n.close(), n = new WebSocket(u), n.onopen = () => {
h = !0;
const a = {
type: "ssh-connect",
host: t.host,
username: t.username,
password: t.password
};
n.send(JSON.stringify(a)), e.writeln("Connected to SSH relay server...");
}, n.onmessage = (a) => {
try {
const r = JSON.parse(a.data);
r.type === "ssh-data" ? e.write(r.content) : r.type === "ssh-error" ? e.writeln(`\r
Error: ${r.message}\r
`) : r.type === "ssh-connected" ? e.writeln(`\r
Connected to ${t.host} as ${t.username}\r
`) : r.type === "ssh-closed" && e.writeln(`\r
SSH connection closed\r
`);
} catch {
e.write(a.data);
}
}, n.onclose = () => {
h = !1, e.writeln(`\r
Connection to SSH relay server closed\r
`);
}, n.onerror = (a) => {
h = !1, e.writeln(`\r
WebSocket error: ${a.message}\r
`);
}, e.onData((a) => {
if (h && n.readyState === WebSocket.OPEN) {
const r = {
type: "ssh-data",
content: a
};
n.send(JSON.stringify(r));
}
}), e.onResize(({ cols: a, rows: r }) => {
if (h && n.readyState === WebSocket.OPEN) {
const g = {
type: "ssh-resize",
cols: a,
rows: r
};
n.send(JSON.stringify(g));
}
});
};
return f(), l.fit(), {
terminal: e,
fitAddon: l,
searchAddon: d,
reconnect: () => {
f();
},
resize: () => {
l.fit();
},
dispose: () => {
n && n.close(), m.disconnect(), e.dispose();
},
search: (a, r) => {
d.findNext(a, r);
},
searchPrevious: (a, r) => {
d.findPrevious(a, r);
}
};
}
const _ = (i, t) => {
const o = i.__vccOpts || i;
for (const [s, e] of t)
o[s] = e;
return o;
}, O = {
name: "SSHTerminal",
props: {
host: {
type: String,
required: !0
},
username: {
type: String,
required: !0
},
password: {
type: String,
required: !0
},
wsUrl: {
type: String,
default: "ws://localhost:8080"
},
options: {
type: Object,
default: () => ({})
}
},
data() {
return {
terminal: null
};
},
mounted() {
this.initTerminal(), window.addEventListener("resize", this.handleResize);
},
beforeDestroy() {
this.terminal && this.terminal.dispose(), window.removeEventListener("resize", this.handleResize);
},
methods: {
initTerminal() {
const i = this.$refs.terminalContainer, t = {
host: this.host,
username: this.username,
password: this.password,
wsUrl: this.wsUrl
};
this.terminal = p(i, t, this.options), this.$emit("ready", this.terminal);
},
handleResize() {
this.terminal && this.terminal.resize();
}
}
}, x = {
ref: "terminalContainer",
class: "ssh-terminal-container"
};
function C(i, t, o, s, e, l) {
return S(), y("div", x, null, 512);
}
const W = /* @__PURE__ */ _(O, [["render", C], ["__scopeId", "data-v-c3dec533"]]);
const $ = {
__name: "SSHTerminal",
props: {
host: {
type: String,
required: !0
},
username: {
type: String,
required: !0
},
password: {
type: String,
required: !0
},
wsUrl: {
type: String,
default: "ws://localhost:8080"
},
options: {
type: Object,
default: () => ({})
}
},
emits: ["ready"],
setup(i, { expose: t, emit: o }) {
const s = i, e = o, l = H(null);
let c = null;
k(() => {
d(), window.addEventListener("resize", m);
}), A(() => {
c && c.dispose(), window.removeEventListener("resize", m);
});
function d() {
const u = l.value, n = {
host: s.host,
username: s.username,
password: s.password,
wsUrl: s.wsUrl
};
c = p(u, n, s.options), e("ready", c);
}
function m() {
c && c.resize();
}
return t({
getTerminal: () => c
}), (u, n) => (S(), y("div", {
ref_key: "terminalContainer",
ref: l,
class: "ssh-terminal-container"
}, null, 512));
}
}, P = /* @__PURE__ */ _($, [["__scopeId", "data-v-5b1c13db"]]);
class b extends HTMLElement {
constructor() {
super(), this.attachShadow({ mode: "open" }), this.container = document.createElement("div"), this.container.className = "ssh-terminal-container", this.styles = document.createElement("style"), this.styles.textContent = `
.ssh-terminal-container {
width: 100%;
height: 100%;
min-height: 300px;
background-color: #000;
}
`, this.shadowRoot.appendChild(this.styles), this.shadowRoot.appendChild(this.container), this.terminal = null;
}
static get observedAttributes() {
return ["host", "username", "password", "ws-url", "options"];
}
connectedCallback() {
this.initTerminal(), this.resizeHandler = this.handleResize.bind(this), window.addEventListener("resize", this.resizeHandler);
}
disconnectedCallback() {
this.terminal && this.terminal.dispose(), window.removeEventListener("resize", this.resizeHandler);
}
attributeChangedCallback(t, o, s) {
o !== s && this.terminal && ["host", "username", "password", "ws-url"].includes(t) && this.initTerminal();
}
initTerminal() {
this.terminal && this.terminal.dispose();
const t = this.getAttribute("host"), o = this.getAttribute("username"), s = this.getAttribute("password"), e = this.getAttribute("ws-url") || "ws://localhost:8080";
let l = {};
try {
const d = this.getAttribute("options");
d && (l = JSON.parse(d));
} catch (d) {
console.error("Invalid options format:", d);
}
if (!t || !o || !s) {
console.error("Missing required attributes: host, username, password");
return;
}
const c = {
host: t,
username: o,
password: s,
wsUrl: e
};
this.terminal = p(this.container, c, l), this.dispatchEvent(new CustomEvent("ready", {
detail: { terminal: this.terminal }
}));
}
handleResize() {
this.terminal && this.terminal.resize();
}
}
customElements.define("ssh-terminal", b);
const N = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
default: b
}, Symbol.toStringTag, { value: "Module" }));
typeof window < "u" && window.customElements && !window.customElements.get("ssh-terminal") && Promise.resolve().then(() => N).then(() => {
console.log("SSH Terminal Web Component registered");
}).catch((i) => {
console.error("Failed to register SSH Terminal Web Component", i);
});
export {
b as SSHTerminalElement,
W as Vue2SSHTerminal,
P as Vue3SSHTerminal,
p as createTerminal
};