diff --git a/orion-visor-ui/src/views/terminal/components/layout/terminal-panel.vue b/orion-visor-ui/src/views/terminal/components/layout/terminal-panel.vue
index 3e9f46cf..4dda783e 100644
--- a/orion-visor-ui/src/views/terminal/components/layout/terminal-panel.vue
+++ b/orion-visor-ui/src/views/terminal/components/layout/terminal-panel.vue
@@ -32,15 +32,25 @@
- {{ item.title }}
+ {{ item.title }}
-
+
-
+
-
+
+
+
@@ -60,6 +70,7 @@
import SshView from '../view/ssh/ssh-view.vue';
import SftpView from '../view/sftp/sftp-view.vue';
import RdpView from '../view/rdp/rdp-view.vue';
+ import VncView from '../view/vnc/vnc-view.vue';
const props = defineProps<{
index: number;
@@ -118,56 +129,62 @@
.terminal-panel-container {
width: 100%;
height: 100%;
- }
- .tab-title-wrapper {
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- padding: 11px 18px 9px 14px;
- background: var(--bg);
- position: relative;
- transition: all .3s;
-
- .tab-title-icon {
- font-size: 16px;
- margin-right: 6px;
- }
-
- &:hover {
- filter: brightness(1.04);
- }
-
- &::after {
- content: '';
- width: calc(100% - 3px);
- height: 2px;
- background: var(--color);
- position: absolute;
- left: 1px;
- bottom: -1px;
- }
- }
-
- .panel-extra {
- margin-right: 8px;
-
- .extra-icon {
- color: var(--color-panel-text-1);
- transition: 0.2s;
- font-size: 16px;
- cursor: pointer;
- width: 24px;
- height: 24px;
+ .tab-title-wrapper {
+ width: 100%;
+ height: 100%;
display: flex;
align-items: center;
- justify-content: center;
- border-radius: 50%;
+ padding: 4px 18px 4px 14px;
+ background: var(--bg);
+ position: relative;
+ transition: all .3s;
+
+ .tab-title-icon {
+ font-size: 16px;
+ margin-right: 6px;
+ }
&:hover {
- background: var(--color-bg-panel-icon-1);
+ filter: brightness(1.04);
}
+
+ &::after {
+ content: '';
+ width: calc(100% - 3px);
+ height: 2px;
+ background: var(--color);
+ position: absolute;
+ left: 1px;
+ bottom: -1px;
+ }
+ }
+
+ .panel-extra {
+ margin-right: 8px;
+
+ .extra-icon {
+ color: var(--color-panel-text-1);
+ transition: 0.2s;
+ font-size: 16px;
+ cursor: pointer;
+ width: 24px;
+ height: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+
+ &:hover {
+ background: var(--color-bg-panel-icon-1);
+ }
+ }
+ }
+
+ .session-container {
+ width: 100%;
+ height: 100%;
+ position: relative;
}
}
diff --git a/orion-visor-ui/src/views/terminal/components/view/vnc/vnc-action-bar.vue b/orion-visor-ui/src/views/terminal/components/view/vnc/vnc-action-bar.vue
new file mode 100644
index 00000000..90992586
--- /dev/null
+++ b/orion-visor-ui/src/views/terminal/components/view/vnc/vnc-action-bar.vue
@@ -0,0 +1,208 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/orion-visor-ui/src/views/terminal/components/view/vnc/vnc-view.vue b/orion-visor-ui/src/views/terminal/components/view/vnc/vnc-view.vue
new file mode 100644
index 00000000..ab30ac32
--- /dev/null
+++ b/orion-visor-ui/src/views/terminal/components/view/vnc/vnc-view.vue
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
diff --git a/orion-visor-ui/src/views/terminal/service/channel/base-guacd-channel.ts b/orion-visor-ui/src/views/terminal/service/channel/base-guacd-channel.ts
index 06b73c50..6510fe72 100644
--- a/orion-visor-ui/src/views/terminal/service/channel/base-guacd-channel.ts
+++ b/orion-visor-ui/src/views/terminal/service/channel/base-guacd-channel.ts
@@ -1,4 +1,4 @@
-import type { GuacdReactiveSessionStatus, IGuacdChannel, ITerminalSession } from '@/views/terminal/interfaces';
+import type { ITerminalSession, IGuacdChannel, GuacdReactiveSessionStatus } from '@/views/terminal/interfaces';
import type { OutputPayload } from '../../types/protocol';
import { InputProtocol, OutputProtocol } from '../../types/protocol';
import { TerminalCloseCode, TerminalMessages } from '@/views/terminal/types/const';
@@ -147,7 +147,8 @@ export default abstract class BaseGuacdChannel {
// 打开 channel
@@ -20,7 +20,7 @@ export default class RdpChannel extends BaseGuacdChannel {
enableAudioInput: sessionSetting.enableAudioInput,
enableAudioOutput: sessionSetting.enableAudioOutput,
driveMountMode: sessionSetting.driveMountMode,
- colorDepth: graphSetting.colorDepth || 16,
+ colorDepth: graphSetting.colorDepth || 24,
forceLossless: graphSetting.forceLossless,
enableWallpaper: graphSetting.enableWallpaper,
enableTheming: graphSetting.enableTheming,
diff --git a/orion-visor-ui/src/views/terminal/service/channel/vnc-channel.ts b/orion-visor-ui/src/views/terminal/service/channel/vnc-channel.ts
new file mode 100644
index 00000000..2216786e
--- /dev/null
+++ b/orion-visor-ui/src/views/terminal/service/channel/vnc-channel.ts
@@ -0,0 +1,34 @@
+import type { IVncSession } from '@/views/terminal/interfaces';
+import type { OutputPayload } from '@/views/terminal/types/protocol';
+import { useTerminalStore } from '@/store';
+import { TerminalSessionTypes } from '@/views/terminal/types/const';
+import { getTerminalAccessToken, openTerminalAccessChannel } from '@/api/terminal/terminal';
+import BaseGuacdChannel from './base-guacd-channel';
+
+// 终端通信会话 VNC 会话实现
+export default class VncChannel extends BaseGuacdChannel {
+
+ // 打开 channel
+ protected async openChannel(): Promise {
+ const setting = useTerminalStore().preference.vncGraphSetting;
+ const { data } = await getTerminalAccessToken({
+ hostId: this.session.info.hostId,
+ connectType: TerminalSessionTypes.VNC.type,
+ extra: {
+ colorDepth: setting.colorDepth || 24,
+ forceLossless: setting.forceLossless,
+ swapRedBlue: setting.swapRedBlue,
+ cursor: setting.cursor,
+ compressLevel: setting.compressLevel,
+ qualityLevel: setting.qualityLevel,
+ }
+ });
+ // 打开 channel
+ this.client = await openTerminalAccessChannel(TerminalSessionTypes.VNC.channel, data);
+ }
+
+ // 处理修改大小
+ processResize({ width, height }: OutputPayload): void {
+ }
+
+}
diff --git a/orion-visor-ui/src/views/terminal/service/handler/rdp-session-clipboard-handler.ts b/orion-visor-ui/src/views/terminal/service/handler/guacd-session-clipboard-handler.ts
similarity index 81%
rename from orion-visor-ui/src/views/terminal/service/handler/rdp-session-clipboard-handler.ts
rename to orion-visor-ui/src/views/terminal/service/handler/guacd-session-clipboard-handler.ts
index 1e73a5fb..8bd66b4c 100644
--- a/orion-visor-ui/src/views/terminal/service/handler/rdp-session-clipboard-handler.ts
+++ b/orion-visor-ui/src/views/terminal/service/handler/guacd-session-clipboard-handler.ts
@@ -1,14 +1,14 @@
-import type { IRdpSession, IRdpSessionClipboardHandler } from '@/views/terminal/interfaces';
+import type { IGuacdSession, IGuacdSessionClipboardHandler } from '@/views/terminal/interfaces';
import Guacamole from 'guacamole-common-js';
import { copyToClipboard } from '@/hooks/copy';
import { isString } from '@/utils/is';
-// rdp 会话剪切板处理器实现
-export default class RdpSessionClipboardHandler implements IRdpSessionClipboardHandler {
+// guacd 会话剪切板处理器实现
+export default class GuacdSessionClipboardHandler implements IGuacdSessionClipboardHandler {
- private readonly session: IRdpSession;
+ private readonly session: IGuacdSession;
- constructor(session: IRdpSession) {
+ constructor(session: IGuacdSession) {
this.session = session;
}
diff --git a/orion-visor-ui/src/views/terminal/service/handler/rdp-session-display-handler.ts b/orion-visor-ui/src/views/terminal/service/handler/guacd-session-display-handler.ts
similarity index 94%
rename from orion-visor-ui/src/views/terminal/service/handler/rdp-session-display-handler.ts
rename to orion-visor-ui/src/views/terminal/service/handler/guacd-session-display-handler.ts
index 59a3b886..bcdefb81 100644
--- a/orion-visor-ui/src/views/terminal/service/handler/rdp-session-display-handler.ts
+++ b/orion-visor-ui/src/views/terminal/service/handler/guacd-session-display-handler.ts
@@ -1,11 +1,11 @@
-import type { IRdpSession, IRdpSessionDisplayHandler } from '@/views/terminal/interfaces';
+import type { IGuacdSession, IGuacdSessionDisplayHandler } from '@/views/terminal/interfaces';
import { useDebounceFn } from '@vueuse/core';
import Guacamole from 'guacamole-common-js';
-// rdp 会话视图处理器实现
-export default class RdpSessionDisplayHandler implements IRdpSessionDisplayHandler {
+// guacd 会话视图处理器实现
+export default class GuacdSessionDisplayHandler implements IGuacdSessionDisplayHandler {
- private readonly session: IRdpSession;
+ private readonly session: IGuacdSession;
public displayWidth: number;
public displayHeight: number;
@@ -21,7 +21,7 @@ export default class RdpSessionDisplayHandler implements IRdpSessionDisplayHandl
private readonly focusSink: () => void;
- constructor(session: IRdpSession) {
+ constructor(session: IGuacdSession) {
this.session = session;
this.displayWidth = 0;
this.displayHeight = 0;
diff --git a/orion-visor-ui/src/views/terminal/service/session/base-guacd-session.ts b/orion-visor-ui/src/views/terminal/service/session/base-guacd-session.ts
new file mode 100644
index 00000000..67214e3f
--- /dev/null
+++ b/orion-visor-ui/src/views/terminal/service/session/base-guacd-session.ts
@@ -0,0 +1,194 @@
+import type {
+ GuacdInitConfig,
+ GuacdReactiveSessionStatus,
+ IGuacdChannel,
+ IGuacdSession,
+ IGuacdSessionClipboardHandler,
+ IGuacdSessionDisplayHandler,
+ TerminalSessionTabItem
+} from '@/views/terminal/interfaces';
+import type { OutputPayload } from '@/views/terminal/types/protocol';
+import { InputProtocol } from '@/views/terminal/types/protocol';
+import { TerminalCloseCode, TerminalMessages } from '@/views/terminal/types/const';
+import { screenshot } from '@/views/terminal/types/utils';
+import Guacamole from 'guacamole-common-js';
+import BaseSession from './base-session';
+import GuacdSessionClipboardHandler from '../handler/guacd-session-clipboard-handler';
+
+export const CONNECT_TIMEOUT = 30000;
+
+// guacd 会话基类
+export default abstract class BaseGuacdSession extends BaseSession implements IGuacdSession {
+
+ public config: GuacdInitConfig;
+
+ public client: Guacamole.Client;
+
+ public displayHandler: IGuacdSessionDisplayHandler;
+
+ public clipboardHandler: IGuacdSessionClipboardHandler;
+
+ protected connectTimeoutId?: number;
+
+ protected constructor(item: TerminalSessionTabItem) {
+ super(item, {
+ closeCode: 0,
+ closeMessage: ''
+ });
+ this.client = undefined as unknown as Guacamole.Client;
+ this.config = {} as unknown as GuacdInitConfig;
+ this.displayHandler = undefined as unknown as IGuacdSessionDisplayHandler;
+ this.clipboardHandler = undefined as unknown as IGuacdSessionClipboardHandler;
+ }
+
+ // 初始化
+ async init(config: GuacdInitConfig) {
+ this.config = config;
+ // 初始化
+ await this.reInit();
+ }
+
+ // 初始化 channel
+ async reInit(): Promise {
+ // 初始化 channel
+ this.channel = this.createChannel();
+ // 创建 client
+ this.client = new Guacamole.Client(this.channel);
+ // 初始化 display
+ this.displayHandler = this.createDisplay();
+ // 初始化剪切板
+ this.clipboardHandler = this.createClipboard();
+ // 初始化 display
+ this.displayHandler.init();
+ // 初始化 channel
+ await this.channel.init();
+ // 注册 client 事件
+ this.registerClientEvent();
+ }
+
+ // 创建 channel
+ protected abstract createChannel(): IGuacdChannel;
+
+ // 创建 display
+ protected abstract createDisplay(): IGuacdSessionDisplayHandler;
+
+ // 创建 clipboard
+ protected createClipboard() {
+ // 创建 clipboard handler
+ return new GuacdSessionClipboardHandler(this);
+ }
+
+ // 注册 client 事件
+ protected registerClientEvent() {
+ // 错误回调
+ this.client.onerror = (state) => {
+ // 错误回调触发关闭
+ this.channel.closeTunnel(state.code, state.message || TerminalMessages.sessionClosed);
+ };
+ // 状态回调
+ this.client.onstatechange = (state) => {
+ if (state === Guacamole.Client.State.CONNECTED) {
+ // 触发连接成功回调
+ this.onConnected();
+ }
+ };
+ // 剪切板回调
+ this.client.onclipboard = this.clipboardHandler.receiveRemoteClipboardData.bind(this);
+ }
+
+ // 连接会话
+ connect(): void {
+ // 清空超时检查任务
+ window.clearTimeout(this.connectTimeoutId);
+ // 设置连接中
+ super.setConnecting();
+ // 连接 client 其实就是打开 channel 和 display
+ this.client.connect();
+ // 发送 connect 命令
+ this.channel.send(InputProtocol.CONNECT, {
+ body: JSON.stringify({
+ width: this.displayHandler?.displayWidth,
+ height: this.displayHandler?.displayHeight,
+ dpi: this.displayHandler?.displayDpi,
+ })
+ });
+ // 定时检查是否连接成功
+ this.connectTimeoutId = window.setTimeout(() => {
+ // 未连接上证明连接超时
+ if (!this.state.connected) {
+ this.channel.closeTunnel(TerminalCloseCode.CONNECT_TIMEOUT, TerminalMessages.connectTimeout);
+ }
+ }, CONNECT_TIMEOUT);
+ }
+
+ // 连接成功回调
+ protected onConnected() {
+ // 手动触发管道已连接
+ this.channel.processConnected({} as unknown as OutputPayload);
+ }
+
+ // 发送键
+ sendKeys(keys: Array): void {
+ if (!this.isWriteable()) {
+ return;
+ }
+ for (let i = 0; i < keys.length; i++) {
+ this.client.sendKeyEvent(1, keys[i]);
+ }
+ for (let i = 0; i < keys.length; i++) {
+ this.client.sendKeyEvent(0, keys[i]);
+ }
+ }
+
+ // 粘贴
+ paste(data: string): void {
+ if (!this.isWriteable()) {
+ return;
+ }
+ // 发送至远程剪切板
+ this.clipboardHandler?.sendDataToRemoteClipboard(data);
+ // 发送粘贴命令
+ setTimeout(() => {
+ this.sendKeys([65507, 118]);
+ }, 100);
+ }
+
+ // 聚焦
+ focus(): void {
+ this.displayHandler?.focus?.();
+ }
+
+ // 失焦
+ blur(): void {
+ this.displayHandler?.blur?.();
+ }
+
+ // 自适应
+ fit(): void {
+ this.displayHandler?.fit(false);
+ }
+
+ // 修改大小
+ resize(width: number, height: number): void {
+ if (!this.isWriteable()) {
+ return;
+ }
+ // 发送重置大小
+ this.channel.send(InputProtocol.RESIZE, { width, height, });
+ }
+
+ // 截屏
+ async screenshot() {
+ await screenshot(this.client?.getDisplay()?.getElement() as HTMLElement);
+ }
+
+ // 断开连接
+ disconnect(): void {
+ super.disconnect();
+ // 关闭 client
+ this.client?.disconnect();
+ // 关闭超时检查任务
+ clearTimeout(this.connectTimeoutId);
+ }
+
+}
diff --git a/orion-visor-ui/src/views/terminal/service/session/rdp-session.ts b/orion-visor-ui/src/views/terminal/service/session/rdp-session.ts
index 7a40e47b..49c34051 100644
--- a/orion-visor-ui/src/views/terminal/service/session/rdp-session.ts
+++ b/orion-visor-ui/src/views/terminal/service/session/rdp-session.ts
@@ -1,104 +1,49 @@
-import type {
- IGuacdChannel,
- IRdpSession,
- TerminalSessionTabItem,
- GuacdInitConfig,
- IRdpSessionDisplayHandler,
- GuacdReactiveSessionStatus,
- IRdpSessionClipboardHandler
-} from '@/views/terminal/interfaces';
-import type { OutputPayload } from '@/views/terminal/types/protocol';
+import type { IGuacdChannel, IGuacdSessionDisplayHandler, IRdpSession, TerminalSessionTabItem } from '@/views/terminal/interfaces';
import { InputProtocol } from '@/views/terminal/types/protocol';
-import { TerminalMessages, fitDisplayValue, TerminalCloseCode } from '@/views/terminal/types/const';
-import { screenshot } from '@/views/terminal/types/utils';
+import { fitDisplayValue } from '@/views/terminal/types/const';
import { Message } from '@arco-design/web-vue';
import { useTerminalStore } from '@/store';
import Guacamole from 'guacamole-common-js';
-import BaseSession from './base-session';
import RdpChannel from '../channel/rdp-channel';
-import RdpSessionDisplayHandler from '../handler/rdp-session-display-handler';
-import RdpSessionClipboardHandler from '../handler/rdp-session-clipboard-handler';
+import BaseGuacdSession from './base-guacd-session';
+import GuacdSessionDisplayHandler from '../handler/guacd-session-display-handler';
export const AUDIO_INPUT_MIMETYPE = 'audio/L16;rate=44100,channels=2';
-export const CONNECT_TIMEOUT = 30000;
-
// RDP 会话实现
-export default class RdpSession extends BaseSession implements IRdpSession {
+export default class RdpSession extends BaseGuacdSession implements IRdpSession {
public fileSystemName: string;
- public config: GuacdInitConfig;
-
- public client: Guacamole.Client;
-
- public displayHandler: IRdpSessionDisplayHandler;
-
- public clipboardHandler: IRdpSessionClipboardHandler;
-
- private connectTimeoutId?: number;
-
constructor(item: TerminalSessionTabItem) {
- super(item, {
- closeCode: 0,
- closeMessage: ''
- });
+ super(item);
this.fileSystemName = 'Shared Driver';
- this.client = undefined as unknown as Guacamole.Client;
- this.config = {} as unknown as GuacdInitConfig;
- this.displayHandler = undefined as unknown as IRdpSessionDisplayHandler;
- this.clipboardHandler = undefined as unknown as IRdpSessionClipboardHandler;
}
- // 初始化
- async init(config: GuacdInitConfig) {
- this.config = config;
- // 初始化
- await this.reInit();
+ // 创建 channel
+ protected createChannel(): IGuacdChannel {
+ return new RdpChannel(this);
}
- // 初始化 channel
- async reInit(): Promise {
+ // 创建 display
+ protected createDisplay(): IGuacdSessionDisplayHandler {
const rdpGraphSetting = useTerminalStore().preference.rdpGraphSetting;
- // 创建 channel
- this.channel = new RdpChannel(this);
- // 创建 client
- this.client = new Guacamole.Client(this.channel);
- // 创建 display handler
- this.displayHandler = new RdpSessionDisplayHandler(this);
- // 创建 clipboard handler
- this.clipboardHandler = new RdpSessionClipboardHandler(this);
- // 设置 display autoFit
+ // 创建 display
+ const displayHandler = new GuacdSessionDisplayHandler(this);
+ // 设置自适应
const autoFit = rdpGraphSetting?.displaySize === fitDisplayValue;
- this.displayHandler.autoFit = autoFit;
+ displayHandler.autoFit = autoFit;
+ // 非自适应设置分辨率
if (!autoFit) {
- this.displayHandler.setDisplaySize(rdpGraphSetting?.displayWidth || 1024, rdpGraphSetting?.displayHeight || 768);
+ displayHandler.setDisplaySize(rdpGraphSetting?.displayWidth || 1024, rdpGraphSetting?.displayHeight || 768);
}
- // 初始化 display
- this.displayHandler.init();
- // 初始化 channel
- await this.channel.init();
- // 注册 client 事件
- this.registerClientEvent();
+ return displayHandler;
}
// 注册 client 事件
- private registerClientEvent() {
- // 错误回调
- this.client.onerror = (state) => {
- // 错误回调触发关闭
- this.channel.closeTunnel(state.code, state.message || TerminalMessages.sessionClosed);
- };
- // 状态回调
- this.client.onstatechange = (state) => {
- if (state === Guacamole.Client.State.CONNECTED) {
- // 触发已连接
- this.onConnected();
- }
- };
- // 剪切板回调
- this.client.onclipboard = this.clipboardHandler.receiveRemoteClipboardData.bind(this);
- // 文件系统回调
+ protected registerClientEvent() {
+ super.registerClientEvent();
+ // 注册文件系统回调
this.client.onfilesystem = (_, fileSystemName) => {
if (fileSystemName) {
this.fileSystemName = fileSystemName;
@@ -117,35 +62,9 @@ export default class RdpSession extends BaseSession {
- // 未连接上证明连接超时
- if (!this.state.connected) {
- this.channel.closeTunnel(TerminalCloseCode.CONNECT_TIMEOUT, TerminalMessages.rdpConnectTimeout);
- }
- }, CONNECT_TIMEOUT);
- }
-
- // 连接成功
- private onConnected() {
- // 手动触发管道已连接
- this.channel.processConnected({} as unknown as OutputPayload);
+ // 连接成功回调
+ protected onConnected() {
+ super.onConnected();
// 监听音频输入
if (useTerminalStore().preference.rdpSessionSetting?.enableAudioInput) {
const requestAudioStream = (client: Guacamole.Client) => {
@@ -173,81 +92,11 @@ export default class RdpSession extends BaseSession): void {
- if (!this.isWriteable()) {
- return;
- }
- for (let i = 0; i < keys.length; i++) {
- this.client.sendKeyEvent(1, keys[i]);
- }
- for (let i = 0; i < keys.length; i++) {
- this.client.sendKeyEvent(0, keys[i]);
- }
- }
-
- // 粘贴
- paste(data: string): void {
- if (!this.isWriteable()) {
- return;
- }
- // 发送至远程剪切板
- this.clipboardHandler?.sendDataToRemoteClipboard(data);
- // 发送粘贴命令
- setTimeout(() => {
- this.sendKeys([65507, 118]);
- }, 100);
- }
-
- // 聚焦
- focus(): void {
- this.displayHandler?.focus?.();
- }
-
- // 失焦
- blur(): void {
- this.displayHandler?.blur?.();
- }
-
- // 自适应
- fit(): void {
- this.displayHandler?.fit(false);
- }
-
- // 修改大小
- resize(width: number, height: number): void {
- if (!this.isWriteable()) {
- return;
- }
- // 发送重置大小
- this.channel.send(InputProtocol.RESIZE, { width, height, });
- }
-
- // 截屏
- async screenshot() {
- await screenshot(this.client?.getDisplay()?.getElement() as HTMLElement);
- }
-
- // 是否可写
- isWriteable(): boolean {
- return this.state.connected && this.state.canWrite;
- }
-
- // 设置为已关闭
- setClosed() {
- // 设置为已关闭
- super.setClosed();
+ // 断开连接
+ disconnect(): void {
+ super.disconnect();
// 关闭文件传输
useTerminalStore().transferManager.rdp.closeBySessionKey(this.sessionKey);
}
- // 断开连接
- disconnect(): void {
- super.disconnect();
- // 关闭 client
- this.client?.disconnect();
- // 关闭超时检查任务
- clearTimeout(this.connectTimeoutId);
- }
-
}
diff --git a/orion-visor-ui/src/views/terminal/service/session/ssh-session.ts b/orion-visor-ui/src/views/terminal/service/session/ssh-session.ts
index d3a57161..0632219c 100644
--- a/orion-visor-ui/src/views/terminal/service/session/ssh-session.ts
+++ b/orion-visor-ui/src/views/terminal/service/session/ssh-session.ts
@@ -7,7 +7,7 @@ import type { XtermAddons } from '@/types/xterm';
import { defaultFontFamily } from '@/types/xterm';
import { useTerminalStore } from '@/store';
import { InputProtocol } from '@/views/terminal/types/protocol';
-import { TerminalShortcutType } from '@/views/terminal/types/const';
+import { BACKSPACE_CHAR, CTRL_H_CHAR, TerminalShortcutType } from '@/views/terminal/types/const';
import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import { WebLinksAddon } from '@xterm/addon-web-links';
@@ -127,15 +127,20 @@ export default class SshSession extends BaseSession) {
+ // 是否替换退格符
+ const replaceBackspace = preference.sshInteractSetting.replaceBackspace;
// 注册输入事件
- this.inst.onData(s => {
- if (!this.state.canWrite || !this.state.connected) {
+ this.inst.onData(command => {
+ // 不可写
+ if (!this.isWriteable()) {
return;
}
+ // 替换退格符
+ if (replaceBackspace) {
+ command = command.replace(BACKSPACE_CHAR, CTRL_H_CHAR);
+ }
// 输入
- this.channel.send(InputProtocol.SSH_INPUT, {
- command: s
- });
+ this.channel.send(InputProtocol.SSH_INPUT, { command });
});
// 启用响铃
if (preference.sshInteractSetting.enableBell) {
@@ -165,7 +170,8 @@ export default class SshSession extends BaseSession {
// 右键粘贴逻辑
if (preference.sshInteractSetting.rightClickPaste) {
- if (!this.state.canWrite || !this.state.connected) {
+ // 不可写
+ if (!this.isWriteable()) {
return;
}
// 未开启右键选中 || 开启并无选中的内容则粘贴
diff --git a/orion-visor-ui/src/views/terminal/service/session/terminal-session-manager.ts b/orion-visor-ui/src/views/terminal/service/session/terminal-session-manager.ts
index 78fa1d38..86444757 100644
--- a/orion-visor-ui/src/views/terminal/service/session/terminal-session-manager.ts
+++ b/orion-visor-ui/src/views/terminal/service/session/terminal-session-manager.ts
@@ -7,6 +7,7 @@ import type {
ISshSession,
ITerminalSession,
ITerminalSessionManager,
+ IVncSession,
SshInitConfig,
TerminalSessionTabItem
} from '@/views/terminal/interfaces';
@@ -17,6 +18,7 @@ import { addEventListen, removeEventListen } from '@/utils/event';
import SshSession from './ssh-session';
import SftpSession from './sftp-session';
import RdpSession from './rdp-session';
+import VncSession from './vnc-session';
// 终端会话管理器实现
export default class TerminalSessionManager implements ITerminalSessionManager {
@@ -80,7 +82,23 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
// 连接会话
session.connect();
} catch (ex) {
- console.error(ex);
+ // 异常关闭
+ session.close();
+ }
+ }
+
+ // 打开 vnc 会话
+ async openVnc(item: TerminalSessionTabItem, config: GuacdInitConfig): Promise {
+ // 获取会话
+ const session: IVncSession = this.getSession(item.key);
+ try {
+ // 初始化 session
+ await session.init(config);
+ // 等待前端渲染完成
+ await sleep(100);
+ // 连接会话
+ session.connect();
+ } catch (ex) {
// 异常关闭
session.close();
}
@@ -110,6 +128,9 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
} else if (item.type === TerminalSessionTypes.RDP.type) {
// RDP 会话
session = new RdpSession(item);
+ } else if (item.type === TerminalSessionTypes.VNC.type) {
+ // VNC 会话
+ session = new VncSession(item);
} else {
return undefined as unknown as T;
}
diff --git a/orion-visor-ui/src/views/terminal/service/session/vnc-session.ts b/orion-visor-ui/src/views/terminal/service/session/vnc-session.ts
new file mode 100644
index 00000000..83bda5a6
--- /dev/null
+++ b/orion-visor-ui/src/views/terminal/service/session/vnc-session.ts
@@ -0,0 +1,35 @@
+import type { IVncSession, TerminalSessionTabItem, IGuacdSessionDisplayHandler, IGuacdChannel } from '@/views/terminal/interfaces';
+import { fitDisplayValue } from '@/views/terminal/types/const';
+import { useTerminalStore } from '@/store';
+import BaseGuacdSession from './base-guacd-session';
+import VncChannel from '../channel/vnc-channel';
+import GuacdSessionDisplayHandler from '../handler/guacd-session-display-handler';
+
+// VNC 会话实现
+export default class VncSession extends BaseGuacdSession implements IVncSession {
+
+ constructor(item: TerminalSessionTabItem) {
+ super(item);
+ }
+
+ // 创建 channel
+ protected createChannel(): IGuacdChannel {
+ return new VncChannel(this);
+ };
+
+ // 创建 display
+ protected createDisplay(): IGuacdSessionDisplayHandler {
+ const vncGraphSetting = useTerminalStore().preference.vncGraphSetting;
+ // 创建 display
+ const displayHandler = new GuacdSessionDisplayHandler(this);
+ // 设置自适应
+ const autoFit = vncGraphSetting?.displaySize === fitDisplayValue;
+ displayHandler.autoFit = autoFit;
+ // 非自适应设置分辨率
+ if (!autoFit) {
+ displayHandler.setDisplaySize(vncGraphSetting?.displayWidth || 1024, vncGraphSetting?.displayHeight || 768);
+ }
+ return displayHandler;
+ };
+
+}