mirror of https://github.com/alibaba/arthas.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
316 lines
9.8 KiB
Vue
316 lines
9.8 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, ref, computed } from "vue";
|
|
import { Terminal } from "xterm"
|
|
import { FitAddon } from 'xterm-addon-fit';
|
|
import { WebglAddon } from "xterm-addon-webgl"
|
|
import { MenuAlt2Icon } from "@heroicons/vue/outline"
|
|
import fullPic from "~/assert/fullsc.png"
|
|
import arthasLogo from "~/assert/arthas.png"
|
|
const { isTunnel = false } = defineProps<{
|
|
isTunnel?: boolean
|
|
}>()
|
|
|
|
let ws: WebSocket | undefined;
|
|
let intervalReadKey = -1
|
|
const DEFAULT_SCROLL_BACK = 1000
|
|
const MAX_SCROLL_BACK = 9999999
|
|
const MIN_SCROLL_BACK = 1
|
|
const ARTHAS_PORT = isTunnel ? "7777" : "8563"
|
|
const ip = ref("")
|
|
const port = ref('')
|
|
const iframe = ref(true)
|
|
const fullSc = ref(true)
|
|
const agentID = ref('')
|
|
const outputHerf = computed(() => {
|
|
console.log(agentID.value)
|
|
return isTunnel?`proxy/${agentID.value}/arthas-output/`:`/arthas-output/`
|
|
})
|
|
// const isTunnel = import.meta.env.MODE === 'tunnel'
|
|
const fitAddon = new FitAddon();
|
|
const webglAddon = new WebglAddon();
|
|
let xterm = new Terminal({ allowProposedApi: true })
|
|
|
|
onMounted(() => {
|
|
ip.value = getUrlParam('ip') ?? window.location.hostname;
|
|
port.value = getUrlParam('port') ?? ARTHAS_PORT;
|
|
if (isTunnel) agentID.value = getUrlParam("agentId") ?? ""
|
|
let _iframe = getUrlParam('iframe')
|
|
if (_iframe && _iframe.trim() !== 'false') iframe.value = false
|
|
|
|
startConnect(true);
|
|
window.addEventListener('resize', function () {
|
|
if (ws !== undefined && ws !== null) {
|
|
const { cols, rows } = fitAddon.proposeDimensions()!
|
|
ws.send(JSON.stringify({ action: 'resize', cols, rows: rows }));
|
|
fitAddon.fit();
|
|
}
|
|
});
|
|
});
|
|
|
|
/** get params in url **/
|
|
function getUrlParam(name: string) {
|
|
const urlparam = new URLSearchParams(window.location.search)
|
|
return urlparam.get(name)
|
|
}
|
|
|
|
function getWsUri() {
|
|
const host = `${ip.value}:${port.value}`
|
|
if (!isTunnel) return `ws://${host}/ws`;
|
|
const path = getUrlParam("path") ?? 'ws'
|
|
const _targetServer = getUrlParam("targetServer")
|
|
let protocol = location.protocol === 'https:' ? 'wss://' : 'ws://';
|
|
const uri = `${protocol}${host}/${encodeURIComponent(path)}?method=connectArthas&id=${agentID.value}`
|
|
if (_targetServer != null) {
|
|
return uri + '&targetServer=' + encodeURIComponent(_targetServer);
|
|
}
|
|
return uri
|
|
}
|
|
/** init websocket **/
|
|
function initWs(silent: boolean) {
|
|
let uri = getWsUri()
|
|
ws = new WebSocket(uri);
|
|
ws.onerror = function () {
|
|
ws ?? ws!.close();
|
|
ws = undefined;
|
|
!silent && alert('Connect error');
|
|
};
|
|
ws.onopen = function () {
|
|
fullSc.value = true
|
|
|
|
let scrollback = getUrlParam('scrollback') ?? '0';
|
|
|
|
const { cols, rows } = initXterm(scrollback)
|
|
xterm.onData(function (data) {
|
|
ws?.send(JSON.stringify({ action: 'read', data: data }))
|
|
});
|
|
ws!.onmessage = function (event: MessageEvent) {
|
|
if (event.type === 'message') {
|
|
var data = event.data;
|
|
xterm.write(data);
|
|
}
|
|
};
|
|
ws?.send(JSON.stringify({ action: 'resize', cols, rows }));
|
|
intervalReadKey = window.setInterval(function () {
|
|
if (ws != null && ws.readyState === 1) {
|
|
ws.send(JSON.stringify({ action: 'read', data: "" }));
|
|
}
|
|
}, 30000);
|
|
}
|
|
ws.onclose = function (message) {
|
|
if (intervalReadKey != -1) {
|
|
window.clearInterval(intervalReadKey)
|
|
intervalReadKey = -1
|
|
}
|
|
if (message.code === 2000) {
|
|
alert(message.reason);
|
|
}
|
|
};
|
|
}
|
|
|
|
/** init xterm **/
|
|
function initXterm(scrollback: string) {
|
|
let scrollNumber = parseInt(scrollback, 10)
|
|
xterm = new Terminal({
|
|
screenReaderMode: false,
|
|
convertEol: true,
|
|
allowProposedApi: true,
|
|
scrollback: isValidNumber(scrollNumber) ? scrollNumber : DEFAULT_SCROLL_BACK
|
|
});
|
|
xterm.loadAddon(fitAddon)
|
|
|
|
xterm.open(document.getElementById('terminal')!);
|
|
|
|
xterm.loadAddon(webglAddon)
|
|
fitAddon.fit()
|
|
return {
|
|
cols: xterm.cols,
|
|
rows: xterm.rows
|
|
}
|
|
}
|
|
|
|
function isValidNumber(scrollNumber: number) {
|
|
return scrollNumber >= MIN_SCROLL_BACK &&
|
|
scrollNumber <= MAX_SCROLL_BACK;
|
|
}
|
|
|
|
const connectGuard = (silent: boolean): boolean => {
|
|
if (ip.value.trim() === '' || port.value.trim() === '') {
|
|
alert('Ip or port can not be empty');
|
|
return false;
|
|
}
|
|
if (isTunnel && agentID.value == '') {
|
|
if (silent) {
|
|
return false;
|
|
}
|
|
alert('AgentId can not be empty');
|
|
return false;
|
|
}
|
|
if (ws) {
|
|
alert('Already connected');
|
|
return false;
|
|
}
|
|
return true
|
|
}
|
|
/** begin connect **/
|
|
function startConnect(silent: boolean = false) {
|
|
if (connectGuard(silent)) {
|
|
// init webSocket
|
|
initWs(silent);
|
|
}
|
|
|
|
}
|
|
|
|
function disconnect() {
|
|
try {
|
|
ws!.close();
|
|
ws!.onmessage = null;
|
|
ws!.onclose = null;
|
|
ws = undefined;
|
|
xterm.dispose();
|
|
fitAddon.dispose()
|
|
webglAddon.dispose()
|
|
fullSc.value = false
|
|
alert('Connection was closed successfully!');
|
|
} catch {
|
|
alert('No connection, please start connect first.');
|
|
}
|
|
}
|
|
|
|
/** full screen show **/
|
|
function xtermFullScreen() {
|
|
var ele = document.getElementById('terminal-card')!;
|
|
requestFullScreen(ele);
|
|
ele.onfullscreenchange = (e: Event) => {
|
|
fitAddon.fit()
|
|
}
|
|
}
|
|
|
|
function requestFullScreen(element: HTMLElement) {
|
|
let requestMethod = element.requestFullscreen;
|
|
if (requestMethod) {
|
|
requestMethod.call(element);
|
|
//@ts-ignore
|
|
} else if (window.ActiveXObject) {
|
|
// @ts-ignore
|
|
var wscript = new ActiveXObject("WScript.Shell");
|
|
if (wscript !== null) {
|
|
wscript.SendKeys("{F11}");
|
|
}
|
|
}
|
|
}
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<div class="flex flex-col h-[100vh] w-[100vw] resize-none">
|
|
<nav v-if="iframe" class="navbar bg-base-100 md:flex-row flex-col w-[100vw]">
|
|
<div class="navbar-start">
|
|
<div class="dropdown dropdown-start 2xl:hidden">
|
|
<label tabindex="0" class="btn btn-ghost btn-sm">
|
|
<MenuAlt2Icon class="w-6 h-6"></MenuAlt2Icon>
|
|
</label>
|
|
<ul tabindex="0" class="dropdown-content menu shadow bg-base-100">
|
|
<li>
|
|
<a class="hover:text-sky-500 dark:hover:text-sky-400 text-sm" href="https://arthas.aliyun.com/doc"
|
|
target="_blank">Documentation
|
|
<span class="sr-only">(current)</span></a>
|
|
</li>
|
|
<li>
|
|
<a class="hover:text-sky-500 dark:hover:text-sky-400 text-sm"
|
|
href="https://arthas.aliyun.com/doc/arthas-tutorials.html" target="_blank">Online
|
|
Tutorials</a>
|
|
</li>
|
|
<li>
|
|
<a class="hover:text-sky-500 dark:hover:text-sky-400 text-sm" href="https://github.com/alibaba/arthas"
|
|
target="_blank">Github</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<a href="https://github.com/alibaba/arthas" target="_blank" title="" class="mr-2 w-20"><img
|
|
:src="arthasLogo" alt="Arthas" title="Welcome to Arthas web console"></a>
|
|
|
|
<ul class="menu menu-vertical 2xl:menu-horizontal hidden">
|
|
<li>
|
|
<a class="hover:text-sky-500 dark:hover:text-sky-400 text-sm" href="https://arthas.aliyun.com/doc"
|
|
target="_blank">Documentation
|
|
<span class="sr-only">(current)</span></a>
|
|
</li>
|
|
<li>
|
|
<a class="hover:text-sky-500 dark:hover:text-sky-400 text-sm"
|
|
href="https://arthas.aliyun.com/doc/arthas-tutorials.html" target="_blank">Online
|
|
Tutorials</a>
|
|
</li>
|
|
<li>
|
|
<a class="hover:text-sky-500 dark:hover:text-sky-400 text-sm" href="https://github.com/alibaba/arthas"
|
|
target="_blank">Github</a>
|
|
</li>
|
|
</ul>
|
|
|
|
</div>
|
|
<div class="navbar-center ">
|
|
<div class=" xl:flex-row form-control"
|
|
:class="{
|
|
'xl:flex-row':isTunnel,
|
|
'lg:flex-row':!isTunnel
|
|
}">
|
|
<label class="input-group input-group-sm mr-2">
|
|
<span>IP</span>
|
|
<input type="text" placeholder="please enter ip address" class="input input-bordered input-sm "
|
|
v-model="ip" />
|
|
</label>
|
|
<label class="input-group input-group-sm mr-2">
|
|
<span>Port</span>
|
|
<input type="text" placeholder="please enter port" class="input input-sm input-bordered" v-model="port" />
|
|
</label>
|
|
<label v-if="isTunnel" class="input-group input-group-sm mr-2">
|
|
<span>AgentId</span>
|
|
<input type="text" placeholder="please enter AgentId" class="input input-sm input-bordered"
|
|
v-model="agentID" />
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="navbar-end">
|
|
<div class="btn-group 2xl:btn-group-horizontal btn-group-horizontal"
|
|
:class="{
|
|
'md:btn-group-vertical':isTunnel
|
|
}">
|
|
<button
|
|
class="btn btn-sm bg-secondary hover:bg-secondary-focus border-none text-secondary-content focus:bg-secondary-focus normal-case"
|
|
@click.prevent="startConnect(true)">Connect</button>
|
|
<button
|
|
class="btn btn-sm bg-secondary hover:bg-secondary-focus border-none text-secondary-content focus:bg-secondary-focus normal-case"
|
|
@click.prevent="disconnect">Disconnect</button>
|
|
<a class="btn btn-sm bg-secondary hover:bg-secondary-focus border-none text-secondary-content focus:bg-secondary-focus normal-case"
|
|
:href="outputHerf" target="_blank">Arthas Output</a>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
<div class="w-full h-0 flex-auto bg-black overscroll-auto" id="terminal-card">
|
|
<div id="terminal" class="w-full h-full"></div>
|
|
</div>
|
|
|
|
<div title="fullscreen" id="fullSc" class="fullSc" v-if="fullSc">
|
|
<button id="fullScBtn" @click="xtermFullScreen"><img :src="fullPic"></button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style>
|
|
#terminal:-webkit-full-screen {
|
|
background-color: rgb(255, 255, 12);
|
|
}
|
|
|
|
.fullSc {
|
|
z-index: 10000;
|
|
position: fixed;
|
|
top: 25%;
|
|
left: 90%;
|
|
}
|
|
|
|
#fullScBtn {
|
|
border-radius: 17px;
|
|
border: 0;
|
|
cursor: pointer;
|
|
background-color: black;
|
|
}
|
|
</style> |