diff --git a/mayfly_go_web/package.json b/mayfly_go_web/package.json
index 4a9f737e..68b5533d 100644
--- a/mayfly_go_web/package.json
+++ b/mayfly_go_web/package.json
@@ -32,6 +32,7 @@
"sortablejs": "^1.15.2",
"splitpanes": "^3.1.5",
"sql-formatter": "^15.0.2",
+ "trzsz": "^1.1.5",
"uuid": "^9.0.1",
"vue": "^3.4.21",
"vue-router": "^4.3.0",
diff --git a/mayfly_go_web/src/assets/iconfont/iconfont.js b/mayfly_go_web/src/assets/iconfont/iconfont.js
index d3bb9224..dca0fc9b 100644
--- a/mayfly_go_web/src/assets/iconfont/iconfont.js
+++ b/mayfly_go_web/src/assets/iconfont/iconfont.js
@@ -1,5 +1,5 @@
(window._iconfont_svg_string_3953964 =
- ''),
+ ''),
(function (c) {
var t = (t = document.getElementsByTagName('script'))[t.length - 1],
a = t.getAttribute('data-injectcss'),
diff --git a/mayfly_go_web/src/components/terminal-rdp/MachineRdp.vue b/mayfly_go_web/src/components/terminal-rdp/MachineRdp.vue
index bf87fe73..59b20593 100644
--- a/mayfly_go_web/src/components/terminal-rdp/MachineRdp.vue
+++ b/mayfly_go_web/src/components/terminal-rdp/MachineRdp.vue
@@ -6,17 +6,28 @@
-
-
-
+
+
+
+
+ Ctrl + Alt + Delete
+ Ctrl + Alt + Backspace
+ Windows + D
+ Windows + E
+ Windows + R
+ Windows
+
-
+
+
+
{
state.client.onclipboard = clipboard.onClipboard;
};
+const installResize = () => {
+ // 在resize事件结束后300毫秒执行
+ useEventListener('resize', debounce(resize, 300));
+};
+
const installDisplay = () => {
let { width, height, force } = state.size;
state.display = state.client.getDisplay();
@@ -172,7 +197,7 @@ const installDisplay = () => {
};
const installClient = () => {
- let tunnel = new Guacamole.WebSocketTunnel(getMachineRdpSocketUrl(props.machineId)) as any;
+ let tunnel = new Guacamole.WebSocketTunnel(getMachineRdpSocketUrl(props.authCert)) as any;
if (state.client) {
state.display?.scale(0);
uninstallKeyboard();
@@ -191,15 +216,18 @@ const installClient = () => {
console.log('statechange', st);
state.status = st;
switch (st) {
- case 0: // 'CONNECTING'
+ case TunnelState.CONNECTING: // 'CONNECTING'
break;
- case 1: // 'OPEN'
+ case TunnelState.OPEN: // 'OPEN'
+ state.status = TerminalStatus.Connected;
emit('statusChange', TerminalStatus.Connected);
break;
- case 2: // 'CLOSED'
+ case TunnelState.CLOSED: // 'CLOSED'
+ state.status = TerminalStatus.Disconnected;
emit('statusChange', TerminalStatus.Disconnected);
break;
- case 3: // 'UNSTABLE'
+ case TunnelState.UNSTABLE: // 'UNSTABLE'
+ state.status = TerminalStatus.Error;
emit('statusChange', TerminalStatus.Error);
break;
}
@@ -207,25 +235,25 @@ const installClient = () => {
state.client.onstatechange = (clientState: any) => {
console.log('clientState', clientState);
- return;
switch (clientState) {
- case 0:
- // states.IDLE;
+ case ClientState.IDLE:
+ console.log('连接空闲');
break;
- case 1:
- break;
- case 2:
+ case ClientState.CONNECTING:
console.log('连接中...');
break;
- case 3:
+ case ClientState.WAITING:
+ console.log('等待服务器响应...');
+ break;
+ case ClientState.CONNECTED:
console.log('连接成功...');
- // states.CONNECTED;
- window.addEventListener('resize', resize);
- viewportRef.value.addEventListener('mouseenter', resize);
- clipboard.setRemoteClipboard(state.client);
+ break;
// eslint-disable-next-line no-fallthrough
- case 4:
- case 5:
+ case ClientState.DISCONNECTING:
+ console.log('断开连接中...');
+ break;
+ case ClientState.DISCONNECTED:
+ console.log('已断开连接...');
break;
}
};
@@ -273,20 +301,25 @@ const resize = () => {
let box = elm.parentElement;
- let pixelDensity = window.devicePixelRatio || 1;
- const width = box.clientWidth * pixelDensity;
- const height = box.clientHeight * pixelDensity;
- state.size.width = width;
- state.size.height = height;
+ state.size.width = box.clientWidth;
+ state.size.height = box.clientHeight;
+
+ const width = parseInt(String(box.clientWidth));
+ const height = parseInt(String(box.clientHeight));
+
if (state.display.getWidth() !== width || state.display.getHeight() !== height) {
- state.client.sendSize(width, height);
+ if (state.status !== TerminalStatus.Connected) {
+ connect(width, height);
+ } else {
+ state.client.sendSize(width, height);
+ }
}
// setting timeout so display has time to get the correct size
- setTimeout(() => {
- const scale = Math.min(box.clientWidth / Math.max(state.display.getWidth(), 1), box.clientHeight / Math.max(state.display.getHeight(), 1));
- state.display.scale(scale);
- console.log(state.size);
- }, 100);
+ // setTimeout(() => {
+ // const scale = Math.min(box.clientWidth / Math.max(state.display.getWidth(), 1), box.clientHeight / Math.max(state.display.getHeight(), 1));
+ // state.display.scale(scale);
+ // console.log(state.size, scale);
+ // }, 100);
};
const handleMouseState = (mouseState: any, showCursor = false) => {
@@ -318,6 +351,7 @@ const connect = (width: number, height: number, force = false) => {
installMouse();
installTouchpad();
installClipboard();
+ installResize();
};
const disconnect = () => {
@@ -348,6 +382,7 @@ const onsubmitClipboard = (val: string) => {
const openFilesystem = async () => {
state.filesystemDialog.protocol = 2;
state.filesystemDialog.machineId = props.machineId;
+ state.filesystemDialog.authCertName = props.authCert;
state.filesystemDialog.fileId = props.machineId;
state.filesystemDialog.path = '/';
state.filesystemDialog.title = `远程桌面文件管理`;
@@ -392,6 +427,19 @@ const closeFullScreen = function () {
unWatchFullscreenChange(watchFullscreen);
};
+const openSendKeyboard = (keys: string[]) => {
+ if (!state.client) {
+ return;
+ }
+ for (let i = 0; i < keys.length; i++) {
+ state.client.sendKeyEvent(1, keys[i]);
+ }
+ for (let j = 0; j < keys.length; j++) {
+ state.client.sendKeyEvent(0, keys[j]);
+ }
+ ElMessage.success('发送组合键成功');
+};
+
const exposes = {
connect,
disconnect,
diff --git a/mayfly_go_web/src/components/terminal-rdp/guac/states.js b/mayfly_go_web/src/components/terminal-rdp/guac/states.js
index 5fa2d994..073c9dae 100644
--- a/mayfly_go_web/src/components/terminal-rdp/guac/states.js
+++ b/mayfly_go_web/src/components/terminal-rdp/guac/states.js
@@ -1,55 +1,78 @@
-export default {
- /**
- * The Guacamole connection has not yet been attempted.
- *
- * @type String
- */
- IDLE : "IDLE",
+export const ClientState = {
+ /**
+ * The client is idle, with no active connection.
+ *
+ * @type number
+ */
+ IDLE: 0,
- /**
- * The Guacamole connection is being established.
- *
- * @type String
- */
- CONNECTING : "CONNECTING",
+ /**
+ * The client is in the process of establishing a connection.
+ *
+ * @type {!number}
+ */
+ CONNECTING: 1,
- /**
- * The Guacamole connection has been successfully established, and the
- * client is now waiting for receipt of initial graphical data.
- *
- * @type String
- */
- WAITING : "WAITING",
+ /**
+ * The client is waiting on further information or a remote server to
+ * establish the connection.
+ *
+ * @type {!number}
+ */
+ WAITING: 2,
- /**
- * The Guacamole connection has been successfully established, and
- * initial graphical data has been received.
- *
- * @type String
- */
- CONNECTED : "CONNECTED",
+ /**
+ * The client is actively connected to a remote server.
+ *
+ * @type {!number}
+ */
+ CONNECTED: 3,
- /**
- * The Guacamole connection has terminated successfully. No errors are
- * indicated.
- *
- * @type String
- */
- DISCONNECTED : "DISCONNECTED",
+ /**
+ * The client is in the process of disconnecting from the remote server.
+ *
+ * @type {!number}
+ */
+ DISCONNECTING: 4,
- /**
- * The Guacamole connection has terminated due to an error reported by
- * the client. The associated error code is stored in statusCode.
- *
- * @type String
- */
- CLIENT_ERROR : "CLIENT_ERROR",
+ /**
+ * The client has completed the connection and is no longer connected.
+ *
+ * @type {!number}
+ */
+ DISCONNECTED: 5,
+};
- /**
- * The Guacamole connection has terminated due to an error reported by
- * the tunnel. The associated error code is stored in statusCode.
- *
- * @type String
- */
- TUNNEL_ERROR : "TUNNEL_ERROR"
-}
+export const TunnelState = {
+ /**
+ * A connection is in pending. It is not yet known whether connection was
+ * successful.
+ *
+ * @type {!number}
+ */
+ CONNECTING: 0,
+
+ /**
+ * Connection was successful, and data is being received.
+ *
+ * @type {!number}
+ */
+ OPEN: 1,
+
+ /**
+ * The connection is closed. Connection may not have been successful, the
+ * tunnel may have been explicitly closed by either side, or an error may
+ * have occurred.
+ *
+ * @type {!number}
+ */
+ CLOSED: 2,
+
+ /**
+ * The connection is open, but communication through the tunnel appears to
+ * be disrupted, and the connection may close as a result.
+ *
+ * @type {!number}
+ */
+ UNSTABLE: 3,
+};
diff --git a/mayfly_go_web/src/components/terminal/TerminalBody.vue b/mayfly_go_web/src/components/terminal/TerminalBody.vue
index ff7c7d47..fb239cd6 100644
--- a/mayfly_go_web/src/components/terminal/TerminalBody.vue
+++ b/mayfly_go_web/src/components/terminal/TerminalBody.vue
@@ -21,6 +21,7 @@ import { debounce } from 'lodash';
import { TerminalStatus } from './common';
import { useEventListener } from '@vueuse/core';
import themes from './themes';
+import { TrzszFilter } from 'trzsz';
const props = defineProps({
// mounted时,是否执行init方法
@@ -101,7 +102,6 @@ function init() {
}
nextTick(() => {
initTerm();
- initSocket();
});
}
@@ -119,23 +119,10 @@ function initTerm() {
term.open(terminalRef.value);
- // 注册自适应组件
- const fitAddon = new FitAddon();
- state.addon.fit = fitAddon;
- term.loadAddon(fitAddon);
- fitTerminal();
- // 注册窗口大小监听器
- useEventListener('resize', debounce(fitTerminal, 400));
+ initSocket();
- // 注册搜索组件
- const searchAddon = new SearchAddon();
- state.addon.search = searchAddon;
- term.loadAddon(searchAddon);
-
- // 注册 url link组件
- const weblinks = new WebLinksAddon();
- state.addon.weblinks = weblinks;
- term.loadAddon(weblinks);
+ // 注册插件
+ loadAddon();
// 注册自定义快捷键
term.attachCustomKeyEventHandler((event: KeyboardEvent) => {
@@ -153,7 +140,6 @@ function initSocket() {
if (!props.socketUrl) {
return;
}
-
socket = new WebSocket(`${props.socketUrl}&rows=${term?.rows}&cols=${term?.cols}`);
// 监听socket连接
socket.onopen = () => {
@@ -161,10 +147,6 @@ function initSocket() {
pingInterval = setInterval(sendPing, 15000);
state.status = TerminalStatus.Connected;
- // 注册 terminal 事件
- term.onResize((event) => sendResize(event.cols, event.rows));
- term.onData((event) => sendCmd(event));
-
focus();
// 如果有初始要执行的命令,则发送执行命令
@@ -184,12 +166,60 @@ function initSocket() {
console.log('terminal socket close...', e.reason);
state.status = TerminalStatus.Disconnected;
};
+}
- // 监听socket消息
- socket.onmessage = (msg: any) => {
- // msg.data是真正后端返回的数据
- write2Term(msg.data);
- };
+function loadAddon() {
+ // 注册自适应组件
+ const fitAddon = new FitAddon();
+ state.addon.fit = fitAddon;
+ term.loadAddon(fitAddon);
+ fitTerminal();
+ // 注册窗口大小监听器
+ useEventListener('resize', debounce(fitTerminal, 400));
+
+ // 注册搜索组件
+ const searchAddon = new SearchAddon();
+ state.addon.search = searchAddon;
+ term.loadAddon(searchAddon);
+
+ // 注册 url link组件
+ const weblinks = new WebLinksAddon();
+ state.addon.weblinks = weblinks;
+ term.loadAddon(weblinks);
+
+ // 注册 trzsz
+ // initialize trzsz filter
+ const trzsz = new TrzszFilter({
+ // write the server output to the terminal
+ writeToTerminal: (data: any) => term.write(typeof data === 'string' ? data : new Uint8Array(data)),
+ // send the user input to the server
+ sendToServer: sendCmd,
+ // the terminal columns
+ terminalColumns: term.cols,
+ // there is a windows shell
+ isWindowsShell: false,
+ });
+
+ // let trzsz process the server output
+ socket?.addEventListener('message', (e) => trzsz.processServerOutput(e.data));
+ // let trzsz process the user input
+ term.onData((data) => trzsz.processTerminalInput(data));
+ term.onBinary((data) => trzsz.processBinaryInput(data));
+ term.onResize((size) => {
+ sendResize(size.cols, size.rows);
+ // tell trzsz the terminal columns has been changed
+ trzsz.setTerminalColumns(size.cols);
+ });
+ window.addEventListener('resize', () => state.addon.fit.fit());
+ // enable drag files or directories to upload
+ terminalRef.value.addEventListener('dragover', (event: Event) => event.preventDefault());
+ terminalRef.value.addEventListener('drop', (event: any) => {
+ event.preventDefault();
+ trzsz
+ .uploadFiles(event.dataTransfer.items)
+ .then(() => console.log('upload success'))
+ .catch((err) => console.log(err));
+ });
}
// 写入内容至终端
diff --git a/mayfly_go_web/src/components/terminal/TerminalLog.vue b/mayfly_go_web/src/components/terminal/TerminalLog.vue
index 7c738195..8d5e94e6 100644
--- a/mayfly_go_web/src/components/terminal/TerminalLog.vue
+++ b/mayfly_go_web/src/components/terminal/TerminalLog.vue
@@ -51,15 +51,15 @@ const extra = computed(() => {
// 定时获取最新日志
const { pause, resume } = useIntervalFn(() => {
writeLog();
-}, 2000);
+}, 500);
watch(
() => logId.value,
(logId: number) => {
+ terminalRef.value?.clear();
if (!logId) {
return;
}
- terminalRef.value?.clear();
writeLog();
}
);
diff --git a/mayfly_go_web/src/views/ops/db/DbTransferList.vue b/mayfly_go_web/src/views/ops/db/DbTransferList.vue
index b974ad4a..e3b75ff5 100644
--- a/mayfly_go_web/src/views/ops/db/DbTransferList.vue
+++ b/mayfly_go_web/src/views/ops/db/DbTransferList.vue
@@ -34,11 +34,9 @@
编辑
- 日志
+ 日志
停止
- 运行
+ 运行
@@ -116,7 +114,6 @@ const state = reactive({
data: null as any,
running: false,
},
- runBtnDisabled: false,
});
const { selectionData, query, editDialog, logsDialog } = toRefs(state);
@@ -153,7 +150,7 @@ const stop = async (id: any) => {
search();
};
-const log = async (data: any) => {
+const log = (data: any) => {
state.logsDialog.logId = data.logId;
state.logsDialog.visible = true;
state.logsDialog.title = '数据库迁移日志';
@@ -167,16 +164,17 @@ const reRun = async (data: any) => {
type: 'warning',
});
try {
- state.runBtnDisabled = true;
- await dbApi.runDbTransferTask.request({ taskId: data.id });
+ let res = await dbApi.runDbTransferTask.request({ taskId: data.id });
+ console.log(res);
ElMessage.success('运行成功');
+ // 拿到日志id之后,弹出日志弹窗
+ log({ logId: res, state: 1 });
} catch (e) {
- state.runBtnDisabled = false;
+ //
}
// 延迟2秒执行,后端异步执行
setTimeout(() => {
search();
- state.runBtnDisabled = false;
}, 2000);
};
diff --git a/mayfly_go_web/src/views/ops/db/InstanceEdit.vue b/mayfly_go_web/src/views/ops/db/InstanceEdit.vue
index 0ebd5ff3..b884360a 100644
--- a/mayfly_go_web/src/views/ops/db/InstanceEdit.vue
+++ b/mayfly_go_web/src/views/ops/db/InstanceEdit.vue
@@ -86,7 +86,7 @@
/>
-