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 @@ + + + + + + + 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; + }; + +}