优化 terminal 逻辑.

This commit is contained in:
lijiahang
2024-06-21 10:15:33 +08:00
parent 9ae5a6c627
commit 8dec40553d
27 changed files with 177 additions and 140 deletions

View File

@@ -5,7 +5,7 @@ type ViewType = 'table' | 'card' | undefined;
/** /**
* 应用状态 * 应用状态
*/ */
export interface AppState extends AppSetting, UserPreferenceLayout, UserPreferenceData, UserPreferenceViews { export interface AppState extends AppSetting, UserPreferenceLayout, UserPreferenceData, UserPreferenceView {
[key: string]: unknown; [key: string]: unknown;
} }
@@ -43,7 +43,7 @@ export interface UserPreferenceData {
/** /**
* 用户偏好 - 页面视图 * 用户偏好 - 页面视图
*/ */
export interface UserPreferenceViews { export interface UserPreferenceView {
hostView: ViewType; hostView: ViewType;
hostKeyView: ViewType; hostKeyView: ViewType;
hostIdentityView: ViewType; hostIdentityView: ViewType;

View File

@@ -76,15 +76,15 @@
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import type { ISshSession } from '@/views/host/terminal/types/terminal.type'; import type { ISshSession } from '../../types/terminal.type';
import type { CommandSnippetWrapperResponse, CommandSnippetQueryResponse } from '@/api/asset/command-snippet'; import type { CommandSnippetWrapperResponse, CommandSnippetQueryResponse } from '@/api/asset/command-snippet';
import { ref, watch, provide } from 'vue'; import { ref, watch, provide } from 'vue';
import useVisible from '@/hooks/visible'; import useVisible from '@/hooks/visible';
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import { deleteCommandSnippet, getCommandSnippetList } from '@/api/asset/command-snippet'; import { deleteCommandSnippet, getCommandSnippetList } from '@/api/asset/command-snippet';
import { useCacheStore, useTerminalStore } from '@/store'; import { useCacheStore, useTerminalStore } from '@/store';
import { openUpdateSnippetKey, removeSnippetKey } from '../types/const'; import { openUpdateSnippetKey, removeSnippetKey } from './types/const';
import { PanelSessionType } from '@/views/host/terminal/types/terminal.const'; import { PanelSessionType } from '../../types/terminal.const';
import CommandSnippetListItem from './command-snippet-list-item.vue'; import CommandSnippetListItem from './command-snippet-list-item.vue';
import CommandSnippetListGroup from './command-snippet-list-group.vue'; import CommandSnippetListGroup from './command-snippet-list-group.vue';
import CommandSnippetFormDrawer from './command-snippet-form-drawer.vue'; import CommandSnippetFormDrawer from './command-snippet-form-drawer.vue';

View File

@@ -52,7 +52,7 @@
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible'; import useVisible from '@/hooks/visible';
import { createCommandSnippet, updateCommandSnippet } from '@/api/asset/command-snippet'; import { createCommandSnippet, updateCommandSnippet } from '@/api/asset/command-snippet';
import formRules from '../types/form.rules'; import formRules from './types/form.rules';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import CommandSnippetGroupSelect from './command-snippet-group-select.vue'; import CommandSnippetGroupSelect from './command-snippet-group-select.vue';

View File

@@ -113,14 +113,14 @@
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import type { ISshSession } from '@/views/host/terminal/types/terminal.type'; import type { ISshSession } from '../../types/terminal.type';
import type { CommandSnippetQueryResponse } from '@/api/asset/command-snippet'; import type { CommandSnippetQueryResponse } from '@/api/asset/command-snippet';
import { useTerminalStore } from '@/store'; import { useTerminalStore } from '@/store';
import { useDebounceFn } from '@vueuse/core'; import { useDebounceFn } from '@vueuse/core';
import { copy } from '@/hooks/copy'; import { copy } from '@/hooks/copy';
import { inject } from 'vue'; import { inject } from 'vue';
import { openUpdateSnippetKey, removeSnippetKey } from '../types/const'; import { openUpdateSnippetKey, removeSnippetKey } from './types/const';
import { PanelSessionType } from '@/views/host/terminal/types/terminal.const'; import { PanelSessionType } from '../../types/terminal.const';
const props = defineProps<{ const props = defineProps<{
item: CommandSnippetQueryResponse; item: CommandSnippetQueryResponse;

View File

@@ -39,12 +39,12 @@
import { onMounted, onUnmounted, watch } from 'vue'; import { onMounted, onUnmounted, watch } from 'vue';
import { addEventListen, removeEventListen } from '@/utils/event'; import { addEventListen, removeEventListen } from '@/utils/event';
import EmptyRecommend from './empty-recommend.vue'; import EmptyRecommend from './empty-recommend.vue';
import TerminalPanelsView from './terminal-panels-view.vue';
import NewConnectionView from '../new-connection/new-connection-view.vue'; import NewConnectionView from '../new-connection/new-connection-view.vue';
import TerminalDisplaySetting from '../setting/display/terminal-display-setting.vue'; import TerminalDisplaySetting from '../setting/display/terminal-display-setting.vue';
import TerminalThemeSetting from '../setting/theme/terminal-theme-setting.vue'; import TerminalThemeSetting from '../setting/theme/terminal-theme-setting.vue';
import TerminalGeneralSetting from '../setting/general/terminal-general-setting.vue'; import TerminalGeneralSetting from '../setting/general/terminal-general-setting.vue';
import TerminalShortcutSetting from '../setting/shortcut/terminal-shortcut-setting.vue'; import TerminalShortcutSetting from '../setting/shortcut/terminal-shortcut-setting.vue';
import TerminalPanelsView from '@/views/host/terminal/components/layout/terminal-panels-view.vue';
const emits = defineEmits(['openCommandSnippet', 'openPathBookmark', 'openTransferList', 'screenshot']); const emits = defineEmits(['openCommandSnippet', 'openPathBookmark', 'openTransferList', 'screenshot']);

View File

@@ -76,15 +76,15 @@
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import { ISshSession } from '@/views/host/terminal/types/terminal.type'; import type { ISshSession } from '../../types/terminal.type';
import type { PathBookmarkWrapperResponse, PathBookmarkQueryResponse } from '@/api/asset/path-bookmark'; import type { PathBookmarkWrapperResponse, PathBookmarkQueryResponse } from '@/api/asset/path-bookmark';
import { ref, provide, onMounted, watch } from 'vue'; import { ref, provide, watch } from 'vue';
import useVisible from '@/hooks/visible'; import useVisible from '@/hooks/visible';
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import { deletePathBookmark, getPathBookmarkList } from '@/api/asset/path-bookmark'; import { deletePathBookmark, getPathBookmarkList } from '@/api/asset/path-bookmark';
import { useCacheStore, useDictStore, useTerminalStore } from '@/store'; import { useCacheStore, useTerminalStore } from '@/store';
import { PanelSessionType } from '@/views/host/terminal/types/terminal.const'; import { PanelSessionType } from '../../types/terminal.const';
import { dictKeys, openUpdatePathKey, removePathKey } from '../types/const'; import { openUpdatePathKey, removePathKey } from './types/const';
import PathBookmarkListItem from './path-bookmark-list-item.vue'; import PathBookmarkListItem from './path-bookmark-list-item.vue';
import PathBookmarkListGroup from './path-bookmark-list-group.vue'; import PathBookmarkListGroup from './path-bookmark-list-group.vue';
import PathBookmarkFormDrawer from './path-bookmark-form-drawer.vue'; import PathBookmarkFormDrawer from './path-bookmark-form-drawer.vue';
@@ -281,11 +281,6 @@
getCurrentSession<ISshSession>(PanelSessionType.SSH.type)?.focus(); getCurrentSession<ISshSession>(PanelSessionType.SSH.type)?.focus();
}; };
//
onMounted(() => {
useDictStore().loadKeys(dictKeys);
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@@ -55,8 +55,9 @@
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible'; import useVisible from '@/hooks/visible';
import { createPathBookmark, updatePathBookmark } from '@/api/asset/path-bookmark'; import { createPathBookmark, updatePathBookmark } from '@/api/asset/path-bookmark';
import formRules from '../types/form.rules'; import formRules from './types/form.rules';
import { PathBookmarkType, pathBookmarkTypeKey } from '../types/const'; import { PathBookmarkType } from './types/const';
import { pathBookmarkTypeKey } from '../../types/terminal.const';
import { useDictStore } from '@/store'; import { useDictStore } from '@/store';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import PathBookmarkGroupSelect from './path-bookmark-group-select.vue'; import PathBookmarkGroupSelect from './path-bookmark-group-select.vue';

View File

@@ -112,15 +112,15 @@
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import type { ISftpSession, ISshSession } from '@/views/host/terminal/types/terminal.type'; import type { ISftpSession, ISshSession } from '../../types/terminal.type';
import type { PathBookmarkQueryResponse } from '@/api/asset/path-bookmark'; import type { PathBookmarkQueryResponse } from '@/api/asset/path-bookmark';
import { useTerminalStore } from '@/store'; import { useTerminalStore } from '@/store';
import { useDebounceFn } from '@vueuse/core'; import { useDebounceFn } from '@vueuse/core';
import { copy } from '@/hooks/copy'; import { copy } from '@/hooks/copy';
import { inject } from 'vue'; import { inject } from 'vue';
import { getParentPath } from '@/utils/file'; import { getParentPath } from '@/utils/file';
import { openUpdatePathKey, PathBookmarkType, removePathKey } from '../types/const'; import { openUpdatePathKey, PathBookmarkType, removePathKey } from './types/const';
import { PanelSessionType } from '@/views/host/terminal/types/terminal.const'; import { PanelSessionType } from '../../types/terminal.const';
const props = defineProps<{ const props = defineProps<{
item: PathBookmarkQueryResponse; item: PathBookmarkQueryResponse;

View File

@@ -9,9 +9,3 @@ export const openUpdatePathKey = Symbol();
// 删除 path key // 删除 path key
export const removePathKey = Symbol(); export const removePathKey = Symbol();
// 路径书签类型 字典项
export const pathBookmarkTypeKey = 'pathBookmarkType';
// 加载的字典值
export const dictKeys = [pathBookmarkTypeKey];

View File

@@ -45,11 +45,12 @@
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import type { ISftpSession } from '../../types/terminal.type';
import useVisible from '@/hooks/visible'; import useVisible from '@/hooks/visible';
import { nextTick, ref } from 'vue'; import { nextTick, ref } from 'vue';
import { useTerminalStore } from '@/store'; import { useTerminalStore } from '@/store';
import { permission10toString } from '@/utils/file'; import { permission10toString } from '@/utils/file';
import SftpSession from '../../handler/sftp-session'; import { PanelSessionType } from '../../types/terminal.const';
const { visible, setVisible } = useVisible(); const { visible, setVisible } = useVisible();
const { sessionManager } = useTerminalStore(); const { sessionManager } = useTerminalStore();
@@ -92,8 +93,8 @@
return false; return false;
} }
// 获取会话 // 获取会话
const session = sessionManager.getSession(sessionId.value); const session = sessionManager.getSession<ISftpSession>(sessionId.value);
if (session instanceof SftpSession) { if (session.type === PanelSessionType.SFTP.type) {
session.chmod(formModel.value.path, formModel.value.mod); session.chmod(formModel.value.path, formModel.value.mod);
} }
} catch (e) { } catch (e) {

View File

@@ -30,10 +30,11 @@
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import type { ISftpSession } from '../../types/terminal.type';
import useVisible from '@/hooks/visible'; import useVisible from '@/hooks/visible';
import { nextTick, ref } from 'vue'; import { nextTick, ref } from 'vue';
import { useTerminalStore } from '@/store'; import { useTerminalStore } from '@/store';
import SftpSession from '../../handler/sftp-session'; import { PanelSessionType } from '../../types/terminal.const';
const { visible, setVisible } = useVisible(); const { visible, setVisible } = useVisible();
const { sessionManager } = useTerminalStore(); const { sessionManager } = useTerminalStore();
@@ -69,8 +70,8 @@
return false; return false;
} }
// 获取会话 // 获取会话
const session = sessionManager.getSession(sessionId.value); const session = sessionManager.getSession<ISftpSession>(sessionId.value);
if (session instanceof SftpSession) { if (session.type === PanelSessionType.SFTP.type) {
if (touch.value) { if (touch.value) {
// 创建文件 // 创建文件
session.touch(formModel.value.path); session.touch(formModel.value.path);

View File

@@ -38,10 +38,11 @@
</script> </script>
<script lang="ts" setup> <script lang="ts" setup>
import type { ISftpSession } from '../../types/terminal.type';
import useVisible from '@/hooks/visible'; import useVisible from '@/hooks/visible';
import { nextTick, ref } from 'vue'; import { nextTick, ref } from 'vue';
import { useTerminalStore } from '@/store'; import { useTerminalStore } from '@/store';
import SftpSession from '../../handler/sftp-session'; import { PanelSessionType } from '../../types/terminal.const';
const { visible, setVisible } = useVisible(); const { visible, setVisible } = useVisible();
const { sessionManager } = useTerminalStore(); const { sessionManager } = useTerminalStore();
@@ -77,8 +78,8 @@
return false; return false;
} }
// 获取会话 // 获取会话
const session = sessionManager.getSession(sessionId.value); const session = sessionManager.getSession<ISftpSession>(sessionId.value);
if (session instanceof SftpSession) { if (session.type === PanelSessionType.SFTP.type) {
session.move(formModel.value.path, formModel.value.target); session.move(formModel.value.path, formModel.value.target);
} }
} catch (e) { } catch (e) {

View File

@@ -0,0 +1,46 @@
import type { ITerminalSession, TerminalPanelTabItem } from '../types/terminal.type';
// 会话基类
export default abstract class BaseSession implements ITerminalSession {
public type: string;
public hostId: number;
public title: string;
public address: string;
public sessionId: string;
public connected: boolean;
public canReconnect: boolean;
public canWrite: boolean;
protected constructor(type: string, tab: TerminalPanelTabItem) {
this.type = type;
this.hostId = tab.hostId;
this.title = tab.title;
this.address = tab.address;
this.sessionId = tab.sessionId;
this.connected = false;
this.canWrite = false;
this.canReconnect = false;
}
// 设置是否可写
setCanWrite(canWrite: boolean): void {
this.canWrite = canWrite;
}
// 连接
connect(): void {
this.connected = true;
}
// 断开连接
disconnect(): void {
this.connected = false;
}
// 关闭
close(): void {
this.connected = false;
}
}

View File

@@ -1,17 +1,11 @@
import type { ISftpSession, ISftpSessionResolver, ITerminalChannel } from '../types/terminal.type'; import type { ISftpSession, ISftpSessionResolver, ITerminalChannel, TerminalPanelTabItem } from '../types/terminal.type';
import { InputProtocol } from '../types/terminal.protocol'; import { InputProtocol } from '../types/terminal.protocol';
import { PanelSessionType } from '../types/terminal.const';
import { Modal } from '@arco-design/web-vue'; import { Modal } from '@arco-design/web-vue';
import BaseSession from './base-session';
// sftp 会话实现 // sftp 会话实现
export default class SftpSession implements ISftpSession { export default class SftpSession extends BaseSession implements ISftpSession {
public readonly hostId: number;
public sessionId: string;
public connected: boolean;
public canReconnect: boolean;
public resolver: ISftpSessionResolver; public resolver: ISftpSessionResolver;
@@ -19,14 +13,10 @@ export default class SftpSession implements ISftpSession {
private readonly channel: ITerminalChannel; private readonly channel: ITerminalChannel;
constructor(hostId: number, constructor(tab: TerminalPanelTabItem,
sessionId: string,
channel: ITerminalChannel) { channel: ITerminalChannel) {
this.hostId = hostId; super(PanelSessionType.SFTP.type, tab);
this.sessionId = sessionId;
this.channel = channel; this.channel = channel;
this.connected = false;
this.canReconnect = false;
this.showHiddenFile = false; this.showHiddenFile = false;
this.resolver = undefined as unknown as ISftpSessionResolver; this.resolver = undefined as unknown as ISftpSessionResolver;
} }
@@ -38,7 +28,7 @@ export default class SftpSession implements ISftpSession {
// 设置已连接 // 设置已连接
connect(): void { connect(): void {
this.connected = true; super.connect();
// 连接回调 // 连接回调
this.resolver.connectCallback(); this.resolver.connectCallback();
} }
@@ -140,14 +130,11 @@ export default class SftpSession implements ISftpSession {
// 断开连接 // 断开连接
disconnect(): void { disconnect(): void {
super.disconnect();
// 发送关闭消息 // 发送关闭消息
this.channel.send(InputProtocol.CLOSE, { this.channel.send(InputProtocol.CLOSE, {
sessionId: this.sessionId sessionId: this.sessionId
}); });
} }
// 关闭
close(): void {
}
} }

View File

@@ -1,13 +1,13 @@
import type { UnwrapRef } from 'vue'; import type { UnwrapRef } from 'vue';
import type { ISearchOptions } from '@xterm/addon-search'; import type { ISearchOptions } from '@xterm/addon-search';
import { SearchAddon } from '@xterm/addon-search';
import type { TerminalPreference } from '@/store/modules/terminal/types'; import type { TerminalPreference } from '@/store/modules/terminal/types';
import type { ISshSession, ISshSessionHandler, ITerminalChannel, XtermAddons, XtermDomRef } from '../types/terminal.type'; import type { ISshSession, ISshSessionHandler, ITerminalChannel, TerminalPanelTabItem, XtermAddons, XtermDomRef } from '../types/terminal.type';
import { useTerminalStore } from '@/store'; import { useTerminalStore } from '@/store';
import { InputProtocol } from '../types/terminal.protocol'; import { InputProtocol } from '../types/terminal.protocol';
import { fontFamilySuffix, TerminalShortcutType, TerminalStatus } from '../types/terminal.const'; import { fontFamilySuffix, PanelSessionType, TerminalShortcutType, TerminalStatus, } from '../types/terminal.const';
import { Terminal } from '@xterm/xterm'; import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit'; import { FitAddon } from '@xterm/addon-fit';
import { SearchAddon } from '@xterm/addon-search';
import { WebLinksAddon } from '@xterm/addon-web-links'; import { WebLinksAddon } from '@xterm/addon-web-links';
import { ImageAddon } from '@xterm/addon-image'; import { ImageAddon } from '@xterm/addon-image';
import { Unicode11Addon } from '@xterm/addon-unicode11'; import { Unicode11Addon } from '@xterm/addon-unicode11';
@@ -16,22 +16,13 @@ import { WebglAddon } from '@xterm/addon-webgl';
import { playBell } from '@/utils/bell'; import { playBell } from '@/utils/bell';
import { addEventListen } from '@/utils/event'; import { addEventListen } from '@/utils/event';
import SshSessionHandler from './ssh-session-handler'; import SshSessionHandler from './ssh-session-handler';
import BaseSession from './base-session';
// ssh 会话实现 // ssh 会话实现
export default class SshSession implements ISshSession { export default class SshSession extends BaseSession implements ISshSession {
public readonly hostId: number;
public sessionId: string;
public inst: Terminal; public inst: Terminal;
public connected: boolean;
public canReconnect: boolean;
public canWrite: boolean;
public status: number; public status: number;
public handler: ISshSessionHandler; public handler: ISshSessionHandler;
@@ -40,15 +31,10 @@ export default class SshSession implements ISshSession {
private readonly addons: XtermAddons; private readonly addons: XtermAddons;
constructor(hostId: number, constructor(tab: TerminalPanelTabItem,
sessionId: string,
channel: ITerminalChannel) { channel: ITerminalChannel) {
this.hostId = hostId; super(PanelSessionType.SSH.type, tab);
this.sessionId = sessionId;
this.channel = channel; this.channel = channel;
this.connected = false;
this.canReconnect = false;
this.canWrite = false;
this.status = TerminalStatus.CONNECTING; this.status = TerminalStatus.CONNECTING;
this.inst = undefined as unknown as Terminal; this.inst = undefined as unknown as Terminal;
this.handler = undefined as unknown as ISshSessionHandler; this.handler = undefined as unknown as ISshSessionHandler;
@@ -213,14 +199,14 @@ export default class SshSession implements ISshSession {
// 设置已连接 // 设置已连接
connect(): void { connect(): void {
super.connect();
this.status = TerminalStatus.CONNECTED; this.status = TerminalStatus.CONNECTED;
this.connected = true;
this.inst.focus(); this.inst.focus();
} }
// 设置是否可写 // 设置是否可写
setCanWrite(canWrite: boolean): void { setCanWrite(canWrite: boolean): void {
this.canWrite = canWrite; super.setCanWrite(canWrite);
if (canWrite) { if (canWrite) {
this.inst.options.cursorBlink = useTerminalStore().preference.displaySetting.cursorBlink; this.inst.options.cursorBlink = useTerminalStore().preference.displaySetting.cursorBlink;
} else { } else {
@@ -229,7 +215,7 @@ export default class SshSession implements ISshSession {
} }
// 写入数据 // 写入数据
write(value: string | Uint8Array): void { write(value: string): void {
this.inst.write(value); this.inst.write(value);
} }
@@ -259,6 +245,7 @@ export default class SshSession implements ISshSession {
// 断开连接 // 断开连接
disconnect(): void { disconnect(): void {
super.disconnect();
// 发送关闭消息 // 发送关闭消息
this.channel.send(InputProtocol.CLOSE, { this.channel.send(InputProtocol.CLOSE, {
sessionId: this.sessionId sessionId: this.sessionId
@@ -267,6 +254,7 @@ export default class SshSession implements ISshSession {
// 关闭 // 关闭
close(): void { close(): void {
super.close();
try { try {
// 卸载插件 // 卸载插件
Object.values(this.addons) Object.values(this.addons)

View File

@@ -1,10 +1,16 @@
import { ISftpSession, ISshSession, ITerminalChannel, ITerminalOutputProcessor, ITerminalSessionManager, OutputPayload } from '../types/terminal.type'; import {
ISftpSession,
ISshSession,
ITerminalChannel,
ITerminalOutputProcessor,
ITerminalSession,
ITerminalSessionManager,
OutputPayload
} from '../types/terminal.type';
import { InputProtocol } from '../types/terminal.protocol'; import { InputProtocol } from '../types/terminal.protocol';
import { TerminalStatus } from '../types/terminal.const'; import { PanelSessionType, TerminalStatus } from '../types/terminal.const';
import { useTerminalStore } from '@/store'; import { useTerminalStore } from '@/store';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import SshSession from './ssh-session';
import SftpSession from './sftp-session';
// 终端输出消息体处理器实现 // 终端输出消息体处理器实现
export default class TerminalOutputProcessor implements ITerminalOutputProcessor { export default class TerminalOutputProcessor implements ITerminalOutputProcessor {
@@ -23,7 +29,8 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
const success = !!Number.parseInt(result); const success = !!Number.parseInt(result);
const session = this.sessionManager.getSession(sessionId); const session = this.sessionManager.getSession(sessionId);
session.canReconnect = !success; session.canReconnect = !success;
if (session instanceof SshSession) { // 处理
this.processWithType(session, ssh => {
// ssh 会话 // ssh 会话
if (success) { if (success) {
// 检查成功发送 connect 命令 // 检查成功发送 connect 命令
@@ -31,15 +38,15 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
this.channel.send(InputProtocol.CONNECT, { this.channel.send(InputProtocol.CONNECT, {
sessionId, sessionId,
terminalType: preference.sessionSetting.terminalEmulationType || 'xterm', terminalType: preference.sessionSetting.terminalEmulationType || 'xterm',
cols: session.inst.cols, cols: ssh.inst.cols,
rows: session.inst.rows rows: ssh.inst.rows
}); });
} else { } else {
// 未成功展示错误信息 // 未成功展示错误信息
session.write(`${msg || ''}\r\n输入回车重新连接...\r\n\r\n`); ssh.write(`${msg || ''}\r\n输入回车重新连接...\r\n\r\n`);
session.status = TerminalStatus.CLOSED; ssh.status = TerminalStatus.CLOSED;
} }
} else if (session instanceof SftpSession) { }, sftp => {
// sftp 会话 // sftp 会话
if (success) { if (success) {
// 检查成功发送 connect 命令 // 检查成功发送 connect 命令
@@ -48,10 +55,10 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
}); });
} else { } else {
// 未成功提示错误信息 // 未成功提示错误信息
session.resolver?.onClose(false, msg); sftp.resolver?.onClose(false, msg);
Message.error(msg || '建立 SFTP 失败'); Message.error(msg || '建立 SFTP 失败');
} }
} });
} }
// 处理连接消息 // 处理连接消息
@@ -59,29 +66,32 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
const success = !!Number.parseInt(result); const success = !!Number.parseInt(result);
const session = this.sessionManager.getSession(sessionId); const session = this.sessionManager.getSession(sessionId);
session.canReconnect = !success; session.canReconnect = !success;
if (session instanceof SshSession) { // 处理
this.processWithType(session, ssh => {
// ssh 会话 // ssh 会话
if (success) { if (success) {
// 设置可写 // 设置可写
session.setCanWrite(true); ssh.setCanWrite(true);
// 执行连接逻辑 // 执行连接逻辑
session.connect(); ssh.connect();
} else { } else {
// 未成功展示错误信息 // 未成功展示错误信息
session.write(`${msg || ''}\r\n输入回车重新连接...\r\n\r\n`); ssh.write(`${msg || ''}\r\n输入回车重新连接...\r\n\r\n`);
session.status = TerminalStatus.CLOSED; ssh.status = TerminalStatus.CLOSED;
} }
} else if (session instanceof SftpSession) { }, sftp => {
// sftp 会话 // sftp 会话
if (success) { if (success) {
// 设置可写
sftp.setCanWrite(true);
// 执行连接逻辑 // 执行连接逻辑
session.connect(); sftp.connect();
} else { } else {
// 未成功提示错误信息 // 未成功提示错误信息
session.resolver?.onClose(false, msg); sftp.resolver?.onClose(false, msg);
Message.error(msg || '打开 SFTP 失败'); Message.error(msg || '打开 SFTP 失败');
} }
} });
} }
// 处理关闭消息 // 处理关闭消息
@@ -94,20 +104,23 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
const isForceClose = !!Number.parseInt(forceClose); const isForceClose = !!Number.parseInt(forceClose);
session.connected = false; session.connected = false;
session.canReconnect = !isForceClose; session.canReconnect = !isForceClose;
if (session instanceof SshSession) { // 处理
this.processWithType(session, ssh => {
// ssh 拼接关闭消息 // ssh 拼接关闭消息
session.write(`\r\n\r\n${msg || ''}\r\n`); ssh.write(`\r\n\r\n${msg || ''}\r\n`);
if (!isForceClose) { if (!isForceClose) {
session.write('输入回车重新连接...\r\n\r\n'); ssh.write('输入回车重新连接...\r\n\r\n');
} }
// 设置状态 // 设置状态
session.status = TerminalStatus.CLOSED; ssh.status = TerminalStatus.CLOSED;
// 设置不可写 // 设置不可写
session.setCanWrite(false); ssh.setCanWrite(false);
} else if (session instanceof SftpSession) { }, sftp => {
// 设置不可写
sftp.setCanWrite(false);
// sftp 设置状态 // sftp 设置状态
session.resolver?.onClose(isForceClose, msg); sftp.resolver?.onClose(isForceClose, msg);
} });
} }
// 处理 pong 消息 // 处理 pong 消息
@@ -184,4 +197,17 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
session && session.resolver.resolveSftpSetContent(result, msg); session && session.resolver.resolveSftpSetContent(result, msg);
} }
// 根据类型处理操作
private processWithType(session: ITerminalSession,
sshProcess: (ssh: ISshSession) => any | void,
sftpProcess: (ssh: ISftpSession) => any | void) {
if (session.type === PanelSessionType.SSH.type) {
// SSH 操作
return sshProcess(session as ISshSession);
} else if (session.type === PanelSessionType.SFTP.type) {
// SFTP 操作
return sftpProcess(session as ISftpSession);
}
}
} }

View File

@@ -35,26 +35,21 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
// 打开 ssh 会话 // 打开 ssh 会话
async openSsh(tab: TerminalPanelTabItem, domRef: XtermDomRef) { async openSsh(tab: TerminalPanelTabItem, domRef: XtermDomRef) {
const sessionId = tab.sessionId;
const hostId = tab.hostId as number;
// 初始化客户端 // 初始化客户端
await this.initChannel(); await this.initChannel();
// 新建会话 // 新建会话
const session = new SshSession( const session = new SshSession(tab, this.channel);
hostId,
sessionId,
this.channel
);
// 初始化 // 初始化
session.init(domRef); session.init(domRef);
// 等待前端渲染完成 // 等待前端渲染完成
await sleep(100); await sleep(100);
// 添加会话 // 添加会话
const sessionId = tab.sessionId;
this.sessions[sessionId] = session; this.sessions[sessionId] = session;
// 发送会话初始化请求 // 发送会话初始化请求
this.channel.send(InputProtocol.CHECK, { this.channel.send(InputProtocol.CHECK, {
sessionId, sessionId,
hostId, hostId: tab.hostId,
connectType: PanelSessionType.SSH.type connectType: PanelSessionType.SSH.type
}); });
return session; return session;
@@ -62,24 +57,19 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
// 打开 sftp 会话 // 打开 sftp 会话
async openSftp(tab: TerminalPanelTabItem, resolver: ISftpSessionResolver): Promise<ISftpSession> { async openSftp(tab: TerminalPanelTabItem, resolver: ISftpSessionResolver): Promise<ISftpSession> {
const sessionId = tab.sessionId;
const hostId = tab.hostId as number;
// 初始化客户端 // 初始化客户端
await this.initChannel(); await this.initChannel();
// 新建会话 // 新建会话
const session = new SftpSession( const session = new SftpSession(tab, this.channel);
hostId,
sessionId,
this.channel
);
// 初始化 // 初始化
session.init(resolver); session.init(resolver);
// 添加会话 // 添加会话
const sessionId = tab.sessionId;
this.sessions[sessionId] = session; this.sessions[sessionId] = session;
// 发送会话初始化请求 // 发送会话初始化请求
this.channel.send(InputProtocol.CHECK, { this.channel.send(InputProtocol.CHECK, {
sessionId, sessionId,
hostId, hostId: tab.hostId,
connectType: PanelSessionType.SFTP.type connectType: PanelSessionType.SFTP.type
}); });
return session; return session;
@@ -145,7 +135,7 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
private dispatchResize() { private dispatchResize() {
// 对所有已连接的会话重置大小 // 对所有已连接的会话重置大小
Object.values(this.sessions) Object.values(this.sessions)
.filter(s => s instanceof SshSession) .filter(s => s.type === PanelSessionType.SSH.type)
.map(s => s as SshSession) .map(s => s as SshSession)
.filter(h => h.connected) .filter(h => h.connected)
.forEach(h => h.fit()); .forEach(h => h.fit());

View File

@@ -56,9 +56,9 @@
import RightSidebar from './components/layout/right-sidebar.vue'; import RightSidebar from './components/layout/right-sidebar.vue';
import MainContent from './components/layout/main-content.vue'; import MainContent from './components/layout/main-content.vue';
import LoadingSkeleton from './components/layout/loading-skeleton.vue'; import LoadingSkeleton from './components/layout/loading-skeleton.vue';
import TransferDrawer from '@/views/host/terminal/components/transfer/transfer-drawer.vue'; import TransferDrawer from './components/transfer/transfer-drawer.vue';
import CommandSnippetDrawer from '@/views/host/command-snippet/components/command-snippet-drawer.vue'; import CommandSnippetDrawer from './components/command-snippet/command-snippet-drawer.vue';
import PathBookmarkDrawer from '@/views/host/path-bookmark/components/path-bookmark-drawer.vue'; import PathBookmarkDrawer from './components/path-bookmark/path-bookmark-drawer.vue';
import '@/assets/style/host-terminal-layout.less'; import '@/assets/style/host-terminal-layout.less';
import '@xterm/xterm/css/xterm.css'; import '@xterm/xterm/css/xterm.css';
@@ -124,7 +124,7 @@
// 卸载处理 // 卸载处理
onUnmounted(() => { onUnmounted(() => {
// 卸载时清除 cache // 卸载时清除 cache
cacheStore.reset('authorizedHostKeys', 'authorizedHostIdentities', 'commandSnippetGroups'); cacheStore.reset('authorizedHostKeys', 'authorizedHostIdentities', 'commandSnippetGroups', 'pathBookmarkGroups');
// 移除关闭视口事件 // 移除关闭视口事件
window.removeEventListener('beforeunload', handleBeforeUnload); window.removeEventListener('beforeunload', handleBeforeUnload);
// 去除 body style // 去除 body style

View File

@@ -403,6 +403,9 @@ export const tabColorKey = 'terminalTabColor';
// SFTP 传输状态 // SFTP 传输状态
export const transferStatusKey = 'sftpTransferStatus'; export const transferStatusKey = 'sftpTransferStatus';
// 路径书签类型
export const pathBookmarkTypeKey = 'pathBookmarkType';
// 加载的字典值 // 加载的字典值
export const dictKeys = [ export const dictKeys = [
fontFamilyKey, fontSizeKey, fontFamilyKey, fontSizeKey,
@@ -410,4 +413,5 @@ export const dictKeys = [
newConnectionTypeKey, extraSshAuthTypeKey, newConnectionTypeKey, extraSshAuthTypeKey,
connectStatusKey, emulationTypeKey, connectStatusKey, emulationTypeKey,
tabColorKey, transferStatusKey, tabColorKey, transferStatusKey,
pathBookmarkTypeKey,
]; ];

View File

@@ -234,13 +234,20 @@ export interface XtermAddons {
// 终端会话定义 // 终端会话定义
export interface ITerminalSession { export interface ITerminalSession {
type: string;
title: string;
address: string;
hostId: number; hostId: number;
sessionId: string; sessionId: string;
// 是否已连接 // 是否已连接
connected: boolean; connected: boolean;
// 是否可以重新连接 // 是否可以重新连接
canReconnect: boolean; canReconnect: boolean;
// 是否可写
canWrite: boolean;
// 设置是否可写
setCanWrite: (canWrite: boolean) => void;
// 连接 // 连接
connect: () => void; connect: () => void;
// 断开连接 // 断开连接
@@ -253,8 +260,6 @@ export interface ITerminalSession {
export interface ISshSession extends ITerminalSession { export interface ISshSession extends ITerminalSession {
// terminal 实例 // terminal 实例
inst: Terminal; inst: Terminal;
// 是否可写
canWrite: boolean;
// 状态 // 状态
status: number; status: number;
// 处理器 // 处理器
@@ -262,10 +267,8 @@ export interface ISshSession extends ITerminalSession {
// 初始化 // 初始化
init: (domRef: XtermDomRef) => void; init: (domRef: XtermDomRef) => void;
// 设置是否可写
setCanWrite: (canWrite: boolean) => void;
// 写入数据 // 写入数据
write: (value: string | Uint8Array) => void; write: (value: string) => void;
// 聚焦 // 聚焦
focus: () => void; focus: () => void;
// 失焦 // 失焦