From c40a4fdf13a1ee2f04483fe03bde96e6eddd987e Mon Sep 17 00:00:00 2001 From: lijiahangmax Date: Fri, 5 Jan 2024 00:51:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=89=93=E5=BC=80=E8=BF=9E=E6=8E=A5.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/store/modules/terminal/index.ts | 47 +----- .../src/store/modules/terminal/types.ts | 36 +++-- .../components/layout/terminal-content.vue | 12 +- .../components/layout/terminal-header.vue | 8 +- .../layout/terminal-left-sidebar.vue | 6 +- .../new-connection/host-list-view.vue | 4 +- .../components/xterm/terminal-view.vue | 72 +++++++++- .../terminal/handler/TerminalDispatcher.ts | 136 ++++++++++++++++++ .../host/terminal/types/terminal.const.ts | 3 +- .../host/terminal/types/terminal.protocol.ts | 6 +- 10 files changed, 251 insertions(+), 79 deletions(-) create mode 100644 orion-ops-ui/src/views/host/terminal/handler/TerminalDispatcher.ts diff --git a/orion-ops-ui/src/store/modules/terminal/index.ts b/orion-ops-ui/src/store/modules/terminal/index.ts index 9f0ba01d..8ad38f55 100644 --- a/orion-ops-ui/src/store/modules/terminal/index.ts +++ b/orion-ops-ui/src/store/modules/terminal/index.ts @@ -1,12 +1,10 @@ -import type { TabItem, TerminalDisplaySetting, TerminalPreference, TerminalState, TerminalThemeSchema } from './types'; -import type { HostQueryResponse } from '@/api/asset/host'; +import type { TerminalDisplaySetting, TerminalPreference, TerminalState, TerminalThemeSchema } from './types'; import { defineStore } from 'pinia'; import { getPreference, updatePreference } from '@/api/user/preference'; import { Message } from '@arco-design/web-vue'; import { useDark } from '@vueuse/core'; import { DEFAULT_SCHEMA } from '@/views/host/terminal/types/terminal.theme'; -import { InnerTabs } from '@/views/host/terminal/types/terminal.const'; -import { getHostTerminalAccessToken } from '@/api/asset/host-terminal'; +import TerminalDispatcher from '@/views/host/terminal/handler/TerminalDispatcher'; // 暗色主题 export const DarkTheme = { @@ -31,11 +29,7 @@ export default defineStore('terminal', { displaySetting: {} as TerminalDisplaySetting, themeSchema: {} as TerminalThemeSchema }, - tabs: { - active: InnerTabs.NEW_CONNECTION.key, - items: [InnerTabs.NEW_CONNECTION, InnerTabs.VIEW_SETTING] - }, - access: undefined + dispatcher: new TerminalDispatcher() }), actions: { @@ -116,41 +110,6 @@ export default defineStore('terminal', { } }, - // 点击 tab - clickTab(key: string) { - this.tabs.active = key; - }, - - // 删除 tab - deleteTab(key: string) { - const tabIndex = this.tabs.items.findIndex(s => s.key === key); - this.tabs.items.splice(tabIndex, 1); - if (key === this.tabs.active && this.tabs.items.length !== 0) { - // 切换为前一个 tab - this.tabs.active = this.tabs.items[Math.max(tabIndex - 1, 0)].key; - } - }, - - // 切换 tab - switchTab(tab: TabItem) { - // 不存在则创建tab - if (!this.tabs.items.find(s => s.key === tab.key)) { - this.tabs.items.push(tab); - } - this.tabs.active = tab.key; - }, - - // 打开终端 - async openTerminal(record: HostQueryResponse) { - // 获取 access - if (!this.access) { - const { data } = await getHostTerminalAccessToken(); - this.access = data; - } - console.log(this.access); - console.log(record); - } - }, }); diff --git a/orion-ops-ui/src/store/modules/terminal/types.ts b/orion-ops-ui/src/store/modules/terminal/types.ts index e1d8fa53..f0af3fe4 100644 --- a/orion-ops-ui/src/store/modules/terminal/types.ts +++ b/orion-ops-ui/src/store/modules/terminal/types.ts @@ -1,11 +1,9 @@ import type { Ref } from 'vue'; -import type { HostTerminalAccessResponse } from '@/api/asset/host-terminal'; export interface TerminalState { isDarkTheme: Ref; preference: TerminalPreference; - tabs: TerminalTabs; - access?: HostTerminalAccessResponse; + dispatcher: ITerminalDispatcher; } // 终端配置 @@ -58,17 +56,33 @@ export interface TerminalThemeSchema { [key: string]: unknown; } -// 终端 tab -export interface TerminalTabs { - active: string; - items: Array; -} - -// tab 元素 -export interface TabItem { +// 终端 tab 元素 +export interface TerminalTabItem { key: string; title: string; type: string; [key: string]: unknown; } + +// 终端调度器 +export interface ITerminalDispatcher { + // 当前活跃 tab + active: string; + // 所有 tab + items: Array; + + // 点击 tab + clickTab: (key: string) => void; + // 删除 tab + deleteTab: (key: string) => void; + // 打开 tab + openTab: (tab: TerminalTabItem) => void; + // 打开终端 + openTerminal: (record: any) => void; + // 注册终端钩子 + registerTerminalHook: (tab: TerminalTabItem) => void; + + // 重置 + reset: () => void; +} diff --git a/orion-ops-ui/src/views/host/terminal/components/layout/terminal-content.vue b/orion-ops-ui/src/views/host/terminal/components/layout/terminal-content.vue index cfaaea27..40199b8d 100644 --- a/orion-ops-ui/src/views/host/terminal/components/layout/terminal-content.vue +++ b/orion-ops-ui/src/views/host/terminal/components/layout/terminal-content.vue @@ -1,8 +1,8 @@ diff --git a/orion-ops-ui/src/views/host/terminal/components/layout/terminal-header.vue b/orion-ops-ui/src/views/host/terminal/components/layout/terminal-header.vue index d4138386..a3b67053 100644 --- a/orion-ops-ui/src/views/host/terminal/components/layout/terminal-header.vue +++ b/orion-ops-ui/src/views/host/terminal/components/layout/terminal-header.vue @@ -10,13 +10,13 @@
- - + diff --git a/orion-ops-ui/src/views/host/terminal/components/layout/terminal-left-sidebar.vue b/orion-ops-ui/src/views/host/terminal/components/layout/terminal-left-sidebar.vue index 83c0adbe..1dd4de56 100644 --- a/orion-ops-ui/src/views/host/terminal/components/layout/terminal-left-sidebar.vue +++ b/orion-ops-ui/src/views/host/terminal/components/layout/terminal-left-sidebar.vue @@ -30,7 +30,7 @@ { icon: 'icon-plus', content: '新建连接', - click: () => terminalStore.switchTab(InnerTabs.NEW_CONNECTION) + click: () => terminalStore.dispatcher.openTab(InnerTabs.NEW_CONNECTION) }, ]; @@ -39,12 +39,12 @@ { icon: 'icon-command', content: '快捷键设置', - click: () => terminalStore.switchTab(InnerTabs.SHORTCUT_SETTING) + click: () => terminalStore.dispatcher.openTab(InnerTabs.SHORTCUT_SETTING) }, { icon: 'icon-palette', content: '外观设置', - click: () => terminalStore.switchTab(InnerTabs.VIEW_SETTING) + click: () => terminalStore.dispatcher.openTab(InnerTabs.VIEW_SETTING) }, ]; diff --git a/orion-ops-ui/src/views/host/terminal/components/new-connection/host-list-view.vue b/orion-ops-ui/src/views/host/terminal/components/new-connection/host-list-view.vue index 47fa8cf0..d14fab5e 100644 --- a/orion-ops-ui/src/views/host/terminal/components/new-connection/host-list-view.vue +++ b/orion-ops-ui/src/views/host/terminal/components/new-connection/host-list-view.vue @@ -20,7 +20,7 @@
- + @@ -116,7 +116,7 @@ arrow-class="terminal-tooltip-content" content="连接主机">
-
+
diff --git a/orion-ops-ui/src/views/host/terminal/components/xterm/terminal-view.vue b/orion-ops-ui/src/views/host/terminal/components/xterm/terminal-view.vue index ac165c9f..46977178 100644 --- a/orion-ops-ui/src/views/host/terminal/components/xterm/terminal-view.vue +++ b/orion-ops-ui/src/views/host/terminal/components/xterm/terminal-view.vue @@ -1,5 +1,14 @@ diff --git a/orion-ops-ui/src/views/host/terminal/handler/TerminalDispatcher.ts b/orion-ops-ui/src/views/host/terminal/handler/TerminalDispatcher.ts new file mode 100644 index 00000000..8a7af115 --- /dev/null +++ b/orion-ops-ui/src/views/host/terminal/handler/TerminalDispatcher.ts @@ -0,0 +1,136 @@ +import type { ITerminalDispatcher, TerminalTabItem } from '@/store/modules/terminal/types'; +import type { HostQueryResponse } from '@/api/asset/host'; +import type { HostTerminalAccessResponse } from '@/api/asset/host-terminal'; +import { getHostTerminalAccessToken } from '@/api/asset/host-terminal'; +import { InnerTabs, TabType } from '@/views/host/terminal/types/terminal.const'; +import { Message } from '@arco-design/web-vue'; +import { sleep } from '@/utils'; +import { InputProtocol, format } from '../types/terminal.protocol'; + +export const wsBase = import.meta.env.VITE_WS_BASE_URL; + +/** + * 终端调度器 + */ +export default class TerminalDispatcher implements ITerminalDispatcher { + + private access?: HostTerminalAccessResponse; + + private client?: WebSocket; + + public active: string; + + public items: Array; + + constructor() { + this.active = InnerTabs.NEW_CONNECTION.key; + this.items = [InnerTabs.NEW_CONNECTION]; + } + + // 点击 tab + clickTab(key: string): void { + this.active = key; + } + + // 删除 tab + deleteTab(key: string): void { + // 获取当前 tab + const tabIndex = this.items.findIndex(s => s.key === key); + if (this.items[tabIndex]?.type === TabType.TERMINAL) { + // 如果是 terminal 则需要关闭 + this.closeTerminal(key); + } + // 删除 tab + this.items.splice(tabIndex, 1); + if (key === this.active && this.items.length !== 0) { + // 切换为前一个 tab + this.active = this.items[Math.max(tabIndex - 1, 0)].key; + } + // fixme 关闭 socket + } + + // 打开 tab + openTab(tab: TerminalTabItem): void { + // 不存在则创建 tab + if (!this.items.find(s => s.key === tab.key)) { + this.items.push(tab); + } + this.active = tab.key; + } + + // 初始化客户端 + async initClient() { + if (this.client) { + return; + } + // 获取 access + const { data: accessData } = await getHostTerminalAccessToken(); + this.access = accessData; + // 打开会话 + this.client = new WebSocket(`${wsBase}/host/terminal/${accessData.accessToken}`); + this.client.onerror = event => { + Message.error('无法连接至服务器'); + console.error('error', event); + }; + this.client.onclose = event => { + console.warn('close', event); + }; + this.client.onmessage = this.handlerMessage; + // 等待会话等待完成 + for (let i = 0; i < 100; i++) { + await sleep(50); + if (this.client.readyState === WebSocket.OPEN) { + break; + } + } + } + + // 处理消息 + handlerMessage({ data }: MessageEvent) { + console.log(data); + } + + // 打开终端 + async openTerminal(record: HostQueryResponse) { + // 初始化客户端 + await this.initClient(); + // uncheck + if (!this.access) { + return; + } + const session = this.access.sessionInitial = (parseInt(this.access.sessionInitial as string, 32) + 1).toString(32); + // 打开会话 + this.openTab({ + type: TabType.TERMINAL, + key: session, + title: record.alias || (`${record.name} ${record.address}`), + hostId: record.id, + address: record.address, + checked: false, + connected: false + }); + } + + // 注册终端钩子 + registerTerminalHook(tab: TerminalTabItem) { + if (!this.client) { + return; + } + // 发送 check 命令 + this.client.send(format(InputProtocol.CHECK, { session: tab.key, hostId: tab.hostId })); + } + + // 关闭终端 + closeTerminal(key: string) { + } + + // 重置 + reset(): void { + this.active = undefined as unknown as string; + this.items = []; + this.access = undefined; + this.client = undefined; + } + +} + diff --git a/orion-ops-ui/src/views/host/terminal/types/terminal.const.ts b/orion-ops-ui/src/views/host/terminal/types/terminal.const.ts index ae970e16..34dd7a51 100644 --- a/orion-ops-ui/src/views/host/terminal/types/terminal.const.ts +++ b/orion-ops-ui/src/views/host/terminal/types/terminal.const.ts @@ -1,5 +1,4 @@ import type { CSSProperties } from 'vue'; -import type { TabItem } from '@/store/modules/terminal/types'; // sidebar 操作类型 export interface SidebarAction { @@ -16,7 +15,7 @@ export const TabType = { }; // 内置 tab -export const InnerTabs: Record = { +export const InnerTabs = { NEW_CONNECTION: { key: 'newConnection', title: '新建连接', diff --git a/orion-ops-ui/src/views/host/terminal/types/terminal.protocol.ts b/orion-ops-ui/src/views/host/terminal/types/terminal.protocol.ts index fdf25a70..045940db 100644 --- a/orion-ops-ui/src/views/host/terminal/types/terminal.protocol.ts +++ b/orion-ops-ui/src/views/host/terminal/types/terminal.protocol.ts @@ -13,7 +13,7 @@ export interface Payload { } // 输入协议 -export const InputProtocol: Record = { +export const InputProtocol = { // 主机连接检查 CHECK: { type: 'ck', @@ -52,7 +52,7 @@ export const InputProtocol: Record = { }; // 输出协议 -export const OutputProtocol: Record = { +export const OutputProtocol = { // 主机连接检查 CHECK: { type: 'ck', @@ -112,7 +112,7 @@ export const parse: Record = (payload: string) => { }; // 格式化参数 -export const format = (payload: Payload, protocol: Protocol) => { +export const format = (protocol: Protocol, payload: Payload) => { payload.type = protocol.type; return protocol.template .map(i => payload[i] || '')