回车重连.

This commit is contained in:
lijiahangmax
2024-04-23 09:15:22 +08:00
parent c66e62623e
commit c8c947d8a8
17 changed files with 247 additions and 123 deletions

View File

@@ -8,7 +8,7 @@ import type {
TerminalShortcutSetting, TerminalShortcutSetting,
TerminalState TerminalState
} from './types'; } 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 type { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
import { getCurrentAuthorizedHost } from '@/api/asset/asset-authorized-data'; import { getCurrentAuthorizedHost } from '@/api/asset/asset-authorized-data';
import type { HostQueryResponse } from '@/api/asset/host'; 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])]; this.hosts.latestHosts = [...new Set([record.id, ...this.hosts.latestHosts])];
// 切换到终端面板页面 // 切换到终端面板页面
@@ -151,8 +151,10 @@ export default defineStore('terminal', {
? Math.max(...seqArr) + 1 ? Math.max(...seqArr) + 1
: 1; : 1;
// 打开 tab // 打开 tab
const sessionId = nextId(10);
this.panelManager.getPanel(panelIndex).openTab({ this.panelManager.getPanel(panelIndex).openTab({
key: nextId(10), key: sessionId,
sessionId,
seq: nextSeq, seq: nextSeq,
title: `(${nextSeq}) ${record.alias || record.name}`, title: `(${nextSeq}) ${record.alias || record.name}`,
hostId: record.id, hostId: record.id,
@@ -163,20 +165,18 @@ export default defineStore('terminal', {
}); });
}, },
// 重新打开 terminal 会话 // 重新打开会话
async reOpenTerminal(hostId: number, sessionId: string, panelIndex: number = 0) { async reOpenSession(sessionId: string, panelIndex: number = 0) {
console.log('rec');
// 添加到最近连接
this.hosts.latestHosts = [...new Set([hostId, ...this.hosts.latestHosts])];
// 切换到终端面板页面 // 切换到终端面板页面
this.tabManager.openTab(TerminalTabs.TERMINAL_PANEL); this.tabManager.openTab(TerminalTabs.TERMINAL_PANEL);
// 获取当前面板并且分配新的 sessionId // 获取当前面板并且分配新的 sessionId
const panel = this.panelManager.getPanel(panelIndex); const panel = this.panelManager.getPanel(panelIndex);
const tab = panel.getTab(sessionId); const tab = panel.getTab(sessionId);
const newSessionId = nextId(10); const newSessionId = tab.sessionId = nextId(10);
tab.key = newSessionId; // 添加到最近连接
// 重新打开 ssh this.hosts.latestHosts = [...new Set([tab.hostId, ...this.hosts.latestHosts])];
await this.sessionManager.reOpenSsh(sessionId, newSessionId); // 重新打开会话
await this.sessionManager.reOpenSession(tab.type, sessionId, newSessionId);
}, },
// 复制并且打开会话 // 复制并且打开会话
@@ -220,7 +220,7 @@ export default defineStore('terminal', {
return; return;
} }
// 获取会话 // 获取会话
return this.sessionManager.getSession<ISshSession>(sessionTab.key); return this.sessionManager.getSession<ISshSession>(sessionTab.sessionId);
}, },
}, },

View File

@@ -16,9 +16,8 @@
</span> </span>
</a-space> </a-space>
</template> </template>
<!-- 终端面板 FIXME --> <!-- 终端面板 这里的 key 只能使用 key 字段, 否则重连有问题 -->
<a-tab-pane v-for="tab in panel.items" <a-tab-pane v-for="tab in panel.items" :key="tab.key">
:key="tab.key">
<!-- 标题 --> <!-- 标题 -->
<template #title> <template #title>
<span class="tab-title-wrapper usn" <span class="tab-title-wrapper usn"
@@ -64,6 +63,9 @@
// 监听 tab 切换 // 监听 tab 切换
watch(() => props.panel.active, (active, before) => { watch(() => props.panel.active, (active, before) => {
console.log(active);
console.log(before);
console.log(props.panel);
// 失焦自动终端 // 失焦自动终端
if (before) { if (before) {
const beforeTab = props.panel.items.find(s => s.key === before); const beforeTab = props.panel.items.find(s => s.key === before);
@@ -78,7 +80,7 @@
sessionManager.getSession<ISshSession>(active)?.focus(); sessionManager.getSession<ISshSession>(active)?.focus();
} }
} }
// 无终端自动关闭 // 无终端自动关闭 FIXME
if (!props.panel.items.length) { if (!props.panel.items.length) {
close(); close();
} }

View File

@@ -80,7 +80,7 @@
</script> </script>
<script lang="ts" setup> <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 type { HostQueryResponse } from '@/api/asset/host';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useTerminalStore } from '@/store'; import { useTerminalStore } from '@/store';
@@ -129,7 +129,7 @@
defineExpose({ open }); defineExpose({ open });
// 打开终端 // 打开终端
const clickHost = (item: HostQueryResponse, tab: PanelSessionTab) => { const clickHost = (item: HostQueryResponse, tab: PanelSessionTabType) => {
openSession(item, tab, panelIndex.value); openSession(item, tab, panelIndex.value);
setVisible(false); setVisible(false);
}; };

View File

@@ -45,12 +45,27 @@
</div> </div>
</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" <a-tag class="close-message"
color="red" color="red"
:title="closeMessage"> :title="closeMessage">
已断开: {{ closeMessage }} 已断开: {{ closeMessage }}
</a-tag> </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> </div>
<!-- 路径编辑模式-右侧操作 --> <!-- 路径编辑模式-右侧操作 -->
<a-space v-else-if="pathEditable" class="sftp-table-header-right"> <a-space v-else-if="pathEditable" class="sftp-table-header-right">
@@ -191,16 +206,16 @@
import { inject, nextTick, ref, watch } from 'vue'; import { inject, nextTick, ref, watch } from 'vue';
import { getParentPath, getPathAnalysis } from '@/utils/file'; import { getParentPath, getPathAnalysis } from '@/utils/file';
import { openSftpCreateModalKey, openSftpUploadModalKey } from '../../types/terminal.const'; import { openSftpCreateModalKey, openSftpUploadModalKey } from '../../types/terminal.const';
import { useTerminalStore } from '@/store';
const props = defineProps<{ const props = defineProps<{
closed: boolean;
closeMessage?: string; closeMessage?: string;
currentPath: string; currentPath: string;
session?: ISftpSession; session?: ISftpSession;
selectedFiles: Array<string>; selectedFiles: Array<string>;
}>(); }>();
const emits = defineEmits(['update:selectedFiles', 'loadFile', 'download']); const emits = defineEmits(['update:selectedFiles', 'loadFile', 'download', 'setLoading']);
const showHiddenFile = ref(false); const showHiddenFile = ref(false);
const analysisPaths = ref<Array<PathAnalysis>>([]); const analysisPaths = ref<Array<PathAnalysis>>([]);
@@ -229,7 +244,7 @@
// 设置命令编辑模式 // 设置命令编辑模式
const setPathEditable = (editable: boolean) => { const setPathEditable = (editable: boolean) => {
// 检查是否断开 // 检查是否断开
if (editable && props.closed) { if (editable && !props.session?.connected) {
return; return;
} }
pathEditable.value = editable; pathEditable.value = editable;
@@ -251,7 +266,7 @@
// 加载文件列表 // 加载文件列表
const loadFileList = (path: string = props.currentPath) => { const loadFileList = (path: string = props.currentPath) => {
// 检查是否断开 // 检查是否断开
if (props.closed) { if (!props.session?.connected) {
return; return;
} }
emits('loadFile', path); emits('loadFile', path);
@@ -284,6 +299,15 @@
} }
}; };
// 重新连接
const reConnect = () => {
if (props.session) {
emits('setLoading', true);
// 重新连接
useTerminalStore().reOpenSession(props.session.sessionId);
}
};
// 下载文件 // 下载文件
const downloadFile = () => { const downloadFile = () => {
emits('download', [...props.selectedFiles]); emits('download', [...props.selectedFiles]);

View File

@@ -158,7 +158,6 @@
session?: ISftpSession; session?: ISftpSession;
list: Array<SftpFile>; list: Array<SftpFile>;
loading: boolean; loading: boolean;
closed: boolean;
selectedFiles: Array<string>; selectedFiles: Array<string>;
}>(); }>();
@@ -214,7 +213,7 @@
const clickFilename = (record: TableData) => { const clickFilename = (record: TableData) => {
if (record.isDir) { if (record.isDir) {
// 检查是否断开 // 检查是否断开
if (props.closed) { if (!props.session?.connected) {
return; return;
} }
// 进入文件夹 // 进入文件夹
@@ -227,7 +226,7 @@
// 编辑文件 // 编辑文件
const editFile = (record: TableData) => { const editFile = (record: TableData) => {
// 检查是否断开 // 检查是否断开
if (props.closed) { if (!props.session?.connected) {
return; return;
} }
emits('editFile', record.name, record.path); emits('editFile', record.name, record.path);
@@ -237,7 +236,7 @@
// 删除文件 // 删除文件
const deleteFile = (path: string) => { const deleteFile = (path: string) => {
// 检查是否断开 // 检查是否断开
if (props.closed) { if (!props.session?.connected) {
return; return;
} }
props.session?.remove([path]); props.session?.remove([path]);
@@ -246,7 +245,7 @@
// 下载文件 // 下载文件
const downloadFile = (path: string) => { const downloadFile = (path: string) => {
// 检查是否断开 // 检查是否断开
if (props.closed) { if (!props.session?.connected) {
return; return;
} }
emits('download', [path]); emits('download', [path]);
@@ -255,7 +254,7 @@
// 移动文件 // 移动文件
const moveFile = (path: string) => { const moveFile = (path: string) => {
// 检查是否断开 // 检查是否断开
if (props.closed) { if (!props.session?.connected) {
return; return;
} }
openSftpMoveModal(props.session?.sessionId as string, path); openSftpMoveModal(props.session?.sessionId as string, path);
@@ -264,7 +263,7 @@
// 文件提权 // 文件提权
const chmodFile = (path: string, permission: number) => { const chmodFile = (path: string, permission: number) => {
// 检查是否断开 // 检查是否断开
if (props.closed) { if (!props.session?.connected) {
return; return;
} }
openSftpChmodModal(props.session?.sessionId as string, path, permission); openSftpChmodModal(props.session?.sessionId as string, path, permission);

View File

@@ -12,17 +12,16 @@
<!-- 表头 --> <!-- 表头 -->
<sftp-table-header class="sftp-table-header" <sftp-table-header class="sftp-table-header"
v-model:selected-files="selectFiles" v-model:selected-files="selectFiles"
:closed="closed"
:close-message="closeMessage" :close-message="closeMessage"
:current-path="currentPath" :current-path="currentPath"
:session="session" :session="session"
@load-file="loadFiles" @load-file="loadFiles"
@download="downloadFiles" /> @download="downloadFiles"
@set-loading="setTableLoading" />
<!-- 表格 --> <!-- 表格 -->
<sftp-table class="sftp-table-wrapper" <sftp-table class="sftp-table-wrapper"
v-model:selected-files="selectFiles" v-model:selected-files="selectFiles"
:session="session" :session="session"
:closed="closed"
:list="fileList" :list="fileList"
:loading="tableLoading" :loading="tableLoading"
@load-file="loadFiles" @load-file="loadFiles"
@@ -63,7 +62,7 @@
</script> </script>
<script lang="ts" setup> <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 { onMounted, onUnmounted, provide, ref } from 'vue';
import { useTerminalStore } from '@/store'; import { useTerminalStore } from '@/store';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
@@ -79,7 +78,7 @@
import SftpUploadModal from './sftp-upload-modal.vue'; import SftpUploadModal from './sftp-upload-modal.vue';
const props = defineProps<{ const props = defineProps<{
tab: TerminalTabItem; tab: TerminalPanelTabItem;
}>(); }>();
const { preference, sessionManager, transferManager } = useTerminalStore(); const { preference, sessionManager, transferManager } = useTerminalStore();
@@ -91,8 +90,7 @@
const fileList = ref<Array<SftpFile>>([]); const fileList = ref<Array<SftpFile>>([]);
const selectFiles = ref<Array<string>>([]); const selectFiles = ref<Array<string>>([]);
const splitSize = ref(1); const splitSize = ref(1);
const closed = ref(false); const closeMessage = ref<string>();
const closeMessage = ref('');
const editorView = ref(false); const editorView = ref(false);
const editorRef = ref(); const editorRef = ref();
const editorFileName = ref(''); const editorFileName = ref('');
@@ -170,7 +168,8 @@
// 连接成功回调 // 连接成功回调
const connectCallback = () => { const connectCallback = () => {
loadFiles('~'); // FIXME TEST
loadFiles(currentPath.value || '~');
}; };
// 加载文件列表 // 加载文件列表
@@ -189,8 +188,7 @@
}; };
// 关闭回调 // 关闭回调
const onClose = (forceClose: string, msg: string) => { const onClose = (forceClose: boolean, msg: string) => {
closed.value = true;
closeMessage.value = msg; closeMessage.value = msg;
setTableLoading(false); setTableLoading(false);
setEditorLoading(false); setEditorLoading(false);
@@ -267,7 +265,7 @@
// 关闭会话 // 关闭会话
onUnmounted(() => { onUnmounted(() => {
sessionManager.closeSession(props.tab.key); sessionManager.closeSession(props.tab.sessionId);
}); });
</script> </script>

View File

@@ -68,7 +68,7 @@
</script> </script>
<script lang="ts" setup> <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 { computed, onMounted, onUnmounted, ref } from 'vue';
import { useDictStore, useTerminalStore } from '@/store'; import { useDictStore, useTerminalStore } from '@/store';
import { copy } from '@/hooks/copy'; import { copy } from '@/hooks/copy';
@@ -79,7 +79,7 @@
import XtermSearchModal from '@/components/xtrem/search-modal/index.vue'; import XtermSearchModal from '@/components/xtrem/search-modal/index.vue';
const props = defineProps<{ const props = defineProps<{
tab: TerminalTabItem; tab: TerminalPanelTabItem;
}>(); }>();
const { getDictValue } = useDictStore(); const { getDictValue } = useDictStore();
@@ -137,7 +137,6 @@
// 初始化会话 // 初始化会话
onMounted(async () => { onMounted(async () => {
console.log('onMounted', props.tab.key);
// 创建终端处理器 // 创建终端处理器
session.value = await sessionManager.openSsh(props.tab, { session.value = await sessionManager.openSsh(props.tab, {
el: terminalRef.value, el: terminalRef.value,
@@ -148,7 +147,7 @@
// 关闭会话 // 关闭会话
onUnmounted(() => { onUnmounted(() => {
sessionManager.closeSession(props.tab.key); sessionManager.closeSession(props.tab.sessionId);
}); });
</script> </script>

View File

@@ -11,6 +11,8 @@ export default class SftpSession implements ISftpSession {
public connected: boolean; public connected: boolean;
public canReconnect: boolean;
public resolver: ISftpSessionResolver; public resolver: ISftpSessionResolver;
private showHiddenFile: boolean; private showHiddenFile: boolean;
@@ -24,6 +26,7 @@ export default class SftpSession implements ISftpSession {
this.sessionId = sessionId; this.sessionId = sessionId;
this.channel = channel; this.channel = channel;
this.connected = false; 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;
} }

View File

@@ -26,6 +26,8 @@ export default class SshSession implements ISshSession {
public connected: boolean; public connected: boolean;
public canReconnect: boolean;
public canWrite: boolean; public canWrite: boolean;
public status: number; public status: number;
@@ -43,6 +45,7 @@ export default class SshSession implements ISshSession {
this.sessionId = sessionId; this.sessionId = sessionId;
this.channel = channel; this.channel = channel;
this.connected = false; this.connected = false;
this.canReconnect = false;
this.canWrite = 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;
@@ -85,7 +88,6 @@ export default class SshSession implements ISshSession {
if (e.type !== 'keydown') { if (e.type !== 'keydown') {
return true; return true;
} }
console.log(e);
// 检测是否为内置快捷键 // 检测是否为内置快捷键
if (this.handler.checkIsBuiltin(e)) { if (this.handler.checkIsBuiltin(e)) {
return true; return true;
@@ -94,11 +96,12 @@ export default class SshSession implements ISshSession {
if (this.handler.checkPreventDefault(e)) { if (this.handler.checkPreventDefault(e)) {
e.preventDefault(); e.preventDefault();
} }
if (e.key === 'Enter') { // 重新连接
console.log("enter start"); if (!this.connected && this.canReconnect && e.key === 'Enter') {
// TODO 回车 重新连接 setTimeout(async () => {
useTerminalStore().reOpenTerminal(this.hostId, this.sessionId); await useTerminalStore().reOpenSession(this.sessionId);
console.log("enter end"); }, 50);
return true;
} }
// 自定义快捷键 // 自定义快捷键
if (preference.shortcutSetting.enabled && preference.shortcutSetting.keys.length) { if (preference.shortcutSetting.enabled && preference.shortcutSetting.keys.length) {

View File

@@ -42,7 +42,6 @@ export default class TerminalChannel implements ITerminalChannel {
// 发送消息 // 发送消息
send(protocol: Protocol, payload: InputPayload): void { send(protocol: Protocol, payload: InputPayload): void {
console.log('send', payload);
// 检查是否连接 // 检查是否连接
if (!this.isConnected()) { if (!this.isConnected()) {
return; return;

View File

@@ -22,6 +22,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
processCheck({ sessionId, result, msg }: OutputPayload): void { processCheck({ sessionId, result, msg }: OutputPayload): void {
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;
if (session instanceof SshSession) { if (session instanceof SshSession) {
// ssh 会话 // ssh 会话
if (success) { if (success) {
@@ -35,7 +36,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
}); });
} else { } else {
// 未成功展示错误信息 // 未成功展示错误信息
session.write(`${msg || ''}`); session.write(`${msg || ''}\r\n输入回车重新连接...\r\n\r\n`);
session.status = TerminalStatus.CLOSED; session.status = TerminalStatus.CLOSED;
} }
} else if (session instanceof SftpSession) { } else if (session instanceof SftpSession) {
@@ -47,7 +48,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
}); });
} else { } else {
// 未成功提示错误信息 // 未成功提示错误信息
session.resolver?.onClose('0', msg); session.resolver?.onClose(false, msg);
Message.error(msg || '建立 SFTP 失败'); Message.error(msg || '建立 SFTP 失败');
} }
} }
@@ -57,6 +58,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
processConnect({ sessionId, result, msg }: OutputPayload): void { processConnect({ sessionId, result, msg }: OutputPayload): void {
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;
if (session instanceof SshSession) { if (session instanceof SshSession) {
// ssh 会话 // ssh 会话
if (success) { if (success) {
@@ -66,7 +68,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
session.connect(); session.connect();
} else { } else {
// 未成功展示错误信息 // 未成功展示错误信息
session.write(`${msg || ''}`); session.write(`${msg || ''}\r\n输入回车重新连接...\r\n\r\n`);
session.status = TerminalStatus.CLOSED; session.status = TerminalStatus.CLOSED;
} }
} else if (session instanceof SftpSession) { } else if (session instanceof SftpSession) {
@@ -76,7 +78,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
session.connect(); session.connect();
} else { } else {
// 未成功提示错误信息 // 未成功提示错误信息
session.resolver?.onClose('0', msg); session.resolver?.onClose(false, msg);
Message.error(msg || '打开 SFTP 失败'); Message.error(msg || '打开 SFTP 失败');
} }
} }
@@ -89,18 +91,22 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
if (!session) { if (!session) {
return; return;
} }
const isForceClose = !!Number.parseInt(forceClose);
session.connected = false;
session.canReconnect = !isForceClose;
if (session instanceof SshSession) { if (session instanceof SshSession) {
// ssh 拼接关闭消息 // ssh 拼接关闭消息
session.write(`\r\n\r\n${msg || ''}\r\n\r\n`); session.write(`\r\n\r\n${msg || ''}\r\n`);
if (!isForceClose) {
session.write('输入回车重新连接...\r\n\r\n');
}
// 设置状态 // 设置状态
session.status = TerminalStatus.CLOSED; session.status = TerminalStatus.CLOSED;
session.connected = false;
// 设置不可写 // 设置不可写
session.setCanWrite(false); session.setCanWrite(false);
} else if (session instanceof SftpSession) { } else if (session instanceof SftpSession) {
// sftp 设置状态 // sftp 设置状态
session.connected = false; session.resolver?.onClose(isForceClose, msg);
session.resolver?.onClose(forceClose, msg);
} }
} }

View File

@@ -1,21 +1,21 @@
import type { ITerminalPanelManager, TerminalPanelTabItem } from '../types/terminal.type'; import type { ITerminalPanelManager } from '../types/terminal.type';
import TerminalTabManager from '../handler/terminal-tab-manager'; 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; active: number;
// 面板列表 // 面板列表
panels: Array<TerminalTabManager<T>>; panels: Array<TerminalPanelTabManager>;
constructor() { constructor() {
this.active = 0; this.active = 0;
this.panels = [new TerminalTabManager()]; this.panels = [new TerminalPanelTabManager()];
} }
// 获取当前面板 // 获取当前面板
getCurrentPanel(): TerminalTabManager<T> { getCurrentPanel(): TerminalPanelTabManager {
return this.panels[this.active]; 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]; return this.panels[index];
}; };
@@ -41,7 +41,7 @@ export default class TerminalPanelManager<T extends TerminalPanelTabItem = Termi
panel.clear(); panel.clear();
} }
this.active = 0; this.active = 0;
this.panels = [new TerminalTabManager()]; this.panels = [new TerminalPanelTabManager()];
}; };
} }

View File

@@ -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 = [];
}
}

View File

@@ -4,10 +4,9 @@ import type {
ITerminalChannel, ITerminalChannel,
ITerminalSession, ITerminalSession,
ITerminalSessionManager, ITerminalSessionManager,
TerminalTabItem, TerminalPanelTabItem,
XtermDomRef XtermDomRef
} from '../types/terminal.type'; } from '../types/terminal.type';
import type { ISshSession } from '../types/terminal.type';
import { sleep } from '@/utils'; import { sleep } from '@/utils';
import { InputProtocol } from '../types/terminal.protocol'; import { InputProtocol } from '../types/terminal.protocol';
import { PanelSessionType } from '../types/terminal.const'; import { PanelSessionType } from '../types/terminal.const';
@@ -35,8 +34,8 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
} }
// 打开 ssh 会话 // 打开 ssh 会话
async openSsh(tab: TerminalTabItem, domRef: XtermDomRef) { async openSsh(tab: TerminalPanelTabItem, domRef: XtermDomRef) {
const sessionId = tab.key; const sessionId = tab.sessionId;
const hostId = tab.hostId as number; const hostId = tab.hostId as number;
// 初始化客户端 // 初始化客户端
await this.initChannel(); await this.initChannel();
@@ -61,28 +60,9 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
return session; 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 会话 // 打开 sftp 会话
async openSftp(tab: TerminalTabItem, resolver: ISftpSessionResolver): Promise<ISftpSession> { async openSftp(tab: TerminalPanelTabItem, resolver: ISftpSessionResolver): Promise<ISftpSession> {
const sessionId = tab.key; const sessionId = tab.sessionId;
const hostId = tab.hostId as number; const hostId = tab.hostId as number;
// 初始化客户端 // 初始化客户端
await this.initChannel(); await this.initChannel();
@@ -105,6 +85,23 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
return session; 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 { getSession<T>(sessionId: string): T {
return this.sessions[sessionId] as T; return this.sessions[sessionId] as T;

View File

@@ -1,13 +1,13 @@
import type { ITerminalTabManager, TerminalTabItem } from '../types/terminal.type'; import type { ITerminalTabManager, TerminalTabItem } from '../types/terminal.type';
// 终端 tab 管理器实现 // 终端 tab 管理器实现
export default class TerminalTabManager<T extends TerminalTabItem = TerminalTabItem> implements ITerminalTabManager<T> { export default class TerminalTabManager implements ITerminalTabManager {
public active: string; public active: string;
public items: Array<T>; public items: Array<TerminalTabItem>;
constructor(def: T | undefined = undefined) { constructor(def: TerminalTabItem | undefined = undefined) {
if (def) { if (def) {
this.active = def.key; this.active = def.key;
this.items = [def]; this.items = [def];
@@ -26,8 +26,8 @@ export default class TerminalTabManager<T extends TerminalTabItem = TerminalTabI
} }
// 获取 tab // 获取 tab
getTab(key: string): T { getTab(key: string): TerminalTabItem {
return this.items.find(s => s.key === key) as T; return this.items.find(s => s.key === key) as TerminalTabItem;
} }
// 点击 tab // 点击 tab
@@ -50,7 +50,7 @@ export default class TerminalTabManager<T extends TerminalTabItem = TerminalTabI
} }
// 打开 tab // 打开 tab
openTab(tab: T): void { openTab(tab: TerminalTabItem): void {
// 不存在则创建 tab // 不存在则创建 tab
if (!this.items.find(s => s.key === tab.key)) { if (!this.items.find(s => s.key === tab.key)) {
this.items.push(tab); this.items.push(tab);

View File

@@ -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 = { export const NewConnectionType = {
GROUP: 'group', 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 = { export const TerminalStatus = {
// 连接中 // 连接中

View File

@@ -19,10 +19,11 @@ export interface TerminalTabItem {
// 终端面板 tab 元素 // 终端面板 tab 元素
export interface TerminalPanelTabItem extends TerminalTabItem { export interface TerminalPanelTabItem extends TerminalTabItem {
type: string;
sessionId: string;
seq: number; seq: number;
hostId: number; hostId: number;
address: string; address: string;
type: string;
color?: string; color?: string;
} }
@@ -74,7 +75,7 @@ export interface LabelExtraSettingModel {
} }
// session tab // session tab
export interface PanelSessionTab { export interface PanelSessionTabType {
type: string; type: string;
icon: 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; active: number;
// 面板列表 // 面板列表
panels: Array<ITerminalTabManager<T>>; panels: Array<ITerminalTabManager<TerminalPanelTabItem>>;
// 获取当前面板 // 获取当前面板
getCurrentPanel: () => ITerminalTabManager<T>; getCurrentPanel: () => ITerminalTabManager<TerminalPanelTabItem>;
// 设置当前面板 // 设置当前面板
setCurrentPanel: (active: number) => void; setCurrentPanel: (active: number) => void;
// 获取面板 // 获取面板
getPanel: (index: number) => ITerminalTabManager<T>; getPanel: (index: number) => ITerminalTabManager<TerminalPanelTabItem>;
// 移除面板 // 移除面板
removePanel: (index: number) => void; removePanel: (index: number) => void;
// 重置 // 重置
@@ -152,11 +153,11 @@ export interface ITerminalPanelManager<T extends TerminalPanelTabItem = Terminal
// 终端会话管理器定义 // 终端会话管理器定义
export interface ITerminalSessionManager { export interface ITerminalSessionManager {
// 打开 ssh 会话 // 打开 ssh 会话
openSsh: (tab: TerminalTabItem, domRef: XtermDomRef) => Promise<ISshSession>; openSsh: (tab: TerminalPanelTabItem, domRef: XtermDomRef) => Promise<ISshSession>;
// 重新打开 ssh 会话
reOpenSsh: (sessionId: string, newSessionId: string) => Promise<void>;
// 打开 sftp 会话 // 打开 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; getSession: <T extends ITerminalSession>(sessionId: string) => T;
// 关闭终端会话 // 关闭终端会话
@@ -232,6 +233,8 @@ export interface ITerminalSession {
sessionId: string; sessionId: string;
// 是否已连接 // 是否已连接
connected: boolean; connected: boolean;
// 是否可以重新连接
canReconnect: boolean;
// 连接 // 连接
connect: () => void; connect: () => void;
@@ -355,7 +358,7 @@ export interface ISftpSessionResolver {
// 连接后回调 // 连接后回调
connectCallback: () => void; connectCallback: () => void;
// 关闭回调 // 关闭回调
onClose: (forceClose: string, msg: string) => void; onClose: (forceClose: boolean, msg: string) => void;
// 接受文件列表响应 // 接受文件列表响应
resolveList: (result: string, path: string, list: Array<SftpFile>) => void; resolveList: (result: string, path: string, list: Array<SftpFile>) => void;
// 接收创建文件夹响应 // 接收创建文件夹响应