🚧重构中🚧
This commit is contained in:
@@ -17,7 +17,7 @@ import { defineStore } from 'pinia';
|
||||
import { getPreference, updatePreference } from '@/api/user/preference';
|
||||
import { nextSessionId } from '@/utils';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { TerminalTabType } from '@/views/host/terminal/types/terminal.const';
|
||||
import { TerminalTabs } from '@/views/host/terminal/types/terminal.const';
|
||||
import TerminalTabManager from '@/views/host/terminal/handler/terminal-tab-manager';
|
||||
import TerminalSessionManager from '@/views/host/terminal/handler/terminal-session-manager';
|
||||
|
||||
@@ -62,7 +62,8 @@ export default defineStore('terminal', {
|
||||
} as TerminalShortcutSetting,
|
||||
},
|
||||
hosts: {} as AuthorizedHostQueryResponse,
|
||||
tabManager: new TerminalTabManager(),
|
||||
tabManager: new TerminalTabManager(TerminalTabs.NEW_CONNECTION),
|
||||
routerTabManager: [new TerminalTabManager()],
|
||||
sessionManager: new TerminalSessionManager()
|
||||
}),
|
||||
|
||||
@@ -130,9 +131,11 @@ export default defineStore('terminal', {
|
||||
},
|
||||
|
||||
// 打开终端
|
||||
openTerminal(record: HostQueryResponse) {
|
||||
openTerminal(record: HostQueryResponse, panelIndex: number = 0) {
|
||||
// 添加到最近连接
|
||||
this.hosts.latestHosts = [...new Set([record.id, ...this.hosts.latestHosts])];
|
||||
// 切换到终端面板页面
|
||||
this.tabManager.openTab(TerminalTabs.TERMINAL_PANEL);
|
||||
// 获取 seq
|
||||
const tabSeqArr = this.tabManager.items
|
||||
.map(s => s.seq)
|
||||
@@ -141,9 +144,9 @@ export default defineStore('terminal', {
|
||||
const nextSeq = tabSeqArr.length
|
||||
? Math.max(...tabSeqArr) + 1
|
||||
: 1;
|
||||
// FIXME
|
||||
// 打开 tab
|
||||
this.tabManager.openTab({
|
||||
type: TerminalTabType.TERMINAL,
|
||||
key: nextSessionId(10),
|
||||
seq: nextSeq,
|
||||
title: `(${nextSeq}) ${record.alias || record.name}`,
|
||||
@@ -164,7 +167,8 @@ export default defineStore('terminal', {
|
||||
// 获取当前终端会话
|
||||
getCurrentTerminalSession(tips: boolean = true) {
|
||||
const tab = this.tabManager.getCurrentTab();
|
||||
if (!tab || tab.type !== TerminalTabType.TERMINAL) {
|
||||
// FIXME
|
||||
if (!tab || tab.type !== 'TERMINAL') {
|
||||
if (tips) {
|
||||
Message.warning('请切换到终端标签页');
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ export interface TerminalState {
|
||||
preference: TerminalPreference;
|
||||
hosts: AuthorizedHostQueryResponse;
|
||||
tabManager: ITerminalTabManager;
|
||||
routerTabManager: Array<ITerminalTabManager>;
|
||||
sessionManager: ITerminalSessionManager;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<a-dropdown class="terminal-context-menu"
|
||||
<a-dropdown class="host-space-context-menu"
|
||||
:popup-max-height="false"
|
||||
trigger="contextMenu"
|
||||
position="bl"
|
||||
@@ -43,44 +43,45 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- 命令 -->
|
||||
<span class="snippet-item-command">
|
||||
{{ item.command }}
|
||||
</span>
|
||||
<span class="snippet-item-command"
|
||||
@click="clickCommand">
|
||||
{{ item.command }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右键菜单 -->
|
||||
<template #content>
|
||||
<!-- 复制 -->
|
||||
<a-doption @click="copyCommand">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<div class="host-space-context-menu-icon">
|
||||
<icon-copy />
|
||||
</div>
|
||||
<div>复制</div>
|
||||
</a-doption>
|
||||
<!-- 粘贴 -->
|
||||
<a-doption @click="paste">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<div class="host-space-context-menu-icon">
|
||||
<icon-paste />
|
||||
</div>
|
||||
<div>粘贴</div>
|
||||
</a-doption>
|
||||
<!-- 执行 -->
|
||||
<a-doption @click="exec">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<div class="host-space-context-menu-icon">
|
||||
<icon-thunderbolt />
|
||||
</div>
|
||||
<div>执行</div>
|
||||
</a-doption>
|
||||
<!-- 修改 -->
|
||||
<a-doption @click="openUpdateSnippet(item)">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<div class="host-space-context-menu-icon">
|
||||
<icon-edit />
|
||||
</div>
|
||||
<div>修改</div>
|
||||
</a-doption>
|
||||
<!-- 删除 -->
|
||||
<a-doption @click="removeSnippet(item.id)">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<div class="host-space-context-menu-icon">
|
||||
<icon-delete />
|
||||
</div>
|
||||
<div>删除</div>
|
||||
@@ -88,7 +89,7 @@
|
||||
<!-- 展开 -->
|
||||
<a-doption v-if="!item.expand"
|
||||
@click="() => item.expand = true">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<div class="host-space-context-menu-icon">
|
||||
<icon-expand />
|
||||
</div>
|
||||
<div>展开</div>
|
||||
@@ -96,7 +97,7 @@
|
||||
<!-- 收起 -->
|
||||
<a-doption v-else
|
||||
@click="() => item.expand = false">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<div class="host-space-context-menu-icon">
|
||||
<icon-shrink />
|
||||
</div>
|
||||
<div>收起</div>
|
||||
@@ -155,6 +156,17 @@
|
||||
}, 50);
|
||||
});
|
||||
|
||||
// 点击命令
|
||||
const clickCommand = (e: PointerEvent) => {
|
||||
if (props.item.expand) {
|
||||
// 获取选中的文本
|
||||
const selectedText = window.getSelection()?.toString();
|
||||
if (selectedText) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 复制命令
|
||||
const copyCommand = () => {
|
||||
copy(props.item.command, false);
|
||||
|
||||
@@ -32,27 +32,25 @@
|
||||
import type { HostQueryResponse } from '@/api/asset/host';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import { InnerTabs, TerminalTabType } from '../../types/terminal.const';
|
||||
import { get } from 'lodash';
|
||||
import { TerminalTabs } from '../../types/terminal.const';
|
||||
|
||||
const totalCount = 7;
|
||||
const { tabManager, hosts, openTerminal } = useTerminalStore();
|
||||
|
||||
const combinedHandlers = ref<Array<CombinedHandlerItem>>([{
|
||||
title: InnerTabs.NEW_CONNECTION.title,
|
||||
settingTab: InnerTabs.NEW_CONNECTION,
|
||||
type: TerminalTabType.SETTING,
|
||||
icon: InnerTabs.NEW_CONNECTION.icon
|
||||
title: TerminalTabs.NEW_CONNECTION.title,
|
||||
tab: TerminalTabs.NEW_CONNECTION,
|
||||
icon: TerminalTabs.NEW_CONNECTION.icon
|
||||
}]);
|
||||
|
||||
// 点击组合操作元素
|
||||
const clickHandlerItem = (item: CombinedHandlerItem) => {
|
||||
if (item.type === TerminalTabType.SETTING) {
|
||||
// 打开内置 tab
|
||||
tabManager.openTab(item.settingTab as TerminalTabItem);
|
||||
} else {
|
||||
if (item.host) {
|
||||
// 打开终端
|
||||
openTerminal(item.host as HostQueryResponse);
|
||||
} else {
|
||||
// 打开 tab
|
||||
tabManager.openTab(item.tab as TerminalTabItem);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -71,7 +69,6 @@
|
||||
.map(s => {
|
||||
return {
|
||||
title: `${s.alias || s.name} (${s.address})`,
|
||||
type: TerminalTabType.TERMINAL,
|
||||
host: s,
|
||||
icon: 'icon-desktop'
|
||||
};
|
||||
@@ -80,15 +77,13 @@
|
||||
combinedHandlers.value.push(...combinedHosts);
|
||||
// 不足显示的行数用设置补充
|
||||
if (totalCount - 1 - combinedHosts.length > 0) {
|
||||
const fillTabs = Object.keys(InnerTabs)
|
||||
.filter(s => s !== 'NEW_CONNECTION')
|
||||
.map(s => get(InnerTabs, s) as TerminalTabItem)
|
||||
const fillTabs = Object.values(TerminalTabs)
|
||||
.filter(s => s.key !== TerminalTabs.NEW_CONNECTION.key)
|
||||
.slice(0, totalCount - 1 - combinedHosts.length)
|
||||
.map(s => {
|
||||
return {
|
||||
title: s.title,
|
||||
settingTab: s,
|
||||
type: TerminalTabType.SETTING,
|
||||
tab: s,
|
||||
icon: s.icon as string
|
||||
};
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { SidebarAction } from '../../types/terminal.type';
|
||||
import { InnerTabs } from '../../types/terminal.const';
|
||||
import { TerminalTabs } from '../../types/terminal.const';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import IconActions from './icon-actions.vue';
|
||||
|
||||
@@ -28,33 +28,33 @@
|
||||
// 顶部操作
|
||||
const topActions: Array<SidebarAction> = [
|
||||
{
|
||||
icon: InnerTabs.NEW_CONNECTION.icon,
|
||||
content: InnerTabs.NEW_CONNECTION.title,
|
||||
click: () => tabManager.openTab(InnerTabs.NEW_CONNECTION)
|
||||
icon: TerminalTabs.NEW_CONNECTION.icon,
|
||||
content: TerminalTabs.NEW_CONNECTION.title,
|
||||
click: () => tabManager.openTab(TerminalTabs.NEW_CONNECTION)
|
||||
},
|
||||
];
|
||||
|
||||
// 底部操作
|
||||
const bottomActions: Array<SidebarAction> = [
|
||||
{
|
||||
icon: InnerTabs.SHORTCUT_SETTING.icon,
|
||||
content: InnerTabs.SHORTCUT_SETTING.title,
|
||||
click: () => tabManager.openTab(InnerTabs.SHORTCUT_SETTING)
|
||||
icon: TerminalTabs.SHORTCUT_SETTING.icon,
|
||||
content: TerminalTabs.SHORTCUT_SETTING.title,
|
||||
click: () => tabManager.openTab(TerminalTabs.SHORTCUT_SETTING)
|
||||
},
|
||||
{
|
||||
icon: InnerTabs.DISPLAY_SETTING.icon,
|
||||
content: InnerTabs.DISPLAY_SETTING.title,
|
||||
click: () => tabManager.openTab(InnerTabs.DISPLAY_SETTING)
|
||||
icon: TerminalTabs.DISPLAY_SETTING.icon,
|
||||
content: TerminalTabs.DISPLAY_SETTING.title,
|
||||
click: () => tabManager.openTab(TerminalTabs.DISPLAY_SETTING)
|
||||
},
|
||||
{
|
||||
icon: InnerTabs.THEME_SETTING.icon,
|
||||
content: InnerTabs.THEME_SETTING.title,
|
||||
click: () => tabManager.openTab(InnerTabs.THEME_SETTING)
|
||||
icon: TerminalTabs.THEME_SETTING.icon,
|
||||
content: TerminalTabs.THEME_SETTING.title,
|
||||
click: () => tabManager.openTab(TerminalTabs.THEME_SETTING)
|
||||
},
|
||||
{
|
||||
icon: InnerTabs.TERMINAL_SETTING.icon,
|
||||
content: InnerTabs.TERMINAL_SETTING.title,
|
||||
click: () => tabManager.openTab(InnerTabs.TERMINAL_SETTING)
|
||||
icon: TerminalTabs.TERMINAL_SETTING.icon,
|
||||
content: TerminalTabs.TERMINAL_SETTING.title,
|
||||
click: () => tabManager.openTab(TerminalTabs.TERMINAL_SETTING)
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -6,23 +6,18 @@
|
||||
<a-tab-pane v-for="tab in tabManager.items"
|
||||
:key="tab.key"
|
||||
:title="tab.title">
|
||||
<!-- 设置 -->
|
||||
<template v-if="tab.type === TerminalTabType.SETTING">
|
||||
<!-- 新建连接 -->
|
||||
<new-connection-view v-if="tab.key === InnerTabs.NEW_CONNECTION.key" />
|
||||
<!-- 快捷键设置 -->
|
||||
<terminal-shortcut-setting v-else-if="tab.key === InnerTabs.SHORTCUT_SETTING.key" />
|
||||
<!-- 显示设置 -->
|
||||
<terminal-display-setting v-else-if="tab.key === InnerTabs.DISPLAY_SETTING.key" />
|
||||
<!-- 主题设置 -->
|
||||
<terminal-theme-setting v-else-if="tab.key === InnerTabs.THEME_SETTING.key" />
|
||||
<!-- 终端设置 -->
|
||||
<terminal-general-setting v-else-if="tab.key === InnerTabs.TERMINAL_SETTING.key" />
|
||||
</template>
|
||||
<!-- 终端 -->
|
||||
<template v-else-if="tab.type === TerminalTabType.TERMINAL">
|
||||
<terminal-view :tab="tab" />
|
||||
</template>
|
||||
<!-- 新建连接 -->
|
||||
<new-connection-view v-if="tab.key === TerminalTabs.NEW_CONNECTION.key" />
|
||||
<!-- 快捷键设置 -->
|
||||
<terminal-shortcut-setting v-else-if="tab.key === TerminalTabs.SHORTCUT_SETTING.key" />
|
||||
<!-- 显示设置 -->
|
||||
<terminal-display-setting v-else-if="tab.key === TerminalTabs.DISPLAY_SETTING.key" />
|
||||
<!-- 主题设置 -->
|
||||
<terminal-theme-setting v-else-if="tab.key === TerminalTabs.THEME_SETTING.key" />
|
||||
<!-- 终端设置 -->
|
||||
<terminal-general-setting v-else-if="tab.key === TerminalTabs.TERMINAL_SETTING.key" />
|
||||
<!-- 主机终端 -->
|
||||
<terminal-panel-view v-else-if="tab.key === TerminalTabs.TERMINAL_PANEL.key" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<!-- 承载页推荐 -->
|
||||
@@ -37,7 +32,7 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TerminalTabType, InnerTabs, TerminalShortcutKeys } from '../../types/terminal.const';
|
||||
import { TerminalTabs, TerminalShortcutKeys } from '../../types/terminal.const';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import { onMounted, onUnmounted, watch } from 'vue';
|
||||
import { addEventListen, removeEventListen } from '@/utils/event';
|
||||
@@ -47,16 +42,17 @@
|
||||
import TerminalThemeSetting from '../setting/theme/terminal-theme-setting.vue';
|
||||
import TerminalGeneralSetting from '../setting/general/terminal-general-setting.vue';
|
||||
import TerminalShortcutSetting from '../setting/shortcut/terminal-shortcut-setting.vue';
|
||||
import TerminalView from '../xterm/terminal-view.vue';
|
||||
import TerminalPanelView from '@/views/host/terminal/components/layout/terminal-panel-view.vue';
|
||||
|
||||
const { preference, tabManager, sessionManager } = useTerminalStore();
|
||||
|
||||
// fixme TerminalTabType.TERMINAL
|
||||
// 监听 tab 修改
|
||||
watch(() => tabManager.active, (active, before) => {
|
||||
if (before) {
|
||||
// 失焦已经切换的终端
|
||||
const beforeTab = tabManager.items.find(s => s.key === before);
|
||||
if (beforeTab && beforeTab?.type === TerminalTabType.TERMINAL) {
|
||||
if (beforeTab && beforeTab?.type === 'TerminalTabType.TERMINAL') {
|
||||
sessionManager.getSession(before)?.blur();
|
||||
}
|
||||
}
|
||||
@@ -66,10 +62,11 @@
|
||||
if (!activeTab) {
|
||||
return;
|
||||
}
|
||||
console.log(activeTab.title);
|
||||
// 修改标题
|
||||
document.title = activeTab.title;
|
||||
// 终端自动聚焦
|
||||
if (activeTab?.type === TerminalTabType.TERMINAL) {
|
||||
if (activeTab?.type === 'TerminalTabType.TERMINAL') {
|
||||
sessionManager.getSession(active)?.focus();
|
||||
}
|
||||
} else {
|
||||
@@ -82,7 +79,7 @@
|
||||
const handlerKeyboard = (event: Event) => {
|
||||
// 当前页面非 terminal 的时候再触发快捷键 (terminal 有内置逻辑)
|
||||
if (tabManager.active
|
||||
&& tabManager.items.find(s => s.key === tabManager.active)?.type === TerminalTabType.TERMINAL) {
|
||||
&& tabManager.items.find(s => s.key === tabManager.active)?.type === 'TerminalTabType.TERMINAL') {
|
||||
return;
|
||||
}
|
||||
const e = event as KeyboardEvent;
|
||||
@@ -114,7 +111,7 @@
|
||||
break;
|
||||
case TerminalShortcutKeys.OPEN_NEW_CONNECT_TAB:
|
||||
// 切换到新建连接 tab
|
||||
tabManager.openTab(InnerTabs.NEW_CONNECTION);
|
||||
tabManager.openTab(TerminalTabs.NEW_CONNECTION);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'terminalPanelView'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
</style>
|
||||
@@ -4,7 +4,7 @@ import type { Terminal } from 'xterm';
|
||||
import useCopy from '@/hooks/copy';
|
||||
import html2canvas from 'html2canvas';
|
||||
import { useTerminalStore, useUserStore } from '@/store';
|
||||
import { InnerTabs } from '../types/terminal.const';
|
||||
import { TerminalTabs } from '../types/terminal.const';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { dateFormat } from '@/utils';
|
||||
@@ -328,7 +328,7 @@ export default class TerminalSessionHandler implements ITerminalSessionHandler {
|
||||
|
||||
// 打开新建连接 tab
|
||||
openNewConnectTab() {
|
||||
this.tabManager.openTab(InnerTabs.NEW_CONNECTION);
|
||||
this.tabManager.openTab(TerminalTabs.NEW_CONNECTION);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { ITerminalTabManager, TerminalTabItem } from '../types/terminal.type';
|
||||
import { InnerTabs } from '../types/terminal.const';
|
||||
|
||||
// 终端 tab 管理器实现
|
||||
export default class TerminalTabManager implements ITerminalTabManager {
|
||||
@@ -8,9 +7,14 @@ export default class TerminalTabManager implements ITerminalTabManager {
|
||||
|
||||
public items: Array<TerminalTabItem>;
|
||||
|
||||
constructor() {
|
||||
this.active = InnerTabs.NEW_CONNECTION.key;
|
||||
this.items = [InnerTabs.NEW_CONNECTION];
|
||||
constructor(def: TerminalTabItem | undefined = undefined) {
|
||||
if (def) {
|
||||
this.active = def.key;
|
||||
this.items = [def];
|
||||
} else {
|
||||
this.active = undefined as unknown as string;
|
||||
this.items = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前 tab
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onBeforeMount, onUnmounted, onMounted } from 'vue';
|
||||
import { dictKeys, InnerTabs } from './types/terminal.const';
|
||||
import { dictKeys, TerminalTabs } from './types/terminal.const';
|
||||
import { useCacheStore, useDictStore, useTerminalStore } from '@/store';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import LayoutHeader from './components/layout/layout-header.vue';
|
||||
@@ -85,7 +85,7 @@
|
||||
// 事件处理
|
||||
onMounted(() => {
|
||||
// 默认标题
|
||||
document.title = InnerTabs.NEW_CONNECTION.title;
|
||||
document.title = TerminalTabs.NEW_CONNECTION.title;
|
||||
// 注册关闭视口事件
|
||||
// FIXME 开发阶段
|
||||
// window.addEventListener('beforeunload', handleBeforeUnload);
|
||||
|
||||
@@ -1,43 +1,36 @@
|
||||
// tab 类型
|
||||
import { ShortcutKeyItem } from './terminal.type';
|
||||
|
||||
// tab 类型
|
||||
export const TerminalTabType = {
|
||||
SETTING: 'setting',
|
||||
TERMINAL: 'terminal',
|
||||
};
|
||||
|
||||
// 内置 tab
|
||||
export const InnerTabs = {
|
||||
// 终端 tab
|
||||
export const TerminalTabs = {
|
||||
NEW_CONNECTION: {
|
||||
key: 'newConnection',
|
||||
title: '新建连接',
|
||||
icon: 'icon-plus',
|
||||
type: TerminalTabType.SETTING
|
||||
},
|
||||
SHORTCUT_SETTING: {
|
||||
key: 'shortcutSetting',
|
||||
title: '快捷键设置',
|
||||
icon: 'icon-command',
|
||||
type: TerminalTabType.SETTING
|
||||
},
|
||||
DISPLAY_SETTING: {
|
||||
key: 'displaySetting',
|
||||
title: '显示设置',
|
||||
icon: 'icon-stamp',
|
||||
type: TerminalTabType.SETTING
|
||||
},
|
||||
THEME_SETTING: {
|
||||
key: 'themeSetting',
|
||||
title: '主题设置',
|
||||
icon: 'icon-palette',
|
||||
type: TerminalTabType.SETTING
|
||||
},
|
||||
TERMINAL_SETTING: {
|
||||
key: 'terminalSetting',
|
||||
title: '终端设置',
|
||||
icon: 'icon-settings',
|
||||
type: TerminalTabType.SETTING
|
||||
},
|
||||
TERMINAL_PANEL: {
|
||||
key: 'terminalPanel',
|
||||
title: '主机终端',
|
||||
icon: 'icon-desktop',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import type { HostQueryResponse } from '@/api/asset/host';
|
||||
export interface TerminalTabItem {
|
||||
key: string;
|
||||
title: string;
|
||||
type: string;
|
||||
icon?: string;
|
||||
|
||||
[key: string]: unknown;
|
||||
@@ -32,9 +31,8 @@ export interface SidebarAction {
|
||||
// 组合操作元素
|
||||
export interface CombinedHandlerItem {
|
||||
icon: string,
|
||||
type: string,
|
||||
title: string;
|
||||
settingTab?: TerminalTabItem;
|
||||
tab?: TerminalTabItem;
|
||||
host?: HostQueryResponse;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user