✨ 回车重连.
This commit is contained in:
@@ -8,7 +8,7 @@ import type {
|
||||
TerminalShortcutSetting,
|
||||
TerminalState
|
||||
} from './types';
|
||||
import type { ISshSession, PanelSessionTab, TerminalPanelTabItem } from '@/views/host/terminal/types/terminal.type';
|
||||
import type { ISshSession, PanelSessionTabType, TerminalPanelTabItem } from '@/views/host/terminal/types/terminal.type';
|
||||
import type { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
|
||||
import { getCurrentAuthorizedHost } from '@/api/asset/asset-authorized-data';
|
||||
import type { HostQueryResponse } from '@/api/asset/host';
|
||||
@@ -135,7 +135,7 @@ export default defineStore('terminal', {
|
||||
},
|
||||
|
||||
// 打开会话
|
||||
openSession(record: HostQueryResponse, type: PanelSessionTab, panelIndex: number = 0) {
|
||||
openSession(record: HostQueryResponse, type: PanelSessionTabType, panelIndex: number = 0) {
|
||||
// 添加到最近连接
|
||||
this.hosts.latestHosts = [...new Set([record.id, ...this.hosts.latestHosts])];
|
||||
// 切换到终端面板页面
|
||||
@@ -151,8 +151,10 @@ export default defineStore('terminal', {
|
||||
? Math.max(...seqArr) + 1
|
||||
: 1;
|
||||
// 打开 tab
|
||||
const sessionId = nextId(10);
|
||||
this.panelManager.getPanel(panelIndex).openTab({
|
||||
key: nextId(10),
|
||||
key: sessionId,
|
||||
sessionId,
|
||||
seq: nextSeq,
|
||||
title: `(${nextSeq}) ${record.alias || record.name}`,
|
||||
hostId: record.id,
|
||||
@@ -163,20 +165,18 @@ export default defineStore('terminal', {
|
||||
});
|
||||
},
|
||||
|
||||
// 重新打开 terminal 会话
|
||||
async reOpenTerminal(hostId: number, sessionId: string, panelIndex: number = 0) {
|
||||
console.log('rec');
|
||||
// 添加到最近连接
|
||||
this.hosts.latestHosts = [...new Set([hostId, ...this.hosts.latestHosts])];
|
||||
// 重新打开会话
|
||||
async reOpenSession(sessionId: string, panelIndex: number = 0) {
|
||||
// 切换到终端面板页面
|
||||
this.tabManager.openTab(TerminalTabs.TERMINAL_PANEL);
|
||||
// 获取当前面板并且分配新的 sessionId
|
||||
const panel = this.panelManager.getPanel(panelIndex);
|
||||
const tab = panel.getTab(sessionId);
|
||||
const newSessionId = nextId(10);
|
||||
tab.key = newSessionId;
|
||||
// 重新打开 ssh
|
||||
await this.sessionManager.reOpenSsh(sessionId, newSessionId);
|
||||
const newSessionId = tab.sessionId = nextId(10);
|
||||
// 添加到最近连接
|
||||
this.hosts.latestHosts = [...new Set([tab.hostId, ...this.hosts.latestHosts])];
|
||||
// 重新打开会话
|
||||
await this.sessionManager.reOpenSession(tab.type, sessionId, newSessionId);
|
||||
},
|
||||
|
||||
// 复制并且打开会话
|
||||
@@ -220,7 +220,7 @@ export default defineStore('terminal', {
|
||||
return;
|
||||
}
|
||||
// 获取会话
|
||||
return this.sessionManager.getSession<ISshSession>(sessionTab.key);
|
||||
return this.sessionManager.getSession<ISshSession>(sessionTab.sessionId);
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
@@ -16,9 +16,8 @@
|
||||
</span>
|
||||
</a-space>
|
||||
</template>
|
||||
<!-- 终端面板 FIXME -->
|
||||
<a-tab-pane v-for="tab in panel.items"
|
||||
:key="tab.key">
|
||||
<!-- 终端面板 这里的 key 只能使用 key 字段, 否则重连有问题 -->
|
||||
<a-tab-pane v-for="tab in panel.items" :key="tab.key">
|
||||
<!-- 标题 -->
|
||||
<template #title>
|
||||
<span class="tab-title-wrapper usn"
|
||||
@@ -64,6 +63,9 @@
|
||||
|
||||
// 监听 tab 切换
|
||||
watch(() => props.panel.active, (active, before) => {
|
||||
console.log(active);
|
||||
console.log(before);
|
||||
console.log(props.panel);
|
||||
// 失焦自动终端
|
||||
if (before) {
|
||||
const beforeTab = props.panel.items.find(s => s.key === before);
|
||||
@@ -78,7 +80,7 @@
|
||||
sessionManager.getSession<ISshSession>(active)?.focus();
|
||||
}
|
||||
}
|
||||
// 无终端自动关闭
|
||||
// 无终端自动关闭 FIXME
|
||||
if (!props.panel.items.length) {
|
||||
close();
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { PanelSessionTab } from '../../types/terminal.type';
|
||||
import type { PanelSessionTabType } from '../../types/terminal.type';
|
||||
import type { HostQueryResponse } from '@/api/asset/host';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useTerminalStore } from '@/store';
|
||||
@@ -129,7 +129,7 @@
|
||||
defineExpose({ open });
|
||||
|
||||
// 打开终端
|
||||
const clickHost = (item: HostQueryResponse, tab: PanelSessionTab) => {
|
||||
const clickHost = (item: HostQueryResponse, tab: PanelSessionTabType) => {
|
||||
openSession(item, tab, panelIndex.value);
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
@@ -45,12 +45,27 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- 已关闭-右侧操作 -->
|
||||
<div v-if="closed" class="sftp-table-header-right">
|
||||
<div v-if="session?.connected === false && closeMessage !== undefined"
|
||||
class="sftp-table-header-right">
|
||||
<!-- 错误信息 -->
|
||||
<a-tag class="close-message"
|
||||
color="red"
|
||||
:title="closeMessage">
|
||||
已断开: {{ closeMessage }}
|
||||
</a-tag>
|
||||
<!-- 重连 FIXME -->
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
:overlay-inverse="true"
|
||||
:auto-fix-position="false"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="重连">
|
||||
<span class="click-icon-wrapper header-action-icon ml8"
|
||||
@click="reConnect">
|
||||
<icon-refresh />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<!-- 路径编辑模式-右侧操作 -->
|
||||
<a-space v-else-if="pathEditable" class="sftp-table-header-right">
|
||||
@@ -191,16 +206,16 @@
|
||||
import { inject, nextTick, ref, watch } from 'vue';
|
||||
import { getParentPath, getPathAnalysis } from '@/utils/file';
|
||||
import { openSftpCreateModalKey, openSftpUploadModalKey } from '../../types/terminal.const';
|
||||
import { useTerminalStore } from '@/store';
|
||||
|
||||
const props = defineProps<{
|
||||
closed: boolean;
|
||||
closeMessage?: string;
|
||||
currentPath: string;
|
||||
session?: ISftpSession;
|
||||
selectedFiles: Array<string>;
|
||||
}>();
|
||||
|
||||
const emits = defineEmits(['update:selectedFiles', 'loadFile', 'download']);
|
||||
const emits = defineEmits(['update:selectedFiles', 'loadFile', 'download', 'setLoading']);
|
||||
|
||||
const showHiddenFile = ref(false);
|
||||
const analysisPaths = ref<Array<PathAnalysis>>([]);
|
||||
@@ -229,7 +244,7 @@
|
||||
// 设置命令编辑模式
|
||||
const setPathEditable = (editable: boolean) => {
|
||||
// 检查是否断开
|
||||
if (editable && props.closed) {
|
||||
if (editable && !props.session?.connected) {
|
||||
return;
|
||||
}
|
||||
pathEditable.value = editable;
|
||||
@@ -251,7 +266,7 @@
|
||||
// 加载文件列表
|
||||
const loadFileList = (path: string = props.currentPath) => {
|
||||
// 检查是否断开
|
||||
if (props.closed) {
|
||||
if (!props.session?.connected) {
|
||||
return;
|
||||
}
|
||||
emits('loadFile', path);
|
||||
@@ -284,6 +299,15 @@
|
||||
}
|
||||
};
|
||||
|
||||
// 重新连接
|
||||
const reConnect = () => {
|
||||
if (props.session) {
|
||||
emits('setLoading', true);
|
||||
// 重新连接
|
||||
useTerminalStore().reOpenSession(props.session.sessionId);
|
||||
}
|
||||
};
|
||||
|
||||
// 下载文件
|
||||
const downloadFile = () => {
|
||||
emits('download', [...props.selectedFiles]);
|
||||
|
||||
@@ -158,7 +158,6 @@
|
||||
session?: ISftpSession;
|
||||
list: Array<SftpFile>;
|
||||
loading: boolean;
|
||||
closed: boolean;
|
||||
selectedFiles: Array<string>;
|
||||
}>();
|
||||
|
||||
@@ -214,7 +213,7 @@
|
||||
const clickFilename = (record: TableData) => {
|
||||
if (record.isDir) {
|
||||
// 检查是否断开
|
||||
if (props.closed) {
|
||||
if (!props.session?.connected) {
|
||||
return;
|
||||
}
|
||||
// 进入文件夹
|
||||
@@ -227,7 +226,7 @@
|
||||
// 编辑文件
|
||||
const editFile = (record: TableData) => {
|
||||
// 检查是否断开
|
||||
if (props.closed) {
|
||||
if (!props.session?.connected) {
|
||||
return;
|
||||
}
|
||||
emits('editFile', record.name, record.path);
|
||||
@@ -237,7 +236,7 @@
|
||||
// 删除文件
|
||||
const deleteFile = (path: string) => {
|
||||
// 检查是否断开
|
||||
if (props.closed) {
|
||||
if (!props.session?.connected) {
|
||||
return;
|
||||
}
|
||||
props.session?.remove([path]);
|
||||
@@ -246,7 +245,7 @@
|
||||
// 下载文件
|
||||
const downloadFile = (path: string) => {
|
||||
// 检查是否断开
|
||||
if (props.closed) {
|
||||
if (!props.session?.connected) {
|
||||
return;
|
||||
}
|
||||
emits('download', [path]);
|
||||
@@ -255,7 +254,7 @@
|
||||
// 移动文件
|
||||
const moveFile = (path: string) => {
|
||||
// 检查是否断开
|
||||
if (props.closed) {
|
||||
if (!props.session?.connected) {
|
||||
return;
|
||||
}
|
||||
openSftpMoveModal(props.session?.sessionId as string, path);
|
||||
@@ -264,7 +263,7 @@
|
||||
// 文件提权
|
||||
const chmodFile = (path: string, permission: number) => {
|
||||
// 检查是否断开
|
||||
if (props.closed) {
|
||||
if (!props.session?.connected) {
|
||||
return;
|
||||
}
|
||||
openSftpChmodModal(props.session?.sessionId as string, path, permission);
|
||||
|
||||
@@ -12,17 +12,16 @@
|
||||
<!-- 表头 -->
|
||||
<sftp-table-header class="sftp-table-header"
|
||||
v-model:selected-files="selectFiles"
|
||||
:closed="closed"
|
||||
:close-message="closeMessage"
|
||||
:current-path="currentPath"
|
||||
:session="session"
|
||||
@load-file="loadFiles"
|
||||
@download="downloadFiles" />
|
||||
@download="downloadFiles"
|
||||
@set-loading="setTableLoading" />
|
||||
<!-- 表格 -->
|
||||
<sftp-table class="sftp-table-wrapper"
|
||||
v-model:selected-files="selectFiles"
|
||||
:session="session"
|
||||
:closed="closed"
|
||||
:list="fileList"
|
||||
:loading="tableLoading"
|
||||
@load-file="loadFiles"
|
||||
@@ -63,7 +62,7 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { ISftpSession, SftpFile, TerminalTabItem } from '../../types/terminal.type';
|
||||
import type { ISftpSession, SftpFile, TerminalPanelTabItem } from '../../types/terminal.type';
|
||||
import { onMounted, onUnmounted, provide, ref } from 'vue';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
@@ -79,7 +78,7 @@
|
||||
import SftpUploadModal from './sftp-upload-modal.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
tab: TerminalTabItem;
|
||||
tab: TerminalPanelTabItem;
|
||||
}>();
|
||||
|
||||
const { preference, sessionManager, transferManager } = useTerminalStore();
|
||||
@@ -91,8 +90,7 @@
|
||||
const fileList = ref<Array<SftpFile>>([]);
|
||||
const selectFiles = ref<Array<string>>([]);
|
||||
const splitSize = ref(1);
|
||||
const closed = ref(false);
|
||||
const closeMessage = ref('');
|
||||
const closeMessage = ref<string>();
|
||||
const editorView = ref(false);
|
||||
const editorRef = ref();
|
||||
const editorFileName = ref('');
|
||||
@@ -170,7 +168,8 @@
|
||||
|
||||
// 连接成功回调
|
||||
const connectCallback = () => {
|
||||
loadFiles('~');
|
||||
// FIXME TEST
|
||||
loadFiles(currentPath.value || '~');
|
||||
};
|
||||
|
||||
// 加载文件列表
|
||||
@@ -189,8 +188,7 @@
|
||||
};
|
||||
|
||||
// 关闭回调
|
||||
const onClose = (forceClose: string, msg: string) => {
|
||||
closed.value = true;
|
||||
const onClose = (forceClose: boolean, msg: string) => {
|
||||
closeMessage.value = msg;
|
||||
setTableLoading(false);
|
||||
setEditorLoading(false);
|
||||
@@ -267,7 +265,7 @@
|
||||
|
||||
// 关闭会话
|
||||
onUnmounted(() => {
|
||||
sessionManager.closeSession(props.tab.key);
|
||||
sessionManager.closeSession(props.tab.sessionId);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { ISshSession, TerminalTabItem, SidebarAction } from '../../types/terminal.type';
|
||||
import type { ISshSession, TerminalPanelTabItem, SidebarAction } from '../../types/terminal.type';
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { useDictStore, useTerminalStore } from '@/store';
|
||||
import { copy } from '@/hooks/copy';
|
||||
@@ -79,7 +79,7 @@
|
||||
import XtermSearchModal from '@/components/xtrem/search-modal/index.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
tab: TerminalTabItem;
|
||||
tab: TerminalPanelTabItem;
|
||||
}>();
|
||||
|
||||
const { getDictValue } = useDictStore();
|
||||
@@ -137,7 +137,6 @@
|
||||
|
||||
// 初始化会话
|
||||
onMounted(async () => {
|
||||
console.log('onMounted', props.tab.key);
|
||||
// 创建终端处理器
|
||||
session.value = await sessionManager.openSsh(props.tab, {
|
||||
el: terminalRef.value,
|
||||
@@ -148,7 +147,7 @@
|
||||
|
||||
// 关闭会话
|
||||
onUnmounted(() => {
|
||||
sessionManager.closeSession(props.tab.key);
|
||||
sessionManager.closeSession(props.tab.sessionId);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -11,6 +11,8 @@ export default class SftpSession implements ISftpSession {
|
||||
|
||||
public connected: boolean;
|
||||
|
||||
public canReconnect: boolean;
|
||||
|
||||
public resolver: ISftpSessionResolver;
|
||||
|
||||
private showHiddenFile: boolean;
|
||||
@@ -24,6 +26,7 @@ export default class SftpSession implements ISftpSession {
|
||||
this.sessionId = sessionId;
|
||||
this.channel = channel;
|
||||
this.connected = false;
|
||||
this.canReconnect = false;
|
||||
this.showHiddenFile = false;
|
||||
this.resolver = undefined as unknown as ISftpSessionResolver;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ export default class SshSession implements ISshSession {
|
||||
|
||||
public connected: boolean;
|
||||
|
||||
public canReconnect: boolean;
|
||||
|
||||
public canWrite: boolean;
|
||||
|
||||
public status: number;
|
||||
@@ -43,6 +45,7 @@ export default class SshSession implements ISshSession {
|
||||
this.sessionId = sessionId;
|
||||
this.channel = channel;
|
||||
this.connected = false;
|
||||
this.canReconnect = false;
|
||||
this.canWrite = false;
|
||||
this.status = TerminalStatus.CONNECTING;
|
||||
this.inst = undefined as unknown as Terminal;
|
||||
@@ -85,7 +88,6 @@ export default class SshSession implements ISshSession {
|
||||
if (e.type !== 'keydown') {
|
||||
return true;
|
||||
}
|
||||
console.log(e);
|
||||
// 检测是否为内置快捷键
|
||||
if (this.handler.checkIsBuiltin(e)) {
|
||||
return true;
|
||||
@@ -94,11 +96,12 @@ export default class SshSession implements ISshSession {
|
||||
if (this.handler.checkPreventDefault(e)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.key === 'Enter') {
|
||||
console.log("enter start");
|
||||
// TODO 回车 重新连接
|
||||
useTerminalStore().reOpenTerminal(this.hostId, this.sessionId);
|
||||
console.log("enter end");
|
||||
// 重新连接
|
||||
if (!this.connected && this.canReconnect && e.key === 'Enter') {
|
||||
setTimeout(async () => {
|
||||
await useTerminalStore().reOpenSession(this.sessionId);
|
||||
}, 50);
|
||||
return true;
|
||||
}
|
||||
// 自定义快捷键
|
||||
if (preference.shortcutSetting.enabled && preference.shortcutSetting.keys.length) {
|
||||
|
||||
@@ -42,7 +42,6 @@ export default class TerminalChannel implements ITerminalChannel {
|
||||
|
||||
// 发送消息
|
||||
send(protocol: Protocol, payload: InputPayload): void {
|
||||
console.log('send', payload);
|
||||
// 检查是否连接
|
||||
if (!this.isConnected()) {
|
||||
return;
|
||||
|
||||
@@ -22,6 +22,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
processCheck({ sessionId, result, msg }: OutputPayload): void {
|
||||
const success = !!Number.parseInt(result);
|
||||
const session = this.sessionManager.getSession(sessionId);
|
||||
session.canReconnect = !success;
|
||||
if (session instanceof SshSession) {
|
||||
// ssh 会话
|
||||
if (success) {
|
||||
@@ -35,7 +36,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
});
|
||||
} else {
|
||||
// 未成功展示错误信息
|
||||
session.write(`[91m${msg || ''}[0m`);
|
||||
session.write(`[91m${msg || ''}\r\n输入回车重新连接...[0m\r\n\r\n`);
|
||||
session.status = TerminalStatus.CLOSED;
|
||||
}
|
||||
} else if (session instanceof SftpSession) {
|
||||
@@ -47,7 +48,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
});
|
||||
} else {
|
||||
// 未成功提示错误信息
|
||||
session.resolver?.onClose('0', msg);
|
||||
session.resolver?.onClose(false, msg);
|
||||
Message.error(msg || '建立 SFTP 失败');
|
||||
}
|
||||
}
|
||||
@@ -57,6 +58,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
processConnect({ sessionId, result, msg }: OutputPayload): void {
|
||||
const success = !!Number.parseInt(result);
|
||||
const session = this.sessionManager.getSession(sessionId);
|
||||
session.canReconnect = !success;
|
||||
if (session instanceof SshSession) {
|
||||
// ssh 会话
|
||||
if (success) {
|
||||
@@ -66,7 +68,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
session.connect();
|
||||
} else {
|
||||
// 未成功展示错误信息
|
||||
session.write(`[91m${msg || ''}[0m`);
|
||||
session.write(`[91m${msg || ''}\r\n输入回车重新连接...[0m\r\n\r\n`);
|
||||
session.status = TerminalStatus.CLOSED;
|
||||
}
|
||||
} else if (session instanceof SftpSession) {
|
||||
@@ -76,7 +78,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
session.connect();
|
||||
} else {
|
||||
// 未成功提示错误信息
|
||||
session.resolver?.onClose('0', msg);
|
||||
session.resolver?.onClose(false, msg);
|
||||
Message.error(msg || '打开 SFTP 失败');
|
||||
}
|
||||
}
|
||||
@@ -89,18 +91,22 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
const isForceClose = !!Number.parseInt(forceClose);
|
||||
session.connected = false;
|
||||
session.canReconnect = !isForceClose;
|
||||
if (session instanceof SshSession) {
|
||||
// ssh 拼接关闭消息
|
||||
session.write(`\r\n\r\n[91m${msg || ''}[0m\r\n\r\n`);
|
||||
session.write(`\r\n\r\n[91m${msg || ''}[0m\r\n`);
|
||||
if (!isForceClose) {
|
||||
session.write('[91m输入回车重新连接...[0m\r\n\r\n');
|
||||
}
|
||||
// 设置状态
|
||||
session.status = TerminalStatus.CLOSED;
|
||||
session.connected = false;
|
||||
// 设置不可写
|
||||
session.setCanWrite(false);
|
||||
} else if (session instanceof SftpSession) {
|
||||
// sftp 设置状态
|
||||
session.connected = false;
|
||||
session.resolver?.onClose(forceClose, msg);
|
||||
session.resolver?.onClose(isForceClose, msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import type { ITerminalPanelManager, TerminalPanelTabItem } from '../types/terminal.type';
|
||||
import TerminalTabManager from '../handler/terminal-tab-manager';
|
||||
import type { ITerminalPanelManager } from '../types/terminal.type';
|
||||
import TerminalPanelTabManager from '../handler/terminal-panel-tab-manager';
|
||||
|
||||
// 终端面板管理器实现
|
||||
export default class TerminalPanelManager<T extends TerminalPanelTabItem = TerminalPanelTabItem> implements ITerminalPanelManager<T> {
|
||||
export default class TerminalPanelManager implements ITerminalPanelManager {
|
||||
|
||||
// 当前面板
|
||||
active: number;
|
||||
// 面板列表
|
||||
panels: Array<TerminalTabManager<T>>;
|
||||
panels: Array<TerminalPanelTabManager>;
|
||||
|
||||
constructor() {
|
||||
this.active = 0;
|
||||
this.panels = [new TerminalTabManager()];
|
||||
this.panels = [new TerminalPanelTabManager()];
|
||||
}
|
||||
|
||||
// 获取当前面板
|
||||
getCurrentPanel(): TerminalTabManager<T> {
|
||||
getCurrentPanel(): TerminalPanelTabManager {
|
||||
return this.panels[this.active];
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export default class TerminalPanelManager<T extends TerminalPanelTabItem = Termi
|
||||
};
|
||||
|
||||
// 获取面板
|
||||
getPanel(index: number): TerminalTabManager<T> {
|
||||
getPanel(index: number): TerminalPanelTabManager {
|
||||
return this.panels[index];
|
||||
};
|
||||
|
||||
@@ -41,7 +41,7 @@ export default class TerminalPanelManager<T extends TerminalPanelTabItem = Termi
|
||||
panel.clear();
|
||||
}
|
||||
this.active = 0;
|
||||
this.panels = [new TerminalTabManager()];
|
||||
this.panels = [new TerminalPanelTabManager()];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import type { ITerminalTabManager, TerminalPanelTabItem } from '../types/terminal.type';
|
||||
|
||||
// 终端面板 tab 管理器实现
|
||||
export default class TerminalPanelTabManager implements ITerminalTabManager<TerminalPanelTabItem> {
|
||||
|
||||
public active: string;
|
||||
|
||||
public items: Array<TerminalPanelTabItem>;
|
||||
|
||||
constructor(def: TerminalPanelTabItem | undefined = undefined) {
|
||||
if (def) {
|
||||
this.active = def.sessionId;
|
||||
this.items = [def];
|
||||
} else {
|
||||
this.active = undefined as unknown as string;
|
||||
this.items = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前 tab
|
||||
getCurrentTab() {
|
||||
if (!this.active) {
|
||||
return undefined;
|
||||
}
|
||||
return this.items.find(s => s.sessionId === this.active);
|
||||
}
|
||||
|
||||
// 获取 tab
|
||||
getTab(sessionId: string): TerminalPanelTabItem {
|
||||
return this.items.find(s => s.sessionId === sessionId) as TerminalPanelTabItem;
|
||||
}
|
||||
|
||||
// 点击 tab
|
||||
clickTab(sessionId: string): void {
|
||||
this.active = sessionId;
|
||||
}
|
||||
|
||||
// 删除 tab
|
||||
deleteTab(sessionId: string): void {
|
||||
// 获取当前 tab
|
||||
const tabIndex = this.items.findIndex(s => s.sessionId === sessionId);
|
||||
// 删除 tab
|
||||
this.items.splice(tabIndex, 1);
|
||||
if (sessionId === this.active && this.items.length !== 0) {
|
||||
// 切换为前一个 tab
|
||||
this.active = this.items[Math.max(tabIndex - 1, 0)].sessionId;
|
||||
} else {
|
||||
this.active = undefined as unknown as string;
|
||||
}
|
||||
}
|
||||
|
||||
// 打开 tab
|
||||
openTab(tab: TerminalPanelTabItem): void {
|
||||
// 不存在则创建 tab
|
||||
if (!this.items.find(s => s.sessionId === tab.sessionId)) {
|
||||
this.items.push(tab);
|
||||
}
|
||||
this.active = tab.sessionId;
|
||||
}
|
||||
|
||||
// 切换到前一个 tab
|
||||
changeToPrevTab() {
|
||||
this.changeToIndex(this.getCurrentTabIndex() - 1);
|
||||
}
|
||||
|
||||
// 切换到后一个 tab
|
||||
changeToNextTab() {
|
||||
this.changeToIndex(this.getCurrentTabIndex() + 1);
|
||||
}
|
||||
|
||||
// 切换索引 tab
|
||||
changeToIndex(index: number) {
|
||||
if (index < 0 || index >= this.items.length) {
|
||||
return;
|
||||
}
|
||||
// 切换 tab
|
||||
this.active = this.items[index].sessionId;
|
||||
}
|
||||
|
||||
// 获取当前索引
|
||||
private getCurrentTabIndex(): number {
|
||||
return this.items.findIndex(s => s.sessionId === this.active);
|
||||
}
|
||||
|
||||
// 清空
|
||||
clear() {
|
||||
this.active = undefined as unknown as string;
|
||||
this.items = [];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,10 +4,9 @@ import type {
|
||||
ITerminalChannel,
|
||||
ITerminalSession,
|
||||
ITerminalSessionManager,
|
||||
TerminalTabItem,
|
||||
TerminalPanelTabItem,
|
||||
XtermDomRef
|
||||
} from '../types/terminal.type';
|
||||
import type { ISshSession } from '../types/terminal.type';
|
||||
import { sleep } from '@/utils';
|
||||
import { InputProtocol } from '../types/terminal.protocol';
|
||||
import { PanelSessionType } from '../types/terminal.const';
|
||||
@@ -35,8 +34,8 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
|
||||
}
|
||||
|
||||
// 打开 ssh 会话
|
||||
async openSsh(tab: TerminalTabItem, domRef: XtermDomRef) {
|
||||
const sessionId = tab.key;
|
||||
async openSsh(tab: TerminalPanelTabItem, domRef: XtermDomRef) {
|
||||
const sessionId = tab.sessionId;
|
||||
const hostId = tab.hostId as number;
|
||||
// 初始化客户端
|
||||
await this.initChannel();
|
||||
@@ -61,28 +60,9 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
|
||||
return session;
|
||||
}
|
||||
|
||||
// 重新打开 ssh 会话
|
||||
async reOpenSsh(sessionId: string, newSessionId: string): Promise<void> {
|
||||
console.log('sessionId', sessionId, 'newSessionId', newSessionId);
|
||||
// 初始化客户端
|
||||
await this.initChannel();
|
||||
// 获取会话并且重新设置 sessionId
|
||||
const session = this.sessions[sessionId] as ISshSession;
|
||||
session.sessionId = newSessionId;
|
||||
this.sessions[sessionId] = undefined as unknown as ISshSession;
|
||||
this.sessions[newSessionId] = session;
|
||||
console.log('ckckck');
|
||||
// 发送会话初始化请求
|
||||
this.channel.send(InputProtocol.CHECK, {
|
||||
sessionId: newSessionId,
|
||||
hostId: session.hostId,
|
||||
connectType: PanelSessionType.SSH.type
|
||||
});
|
||||
}
|
||||
|
||||
// 打开 sftp 会话
|
||||
async openSftp(tab: TerminalTabItem, resolver: ISftpSessionResolver): Promise<ISftpSession> {
|
||||
const sessionId = tab.key;
|
||||
async openSftp(tab: TerminalPanelTabItem, resolver: ISftpSessionResolver): Promise<ISftpSession> {
|
||||
const sessionId = tab.sessionId;
|
||||
const hostId = tab.hostId as number;
|
||||
// 初始化客户端
|
||||
await this.initChannel();
|
||||
@@ -105,6 +85,23 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
|
||||
return session;
|
||||
}
|
||||
|
||||
// 重新打开会话
|
||||
async reOpenSession(type: string, sessionId: string, newSessionId: string): Promise<void> {
|
||||
// 初始化客户端
|
||||
await this.initChannel();
|
||||
// 获取会话并且重新设置 sessionId
|
||||
const session = this.sessions[sessionId] as ITerminalSession;
|
||||
session.sessionId = newSessionId;
|
||||
this.sessions[sessionId] = undefined as unknown as ITerminalSession;
|
||||
this.sessions[newSessionId] = session;
|
||||
// 发送会话初始化请求
|
||||
this.channel.send(InputProtocol.CHECK, {
|
||||
sessionId: newSessionId,
|
||||
hostId: session.hostId,
|
||||
connectType: type,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取终端会话
|
||||
getSession<T>(sessionId: string): T {
|
||||
return this.sessions[sessionId] as T;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { ITerminalTabManager, TerminalTabItem } from '../types/terminal.type';
|
||||
|
||||
// 终端 tab 管理器实现
|
||||
export default class TerminalTabManager<T extends TerminalTabItem = TerminalTabItem> implements ITerminalTabManager<T> {
|
||||
export default class TerminalTabManager implements ITerminalTabManager {
|
||||
|
||||
public active: string;
|
||||
|
||||
public items: Array<T>;
|
||||
public items: Array<TerminalTabItem>;
|
||||
|
||||
constructor(def: T | undefined = undefined) {
|
||||
constructor(def: TerminalTabItem | undefined = undefined) {
|
||||
if (def) {
|
||||
this.active = def.key;
|
||||
this.items = [def];
|
||||
@@ -26,8 +26,8 @@ export default class TerminalTabManager<T extends TerminalTabItem = TerminalTabI
|
||||
}
|
||||
|
||||
// 获取 tab
|
||||
getTab(key: string): T {
|
||||
return this.items.find(s => s.key === key) as T;
|
||||
getTab(key: string): TerminalTabItem {
|
||||
return this.items.find(s => s.key === key) as TerminalTabItem;
|
||||
}
|
||||
|
||||
// 点击 tab
|
||||
@@ -50,7 +50,7 @@ export default class TerminalTabManager<T extends TerminalTabItem = TerminalTabI
|
||||
}
|
||||
|
||||
// 打开 tab
|
||||
openTab(tab: T): void {
|
||||
openTab(tab: TerminalTabItem): void {
|
||||
// 不存在则创建 tab
|
||||
if (!this.items.find(s => s.key === tab.key)) {
|
||||
this.items.push(tab);
|
||||
|
||||
@@ -34,6 +34,18 @@ export const TerminalTabs = {
|
||||
},
|
||||
};
|
||||
|
||||
// 面板会话 tab 类型
|
||||
export const PanelSessionType = {
|
||||
SSH: {
|
||||
type: 'SSH',
|
||||
icon: 'icon-desktop'
|
||||
},
|
||||
SFTP: {
|
||||
type: 'SFTP',
|
||||
icon: 'icon-folder'
|
||||
},
|
||||
};
|
||||
|
||||
// 新建连接类型
|
||||
export const NewConnectionType = {
|
||||
GROUP: 'group',
|
||||
@@ -97,18 +109,6 @@ export const FILE_TYPE = {
|
||||
}
|
||||
};
|
||||
|
||||
// 面板会话 tab 类型
|
||||
export const PanelSessionType = {
|
||||
SSH: {
|
||||
type: 'SSH',
|
||||
icon: 'icon-desktop'
|
||||
},
|
||||
SFTP: {
|
||||
type: 'SFTP',
|
||||
icon: 'icon-folder'
|
||||
},
|
||||
};
|
||||
|
||||
// 终端状态
|
||||
export const TerminalStatus = {
|
||||
// 连接中
|
||||
|
||||
@@ -19,10 +19,11 @@ export interface TerminalTabItem {
|
||||
|
||||
// 终端面板 tab 元素
|
||||
export interface TerminalPanelTabItem extends TerminalTabItem {
|
||||
type: string;
|
||||
sessionId: string;
|
||||
seq: number;
|
||||
hostId: number;
|
||||
address: string;
|
||||
type: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
@@ -74,7 +75,7 @@ export interface LabelExtraSettingModel {
|
||||
}
|
||||
|
||||
// session tab
|
||||
export interface PanelSessionTab {
|
||||
export interface PanelSessionTabType {
|
||||
type: string;
|
||||
icon: string;
|
||||
}
|
||||
@@ -131,18 +132,18 @@ export interface ITerminalTabManager<T extends TerminalTabItem = TerminalTabItem
|
||||
}
|
||||
|
||||
// 终端面板管理器定义
|
||||
export interface ITerminalPanelManager<T extends TerminalPanelTabItem = TerminalPanelTabItem> {
|
||||
export interface ITerminalPanelManager {
|
||||
// 当前面板
|
||||
active: number;
|
||||
// 面板列表
|
||||
panels: Array<ITerminalTabManager<T>>;
|
||||
panels: Array<ITerminalTabManager<TerminalPanelTabItem>>;
|
||||
|
||||
// 获取当前面板
|
||||
getCurrentPanel: () => ITerminalTabManager<T>;
|
||||
getCurrentPanel: () => ITerminalTabManager<TerminalPanelTabItem>;
|
||||
// 设置当前面板
|
||||
setCurrentPanel: (active: number) => void;
|
||||
// 获取面板
|
||||
getPanel: (index: number) => ITerminalTabManager<T>;
|
||||
getPanel: (index: number) => ITerminalTabManager<TerminalPanelTabItem>;
|
||||
// 移除面板
|
||||
removePanel: (index: number) => void;
|
||||
// 重置
|
||||
@@ -152,11 +153,11 @@ export interface ITerminalPanelManager<T extends TerminalPanelTabItem = Terminal
|
||||
// 终端会话管理器定义
|
||||
export interface ITerminalSessionManager {
|
||||
// 打开 ssh 会话
|
||||
openSsh: (tab: TerminalTabItem, domRef: XtermDomRef) => Promise<ISshSession>;
|
||||
// 重新打开 ssh 会话
|
||||
reOpenSsh: (sessionId: string, newSessionId: string) => Promise<void>;
|
||||
openSsh: (tab: TerminalPanelTabItem, domRef: XtermDomRef) => Promise<ISshSession>;
|
||||
// 打开 sftp 会话
|
||||
openSftp: (tab: TerminalTabItem, resolver: ISftpSessionResolver) => Promise<ISftpSession>;
|
||||
openSftp: (tab: TerminalPanelTabItem, resolver: ISftpSessionResolver) => Promise<ISftpSession>;
|
||||
// 重新打开会话
|
||||
reOpenSession: (type: string, sessionId: string, newSessionId: string) => Promise<void>;
|
||||
// 获取终端会话
|
||||
getSession: <T extends ITerminalSession>(sessionId: string) => T;
|
||||
// 关闭终端会话
|
||||
@@ -232,6 +233,8 @@ export interface ITerminalSession {
|
||||
sessionId: string;
|
||||
// 是否已连接
|
||||
connected: boolean;
|
||||
// 是否可以重新连接
|
||||
canReconnect: boolean;
|
||||
|
||||
// 连接
|
||||
connect: () => void;
|
||||
@@ -355,7 +358,7 @@ export interface ISftpSessionResolver {
|
||||
// 连接后回调
|
||||
connectCallback: () => void;
|
||||
// 关闭回调
|
||||
onClose: (forceClose: string, msg: string) => void;
|
||||
onClose: (forceClose: boolean, msg: string) => void;
|
||||
// 接受文件列表响应
|
||||
resolveList: (result: string, path: string, list: Array<SftpFile>) => void;
|
||||
// 接收创建文件夹响应
|
||||
|
||||
Reference in New Issue
Block a user