♻️ 修改终端交互逻辑.

This commit is contained in:
lijiahang
2024-02-05 11:51:25 +08:00
parent b4ceb3839c
commit 13786bbc7c
11 changed files with 105 additions and 78 deletions

View File

@@ -66,13 +66,13 @@ public class TerminalPreferenceStrategy implements IPreferenceStrategy<TerminalP
new TerminalPreferenceModel.ShortcutKeysModel("changeToPrevTab", true, true, true, "BracketLeft", true), new TerminalPreferenceModel.ShortcutKeysModel("changeToPrevTab", true, true, true, "BracketLeft", true),
new TerminalPreferenceModel.ShortcutKeysModel("changeToNextTab", true, true, true, "BracketRight", true), new TerminalPreferenceModel.ShortcutKeysModel("changeToNextTab", true, true, true, "BracketRight", true),
new TerminalPreferenceModel.ShortcutKeysModel("openNewConnectTab", true, true, true, "KeyN", true), new TerminalPreferenceModel.ShortcutKeysModel("openNewConnectTab", true, true, true, "KeyN", true),
// 终端面板快捷键 // 会话快捷键
new TerminalPreferenceModel.ShortcutKeysModel("openNewConnectModal", true, false, true, "KeyN", true), new TerminalPreferenceModel.ShortcutKeysModel("openNewConnectModal", true, false, true, "KeyN", true),
new TerminalPreferenceModel.ShortcutKeysModel("copyTerminal", true, false, true, "KeyO", true), new TerminalPreferenceModel.ShortcutKeysModel("copySession", true, false, true, "KeyO", true),
new TerminalPreferenceModel.ShortcutKeysModel("closeTerminal", true, false, true, "KeyW", true), new TerminalPreferenceModel.ShortcutKeysModel("closeSession", true, false, true, "KeyW", true),
new TerminalPreferenceModel.ShortcutKeysModel("changeToPrevTerminal", true, false, true, "BracketLeft", true), new TerminalPreferenceModel.ShortcutKeysModel("changeToPrevSession", true, false, true, "BracketLeft", true),
new TerminalPreferenceModel.ShortcutKeysModel("changeToNextTerminal", true, false, true, "BracketRight", true), new TerminalPreferenceModel.ShortcutKeysModel("changeToNextSession", true, false, true, "BracketRight", true),
// 终端会话快捷键 // 终端快捷键
new TerminalPreferenceModel.ShortcutKeysModel("copy", true, true, false, "KeyC", true), new TerminalPreferenceModel.ShortcutKeysModel("copy", true, true, false, "KeyC", true),
new TerminalPreferenceModel.ShortcutKeysModel("paste", true, true, false, "KeyV", true), new TerminalPreferenceModel.ShortcutKeysModel("paste", true, true, false, "KeyV", true),
new TerminalPreferenceModel.ShortcutKeysModel("toTop", true, true, false, "ArrowUp", true), new TerminalPreferenceModel.ShortcutKeysModel("toTop", true, true, false, "ArrowUp", true),

View File

@@ -8,6 +8,7 @@ import type {
TerminalShortcutSetting, TerminalShortcutSetting,
TerminalState TerminalState
} from './types'; } from './types';
import type { PanelSessionTab, 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';
@@ -17,7 +18,7 @@ import { defineStore } from 'pinia';
import { getPreference, updatePreference } from '@/api/user/preference'; import { getPreference, updatePreference } from '@/api/user/preference';
import { nextSessionId } from '@/utils'; import { nextSessionId } from '@/utils';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { TerminalPanelTabType, TerminalTabs } from '@/views/host/terminal/types/terminal.const'; import { PanelSessionType, TerminalTabs } from '@/views/host/terminal/types/terminal.const';
import TerminalTabManager from '@/views/host/terminal/handler/terminal-tab-manager'; import TerminalTabManager from '@/views/host/terminal/handler/terminal-tab-manager';
import TerminalSessionManager from '@/views/host/terminal/handler/terminal-session-manager'; import TerminalSessionManager from '@/views/host/terminal/handler/terminal-session-manager';
import TerminalPanelManager from '@/views/host/terminal/handler/terminal-panel-manager'; import TerminalPanelManager from '@/views/host/terminal/handler/terminal-panel-manager';
@@ -131,8 +132,8 @@ export default defineStore('terminal', {
}); });
}, },
// 打开终端 // 打开会话
openTerminal(record: HostQueryResponse, panelIndex: number = 0) { openSession(record: HostQueryResponse, session: PanelSessionTab, panelIndex: number = 0) {
// 添加到最近连接 // 添加到最近连接
this.hosts.latestHosts = [...new Set([record.id, ...this.hosts.latestHosts])]; this.hosts.latestHosts = [...new Set([record.id, ...this.hosts.latestHosts])];
// 切换到终端面板页面 // 切换到终端面板页面
@@ -154,17 +155,21 @@ export default defineStore('terminal', {
title: `(${nextSeq}) ${record.alias || record.name}`, title: `(${nextSeq}) ${record.alias || record.name}`,
hostId: record.id, hostId: record.id,
address: record.address, address: record.address,
icon: 'icon-desktop', icon: session.icon,
type: TerminalPanelTabType.TERMINAL type: session.type
}); });
}, },
// 复制并且打开终端 // 复制并且打开会话
copyTerminalSession(hostId: number, panelIndex: number = 0) { copySession(item: TerminalPanelTabItem, panelIndex: number = 0) {
const host = this.hosts.hostList const host = this.hosts.hostList
.find(s => s.id === hostId); .find(s => s.id === item.hostId);
if (host) { if (host) {
this.openTerminal(host, panelIndex); const sessionType = {
type: item.type,
icon: item.icon
};
this.openSession(host, sessionType, panelIndex);
} }
}, },
@@ -189,14 +194,14 @@ export default defineStore('terminal', {
// 获取当前终端会话 // 获取当前终端会话
getCurrentTerminalSession() { getCurrentTerminalSession() {
// 获取面板会话 // 获取面板会话
const panelTab = this.panelManager const sessionTab = this.panelManager
.getCurrentPanel() .getCurrentPanel()
.getCurrentTab(); .getCurrentTab();
if (!panelTab || panelTab.type !== TerminalPanelTabType.TERMINAL) { if (!sessionTab || sessionTab.type !== PanelSessionType.TERMINAL.type) {
return; return;
} }
// 获取会话 // 获取会话
return this.sessionManager.getSession(panelTab.key); return this.sessionManager.getSession(sessionTab.key);
}, },
}, },

View File

@@ -2,11 +2,11 @@
<!-- 搜索 --> <!-- 搜索 -->
<a-card class="general-card table-search-card"> <a-card class="general-card table-search-card">
<query-header :model="formModel" <query-header :model="formModel"
label-align="left" label-align="left"
:itemOptions="{ 5: { span: 2 } }" :itemOptions="{ 6: { span: 2 } }"
@submit="fetchTableData" @submit="fetchTableData"
@reset="fetchTableData" @reset="fetchTableData"
@keyup.enter="() => fetchTableData()"> @keyup.enter="() => fetchTableData()">
<!-- 连接用户 --> <!-- 连接用户 -->
<a-form-item field="userId" label="连接用户" label-col-flex="50px"> <a-form-item field="userId" label="连接用户" label-col-flex="50px">
<user-selector v-model="formModel.userId" <user-selector v-model="formModel.userId"
@@ -30,6 +30,13 @@
:options="toOptions(connectStatusKey)" :options="toOptions(connectStatusKey)"
allow-clear /> allow-clear />
</a-form-item> </a-form-item>
<!-- 类型 -->
<a-form-item field="type" label="类型" label-col-flex="50px">
<a-select v-model="formModel.type"
placeholder="请选择类型"
:options="toOptions(connectTypeKey)"
allow-clear />
</a-form-item>
<!-- token --> <!-- token -->
<a-form-item field="token" label="token" label-col-flex="50px"> <a-form-item field="token" label="token" label-col-flex="50px">
<a-input v-model="formModel.token" placeholder="请输入token" allow-clear /> <a-input v-model="formModel.token" placeholder="请输入token" allow-clear />
@@ -107,7 +114,7 @@
import { getHostConnectLogPage } from '@/api/asset/host-connect-log'; import { getHostConnectLogPage } from '@/api/asset/host-connect-log';
import useLoading from '@/hooks/loading'; import useLoading from '@/hooks/loading';
import columns from '../types/table.columns'; import columns from '../types/table.columns';
import { connectStatusKey } from '../types/const'; import { connectStatusKey, connectTypeKey } from '../types/const';
import { usePagination } from '@/types/table'; import { usePagination } from '@/types/table';
import { useDictStore } from '@/store'; import { useDictStore } from '@/store';
import useCopy from '@/hooks/copy'; import useCopy from '@/hooks/copy';

View File

@@ -1,5 +1,8 @@
// 主机连接状态 字典项 // 主机连接状态 字典项
export const connectStatusKey = 'hostConnectStatus'; export const connectStatusKey = 'hostConnectStatus';
// 主机连接类型 字典项
export const connectTypeKey = 'hostConnectType';
// 加载的字典值 // 加载的字典值
export const dictKeys = [connectStatusKey]; export const dictKeys = [connectStatusKey, connectTypeKey];

View File

@@ -31,13 +31,11 @@ const columns = [
ellipsis: true, ellipsis: true,
tooltip: true, tooltip: true,
}, { }, {
title: 'token', title: '类型',
dataIndex: 'token', dataIndex: 'type',
slotName: 'token', slotName: 'type',
width: 68,
align: 'left', align: 'left',
width: 180,
ellipsis: true,
tooltip: true,
}, { }, {
title: '状态', title: '状态',
dataIndex: 'status', dataIndex: 'status',
@@ -45,22 +43,23 @@ const columns = [
align: 'left', align: 'left',
width: 90, width: 90,
}, { }, {
title: '开始时间', title: 'token',
dataIndex: 'startTime', dataIndex: 'token',
slotName: 'startTime', slotName: 'token',
align: 'left', align: 'left',
width: 180, width: 120,
render: ({ record }) => { ellipsis: true,
return record.startTime && dateFormat(new Date(record.startTime)); tooltip: true,
},
}, { }, {
title: '结束时间', title: '连接时间',
dataIndex: 'endTime', dataIndex: 'connectTime',
slotName: 'endTime', slotName: 'connectTime',
align: 'left', align: 'left',
width: 180, width: 310,
render: ({ record }) => { render: ({ record }) => {
return record.endTime && dateFormat(new Date(record.endTime)); return (record.startTime && dateFormat(new Date(record.startTime)))
+ ' - '
+ (record.endTime && dateFormat(new Date(record.endTime)) || '现在');
}, },
}, },
] as TableColumnData[]; ] as TableColumnData[];

View File

@@ -32,10 +32,10 @@
import type { HostQueryResponse } from '@/api/asset/host'; import type { HostQueryResponse } from '@/api/asset/host';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useTerminalStore } from '@/store'; import { useTerminalStore } from '@/store';
import { TerminalTabs } from '../../types/terminal.const'; import { PanelSessionType, TerminalTabs } from '../../types/terminal.const';
const totalCount = 7; const totalCount = 7;
const { tabManager, hosts, openTerminal } = useTerminalStore(); const { tabManager, hosts, openSession } = useTerminalStore();
const combinedHandlers = ref<Array<CombinedHandlerItem>>([{ const combinedHandlers = ref<Array<CombinedHandlerItem>>([{
title: TerminalTabs.NEW_CONNECTION.title, title: TerminalTabs.NEW_CONNECTION.title,
@@ -46,8 +46,8 @@
// 点击组合操作元素 // 点击组合操作元素
const clickHandlerItem = (item: CombinedHandlerItem) => { const clickHandlerItem = (item: CombinedHandlerItem) => {
if (item.host) { if (item.host) {
// 打开终端 // 打开会话
openTerminal(item.host as HostQueryResponse); openSession(item.host as HostQueryResponse, PanelSessionType.TERMINAL);
} else { } else {
// 打开 tab // 打开 tab
tabManager.openTab(item.tab as TerminalTabItem); tabManager.openTab(item.tab as TerminalTabItem);

View File

@@ -45,7 +45,7 @@
import type { ITerminalTabManager, TerminalPanelTabItem } from '../../types/terminal.type'; import type { ITerminalTabManager, TerminalPanelTabItem } from '../../types/terminal.type';
import { watch } from 'vue'; import { watch } from 'vue';
import { useTerminalStore } from '@/store'; import { useTerminalStore } from '@/store';
import { TerminalPanelTabType } from '../../types/terminal.const'; import { PanelSessionType } from '../../types/terminal.const';
import TerminalView from '../xterm/terminal-view.vue'; import TerminalView from '../xterm/terminal-view.vue';
const props = defineProps<{ const props = defineProps<{
@@ -62,14 +62,14 @@
// 失焦自动终端 // 失焦自动终端
if (before) { if (before) {
const beforeTab = props.panel.items.find(s => s.key === before); const beforeTab = props.panel.items.find(s => s.key === before);
if (beforeTab && beforeTab?.type === TerminalPanelTabType.TERMINAL) { if (beforeTab && beforeTab?.type === PanelSessionType.TERMINAL.type) {
sessionManager.getSession(before)?.blur(); sessionManager.getSession(before)?.blur();
} }
} }
// 终端自动聚焦 // 终端自动聚焦
if (active) { if (active) {
const activeTab = props.panel.items.find(s => s.key === active); const activeTab = props.panel.items.find(s => s.key === active);
if (activeTab && activeTab?.type === TerminalPanelTabType.TERMINAL) { if (activeTab && activeTab?.type === PanelSessionType.TERMINAL.type) {
sessionManager.getSession(active)?.focus(); sessionManager.getSession(active)?.focus();
} }
} }

View File

@@ -26,7 +26,7 @@
import TerminalPanel from './terminal-panel.vue'; import TerminalPanel from './terminal-panel.vue';
import HostListModal from '../new-connection/host-list-modal.vue'; import HostListModal from '../new-connection/host-list-modal.vue';
const { preference, tabManager, panelManager, copyTerminalSession } = useTerminalStore(); const { preference, tabManager, panelManager, copySession } = useTerminalStore();
const hostModal = ref(); const hostModal = ref();
@@ -67,10 +67,10 @@
hostModal.value.open(panelManager.active); hostModal.value.open(panelManager.active);
break; break;
case TerminalShortcutKeys.COPY_TERMINAL: case TerminalShortcutKeys.COPY_TERMINAL:
// 复制终端 // 复制会话
const hostId = panelManager.getCurrentPanel().getCurrentTab()?.hostId; const currentTab = panelManager.getCurrentPanel().getCurrentTab();
if (hostId) { if (currentTab) {
copyTerminalSession(hostId, panelManager.active); copySession(currentTab, panelManager.active);
} }
break; break;
case TerminalShortcutKeys.CLOSE_TERMINAL: case TerminalShortcutKeys.CLOSE_TERMINAL:
@@ -80,11 +80,11 @@
panel.deleteTab(panel.active); panel.deleteTab(panel.active);
} }
break; break;
case TerminalShortcutKeys.CHANGE_TO_PREV_TERMINAL: case TerminalShortcutKeys.CHANGE_TO_PREV_SESSION:
// 切换至前一个终端 // 切换至前一个终端
panelManager.getCurrentPanel().changeToPrevTab(); panelManager.getCurrentPanel().changeToPrevTab();
break; break;
case TerminalShortcutKeys.CHANGE_TO_NEXT_TERMINAL: case TerminalShortcutKeys.CHANGE_TO_NEXT_SESSION:
// 切换至后一个终端 // 切换至后一个终端
panelManager.getCurrentPanel().changeToNextTab(); panelManager.getCurrentPanel().changeToNextTab();
break; break;

View File

@@ -52,9 +52,10 @@
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';
import { PanelSessionType } from '../../types/terminal.const';
import useVisible from '@/hooks/visible'; import useVisible from '@/hooks/visible';
const { hosts, openTerminal } = useTerminalStore(); const { hosts, openSession } = useTerminalStore();
const { visible, setVisible } = useVisible(); const { visible, setVisible } = useVisible();
const panelIndex = ref(); const panelIndex = ref();
@@ -97,7 +98,7 @@
// 打开终端 // 打开终端
const clickHost = (item: HostQueryResponse) => { const clickHost = (item: HostQueryResponse) => {
openTerminal(item, panelIndex.value); openSession(item, PanelSessionType.TERMINAL, panelIndex.value);
setVisible(false); setVisible(false);
}; };

View File

@@ -20,13 +20,13 @@
<!-- 左侧图标-名称 --> <!-- 左侧图标-名称 -->
<div class="flex-center host-item-left"> <div class="flex-center host-item-left">
<!-- 图标 --> <!-- 图标 -->
<span class="host-item-left-icon" @click="openTerminal(item)"> <span class="host-item-left-icon">
<icon-desktop /> <icon-desktop />
</span> </span>
<!-- 名称 --> <!-- 名称 -->
<span class="host-item-left-name"> <span class="host-item-left-name">
<!-- 名称文本 --> <!-- 名称文本 -->
<template v-if="!item.editable"> <template v-if="!item.editable">
<!-- 文本 --> <!-- 文本 -->
<a-tooltip position="top" <a-tooltip position="top"
:mini="true" :mini="true"
@@ -42,7 +42,7 @@
</template> </template>
</span> </span>
</a-tooltip> </a-tooltip>
<!-- 修改别名 --> <!-- 修改别名 -->
<a-tooltip position="top" <a-tooltip position="top"
:mini="true" :mini="true"
:auto-fix-position="false" :auto-fix-position="false"
@@ -54,7 +54,7 @@
</a-tooltip> </a-tooltip>
</template> </template>
<!-- 名称输入框 --> <!-- 名称输入框 -->
<template v-else> <template v-else>
<a-input v-model="item.alias" <a-input v-model="item.alias"
ref="aliasNameInput" ref="aliasNameInput"
class="host-item-left-name-input" class="host-item-left-name-input"
@@ -76,7 +76,7 @@
</template> </template>
</a-input> </a-input>
</template> </template>
</span> </span>
</div> </div>
<!-- 中间ip --> <!-- 中间ip -->
<div class="flex-center host-item-center"> <div class="flex-center host-item-center">
@@ -111,19 +111,32 @@
</div> </div>
<!-- 操作 --> <!-- 操作 -->
<div class="host-item-right-actions"> <div class="host-item-right-actions">
<!-- 连接主机 --> <!-- 打开 SSH -->
<a-tooltip position="top" <a-tooltip position="top"
:mini="true" :mini="true"
:auto-fix-position="false" :auto-fix-position="false"
content-class="terminal-tooltip-content" content-class="terminal-tooltip-content"
arrow-class="terminal-tooltip-content" arrow-class="terminal-tooltip-content"
content="连接主机"> content="打开 SSH">
<div class="terminal-sidebar-icon-wrapper"> <div class="terminal-sidebar-icon-wrapper">
<div class="terminal-sidebar-icon" @click="openTerminal(item)"> <div class="terminal-sidebar-icon" @click="openSession(item, PanelSessionType.TERMINAL)">
<icon-thunderbolt /> <icon-thunderbolt />
</div> </div>
</div> </div>
</a-tooltip> </a-tooltip>
<!-- 打开 SFTP -->
<a-tooltip position="top"
:mini="true"
:auto-fix-position="false"
content-class="terminal-tooltip-content"
arrow-class="terminal-tooltip-content"
content="打开 SFTP">
<div class="terminal-sidebar-icon-wrapper">
<div class="terminal-sidebar-icon" @click="openSession(item, PanelSessionType.SFTP)">
<icon-folder />
</div>
</div>
</a-tooltip>
<!-- 连接设置 --> <!-- 连接设置 -->
<a-tooltip position="top" <a-tooltip position="top"
:mini="true" :mini="true"
@@ -172,7 +185,7 @@
import { dataColor } from '@/utils'; import { dataColor } from '@/utils';
import { tagColor } from '@/views/asset/host-list/types/const'; import { tagColor } from '@/views/asset/host-list/types/const';
import { updateHostAlias } from '@/api/asset/host-extra'; import { updateHostAlias } from '@/api/asset/host-extra';
import { openSshModalKey } from '../../types/terminal.const'; import { openSshModalKey, PanelSessionType } from '../../types/terminal.const';
import { useTerminalStore } from '@/store'; import { useTerminalStore } from '@/store';
const props = defineProps<{ const props = defineProps<{
@@ -180,7 +193,7 @@
emptyValue: string emptyValue: string
}>(); }>();
const { openTerminal } = useTerminalStore(); const { openSession } = useTerminalStore();
const { toggle: toggleFavorite, loading: favoriteLoading } = useFavorite('HOST'); const { toggle: toggleFavorite, loading: favoriteLoading } = useFavorite('HOST');
const aliasNameInput = ref(); const aliasNameInput = ref();
@@ -311,7 +324,6 @@
border-radius: 32px; border-radius: 32px;
margin-right: 10px; margin-right: 10px;
font-size: 16px; font-size: 16px;
cursor: pointer;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;

View File

@@ -26,15 +26,15 @@
@set-editable="setEditableStatus" @set-editable="setEditableStatus"
@clear-editable="clearEditableStatus" @clear-editable="clearEditableStatus"
@update-enabled="updateEnabledStatus" /> @update-enabled="updateEnabledStatus" />
<!-- 终端面板快捷键 --> <!-- 会话快捷键 -->
<terminal-shortcut-keys-block title="终端面板快捷键" <terminal-shortcut-keys-block title="会话快捷键"
:type="TerminalShortcutType.PANEL" :type="TerminalShortcutType.SESSION"
:items="shortcutKeys" :items="shortcutKeys"
@set-editable="setEditableStatus" @set-editable="setEditableStatus"
@clear-editable="clearEditableStatus" @clear-editable="clearEditableStatus"
@update-enabled="updateEnabledStatus" /> @update-enabled="updateEnabledStatus" />
<!-- 终端会话快捷键 --> <!-- 终端快捷键 -->
<terminal-shortcut-keys-block title="终端会话快捷键" <terminal-shortcut-keys-block title="终端快捷键"
:type="TerminalShortcutType.TERMINAL" :type="TerminalShortcutType.TERMINAL"
:items="shortcutKeys" :items="shortcutKeys"
@set-editable="setEditableStatus" @set-editable="setEditableStatus"