mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-02-08 01:20:07 +08:00
fix: 对于 Redis 终端、容器终端也同步改造
This commit is contained in:
parent
fa83199d7b
commit
74b6af64e9
@ -1,6 +1,8 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -196,7 +198,15 @@ func wshandleError(ws *websocket.Conn, err error) bool {
|
|||||||
global.LOG.Errorf("handler ws faled:, err: %v", err)
|
global.LOG.Errorf("handler ws faled:, err: %v", err)
|
||||||
dt := time.Now().Add(time.Second)
|
dt := time.Now().Add(time.Second)
|
||||||
if ctlerr := ws.WriteControl(websocket.CloseMessage, []byte(err.Error()), dt); ctlerr != nil {
|
if ctlerr := ws.WriteControl(websocket.CloseMessage, []byte(err.Error()), dt); ctlerr != nil {
|
||||||
_ = ws.WriteMessage(websocket.TextMessage, []byte(err.Error()))
|
wsData, err := json.Marshal(terminal.WsMsg{
|
||||||
|
Type: terminal.WsMsgCmd,
|
||||||
|
Data: base64.StdEncoding.EncodeToString([]byte(err.Error())),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
_ = ws.WriteMessage(websocket.TextMessage, []byte("{\"type\":\"cmd\",\"data\":\"failed to encoding to json\"}"))
|
||||||
|
} else {
|
||||||
|
_ = ws.WriteMessage(websocket.TextMessage, wsData)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -52,11 +52,17 @@ func (sws *LocalWsSession) handleSlaveEvent(exitCh chan bool) {
|
|||||||
func (sws *LocalWsSession) masterWrite(data []byte) error {
|
func (sws *LocalWsSession) masterWrite(data []byte) error {
|
||||||
sws.writeMutex.Lock()
|
sws.writeMutex.Lock()
|
||||||
defer sws.writeMutex.Unlock()
|
defer sws.writeMutex.Unlock()
|
||||||
err := sws.wsConn.WriteMessage(websocket.TextMessage, data)
|
wsData, err := json.Marshal(WsMsg{
|
||||||
|
Type: WsMsgCmd,
|
||||||
|
Data: base64.StdEncoding.EncodeToString(data),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to encoding to json")
|
||||||
|
}
|
||||||
|
err = sws.wsConn.WriteMessage(websocket.TextMessage, wsData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "failed to write to master")
|
return errors.Wrapf(err, "failed to write to master")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,21 +80,27 @@ func (sws *LocalWsSession) receiveWsMsg(exitCh chan bool) {
|
|||||||
global.LOG.Errorf("reading webSocket message failed, err: %v", err)
|
global.LOG.Errorf("reading webSocket message failed, err: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
msgObj := wsMsg{}
|
msgObj := WsMsg{}
|
||||||
_ = json.Unmarshal(wsData, &msgObj)
|
_ = json.Unmarshal(wsData, &msgObj)
|
||||||
switch msgObj.Type {
|
switch msgObj.Type {
|
||||||
case wsMsgResize:
|
case WsMsgResize:
|
||||||
if msgObj.Cols > 0 && msgObj.Rows > 0 {
|
if msgObj.Cols > 0 && msgObj.Rows > 0 {
|
||||||
if err := sws.slave.ResizeTerminal(msgObj.Cols, msgObj.Rows); err != nil {
|
if err := sws.slave.ResizeTerminal(msgObj.Cols, msgObj.Rows); err != nil {
|
||||||
global.LOG.Errorf("ssh pty change windows size failed, err: %v", err)
|
global.LOG.Errorf("ssh pty change windows size failed, err: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case wsMsgCmd:
|
case WsMsgCmd:
|
||||||
decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Data)
|
decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.LOG.Errorf("websock cmd string base64 decoding failed, err: %v", err)
|
global.LOG.Errorf("websock cmd string base64 decoding failed, err: %v", err)
|
||||||
}
|
}
|
||||||
sws.sendWebsocketInputCommandToSshSessionStdinPipe(decodeBytes)
|
sws.sendWebsocketInputCommandToSshSessionStdinPipe(decodeBytes)
|
||||||
|
case WsMsgHeartbeat:
|
||||||
|
// 接收到心跳包后将心跳包原样返回,可以用于网络延迟检测等情况
|
||||||
|
err = wsConn.WriteMessage(websocket.TextMessage, wsData)
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("ssh sending heartbeat to webSocket failed, err: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,17 +35,17 @@ func (w *safeBuffer) Reset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
wsMsgCmd = "cmd"
|
WsMsgCmd = "cmd"
|
||||||
wsMsgResize = "resize"
|
WsMsgResize = "resize"
|
||||||
wsMsgHeartbeat = "heartbeat"
|
WsMsgHeartbeat = "heartbeat"
|
||||||
)
|
)
|
||||||
|
|
||||||
type wsMsg struct {
|
type WsMsg struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Data string `json:"data,omitempty"` // wsMsgCmd
|
Data string `json:"data,omitempty"` // WsMsgCmd
|
||||||
Cols int `json:"cols,omitempty"` // wsMsgResize
|
Cols int `json:"cols,omitempty"` // WsMsgResize
|
||||||
Rows int `json:"rows,omitempty"` // wsMsgResize
|
Rows int `json:"rows,omitempty"` // WsMsgResize
|
||||||
Timestamp int `json:"timestamp,omitempty"` // wsMsgHeartbeat
|
Timestamp int `json:"timestamp,omitempty"` // WsMsgHeartbeat
|
||||||
}
|
}
|
||||||
|
|
||||||
type LogicSshWsSession struct {
|
type LogicSshWsSession struct {
|
||||||
@ -127,22 +127,22 @@ func (sws *LogicSshWsSession) receiveWsMsg(exitCh chan bool) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
msgObj := wsMsg{}
|
msgObj := WsMsg{}
|
||||||
_ = json.Unmarshal(wsData, &msgObj)
|
_ = json.Unmarshal(wsData, &msgObj)
|
||||||
switch msgObj.Type {
|
switch msgObj.Type {
|
||||||
case wsMsgResize:
|
case WsMsgResize:
|
||||||
if msgObj.Cols > 0 && msgObj.Rows > 0 {
|
if msgObj.Cols > 0 && msgObj.Rows > 0 {
|
||||||
if err := sws.session.WindowChange(msgObj.Rows, msgObj.Cols); err != nil {
|
if err := sws.session.WindowChange(msgObj.Rows, msgObj.Cols); err != nil {
|
||||||
global.LOG.Errorf("ssh pty change windows size failed, err: %v", err)
|
global.LOG.Errorf("ssh pty change windows size failed, err: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case wsMsgCmd:
|
case WsMsgCmd:
|
||||||
decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Data)
|
decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.LOG.Errorf("websock cmd string base64 decoding failed, err: %v", err)
|
global.LOG.Errorf("websock cmd string base64 decoding failed, err: %v", err)
|
||||||
}
|
}
|
||||||
sws.sendWebsocketInputCommandToSshSessionStdinPipe(decodeBytes)
|
sws.sendWebsocketInputCommandToSshSessionStdinPipe(decodeBytes)
|
||||||
case wsMsgHeartbeat:
|
case WsMsgHeartbeat:
|
||||||
// 接收到心跳包后将心跳包原样返回,可以用于网络延迟检测等情况
|
// 接收到心跳包后将心跳包原样返回,可以用于网络延迟检测等情况
|
||||||
err = wsConn.WriteMessage(websocket.TextMessage, wsData)
|
err = wsConn.WriteMessage(websocket.TextMessage, wsData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -173,8 +173,8 @@ func (sws *LogicSshWsSession) sendComboOutput(exitCh chan bool) {
|
|||||||
}
|
}
|
||||||
bs := sws.comboOutput.Bytes()
|
bs := sws.comboOutput.Bytes()
|
||||||
if len(bs) > 0 {
|
if len(bs) > 0 {
|
||||||
wsData, err := json.Marshal(wsMsg{
|
wsData, err := json.Marshal(WsMsg{
|
||||||
Type: wsMsgCmd,
|
Type: WsMsgCmd,
|
||||||
Data: base64.StdEncoding.EncodeToString(bs),
|
Data: base64.StdEncoding.EncodeToString(bs),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -68,15 +68,17 @@ const initError = (errorInfo: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function onClose() {
|
function onClose(isKeepShow: boolean = false) {
|
||||||
window.removeEventListener('resize', changeTerminalSize);
|
window.removeEventListener('resize', changeTerminalSize);
|
||||||
try {
|
try {
|
||||||
terminalSocket.value?.close();
|
terminalSocket.value?.close();
|
||||||
} catch {}
|
} catch {}
|
||||||
|
if (!isKeepShow) {
|
||||||
try {
|
try {
|
||||||
term.value.dispose();
|
term.value.dispose();
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// terminal 相关代码 start
|
// terminal 相关代码 start
|
||||||
|
|
||||||
@ -162,7 +164,7 @@ const onWSReceive = (message: MessageEvent) => {
|
|||||||
switch (wsMsg.type) {
|
switch (wsMsg.type) {
|
||||||
case 'cmd': {
|
case 'cmd': {
|
||||||
term.value.element && term.value.focus();
|
term.value.element && term.value.focus();
|
||||||
term.value.write(Base64.decode(wsMsg.data));
|
wsMsg.data && term.value.write(Base64.decode(wsMsg.data)); // 这里理论上不用判断,但是Redis和Ctr还没实现Alive处理,所以exit后会一直发数据,todo
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'heartbeat': {
|
case 'heartbeat': {
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
{{ $t('commons.button.conn') }}
|
{{ $t('commons.button.conn') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-else @click="handleClose()">{{ $t('commons.button.disconn') }}</el-button>
|
<el-button v-else @click="handleClose()">{{ $t('commons.button.disconn') }}</el-button>
|
||||||
<div style="height: calc(100vh - 302px)" :id="'terminal-exec'"></div>
|
<Terminal style="height: calc(100vh - 302px)" ref="terminalRef"></Terminal>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
</template>
|
</template>
|
||||||
@ -52,24 +52,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { ElForm, FormInstance } from 'element-plus';
|
import { ElForm, FormInstance } from 'element-plus';
|
||||||
import { Terminal } from 'xterm';
|
|
||||||
import { AttachAddon } from 'xterm-addon-attach';
|
|
||||||
import { Base64 } from 'js-base64';
|
|
||||||
import 'xterm/css/xterm.css';
|
|
||||||
import { FitAddon } from 'xterm-addon-fit';
|
|
||||||
import { Rules } from '@/global/form-rules';
|
import { Rules } from '@/global/form-rules';
|
||||||
import { isJson } from '@/utils/util';
|
import Terminal from '@/components/terminal/index.vue';
|
||||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
|
|
||||||
const terminalVisiable = ref(false);
|
const terminalVisiable = ref(false);
|
||||||
const terminalOpen = ref(false);
|
const terminalOpen = ref(false);
|
||||||
const fitAddon = new FitAddon();
|
|
||||||
let terminalSocket = ref(null) as unknown as WebSocket;
|
|
||||||
let term = ref(null) as unknown as Terminal;
|
|
||||||
const loading = ref(true);
|
|
||||||
const runRealTerminal = () => {
|
|
||||||
loading.value = false;
|
|
||||||
};
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
isCustom: false,
|
isCustom: false,
|
||||||
command: '',
|
command: '',
|
||||||
@ -78,6 +66,7 @@ const form = reactive({
|
|||||||
});
|
});
|
||||||
type FormInstance = InstanceType<typeof ElForm>;
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
const formRef = ref<FormInstance>();
|
const formRef = ref<FormInstance>();
|
||||||
|
const terminalRef = ref<InstanceType<typeof Terminal> | null>(null);
|
||||||
|
|
||||||
interface DialogProps {
|
interface DialogProps {
|
||||||
containerID: string;
|
containerID: string;
|
||||||
@ -89,125 +78,31 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
|
|||||||
form.user = '';
|
form.user = '';
|
||||||
form.command = '/bin/bash';
|
form.command = '/bin/bash';
|
||||||
terminalOpen.value = false;
|
terminalOpen.value = false;
|
||||||
window.addEventListener('resize', changeTerminalSize);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onChangeCommand = async () => {
|
const onChangeCommand = async () => {
|
||||||
form.command = '';
|
form.command = '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const onWSReceive = (message: any) => {
|
|
||||||
if (!isJson(message.data)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data = JSON.parse(message.data);
|
|
||||||
term.element && term.focus();
|
|
||||||
term.write(data.Data);
|
|
||||||
};
|
|
||||||
|
|
||||||
const errorRealTerminal = (ex: any) => {
|
|
||||||
let message = ex.message;
|
|
||||||
if (!message) message = 'disconnected';
|
|
||||||
term.write(`\x1b[31m${message}\x1b[m\r\n`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeRealTerminal = (ev: CloseEvent) => {
|
|
||||||
term.write(ev.reason);
|
|
||||||
};
|
|
||||||
|
|
||||||
const initTerm = (formEl: FormInstance | undefined) => {
|
const initTerm = (formEl: FormInstance | undefined) => {
|
||||||
if (!formEl) return;
|
if (!formEl) return;
|
||||||
formEl.validate(async (valid) => {
|
formEl.validate(async (valid) => {
|
||||||
if (!valid) return;
|
if (!valid) return;
|
||||||
let href = window.location.href;
|
|
||||||
let protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss';
|
|
||||||
let ipLocal = href.split('//')[1].split('/')[0];
|
|
||||||
terminalOpen.value = true;
|
terminalOpen.value = true;
|
||||||
let ifm = document.getElementById('terminal-exec') as HTMLInputElement | null;
|
terminalRef.value!.acceptParams({
|
||||||
term = new Terminal({
|
endpoint: '/api/v1/containers/exec',
|
||||||
lineHeight: 1.2,
|
args: `containerid=${form.containerID}&user=${form.user}&command=${form.command}`,
|
||||||
fontSize: 12,
|
error: '',
|
||||||
fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
|
|
||||||
theme: {
|
|
||||||
background: '#000000',
|
|
||||||
},
|
|
||||||
cursorBlink: true,
|
|
||||||
cursorStyle: 'underline',
|
|
||||||
scrollback: 100,
|
|
||||||
tabStopWidth: 4,
|
|
||||||
});
|
});
|
||||||
if (ifm) {
|
|
||||||
term.open(ifm);
|
|
||||||
terminalSocket = new WebSocket(
|
|
||||||
`${protocol}://${ipLocal}/api/v1/containers/exec?containerid=${form.containerID}&cols=${term.cols}&rows=${term.rows}&user=${form.user}&command=${form.command}`,
|
|
||||||
);
|
|
||||||
terminalSocket.onopen = runRealTerminal;
|
|
||||||
terminalSocket.onmessage = onWSReceive;
|
|
||||||
terminalSocket.onclose = closeRealTerminal;
|
|
||||||
terminalSocket.onerror = errorRealTerminal;
|
|
||||||
term.onData((data: any) => {
|
|
||||||
if (isWsOpen()) {
|
|
||||||
terminalSocket.send(
|
|
||||||
JSON.stringify({
|
|
||||||
type: 'cmd',
|
|
||||||
cmd: Base64.encode(data),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
term.loadAddon(new AttachAddon(terminalSocket));
|
|
||||||
term.loadAddon(fitAddon);
|
|
||||||
setTimeout(() => {
|
|
||||||
fitAddon.fit();
|
|
||||||
if (isWsOpen()) {
|
|
||||||
terminalSocket.send(
|
|
||||||
JSON.stringify({
|
|
||||||
type: 'resize',
|
|
||||||
cols: term.cols,
|
|
||||||
rows: term.rows,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, 30);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const fitTerm = () => {
|
|
||||||
fitAddon.fit();
|
|
||||||
};
|
|
||||||
|
|
||||||
const isWsOpen = () => {
|
|
||||||
const readyState = terminalSocket && terminalSocket.readyState;
|
|
||||||
if (readyState) {
|
|
||||||
return readyState === 1;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
window.removeEventListener('resize', changeTerminalSize);
|
terminalRef.value?.onClose();
|
||||||
if (isWsOpen()) {
|
|
||||||
terminalSocket && terminalSocket.close();
|
|
||||||
term.dispose();
|
|
||||||
}
|
|
||||||
terminalVisiable.value = false;
|
terminalVisiable.value = false;
|
||||||
terminalOpen.value = false;
|
terminalOpen.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeTerminalSize() {
|
|
||||||
fitTerm();
|
|
||||||
const { cols, rows } = term;
|
|
||||||
if (isWsOpen()) {
|
|
||||||
terminalSocket.send(
|
|
||||||
JSON.stringify({
|
|
||||||
type: 'resize',
|
|
||||||
cols: cols,
|
|
||||||
rows: rows,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
acceptParams,
|
acceptParams,
|
||||||
});
|
});
|
||||||
|
@ -20,7 +20,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #main v-if="redisIsExist && !isOnSetting">
|
<template #main v-if="redisIsExist && !isOnSetting">
|
||||||
<Terminal :key="isRefresh" ref="terminalRef" />
|
<Terminal
|
||||||
|
style="height: calc(100vh - 370px)"
|
||||||
|
:key="isRefresh"
|
||||||
|
ref="terminalRef"
|
||||||
|
v-show="terminalShow"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</LayoutContent>
|
</LayoutContent>
|
||||||
|
|
||||||
@ -55,7 +60,7 @@
|
|||||||
import LayoutContent from '@/layout/layout-content.vue';
|
import LayoutContent from '@/layout/layout-content.vue';
|
||||||
import Setting from '@/views/database/redis/setting/index.vue';
|
import Setting from '@/views/database/redis/setting/index.vue';
|
||||||
import Password from '@/views/database/redis/password/index.vue';
|
import Password from '@/views/database/redis/password/index.vue';
|
||||||
import Terminal from '@/views/database/redis/terminal/index.vue';
|
import Terminal from '@/components/terminal/index.vue';
|
||||||
import AppStatus from '@/components/app-status/index.vue';
|
import AppStatus from '@/components/app-status/index.vue';
|
||||||
import { nextTick, onBeforeUnmount, ref } from 'vue';
|
import { nextTick, onBeforeUnmount, ref } from 'vue';
|
||||||
import { App } from '@/api/interface/app';
|
import { App } from '@/api/interface/app';
|
||||||
@ -65,12 +70,13 @@ import router from '@/routers';
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const maskShow = ref(true);
|
const maskShow = ref(true);
|
||||||
|
|
||||||
const terminalRef = ref();
|
const terminalRef = ref<InstanceType<typeof Terminal> | null>(null);
|
||||||
const settingRef = ref();
|
const settingRef = ref();
|
||||||
const isOnSetting = ref(false);
|
const isOnSetting = ref(false);
|
||||||
const redisIsExist = ref(false);
|
const redisIsExist = ref(false);
|
||||||
const redisStatus = ref();
|
const redisStatus = ref();
|
||||||
const redisName = ref();
|
const redisName = ref();
|
||||||
|
const terminalShow = ref(false);
|
||||||
|
|
||||||
const redisCommandPort = ref();
|
const redisCommandPort = ref();
|
||||||
const commandVisiable = ref(false);
|
const commandVisiable = ref(false);
|
||||||
@ -80,6 +86,7 @@ const isRefresh = ref();
|
|||||||
const onSetting = async () => {
|
const onSetting = async () => {
|
||||||
isOnSetting.value = true;
|
isOnSetting.value = true;
|
||||||
terminalRef.value?.onClose(false);
|
terminalRef.value?.onClose(false);
|
||||||
|
terminalShow.value = false;
|
||||||
settingRef.value!.acceptParams({ status: redisStatus.value, redisName: redisName.value });
|
settingRef.value!.acceptParams({ status: redisStatus.value, redisName: redisName.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -114,19 +121,32 @@ const checkExist = (data: App.CheckInstalled) => {
|
|||||||
if (redisStatus.value === 'Running') {
|
if (redisStatus.value === 'Running') {
|
||||||
loadDashboardPort();
|
loadDashboardPort();
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
terminalRef.value.acceptParams();
|
terminalShow.value = true;
|
||||||
|
terminalRef.value.acceptParams({
|
||||||
|
endpoint: '/api/v1/databases/redis/exec',
|
||||||
|
args: '',
|
||||||
|
error: '',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const initTerminal = async () => {
|
const initTerminal = async () => {
|
||||||
if (redisStatus.value === 'Running') {
|
if (redisStatus.value === 'Running') {
|
||||||
terminalRef.value.acceptParams();
|
nextTick(() => {
|
||||||
|
terminalShow.value = true;
|
||||||
|
terminalRef.value.acceptParams({
|
||||||
|
endpoint: '/api/v1/databases/redis/exec',
|
||||||
|
args: '',
|
||||||
|
error: '',
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const closeTerminal = async (isKeepShow: boolean) => {
|
const closeTerminal = async (isKeepShow: boolean) => {
|
||||||
isRefresh.value = !isRefresh.value;
|
isRefresh.value = !isRefresh.value;
|
||||||
terminalRef.value?.onClose(isKeepShow);
|
terminalRef.value?.onClose(isKeepShow);
|
||||||
|
terminalShow.value = isKeepShow;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onBefore = () => {
|
const onBefore = () => {
|
||||||
|
@ -1,149 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-show="terminalShow" style="height: 100%">
|
|
||||||
<div style="height: calc(100vh - 370px)" :id="'terminal-exec'"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { Terminal } from 'xterm';
|
|
||||||
import { AttachAddon } from 'xterm-addon-attach';
|
|
||||||
import { Base64 } from 'js-base64';
|
|
||||||
import 'xterm/css/xterm.css';
|
|
||||||
import { FitAddon } from 'xterm-addon-fit';
|
|
||||||
import { isJson } from '@/utils/util';
|
|
||||||
|
|
||||||
const fitAddon = new FitAddon();
|
|
||||||
let terminalSocket = ref(null) as unknown as WebSocket;
|
|
||||||
let term = ref(null) as unknown as Terminal;
|
|
||||||
const loading = ref(true);
|
|
||||||
const runRealTerminal = () => {
|
|
||||||
loading.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const terminalShow = ref(false);
|
|
||||||
|
|
||||||
const acceptParams = async (): Promise<void> => {
|
|
||||||
terminalShow.value = true;
|
|
||||||
initTerm();
|
|
||||||
window.addEventListener('resize', changeTerminalSize);
|
|
||||||
};
|
|
||||||
const onClose = async (isKeepShow: boolean) => {
|
|
||||||
window.removeEventListener('resize', changeTerminalSize);
|
|
||||||
if (isWsOpen()) {
|
|
||||||
terminalSocket && terminalSocket.close();
|
|
||||||
}
|
|
||||||
if (!isKeepShow) {
|
|
||||||
term.dispose();
|
|
||||||
}
|
|
||||||
terminalShow.value = isKeepShow;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onWSReceive = (message: any) => {
|
|
||||||
if (!isJson(message.data)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data = JSON.parse(message.data);
|
|
||||||
term.element && term.focus();
|
|
||||||
term.write(data.Data);
|
|
||||||
};
|
|
||||||
|
|
||||||
const errorRealTerminal = (ex: any) => {
|
|
||||||
let message = ex.message;
|
|
||||||
if (!message) message = 'disconnected';
|
|
||||||
term.write(`\x1b[31m${message}\x1b[m\r\n`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeRealTerminal = (ev: CloseEvent) => {
|
|
||||||
term.write(ev.reason);
|
|
||||||
};
|
|
||||||
|
|
||||||
const initTerm = () => {
|
|
||||||
let ifm = document.getElementById('terminal-exec') as HTMLInputElement | null;
|
|
||||||
let href = window.location.href;
|
|
||||||
let protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss';
|
|
||||||
let ipLocal = href.split('//')[1].split('/')[0];
|
|
||||||
term = new Terminal({
|
|
||||||
lineHeight: 1.2,
|
|
||||||
fontSize: 12,
|
|
||||||
fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
|
|
||||||
theme: {
|
|
||||||
background: '#000000',
|
|
||||||
},
|
|
||||||
cursorBlink: true,
|
|
||||||
cursorStyle: 'underline',
|
|
||||||
scrollback: 100,
|
|
||||||
tabStopWidth: 4,
|
|
||||||
});
|
|
||||||
if (ifm) {
|
|
||||||
term.open(ifm);
|
|
||||||
terminalSocket = new WebSocket(
|
|
||||||
`${protocol}://${ipLocal}/api/v1/databases/redis/exec?cols=${term.cols}&rows=${term.rows}`,
|
|
||||||
);
|
|
||||||
terminalSocket.onopen = runRealTerminal;
|
|
||||||
terminalSocket.onmessage = onWSReceive;
|
|
||||||
terminalSocket.onclose = closeRealTerminal;
|
|
||||||
terminalSocket.onerror = errorRealTerminal;
|
|
||||||
term.onData((data: any) => {
|
|
||||||
if (isWsOpen()) {
|
|
||||||
terminalSocket.send(
|
|
||||||
JSON.stringify({
|
|
||||||
type: 'cmd',
|
|
||||||
cmd: Base64.encode(data),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
term.loadAddon(new AttachAddon(terminalSocket));
|
|
||||||
term.loadAddon(fitAddon);
|
|
||||||
setTimeout(() => {
|
|
||||||
fitAddon.fit();
|
|
||||||
if (isWsOpen()) {
|
|
||||||
terminalSocket.send(
|
|
||||||
JSON.stringify({
|
|
||||||
type: 'resize',
|
|
||||||
cols: term.cols,
|
|
||||||
rows: term.rows,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, 30);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fitTerm = () => {
|
|
||||||
fitAddon.fit();
|
|
||||||
};
|
|
||||||
|
|
||||||
const isWsOpen = () => {
|
|
||||||
const readyState = terminalSocket && terminalSocket.readyState;
|
|
||||||
if (readyState) {
|
|
||||||
return readyState === 1;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
function changeTerminalSize() {
|
|
||||||
fitTerm();
|
|
||||||
const { cols, rows } = term;
|
|
||||||
if (isWsOpen()) {
|
|
||||||
terminalSocket.send(
|
|
||||||
JSON.stringify({
|
|
||||||
type: 'resize',
|
|
||||||
cols: cols,
|
|
||||||
rows: rows,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defineExpose({
|
|
||||||
acceptParams,
|
|
||||||
onClose,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
#terminal {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
Loading…
x
Reference in New Issue
Block a user