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