⚡ 优化 terminal 逻辑.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export interface UserPreferenceData {
|
||||
/**
|
||||
* 用户偏好 - 页面视图
|
||||
*/
|
||||
export interface UserPreferenceViews {
|
||||
export interface UserPreferenceView {
|
||||
hostView: ViewType;
|
||||
hostKeyView: ViewType;
|
||||
hostIdentityView: ViewType;
|
||||
|
||||
@@ -76,15 +76,15 @@
|
||||
</script>
|
||||
|
||||
<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 { ref, watch, provide } from 'vue';
|
||||
import useVisible from '@/hooks/visible';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { deleteCommandSnippet, getCommandSnippetList } from '@/api/asset/command-snippet';
|
||||
import { useCacheStore, useTerminalStore } from '@/store';
|
||||
import { openUpdateSnippetKey, removeSnippetKey } from '../types/const';
|
||||
import { PanelSessionType } from '@/views/host/terminal/types/terminal.const';
|
||||
import { openUpdateSnippetKey, removeSnippetKey } from './types/const';
|
||||
import { PanelSessionType } from '../../types/terminal.const';
|
||||
import CommandSnippetListItem from './command-snippet-list-item.vue';
|
||||
import CommandSnippetListGroup from './command-snippet-list-group.vue';
|
||||
import CommandSnippetFormDrawer from './command-snippet-form-drawer.vue';
|
||||
@@ -52,7 +52,7 @@
|
||||
import useLoading from '@/hooks/loading';
|
||||
import useVisible from '@/hooks/visible';
|
||||
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 CommandSnippetGroupSelect from './command-snippet-group-select.vue';
|
||||
|
||||
@@ -113,14 +113,14 @@
|
||||
</script>
|
||||
|
||||
<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 { useTerminalStore } from '@/store';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
import { copy } from '@/hooks/copy';
|
||||
import { inject } from 'vue';
|
||||
import { openUpdateSnippetKey, removeSnippetKey } from '../types/const';
|
||||
import { PanelSessionType } from '@/views/host/terminal/types/terminal.const';
|
||||
import { openUpdateSnippetKey, removeSnippetKey } from './types/const';
|
||||
import { PanelSessionType } from '../../types/terminal.const';
|
||||
|
||||
const props = defineProps<{
|
||||
item: CommandSnippetQueryResponse;
|
||||
@@ -39,12 +39,12 @@
|
||||
import { onMounted, onUnmounted, watch } from 'vue';
|
||||
import { addEventListen, removeEventListen } from '@/utils/event';
|
||||
import EmptyRecommend from './empty-recommend.vue';
|
||||
import TerminalPanelsView from './terminal-panels-view.vue';
|
||||
import NewConnectionView from '../new-connection/new-connection-view.vue';
|
||||
import TerminalDisplaySetting from '../setting/display/terminal-display-setting.vue';
|
||||
import TerminalThemeSetting from '../setting/theme/terminal-theme-setting.vue';
|
||||
import TerminalGeneralSetting from '../setting/general/terminal-general-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']);
|
||||
|
||||
|
||||
@@ -76,15 +76,15 @@
|
||||
</script>
|
||||
|
||||
<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 { ref, provide, onMounted, watch } from 'vue';
|
||||
import { ref, provide, watch } from 'vue';
|
||||
import useVisible from '@/hooks/visible';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { deletePathBookmark, getPathBookmarkList } from '@/api/asset/path-bookmark';
|
||||
import { useCacheStore, useDictStore, useTerminalStore } from '@/store';
|
||||
import { PanelSessionType } from '@/views/host/terminal/types/terminal.const';
|
||||
import { dictKeys, openUpdatePathKey, removePathKey } from '../types/const';
|
||||
import { useCacheStore, useTerminalStore } from '@/store';
|
||||
import { PanelSessionType } from '../../types/terminal.const';
|
||||
import { openUpdatePathKey, removePathKey } from './types/const';
|
||||
import PathBookmarkListItem from './path-bookmark-list-item.vue';
|
||||
import PathBookmarkListGroup from './path-bookmark-list-group.vue';
|
||||
import PathBookmarkFormDrawer from './path-bookmark-form-drawer.vue';
|
||||
@@ -281,11 +281,6 @@
|
||||
getCurrentSession<ISshSession>(PanelSessionType.SSH.type)?.focus();
|
||||
};
|
||||
|
||||
// 加载字典值
|
||||
onMounted(() => {
|
||||
useDictStore().loadKeys(dictKeys);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@@ -55,8 +55,9 @@
|
||||
import useLoading from '@/hooks/loading';
|
||||
import useVisible from '@/hooks/visible';
|
||||
import { createPathBookmark, updatePathBookmark } from '@/api/asset/path-bookmark';
|
||||
import formRules from '../types/form.rules';
|
||||
import { PathBookmarkType, pathBookmarkTypeKey } from '../types/const';
|
||||
import formRules from './types/form.rules';
|
||||
import { PathBookmarkType } from './types/const';
|
||||
import { pathBookmarkTypeKey } from '../../types/terminal.const';
|
||||
import { useDictStore } from '@/store';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import PathBookmarkGroupSelect from './path-bookmark-group-select.vue';
|
||||
@@ -112,15 +112,15 @@
|
||||
</script>
|
||||
|
||||
<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 { useTerminalStore } from '@/store';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
import { copy } from '@/hooks/copy';
|
||||
import { inject } from 'vue';
|
||||
import { getParentPath } from '@/utils/file';
|
||||
import { openUpdatePathKey, PathBookmarkType, removePathKey } from '../types/const';
|
||||
import { PanelSessionType } from '@/views/host/terminal/types/terminal.const';
|
||||
import { openUpdatePathKey, PathBookmarkType, removePathKey } from './types/const';
|
||||
import { PanelSessionType } from '../../types/terminal.const';
|
||||
|
||||
const props = defineProps<{
|
||||
item: PathBookmarkQueryResponse;
|
||||
@@ -9,9 +9,3 @@ export const openUpdatePathKey = Symbol();
|
||||
|
||||
// 删除 path key
|
||||
export const removePathKey = Symbol();
|
||||
|
||||
// 路径书签类型 字典项
|
||||
export const pathBookmarkTypeKey = 'pathBookmarkType';
|
||||
|
||||
// 加载的字典值
|
||||
export const dictKeys = [pathBookmarkTypeKey];
|
||||
@@ -45,11 +45,12 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { ISftpSession } from '../../types/terminal.type';
|
||||
import useVisible from '@/hooks/visible';
|
||||
import { nextTick, ref } from 'vue';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import { permission10toString } from '@/utils/file';
|
||||
import SftpSession from '../../handler/sftp-session';
|
||||
import { PanelSessionType } from '../../types/terminal.const';
|
||||
|
||||
const { visible, setVisible } = useVisible();
|
||||
const { sessionManager } = useTerminalStore();
|
||||
@@ -92,8 +93,8 @@
|
||||
return false;
|
||||
}
|
||||
// 获取会话
|
||||
const session = sessionManager.getSession(sessionId.value);
|
||||
if (session instanceof SftpSession) {
|
||||
const session = sessionManager.getSession<ISftpSession>(sessionId.value);
|
||||
if (session.type === PanelSessionType.SFTP.type) {
|
||||
session.chmod(formModel.value.path, formModel.value.mod);
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -30,10 +30,11 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { ISftpSession } from '../../types/terminal.type';
|
||||
import useVisible from '@/hooks/visible';
|
||||
import { nextTick, ref } from 'vue';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import SftpSession from '../../handler/sftp-session';
|
||||
import { PanelSessionType } from '../../types/terminal.const';
|
||||
|
||||
const { visible, setVisible } = useVisible();
|
||||
const { sessionManager } = useTerminalStore();
|
||||
@@ -69,8 +70,8 @@
|
||||
return false;
|
||||
}
|
||||
// 获取会话
|
||||
const session = sessionManager.getSession(sessionId.value);
|
||||
if (session instanceof SftpSession) {
|
||||
const session = sessionManager.getSession<ISftpSession>(sessionId.value);
|
||||
if (session.type === PanelSessionType.SFTP.type) {
|
||||
if (touch.value) {
|
||||
// 创建文件
|
||||
session.touch(formModel.value.path);
|
||||
|
||||
@@ -38,10 +38,11 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { ISftpSession } from '../../types/terminal.type';
|
||||
import useVisible from '@/hooks/visible';
|
||||
import { nextTick, ref } from 'vue';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import SftpSession from '../../handler/sftp-session';
|
||||
import { PanelSessionType } from '../../types/terminal.const';
|
||||
|
||||
const { visible, setVisible } = useVisible();
|
||||
const { sessionManager } = useTerminalStore();
|
||||
@@ -77,8 +78,8 @@
|
||||
return false;
|
||||
}
|
||||
// 获取会话
|
||||
const session = sessionManager.getSession(sessionId.value);
|
||||
if (session instanceof SftpSession) {
|
||||
const session = sessionManager.getSession<ISftpSession>(sessionId.value);
|
||||
if (session.type === PanelSessionType.SFTP.type) {
|
||||
session.move(formModel.value.path, formModel.value.target);
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 { PanelSessionType } from '../types/terminal.const';
|
||||
import { Modal } from '@arco-design/web-vue';
|
||||
import BaseSession from './base-session';
|
||||
|
||||
// sftp 会话实现
|
||||
export default class SftpSession implements ISftpSession {
|
||||
|
||||
public readonly hostId: number;
|
||||
|
||||
public sessionId: string;
|
||||
|
||||
public connected: boolean;
|
||||
|
||||
public canReconnect: boolean;
|
||||
export default class SftpSession extends BaseSession implements ISftpSession {
|
||||
|
||||
public resolver: ISftpSessionResolver;
|
||||
|
||||
@@ -19,14 +13,10 @@ export default class SftpSession implements ISftpSession {
|
||||
|
||||
private readonly channel: ITerminalChannel;
|
||||
|
||||
constructor(hostId: number,
|
||||
sessionId: string,
|
||||
constructor(tab: TerminalPanelTabItem,
|
||||
channel: ITerminalChannel) {
|
||||
this.hostId = hostId;
|
||||
this.sessionId = sessionId;
|
||||
super(PanelSessionType.SFTP.type, tab);
|
||||
this.channel = channel;
|
||||
this.connected = false;
|
||||
this.canReconnect = false;
|
||||
this.showHiddenFile = false;
|
||||
this.resolver = undefined as unknown as ISftpSessionResolver;
|
||||
}
|
||||
@@ -38,7 +28,7 @@ export default class SftpSession implements ISftpSession {
|
||||
|
||||
// 设置已连接
|
||||
connect(): void {
|
||||
this.connected = true;
|
||||
super.connect();
|
||||
// 连接回调
|
||||
this.resolver.connectCallback();
|
||||
}
|
||||
@@ -140,14 +130,11 @@ export default class SftpSession implements ISftpSession {
|
||||
|
||||
// 断开连接
|
||||
disconnect(): void {
|
||||
super.disconnect();
|
||||
// 发送关闭消息
|
||||
this.channel.send(InputProtocol.CLOSE, {
|
||||
sessionId: this.sessionId
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭
|
||||
close(): void {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { UnwrapRef } from 'vue';
|
||||
import type { ISearchOptions } from '@xterm/addon-search';
|
||||
import { SearchAddon } from '@xterm/addon-search';
|
||||
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 { 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 { FitAddon } from '@xterm/addon-fit';
|
||||
import { SearchAddon } from '@xterm/addon-search';
|
||||
import { WebLinksAddon } from '@xterm/addon-web-links';
|
||||
import { ImageAddon } from '@xterm/addon-image';
|
||||
import { Unicode11Addon } from '@xterm/addon-unicode11';
|
||||
@@ -16,22 +16,13 @@ import { WebglAddon } from '@xterm/addon-webgl';
|
||||
import { playBell } from '@/utils/bell';
|
||||
import { addEventListen } from '@/utils/event';
|
||||
import SshSessionHandler from './ssh-session-handler';
|
||||
import BaseSession from './base-session';
|
||||
|
||||
// ssh 会话实现
|
||||
export default class SshSession implements ISshSession {
|
||||
|
||||
public readonly hostId: number;
|
||||
|
||||
public sessionId: string;
|
||||
export default class SshSession extends BaseSession implements ISshSession {
|
||||
|
||||
public inst: Terminal;
|
||||
|
||||
public connected: boolean;
|
||||
|
||||
public canReconnect: boolean;
|
||||
|
||||
public canWrite: boolean;
|
||||
|
||||
public status: number;
|
||||
|
||||
public handler: ISshSessionHandler;
|
||||
@@ -40,15 +31,10 @@ export default class SshSession implements ISshSession {
|
||||
|
||||
private readonly addons: XtermAddons;
|
||||
|
||||
constructor(hostId: number,
|
||||
sessionId: string,
|
||||
constructor(tab: TerminalPanelTabItem,
|
||||
channel: ITerminalChannel) {
|
||||
this.hostId = hostId;
|
||||
this.sessionId = sessionId;
|
||||
super(PanelSessionType.SSH.type, tab);
|
||||
this.channel = channel;
|
||||
this.connected = false;
|
||||
this.canReconnect = false;
|
||||
this.canWrite = false;
|
||||
this.status = TerminalStatus.CONNECTING;
|
||||
this.inst = undefined as unknown as Terminal;
|
||||
this.handler = undefined as unknown as ISshSessionHandler;
|
||||
@@ -213,14 +199,14 @@ export default class SshSession implements ISshSession {
|
||||
|
||||
// 设置已连接
|
||||
connect(): void {
|
||||
super.connect();
|
||||
this.status = TerminalStatus.CONNECTED;
|
||||
this.connected = true;
|
||||
this.inst.focus();
|
||||
}
|
||||
|
||||
// 设置是否可写
|
||||
setCanWrite(canWrite: boolean): void {
|
||||
this.canWrite = canWrite;
|
||||
super.setCanWrite(canWrite);
|
||||
if (canWrite) {
|
||||
this.inst.options.cursorBlink = useTerminalStore().preference.displaySetting.cursorBlink;
|
||||
} else {
|
||||
@@ -229,7 +215,7 @@ export default class SshSession implements ISshSession {
|
||||
}
|
||||
|
||||
// 写入数据
|
||||
write(value: string | Uint8Array): void {
|
||||
write(value: string): void {
|
||||
this.inst.write(value);
|
||||
}
|
||||
|
||||
@@ -259,6 +245,7 @@ export default class SshSession implements ISshSession {
|
||||
|
||||
// 断开连接
|
||||
disconnect(): void {
|
||||
super.disconnect();
|
||||
// 发送关闭消息
|
||||
this.channel.send(InputProtocol.CLOSE, {
|
||||
sessionId: this.sessionId
|
||||
@@ -267,6 +254,7 @@ export default class SshSession implements ISshSession {
|
||||
|
||||
// 关闭
|
||||
close(): void {
|
||||
super.close();
|
||||
try {
|
||||
// 卸载插件
|
||||
Object.values(this.addons)
|
||||
|
||||
@@ -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 { TerminalStatus } from '../types/terminal.const';
|
||||
import { PanelSessionType, TerminalStatus } from '../types/terminal.const';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import SshSession from './ssh-session';
|
||||
import SftpSession from './sftp-session';
|
||||
|
||||
// 终端输出消息体处理器实现
|
||||
export default class TerminalOutputProcessor implements ITerminalOutputProcessor {
|
||||
@@ -23,7 +29,8 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
const success = !!Number.parseInt(result);
|
||||
const session = this.sessionManager.getSession(sessionId);
|
||||
session.canReconnect = !success;
|
||||
if (session instanceof SshSession) {
|
||||
// 处理
|
||||
this.processWithType(session, ssh => {
|
||||
// ssh 会话
|
||||
if (success) {
|
||||
// 检查成功发送 connect 命令
|
||||
@@ -31,15 +38,15 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
this.channel.send(InputProtocol.CONNECT, {
|
||||
sessionId,
|
||||
terminalType: preference.sessionSetting.terminalEmulationType || 'xterm',
|
||||
cols: session.inst.cols,
|
||||
rows: session.inst.rows
|
||||
cols: ssh.inst.cols,
|
||||
rows: ssh.inst.rows
|
||||
});
|
||||
} else {
|
||||
// 未成功展示错误信息
|
||||
session.write(`[91m${msg || ''}\r\n输入回车重新连接...[0m\r\n\r\n`);
|
||||
session.status = TerminalStatus.CLOSED;
|
||||
ssh.write(`[91m${msg || ''}\r\n输入回车重新连接...[0m\r\n\r\n`);
|
||||
ssh.status = TerminalStatus.CLOSED;
|
||||
}
|
||||
} else if (session instanceof SftpSession) {
|
||||
}, sftp => {
|
||||
// sftp 会话
|
||||
if (success) {
|
||||
// 检查成功发送 connect 命令
|
||||
@@ -48,10 +55,10 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
});
|
||||
} else {
|
||||
// 未成功提示错误信息
|
||||
session.resolver?.onClose(false, msg);
|
||||
sftp.resolver?.onClose(false, msg);
|
||||
Message.error(msg || '建立 SFTP 失败');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 处理连接消息
|
||||
@@ -59,29 +66,32 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
const success = !!Number.parseInt(result);
|
||||
const session = this.sessionManager.getSession(sessionId);
|
||||
session.canReconnect = !success;
|
||||
if (session instanceof SshSession) {
|
||||
// 处理
|
||||
this.processWithType(session, ssh => {
|
||||
// ssh 会话
|
||||
if (success) {
|
||||
// 设置可写
|
||||
session.setCanWrite(true);
|
||||
ssh.setCanWrite(true);
|
||||
// 执行连接逻辑
|
||||
session.connect();
|
||||
ssh.connect();
|
||||
} else {
|
||||
// 未成功展示错误信息
|
||||
session.write(`[91m${msg || ''}\r\n输入回车重新连接...[0m\r\n\r\n`);
|
||||
session.status = TerminalStatus.CLOSED;
|
||||
ssh.write(`[91m${msg || ''}\r\n输入回车重新连接...[0m\r\n\r\n`);
|
||||
ssh.status = TerminalStatus.CLOSED;
|
||||
}
|
||||
} else if (session instanceof SftpSession) {
|
||||
}, sftp => {
|
||||
// sftp 会话
|
||||
if (success) {
|
||||
// 设置可写
|
||||
sftp.setCanWrite(true);
|
||||
// 执行连接逻辑
|
||||
session.connect();
|
||||
sftp.connect();
|
||||
} else {
|
||||
// 未成功提示错误信息
|
||||
session.resolver?.onClose(false, msg);
|
||||
sftp.resolver?.onClose(false, msg);
|
||||
Message.error(msg || '打开 SFTP 失败');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 处理关闭消息
|
||||
@@ -94,20 +104,23 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
const isForceClose = !!Number.parseInt(forceClose);
|
||||
session.connected = false;
|
||||
session.canReconnect = !isForceClose;
|
||||
if (session instanceof SshSession) {
|
||||
// 处理
|
||||
this.processWithType(session, ssh => {
|
||||
// ssh 拼接关闭消息
|
||||
session.write(`\r\n\r\n[91m${msg || ''}[0m\r\n`);
|
||||
ssh.write(`\r\n\r\n[91m${msg || ''}[0m\r\n`);
|
||||
if (!isForceClose) {
|
||||
session.write('[91m输入回车重新连接...[0m\r\n\r\n');
|
||||
ssh.write('[91m输入回车重新连接...[0m\r\n\r\n');
|
||||
}
|
||||
// 设置状态
|
||||
session.status = TerminalStatus.CLOSED;
|
||||
ssh.status = TerminalStatus.CLOSED;
|
||||
// 设置不可写
|
||||
session.setCanWrite(false);
|
||||
} else if (session instanceof SftpSession) {
|
||||
ssh.setCanWrite(false);
|
||||
}, sftp => {
|
||||
// 设置不可写
|
||||
sftp.setCanWrite(false);
|
||||
// sftp 设置状态
|
||||
session.resolver?.onClose(isForceClose, msg);
|
||||
}
|
||||
sftp.resolver?.onClose(isForceClose, msg);
|
||||
});
|
||||
}
|
||||
|
||||
// 处理 pong 消息
|
||||
@@ -184,4 +197,17 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,26 +35,21 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
|
||||
|
||||
// 打开 ssh 会话
|
||||
async openSsh(tab: TerminalPanelTabItem, domRef: XtermDomRef) {
|
||||
const sessionId = tab.sessionId;
|
||||
const hostId = tab.hostId as number;
|
||||
// 初始化客户端
|
||||
await this.initChannel();
|
||||
// 新建会话
|
||||
const session = new SshSession(
|
||||
hostId,
|
||||
sessionId,
|
||||
this.channel
|
||||
);
|
||||
const session = new SshSession(tab, this.channel);
|
||||
// 初始化
|
||||
session.init(domRef);
|
||||
// 等待前端渲染完成
|
||||
await sleep(100);
|
||||
// 添加会话
|
||||
const sessionId = tab.sessionId;
|
||||
this.sessions[sessionId] = session;
|
||||
// 发送会话初始化请求
|
||||
this.channel.send(InputProtocol.CHECK, {
|
||||
sessionId,
|
||||
hostId,
|
||||
hostId: tab.hostId,
|
||||
connectType: PanelSessionType.SSH.type
|
||||
});
|
||||
return session;
|
||||
@@ -62,24 +57,19 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
|
||||
|
||||
// 打开 sftp 会话
|
||||
async openSftp(tab: TerminalPanelTabItem, resolver: ISftpSessionResolver): Promise<ISftpSession> {
|
||||
const sessionId = tab.sessionId;
|
||||
const hostId = tab.hostId as number;
|
||||
// 初始化客户端
|
||||
await this.initChannel();
|
||||
// 新建会话
|
||||
const session = new SftpSession(
|
||||
hostId,
|
||||
sessionId,
|
||||
this.channel
|
||||
);
|
||||
const session = new SftpSession(tab, this.channel);
|
||||
// 初始化
|
||||
session.init(resolver);
|
||||
// 添加会话
|
||||
const sessionId = tab.sessionId;
|
||||
this.sessions[sessionId] = session;
|
||||
// 发送会话初始化请求
|
||||
this.channel.send(InputProtocol.CHECK, {
|
||||
sessionId,
|
||||
hostId,
|
||||
hostId: tab.hostId,
|
||||
connectType: PanelSessionType.SFTP.type
|
||||
});
|
||||
return session;
|
||||
@@ -145,7 +135,7 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
|
||||
private dispatchResize() {
|
||||
// 对所有已连接的会话重置大小
|
||||
Object.values(this.sessions)
|
||||
.filter(s => s instanceof SshSession)
|
||||
.filter(s => s.type === PanelSessionType.SSH.type)
|
||||
.map(s => s as SshSession)
|
||||
.filter(h => h.connected)
|
||||
.forEach(h => h.fit());
|
||||
|
||||
@@ -56,9 +56,9 @@
|
||||
import RightSidebar from './components/layout/right-sidebar.vue';
|
||||
import MainContent from './components/layout/main-content.vue';
|
||||
import LoadingSkeleton from './components/layout/loading-skeleton.vue';
|
||||
import TransferDrawer from '@/views/host/terminal/components/transfer/transfer-drawer.vue';
|
||||
import CommandSnippetDrawer from '@/views/host/command-snippet/components/command-snippet-drawer.vue';
|
||||
import PathBookmarkDrawer from '@/views/host/path-bookmark/components/path-bookmark-drawer.vue';
|
||||
import TransferDrawer from './components/transfer/transfer-drawer.vue';
|
||||
import CommandSnippetDrawer from './components/command-snippet/command-snippet-drawer.vue';
|
||||
import PathBookmarkDrawer from './components/path-bookmark/path-bookmark-drawer.vue';
|
||||
import '@/assets/style/host-terminal-layout.less';
|
||||
import '@xterm/xterm/css/xterm.css';
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
// 卸载处理
|
||||
onUnmounted(() => {
|
||||
// 卸载时清除 cache
|
||||
cacheStore.reset('authorizedHostKeys', 'authorizedHostIdentities', 'commandSnippetGroups');
|
||||
cacheStore.reset('authorizedHostKeys', 'authorizedHostIdentities', 'commandSnippetGroups', 'pathBookmarkGroups');
|
||||
// 移除关闭视口事件
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
// 去除 body style
|
||||
|
||||
@@ -403,6 +403,9 @@ export const tabColorKey = 'terminalTabColor';
|
||||
// SFTP 传输状态
|
||||
export const transferStatusKey = 'sftpTransferStatus';
|
||||
|
||||
// 路径书签类型
|
||||
export const pathBookmarkTypeKey = 'pathBookmarkType';
|
||||
|
||||
// 加载的字典值
|
||||
export const dictKeys = [
|
||||
fontFamilyKey, fontSizeKey,
|
||||
@@ -410,4 +413,5 @@ export const dictKeys = [
|
||||
newConnectionTypeKey, extraSshAuthTypeKey,
|
||||
connectStatusKey, emulationTypeKey,
|
||||
tabColorKey, transferStatusKey,
|
||||
pathBookmarkTypeKey,
|
||||
];
|
||||
|
||||
@@ -234,13 +234,20 @@ export interface XtermAddons {
|
||||
|
||||
// 终端会话定义
|
||||
export interface ITerminalSession {
|
||||
type: string;
|
||||
title: string;
|
||||
address: string;
|
||||
hostId: number;
|
||||
sessionId: string;
|
||||
// 是否已连接
|
||||
connected: boolean;
|
||||
// 是否可以重新连接
|
||||
canReconnect: boolean;
|
||||
// 是否可写
|
||||
canWrite: boolean;
|
||||
|
||||
// 设置是否可写
|
||||
setCanWrite: (canWrite: boolean) => void;
|
||||
// 连接
|
||||
connect: () => void;
|
||||
// 断开连接
|
||||
@@ -253,8 +260,6 @@ export interface ITerminalSession {
|
||||
export interface ISshSession extends ITerminalSession {
|
||||
// terminal 实例
|
||||
inst: Terminal;
|
||||
// 是否可写
|
||||
canWrite: boolean;
|
||||
// 状态
|
||||
status: number;
|
||||
// 处理器
|
||||
@@ -262,10 +267,8 @@ export interface ISshSession extends ITerminalSession {
|
||||
|
||||
// 初始化
|
||||
init: (domRef: XtermDomRef) => void;
|
||||
// 设置是否可写
|
||||
setCanWrite: (canWrite: boolean) => void;
|
||||
// 写入数据
|
||||
write: (value: string | Uint8Array) => void;
|
||||
write: (value: string) => void;
|
||||
// 聚焦
|
||||
focus: () => void;
|
||||
// 失焦
|
||||
|
||||
Reference in New Issue
Block a user