🔨 添加 sftp 会话类型.

This commit is contained in:
lijiahangmax
2024-02-06 11:52:02 +08:00
parent 13b2a7030c
commit 9b79ebb6e6
14 changed files with 219 additions and 103 deletions

View File

@@ -8,7 +8,7 @@ import type {
TerminalShortcutSetting,
TerminalState
} from './types';
import type { PanelSessionTab, TerminalPanelTabItem } from '@/views/host/terminal/types/terminal.type';
import type { ISshSession, PanelSessionTab, 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';
@@ -64,7 +64,8 @@ export default defineStore('terminal', {
} as TerminalShortcutSetting,
},
hosts: {} as AuthorizedHostQueryResponse,
tabManager: new TerminalTabManager(TerminalTabs.NEW_CONNECTION),
// fixme
tabManager: new TerminalTabManager(TerminalTabs.TERMINAL_PANEL),
panelManager: new TerminalPanelManager(),
sessionManager: new TerminalSessionManager()
}),
@@ -173,8 +174,8 @@ export default defineStore('terminal', {
}
},
// 检查当前是否为终端页面 并且获取当前终端会话
getAndCheckCurrentTerminalSession(tips: boolean = true) {
// 检查当前是否为终端页面 并且获取当前 ssh 会话
getAndCheckCurrentSshSession(tips: boolean = true) {
// 获取当前 activeTab
const activeTab = this.tabManager.active;
if (activeTab !== TerminalTabs.TERMINAL_PANEL.key) {
@@ -184,15 +185,15 @@ export default defineStore('terminal', {
return;
}
// 获取当前会话
const session = this.getCurrentTerminalSession();
const session = this.getCurrentSshSession();
if (!session && tips) {
Message.warning('请打开终端');
}
return session;
},
// 获取当前终端会话
getCurrentTerminalSession() {
// 获取当前 ssh 会话
getCurrentSshSession() {
// 获取面板会话
const sessionTab = this.panelManager
.getCurrentPanel()
@@ -201,7 +202,7 @@ export default defineStore('terminal', {
return;
}
// 获取会话
return this.sessionManager.getSession(sessionTab.key);
return this.sessionManager.getSession<ISshSession>(sessionTab.key);
},
},

View File

@@ -83,7 +83,7 @@
const { loading, setLoading } = useLoading();
const { visible, setVisible } = useVisible();
const { getCurrentTerminalSession } = useTerminalStore();
const { getCurrentSshSession } = useTerminalStore();
const cacheStore = useCacheStore();
const formDrawer = ref();
@@ -267,7 +267,7 @@
// 关闭回调
const onClose = () => {
// 聚焦终端
getCurrentTerminalSession()?.focus();
getCurrentSshSession()?.focus();
};
</script>

View File

@@ -125,7 +125,7 @@
}>();
const { copy } = useCopy();
const { getAndCheckCurrentTerminalSession } = useTerminalStore();
const { getAndCheckCurrentSshSession } = useTerminalStore();
let clickCount = 0;
@@ -184,7 +184,7 @@
// 写入命令
const write = (command: string) => {
const handler = getAndCheckCurrentTerminalSession()?.handler;
const handler = getAndCheckCurrentSshSession()?.handler;
if (handler && handler.enabledStatus('checkAppendMissing')) {
handler.checkAppendMissing(command);
}

View File

@@ -45,17 +45,17 @@
import TerminalShortcutSetting from '../setting/shortcut/terminal-shortcut-setting.vue';
import TerminalPanelsView from '@/views/host/terminal/components/layout/terminal-panels-view.vue';
const { preference, tabManager, getCurrentTerminalSession } = useTerminalStore();
const { preference, tabManager, getCurrentSshSession } = useTerminalStore();
// 监听 tab 切换
watch(() => tabManager.active, (active, before) => {
// 失焦 tab
if (before === TerminalTabs.TERMINAL_PANEL.key) {
getCurrentTerminalSession()?.blur();
getCurrentSshSession()?.blur();
}
// 聚焦 tab
if (active === TerminalTabs.TERMINAL_PANEL.key) {
getCurrentTerminalSession()?.focus();
getCurrentSshSession()?.focus();
}
// 修改标题
document.title = Object.values(TerminalTabs)

View File

@@ -28,7 +28,7 @@
const emits = defineEmits(['openTransfer']);
const { getAndCheckCurrentTerminalSession } = useTerminalStore();
const { getAndCheckCurrentSshSession } = useTerminalStore();
const snippetRef = ref();
@@ -59,7 +59,7 @@
// 终端截屏
const screenshot = () => {
const handler = getAndCheckCurrentTerminalSession()?.handler;
const handler = getAndCheckCurrentSshSession()?.handler;
if (handler && handler.enabledStatus('screenshot')) {
handler.screenshot();
}

View File

@@ -44,7 +44,7 @@
</script>
<script lang="ts" setup>
import type { ITerminalTabManager, TerminalPanelTabItem } from '../../types/terminal.type';
import type { ISshSession, ITerminalTabManager, TerminalPanelTabItem } from '../../types/terminal.type';
import { watch } from 'vue';
import { useTerminalStore } from '@/store';
import { PanelSessionType } from '../../types/terminal.const';
@@ -66,14 +66,14 @@
if (before) {
const beforeTab = props.panel.items.find(s => s.key === before);
if (beforeTab && beforeTab?.type === PanelSessionType.SSH.type) {
sessionManager.getSession(before)?.blur();
sessionManager.getSession<ISshSession>(before)?.blur();
}
}
// 终端自动聚焦
if (active) {
const activeTab = props.panel.items.find(s => s.key === active);
if (activeTab && activeTab?.type === PanelSessionType.SSH.type) {
sessionManager.getSession(active)?.focus();
sessionManager.getSession<ISshSession>(active)?.focus();
}
}
// 无终端自动关闭
@@ -214,12 +214,13 @@
}
.arco-tabs-tab-title {
padding: 11px 18px 11px 14px;
padding: 11px 18px 7px 14px;
background: var(--color-bg-panel-tabs);
font-size: 14px;
height: var(--panel-nav-height);
display: flex;
align-items: center;
border-bottom: 4px transparent solid;
&::before {
display: none;

View File

@@ -31,12 +31,12 @@
</script>
<script lang="ts" setup>
import type { ContextMenuItem, ITerminalSession } from '../../types/terminal.type';
import type { ContextMenuItem, ISshSession } from '../../types/terminal.type';
import { ActionBarItems } from '../../types/terminal.const';
import { useTerminalStore } from '@/store';
defineProps<{
session: ITerminalSession | undefined
session: ISshSession | undefined
}>();
const emits = defineEmits(['click']);

View File

@@ -67,7 +67,7 @@
</script>
<script lang="ts" setup>
import type { ITerminalSession, TerminalTabItem, SidebarAction } from '../../types/terminal.type';
import type { ISshSession, TerminalTabItem, SidebarAction } from '../../types/terminal.type';
import { computed, onMounted, onUnmounted, ref } from 'vue';
import { useDictStore, useTerminalStore } from '@/store';
import useCopy from '@/hooks/copy';
@@ -89,7 +89,7 @@
const searchModal = ref();
const commandInput = ref();
const terminalRef = ref();
const session = ref<ITerminalSession>();
const session = ref<ISshSession>();
// 发送命令
const writeCommandInput = async (e: KeyboardEvent) => {
@@ -138,7 +138,7 @@
// 初始化会话
onMounted(async () => {
// 创建终端处理器
session.value = await sessionManager.openSession(props.tab, {
session.value = await sessionManager.openSsh(props.tab, {
el: terminalRef.value,
editorModal: editorModal.value,
searchModal: searchModal.value

View File

@@ -0,0 +1,41 @@
import type { ISftpSession, ITerminalChannel } from '../types/terminal.type';
import { InputProtocol } from '../types/terminal.protocol';
// sftp 会话实现
export default class SftpSession implements ISftpSession {
public readonly hostId: number;
public sessionId: string;
public connected: boolean;
private readonly channel: ITerminalChannel;
constructor(hostId: number,
sessionId: string,
channel: ITerminalChannel) {
this.hostId = hostId;
this.sessionId = sessionId;
this.channel = channel;
this.connected = false;
}
// 设置已连接
connect(): void {
this.connected = true;
}
// 断开连接
disconnect(): void {
// 发送关闭消息
this.channel.send(InputProtocol.CLOSE, {
sessionId: this.sessionId
});
}
// 关闭
close(): void {
}
}

View File

@@ -1,5 +1,5 @@
import type { ShortcutKey, TerminalInteractSetting, TerminalShortcutKey } from '@/store/modules/terminal/types';
import type { ITerminalSession, ITerminalSessionHandler, TerminalDomRef } from '../types/terminal.type';
import type { ISshSession, ISshSessionHandler, XtermDomRef } from '../types/terminal.type';
import type { Terminal } from 'xterm';
import useCopy from '@/hooks/copy';
import html2canvas from 'html2canvas';
@@ -31,21 +31,21 @@ const preventKeys: Array<ShortcutKey> = [
const { copy: copyValue, readText } = useCopy();
// 终端会话处理器实现
export default class TerminalSessionHandler implements ITerminalSessionHandler {
// ssh 会话处理器实现
export default class SshSessionHandler implements ISshSessionHandler {
private readonly domRef: TerminalDomRef;
private readonly domRef: XtermDomRef;
private readonly inst: Terminal;
private readonly session: ITerminalSession;
private readonly session: ISshSession;
private readonly interactSetting: TerminalInteractSetting;
private readonly shortcutKeys: Array<TerminalShortcutKey>;
constructor(session: ITerminalSession,
domRef: TerminalDomRef) {
constructor(session: ISshSession,
domRef: XtermDomRef) {
this.session = session;
this.inst = session.inst;
this.domRef = domRef;

View File

@@ -1,6 +1,6 @@
import type { UnwrapRef } from 'vue';
import type { TerminalPreference } from '@/store/modules/terminal/types';
import type { ITerminalChannel, ITerminalSession, ITerminalSessionHandler, TerminalAddons, TerminalDomRef } from '../types/terminal.type';
import type { ISshSession, ISshSessionHandler, ITerminalChannel, XtermAddons, XtermDomRef } from '../types/terminal.type';
import { useTerminalStore } from '@/store';
import { InputProtocol } from '../types/terminal.protocol';
import { fontFamilySuffix, TerminalShortcutType, TerminalStatus } from '../types/terminal.const';
@@ -13,10 +13,10 @@ import { CanvasAddon } from 'xterm-addon-canvas';
import { WebglAddon } from 'xterm-addon-webgl';
import { playBell } from '@/utils/bell';
import { addEventListen } from '@/utils/event';
import TerminalSessionHandler from './terminal-session-handler';
import SshSessionHandler from './ssh-session-handler';
// 终端会话实现
export default class TerminalSession implements ITerminalSession {
// ssh 会话实现
export default class SshSession implements ISshSession {
public readonly hostId: number;
@@ -30,11 +30,11 @@ export default class TerminalSession implements ITerminalSession {
public status: number;
public handler: ITerminalSessionHandler;
public handler: ISshSessionHandler;
private readonly channel: ITerminalChannel;
private readonly addons: TerminalAddons;
private readonly addons: XtermAddons;
constructor(hostId: number,
sessionId: string,
@@ -46,12 +46,12 @@ export default class TerminalSession implements ITerminalSession {
this.canWrite = false;
this.status = TerminalStatus.CONNECTING;
this.inst = undefined as unknown as Terminal;
this.handler = undefined as unknown as ITerminalSessionHandler;
this.addons = {} as TerminalAddons;
this.handler = undefined as unknown as ISshSessionHandler;
this.addons = {} as XtermAddons;
}
// 初始化
init(domRef: TerminalDomRef): void {
init(domRef: XtermDomRef): void {
const { preference } = useTerminalStore();
// 初始化实例
this.inst = new Terminal({
@@ -65,7 +65,7 @@ export default class TerminalSession implements ITerminalSession {
scrollback: preference.sessionSetting.scrollBackLine,
});
// 处理器
this.handler = new TerminalSessionHandler(this, domRef);
this.handler = new SshSessionHandler(this, domRef);
// 注册快捷键
this.registerShortcut(preference);
// 注册事件

View File

@@ -1,7 +1,10 @@
import { ITerminalChannel, ITerminalOutputProcessor, ITerminalSessionManager, OutputPayload } from '../types/terminal.type';
import { ISshSession, ITerminalChannel, ITerminalOutputProcessor, ITerminalSessionManager, OutputPayload } from '../types/terminal.type';
import { InputProtocol } from '../types/terminal.protocol';
import { 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 {
@@ -19,50 +22,81 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
processCheck({ sessionId, result, msg }: OutputPayload): void {
const success = !!Number.parseInt(result);
const session = this.sessionManager.getSession(sessionId);
// 未成功展示错误信息
if (!success) {
session.write(`${msg || ''}`);
session.status = TerminalStatus.CLOSED;
return;
if (session instanceof SshSession) {
// ssh 会话
if (success) {
// 检查成功发送 connect 命令
const { preference } = useTerminalStore();
this.channel.send(InputProtocol.CONNECT, {
sessionId,
terminalType: preference.sessionSetting.terminalEmulationType || 'xterm',
cols: session.inst.cols,
rows: session.inst.rows
});
} else {
// 未成功展示错误信息
session.write(`${msg || ''}`);
session.status = TerminalStatus.CLOSED;
}
} else if (session instanceof SftpSession) {
// sftp 会话
if (success) {
// 检查成功发送 connect 命令
// TODO
} else {
// 未成功提示错误信息
Message.error(msg || '建立 SFTP 失败');
}
}
const { preference } = useTerminalStore();
// 发送 connect 命令
this.channel.send(InputProtocol.CONNECT, {
sessionId,
terminalType: preference.sessionSetting.terminalEmulationType || 'xterm',
cols: session.inst.cols,
rows: session.inst.rows
});
}
// 处理连接消息
processConnect({ sessionId, result, msg }: OutputPayload): void {
const success = !!Number.parseInt(result);
const session = this.sessionManager.getSession(sessionId);
// 未成功展示错误信息
if (!success) {
session.write(`${msg || ''}`);
session.status = TerminalStatus.CLOSED;
return;
if (session instanceof SshSession) {
// ssh 会话
if (success) {
// 设置可写
session.setCanWrite(true);
// 执行连接逻辑
session.connect();
} else {
// 未成功展示错误信息
session.write(`${msg || ''}`);
session.status = TerminalStatus.CLOSED;
}
} else if (session instanceof SftpSession) {
// sftp 会话
if (success) {
// 执行连接逻辑
session.connect();
} else {
// 未成功提示错误信息
Message.error(msg || '打开 SFTP 失败');
}
}
// 设置可写
session.setCanWrite(true);
// 执行连接逻辑
session.connect();
}
// 处理关闭消息
processClose({ sessionId, msg }: OutputPayload): void {
const session = this.sessionManager.getSession(sessionId);
// 关闭 tab 则无需处理
if (session) {
// 提示消息
// 无需处理 (直接关闭 tab )
if (!session) {
return;
}
if (session instanceof SshSession) {
// ssh 拼接关闭消息
session.write(`\r\n${msg || ''}`);
// 设置状态
session.status = TerminalStatus.CLOSED;
session.connected = false;
// 设置不可写
session.setCanWrite(false);
} else if (session instanceof SftpSession) {
// sftp 设置状态
session.connected = false;
}
}
@@ -73,7 +107,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
// 处理输出消息
processOutput({ sessionId, body }: OutputPayload): void {
const session = this.sessionManager.getSession(sessionId);
const session = this.sessionManager.getSession<ISshSession>(sessionId);
session && session.write(body);
}

View File

@@ -1,10 +1,12 @@
import type { ITerminalChannel, ITerminalSession, ITerminalSessionManager, TerminalDomRef, TerminalTabItem } from '../types/terminal.type';
import type { ISftpSession, ITerminalChannel, ITerminalSession, ITerminalSessionManager, TerminalTabItem, XtermDomRef } from '../types/terminal.type';
import { sleep } from '@/utils';
import { InputProtocol } from '../types/terminal.protocol';
import { PanelSessionType } from '../types/terminal.const';
import { useDebounceFn } from '@vueuse/core';
import { addEventListen, removeEventListen } from '@/utils/event';
import TerminalSession from './terminal-session';
import TerminalChannel from './terminal-channel';
import SshSession from './ssh-session';
import SftpSession from './sftp-session';
// 终端会话管理器实现
export default class TerminalSessionManager implements ITerminalSessionManager {
@@ -23,15 +25,15 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
this.dispatchResizeFn = useDebounceFn(this.dispatchResize).bind(this);
}
// 打开终端会话
async openSession(tab: TerminalTabItem,
domRef: TerminalDomRef) {
// 打开 ssh 会话
async openSsh(tab: TerminalTabItem,
domRef: XtermDomRef) {
const sessionId = tab.key;
const hostId = tab.hostId as number;
// 初始化客户端
await this.initChannel();
// 新建会话
const session = new TerminalSession(
const session = new SshSession(
hostId,
sessionId,
this.channel
@@ -46,14 +48,37 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
this.channel.send(InputProtocol.CHECK, {
sessionId,
hostId,
connectType: 'SSH'
connectType: PanelSessionType.SSH.type
});
return session;
}
// 打开 sftp 会话
async openSftp(tab: TerminalTabItem): Promise<ISftpSession> {
const sessionId = tab.key;
const hostId = tab.hostId as number;
// 初始化客户端
await this.initChannel();
// 新建会话
const session = new SftpSession(
hostId,
sessionId,
this.channel
);
// 添加会话
this.sessions[sessionId] = session;
// 发送会话初始化请求
this.channel.send(InputProtocol.CHECK, {
sessionId,
hostId,
connectType: PanelSessionType.SFTP.type
});
return session;
}
// 获取终端会话
getSession(sessionId: string): ITerminalSession {
return this.sessions[sessionId];
getSession<T>(sessionId: string): T {
return this.sessions[sessionId] as T;
}
// 关闭终端会话
@@ -64,7 +89,7 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
}
// 关闭连接
session.disconnect();
// 关闭 session
// 关闭会话
session.close();
// 移除 session
this.sessions[sessionId] = undefined as unknown as ITerminalSession;
@@ -94,6 +119,8 @@ export default class TerminalSessionManager implements ITerminalSessionManager {
private dispatchResize() {
// 对所有已连接的会话重置大小
Object.values(this.sessions)
.filter(s => s instanceof SshSession)
.map(s => s as SshSession)
.filter(h => h.connected)
.forEach(h => h.fit());
}

View File

@@ -96,13 +96,6 @@ export interface OutputPayload {
[key: string]: string;
}
// 终端 dom 元素引用
export interface TerminalDomRef {
el: HTMLElement;
searchModal: any;
editorModal: any;
}
// 终端 tab 管理器定义
export interface ITerminalTabManager<T extends TerminalTabItem = TerminalTabItem> {
// 当前 tab
@@ -149,10 +142,12 @@ export interface ITerminalPanelManager<T extends TerminalPanelTabItem = Terminal
// 终端会话管理器定义
export interface ITerminalSessionManager {
// 打开终端会话
openSession: (tab: TerminalTabItem, domRef: TerminalDomRef) => Promise<ITerminalSession>;
// 打开 ssh 会话
openSsh: (tab: TerminalTabItem, domRef: XtermDomRef) => Promise<ISshSession>;
// 打开 sftp 会话
openSftp: (tab: TerminalTabItem) => Promise<ISftpSession>;
// 获取终端会话
getSession: (sessionId: string) => ITerminalSession;
getSession: <T extends ITerminalSession>(sessionId: string) => T;
// 关闭终端会话
closeSession: (sessionId: string) => void;
// 重置
@@ -185,8 +180,15 @@ export interface ITerminalOutputProcessor {
processOutput: (payload: OutputPayload) => void;
}
// 终端 dom 元素引用
export interface XtermDomRef {
el: HTMLElement;
searchModal: any;
editorModal: any;
}
// 终端插件
export interface TerminalAddons {
export interface XtermAddons {
fit: FitAddon;
webgl: WebglAddon;
canvas: CanvasAddon;
@@ -199,21 +201,30 @@ export interface TerminalAddons {
export interface ITerminalSession {
hostId: number;
sessionId: string;
// terminal 实例
inst: Terminal;
// 是否已连接
connected: boolean;
// 连接
connect: () => void;
// 断开连接
disconnect: () => void;
// 关闭
close: () => void;
}
// ssh 会话定义
export interface ISshSession extends ITerminalSession {
// terminal 实例
inst: Terminal;
// 是否可写
canWrite: boolean;
// 状态
status: number;
// 处理器
handler: ITerminalSessionHandler;
handler: ISshSessionHandler;
// 初始化
init: (domRef: TerminalDomRef) => void;
// 连接
connect: () => void;
init: (domRef: XtermDomRef) => void;
// 设置是否可写
setCanWrite: (canWrite: boolean) => void;
// 写入数据
@@ -226,14 +237,10 @@ export interface ITerminalSession {
fit: () => void;
// 查找
find: (word: string, next: boolean, options: ISearchOptions) => void;
// 断开连接
disconnect: () => void;
// 关闭
close: () => void;
}
// 终端会话处理器定义
export interface ITerminalSessionHandler {
// ssh 会话处理器定义
export interface ISshSessionHandler {
// 检测是否忽略默认行为
checkPreventDefault: (e: KeyboardEvent) => boolean;
// 启用状态
@@ -278,3 +285,8 @@ export interface ITerminalSessionHandler {
// 检查追加缺失的部分
checkAppendMissing: (value: string) => void;
}
// sftp 会话定义
export interface ISftpSession extends ITerminalSession {
}