🔨 重构终端前端逻辑

This commit is contained in:
lijiahangmax
2024-02-02 01:07:03 +08:00
parent 697de97473
commit ae52a556d9
20 changed files with 413 additions and 119 deletions

View File

@@ -75,4 +75,6 @@ public interface ErrorMessage {
String SESSION_ABSENT = "会话不存在";
String CONNECT_ERROR = "连接失败";
}

View File

@@ -1,5 +1,7 @@
package com.orion.ops.module.asset.handler.host.terminal.handler;
import com.orion.lang.exception.argument.InvalidArgumentException;
import com.orion.lang.utils.Exceptions;
import com.orion.lang.utils.collect.Maps;
import com.orion.ops.framework.biz.operator.log.core.service.OperatorLogFrameworkService;
import com.orion.ops.framework.biz.operator.log.core.uitls.OperatorLogFiller;
@@ -79,9 +81,12 @@ public class TerminalCheckHandler extends AbstractTerminalHandler<TerminalCheckR
// 设置到缓存中
channel.getAttributes().put(sessionId, connect);
log.info("TerminalCheckHandler-handle success userId: {}, hostId: {}, sessionId: {}", userId, hostId, sessionId);
} catch (Exception e) {
} catch (InvalidArgumentException e) {
ex = e;
log.error("TerminalCheckHandler-handle error userId: {}, hostId: {}, sessionId: {}", userId, hostId, sessionId, e);
} catch (Exception e) {
ex = Exceptions.runtime(ErrorMessage.CONNECT_ERROR);
log.error("TerminalCheckHandler-handle exception userId: {}, hostId: {}, sessionId: {}", userId, hostId, sessionId, e);
}
// 记录主机日志
this.saveTerminalLog(channel, userId, host, startTime, ex, sessionId);

View File

@@ -2,7 +2,6 @@
{
"name": "catppuccin-mocha",
"dark": true,
"headerBackgroundColor": "#121222",
"schema": {
"background": "#1E1E2E",
"foreground": "#CDD6F4",
@@ -27,7 +26,6 @@
{
"name": "MaterialDesignColors",
"dark": true,
"headerBackgroundColor": "#111A1E",
"schema": {
"background": "#1D262A",
"foreground": "#E7EBED",
@@ -52,7 +50,6 @@
{
"name": "catppuccin-macchiato",
"dark": true,
"headerBackgroundColor": "#181B2E",
"schema": {
"background": "#24273A",
"foreground": "#CAD3F5",
@@ -77,7 +74,6 @@
{
"name": "OneHalfDark",
"dark": true,
"headerBackgroundColor": "#1C2028",
"schema": {
"background": "#282C34",
"foreground": "#DCDFE4",
@@ -102,7 +98,6 @@
{
"name": "Dracula",
"dark": true,
"headerBackgroundColor": "#12131D",
"schema": {
"background": "#1E1F29",
"foreground": "#F8F8F2",
@@ -127,7 +122,6 @@
{
"name": "Atom",
"dark": true,
"headerBackgroundColor": "#0A0B0D",
"schema": {
"background": "#161719",
"foreground": "#C5C8C6",
@@ -152,7 +146,6 @@
{
"name": "Apple System Colors",
"dark": true,
"headerBackgroundColor": "#121212",
"schema": {
"background": "#1E1E1E",
"foreground": "#FFFFFF",
@@ -177,7 +170,6 @@
{
"name": "Builtin Tango Light",
"dark": false,
"headerBackgroundColor": "#F3F3F3",
"schema": {
"background": "#FFFFFF",
"foreground": "#000000",
@@ -202,7 +194,6 @@
{
"name": "Duotone Dark",
"dark": true,
"headerBackgroundColor": "#13111B",
"schema": {
"background": "#1F1D27",
"foreground": "#B7A1FF",
@@ -227,7 +218,6 @@
{
"name": "BlulocoLight",
"dark": false,
"headerBackgroundColor": "#EDEDED",
"schema": {
"background": "#F9F9F9",
"foreground": "#373A41",
@@ -252,7 +242,6 @@
{
"name": "Chester",
"dark": true,
"headerBackgroundColor": "#202A37",
"schema": {
"background": "#2C3643",
"foreground": "#FFFFFF",
@@ -277,7 +266,6 @@
{
"name": "CLRS",
"dark": false,
"headerBackgroundColor": "#F3F3F3",
"schema": {
"background": "#FFFFFF",
"foreground": "#262626",
@@ -302,7 +290,6 @@
{
"name": "Calamity",
"dark": true,
"headerBackgroundColor": "#231C27",
"schema": {
"background": "#2F2833",
"foreground": "#D5CED9",
@@ -327,7 +314,6 @@
{
"name": "Tomorrow",
"dark": false,
"headerBackgroundColor": "#F3F3F3",
"schema": {
"background": "#FFFFFF",
"foreground": "#4D4D4C",

View File

@@ -5,7 +5,6 @@ import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.serializer.ValueFilter;
import com.orion.lang.utils.Colors;
import com.orion.lang.utils.Strings;
import com.orion.lang.utils.collect.Lists;
import com.orion.lang.utils.io.FileReaders;
import com.orion.lang.utils.io.Files1;
@@ -59,8 +58,6 @@ public class TerminalThemeGenerator {
TerminalTheme theme = new TerminalTheme();
theme.setName(schema.getString("name"));
theme.setDark(Colors.isDarkColor(background));
// header 颜色为深 12
theme.setHeaderBackgroundColor(adjustColor(background, -12));
theme.setSchema(JSON.parseObject(JSON.toJSONString(schema), TerminalThemeSchema.class));
return theme;
}).collect(Collectors.toList());
@@ -113,8 +110,6 @@ public class TerminalThemeGenerator {
@JSONField(ordinal = 1)
private Boolean dark;
@JSONField(ordinal = 2)
private String headerBackgroundColor;
@JSONField(ordinal = 3)
private TerminalThemeSchema schema;
}
@@ -162,26 +157,4 @@ public class TerminalThemeGenerator {
private String brightWhite;
}
/**
* 调整颜色
*
* @param color color
* @param range 正数越浅 负数越深
* @return color
*/
private static String adjustColor(String color, int range) {
StringBuilder newColor = new StringBuilder("#");
for (int i = 0; i < 3; i++) {
int c = Integer.parseInt(color.substring(i * 2 + 1, i * 2 + 3), 16);
c += range;
if (c < 0) {
c = 0;
} else if (c > 255) {
c = 255;
}
newColor.append(Strings.leftPad(Integer.toString(c, 16), 2, "0"));
}
return newColor.toString();
}
}

View File

@@ -4,7 +4,6 @@ import axios from 'axios';
export interface TerminalTheme {
name: string;
dark: boolean;
headerBackgroundColor: string;
schema: TerminalThemeSchema;
}

View File

@@ -1,7 +1,8 @@
// 亮色主题配色常量
body {
--color-bg-header: #232323;
--color-bg-sidebar: #F2F3F4;
--color-bg-sidebar: #EBECED;
--color-bg-panel: var(--color-bg-sidebar);
--color-bg-content: #FEFEFE;
--color-sidebar-icon: #737070;
--color-sidebar-icon-bg: #D7D8DB;
@@ -11,6 +12,14 @@ body {
--color-content-text-1: rgba(0, 0, 0, .8);
--color-content-text-2: rgba(0, 0, 0, .85);
--color-content-text-3: rgba(0, 0, 0, .95);
--color-bg-panel-tabs: var(--color-bg-panel);
--color-bg-panel-tabs-active: #F9F9F9;
--color-bg-panel-icon-1: #F5F5F5;
--color-bg-panel-bar: #F3F4F5;
--color-panel-text-1: var(--color-content-text-1);
--color-panel-text-2: var(--color-content-text-3);
--color-panel-gradient-start: rgba(218, 218, 218, 1);
--color-panel-gradient-end: rgba(218, 218, 218, 0);
--search-bg-focus: rgba(234, 234, 234, .75);
--search-bg: rgba(234, 234, 234, .95);
--search-color-text: #0E0E0E;
@@ -25,6 +34,7 @@ body {
body[terminal-theme='dark'] {
--color-bg-header: #232323;
--color-bg-sidebar: #2C2E31;
--color-bg-panel: var(--color-bg-sidebar);
--color-bg-content: #1A1B1C;
--color-sidebar-icon: #C3C6C9;
--color-sidebar-icon-bg: #3D3E3F;
@@ -34,6 +44,14 @@ body[terminal-theme='dark'] {
--color-content-text-1: rgba(255, 255, 255, .8);
--color-content-text-2: rgba(255, 255, 255, .85);
--color-content-text-3: rgba(255, 255, 255, .95);
--color-bg-panel-tabs: var(--color-bg-panel);
--color-bg-panel-tabs-active: #434343;
--color-bg-panel-icon-1: var(--color-bg-panel-tabs-active);
--color-bg-panel-bar: #323538;
--color-panel-text-1: var(--color-content-text-1);
--color-panel-text-2: var(--color-content-text-3);
--color-panel-gradient-start: rgba(38, 38, 38, 1);
--color-panel-gradient-end: rgba(38, 38, 38, 0);
--search-bg: rgba(12, 12, 12, .75);
--search-bg-focus: rgba(12, 12, 12, .95);
--search-color-text: #E0E0E0;
@@ -47,14 +65,14 @@ body[terminal-theme='dark'] {
// 布局常量
.host-terminal-layout {
--header-height: 44px;
--panel-nav-height: 40px;
--sidebar-width: 44px;
--sidebar-icon-wrapper-size: var(--header-height);
--sidebar-icon-size: 32px;
--sidebar-icon-font-size: 22px;
--color-bg-header-icon-1: #434343;
--color-header-tabs-bg: var(--color-bg-header);
--color-header-tabs-bg-hover: #434343;
--color-bg-header-tabs: var(--color-bg-header);
--color-bg-header-tabs-active: #434343;
--color-bg-header-icon-1: var(--color-bg-header-tabs-active);
--color-header-text-1: rgba(255, 255, 255, .75);
--color-header-text-2: rgba(255, 255, 255, .9);
--color-gradient-start: rgba(38, 38, 38, 1);

View File

@@ -17,9 +17,10 @@ import { defineStore } from 'pinia';
import { getPreference, updatePreference } from '@/api/user/preference';
import { nextSessionId } from '@/utils';
import { Message } from '@arco-design/web-vue';
import { TerminalTabs } from '@/views/host/terminal/types/terminal.const';
import { TerminalPanelTabType, 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';
import TerminalPanelManager from '@/views/host/terminal/handler/terminal-panel-manager';
// 终端偏好项
export const TerminalPreferenceItem = {
@@ -63,7 +64,7 @@ export default defineStore('terminal', {
},
hosts: {} as AuthorizedHostQueryResponse,
tabManager: new TerminalTabManager(TerminalTabs.NEW_CONNECTION),
routerTabManager: [new TerminalTabManager()],
panelManager: new TerminalPanelManager(),
sessionManager: new TerminalSessionManager()
}),
@@ -137,43 +138,57 @@ export default defineStore('terminal', {
// 切换到终端面板页面
this.tabManager.openTab(TerminalTabs.TERMINAL_PANEL);
// 获取 seq
const tabSeqArr = this.tabManager.items
const seqArr = this.panelManager
.getPanel(panelIndex)
.items
.map(s => s.seq)
.filter(Boolean)
.map(Number);
const nextSeq = tabSeqArr.length
? Math.max(...tabSeqArr) + 1
const nextSeq = seqArr.length
? Math.max(...seqArr) + 1
: 1;
// FIXME
// 打开 tab
this.tabManager.openTab({
this.panelManager.getPanel(panelIndex).openTab({
key: nextSessionId(10),
seq: nextSeq,
title: `(${nextSeq}) ${record.alias || record.name}`,
hostId: record.id,
address: record.address
address: record.address,
icon: 'icon-desktop',
type: TerminalPanelTabType.TERMINAL
});
},
// 复制并且打开终端
openCopyTerminal(hostId: number) {
openCopyTerminal(hostId: number, panelIndex: number = 0) {
const host = this.hosts.hostList
.find(s => s.id === hostId);
if (host) {
this.openTerminal(host);
this.openTerminal(host, panelIndex);
}
},
// 获取当前终端会话
getCurrentTerminalSession(tips: boolean = true) {
// 获取当前 tab
const tab = this.tabManager.getCurrentTab();
// FIXME
if (!tab || tab.type !== 'TERMINAL') {
if (!tab || tab.key !== TerminalTabs.TERMINAL_PANEL.key) {
if (tips) {
Message.warning('请切换到终端标签页');
}
return;
}
// 获取面板会话
const activeTab = this.panelManager
.getCurrentPanel()
.getCurrentTab();
if (!activeTab || activeTab.type !== TerminalPanelTabType.TERMINAL) {
if (tips) {
Message.warning('请打开终端');
}
return;
}
// 获取会话
return this.sessionManager.getSession(tab.key);
},

View File

@@ -1,4 +1,4 @@
import type { ITerminalSessionManager, ITerminalTabManager } from '@/views/host/terminal/types/terminal.type';
import type { ITerminalPanelManager, ITerminalSessionManager, ITerminalTabManager } from '@/views/host/terminal/types/terminal.type';
import type { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
import type { TerminalTheme } from '@/api/asset/host-terminal';
@@ -6,7 +6,7 @@ export interface TerminalState {
preference: TerminalPreference;
hosts: AuthorizedHostQueryResponse;
tabManager: ITerminalTabManager;
routerTabManager: Array<ITerminalTabManager>;
panelManager: ITerminalPanelManager;
sessionManager: ITerminalSessionManager;
}

View File

@@ -18,8 +18,17 @@
@tab-click="k => tabManager.clickTab(k as string)"
@delete="k => tabManager.deleteTab(k as string)">
<a-tab-pane v-for="tab in tabManager.items"
:key="tab.key"
:title="tab.title" />
:key="tab.key">
<!-- 标题 -->
<template #title>
<span class="tab-title-wrapper">
<span class="tab-title-icon">
<component :is="tab.icon" />
</span>
{{ tab.title }}
</span>
</template>
</a-tab-pane>
</a-tabs>
</div>
<!-- 右侧操作 -->
@@ -45,6 +54,7 @@
import { computed } from 'vue';
import { useTerminalStore } from '@/store';
import IconActions from '../layout/icon-actions.vue';
import DictKeySelector from '@/components/system/dict-key/dict-key-selector.vue';
const { isFullscreen, toggle: toggleFullScreen } = useFullscreen();
const { tabManager } = useTerminalStore();
@@ -118,6 +128,16 @@
}
}
.tab-title-wrapper {
display: flex;
align-items: center;
.tab-title-icon {
font-size: 16px;
margin-right: 4px;
}
}
:deep(.arco-tabs-nav) {
height: 100%;
@@ -151,7 +171,7 @@
margin: 0;
padding: 0;
color: var(--color-header-text-1);
background: var(--color-header-tabs-bg);
background: var(--color-bg-header-tabs);
position: relative;
&:hover {
@@ -177,8 +197,9 @@
.arco-tabs-tab-title {
padding: 11px 18px;
background: var(--color-header-tabs-bg);
font-size: 12px;
background: var(--color-bg-header-tabs);
font-size: 14px;
height: var(--header-height);
display: flex;
align-items: center;
@@ -213,11 +234,11 @@
}
:deep(.arco-tabs-tab-active) {
background: var(--color-header-tabs-bg-hover);
background: var(--color-bg-header-tabs-active);
color: var(--color-header-text-2) !important;
.arco-tabs-tab-title {
background: var(--color-header-tabs-bg-hover);
background: var(--color-bg-header-tabs-active);
}
&:hover::after {

View File

@@ -2,7 +2,8 @@
<div class="terminal-content">
<!-- 内容 tabs -->
<a-tabs v-if="tabManager.active"
v-model:active-key="tabManager.active">
v-model:active-key="tabManager.active"
class="main-tabs">
<a-tab-pane v-for="tab in tabManager.items"
:key="tab.key"
:title="tab.title">
@@ -16,8 +17,8 @@
<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" />
<!-- 终端面板 -->
<terminal-panels-view v-else-if="tab.key === TerminalTabs.TERMINAL_PANEL.key" />
</a-tab-pane>
</a-tabs>
<!-- 承载页推荐 -->
@@ -42,11 +43,11 @@
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 TerminalPanelView from '@/views/host/terminal/components/layout/terminal-panel-view.vue';
import TerminalPanelsView from '@/views/host/terminal/components/layout/terminal-panels-view.vue';
const { preference, tabManager, sessionManager } = useTerminalStore();
// fixme TerminalTabType.TERMINAL
// fixme title逻辑 失焦逻辑
// 监听 tab 修改
watch(() => tabManager.active, (active, before) => {
if (before) {
@@ -58,11 +59,10 @@
}
if (active) {
// 获取 activeTab
const activeTab = tabManager.items.find(s => s.key === active);
const activeTab = tabManager.getCurrentTab();
if (!activeTab) {
return;
}
console.log(activeTab.title);
// 修改标题
document.title = activeTab.title;
// 终端自动聚焦
@@ -78,8 +78,8 @@
// 处理快捷键逻辑
const handlerKeyboard = (event: Event) => {
// 当前页面非 terminal 的时候再触发快捷键 (terminal 有内置逻辑)
if (tabManager.active
&& tabManager.items.find(s => s.key === tabManager.active)?.type === 'TerminalTabType.TERMINAL') {
// fixme panel 无数据继续触发
if (tabManager.getCurrentTab()?.key === TerminalTabs.TERMINAL_PANEL.key) {
return;
}
const e = event as KeyboardEvent;
@@ -140,15 +140,15 @@
height: 100%;
position: relative;
:deep(.arco-tabs) {
:deep(.main-tabs) {
width: 100%;
height: 100%;
.arco-tabs-nav {
> .arco-tabs-nav {
display: none;
}
.arco-tabs-content {
> .arco-tabs-content {
padding: 0;
width: 100%;
height: 100%;

View File

@@ -1,17 +0,0 @@
<template>
</template>
<script lang="ts">
export default {
name: 'terminalPanelView'
};
</script>
<script lang="ts" setup>
</script>
<style lang="less" scoped>
</style>

View File

@@ -0,0 +1,186 @@
<template>
<div class="terminal-panel-container">
<a-tabs v-model:active-key="panel.active"
:editable="true"
:auto-switch="true"
@tab-click="k => panel.clickTab(k as string)"
@delete="k => panel.deleteTab(k as string)">
<a-tab-pane v-for="tab in panel.items"
:key="tab.key">
<!-- 标题 -->
<template #title>
<span class="tab-title-wrapper">
<span class="tab-title-icon">
<component :is="tab.icon" />
</span>
{{ tab.title }}
</span>
</template>
<!-- 终端 -->
<terminal-view :tab="tab" />
</a-tab-pane>
</a-tabs>
</div>
</template>
<script lang="ts">
export default {
name: 'terminalPanel'
};
</script>
<script lang="ts" setup>
import type { ITerminalTabManager } from '../../types/terminal.type';
import TerminalView from '@/views/host/terminal/components/xterm/terminal-view.vue';
const props = defineProps<{
panel: ITerminalTabManager
}>();
// FIXME 全部关闭则关闭
</script>
<style lang="less" scoped>
.terminal-panel-container {
width: 100%;
height: 100%;
}
.tab-title-wrapper {
display: flex;
align-items: center;
.tab-title-icon {
font-size: 16px;
margin-right: 4px;
}
}
:deep(.arco-tabs) {
width: 100%;
height: 100%;
.arco-tabs-content {
padding: 0;
width: 100%;
height: calc(100% - var(--panel-nav-height));
overflow: auto;
&::-webkit-scrollbar {
display: none;
}
}
}
:deep(.arco-tabs-nav) {
height: var(--panel-nav-height);
background: var(--color-bg-panel);
&::before {
display: none;
}
&-tab {
height: 100%;
}
&-ink {
display: none;
}
&-button .arco-icon-hover:hover {
color: var(--color-panel-text-2);
&::before {
background: var(--color-bg-panel-icon-1);
}
}
}
:deep(.arco-tabs-nav-type-line .arco-tabs-tab:hover .arco-tabs-tab-title::before) {
background: transparent;
}
:deep(.arco-tabs-tab) {
height: 100%;
margin: 0;
padding: 0;
color: var(--color-panel-text-1);
background: var(--color-bg-panel-tabs);
position: relative;
&:hover {
color: var(--color-panel-text-2);
transition: .2s;
}
&::after {
content: '';
width: 54px;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
position: absolute;
right: 0;
top: 0;
}
&:hover::after {
background: linear-gradient(270deg, var(--color-panel-gradient-start) 45%, var(--color-panel-gradient-end) 120%);
}
.arco-tabs-tab-title {
padding: 11px 18px;
background: var(--color-bg-panel-tabs);
font-size: 14px;
height: var(--panel-nav-height);
display: flex;
align-items: center;
&::before {
display: none;
}
}
&:hover .arco-tabs-tab-close-btn {
display: unset;
}
&-close-btn {
margin: 0 8px 0 0;
padding: 4px;
border-radius: 4px;
position: absolute;
right: 0;
z-index: 4;
display: none;
color: var(--color-panel-text-2);
&:hover {
transition: .2s;
background: var(--color-bg-panel-icon-1);
}
&::before {
display: none;
}
}
}
:deep(.arco-tabs-tab-active) {
background: var(--color-bg-panel-tabs-active);
color: var(--color-panel-text-2) !important;
.arco-tabs-tab-title {
background: var(--color-bg-panel-tabs-active);
}
&:hover::after {
background: linear-gradient(270deg, var(--color-panel-gradient-start) 45%, var(--color-panel-gradient-end) 120%);
}
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<div class="terminal-panels-container">
<!-- 面板 -->
<terminal-panel v-for="panelIndex in panelManager.panels.length"
:key="panelIndex"
:panel="panelManager.panels[panelIndex - 1]" />
</div>
</template>
<script lang="ts">
export default {
name: 'terminalPanelView'
};
</script>
<script lang="ts" setup>
import { useTerminalStore } from '@/store';
import TerminalPanel from './terminal-panel.vue';
import { onUnmounted } from 'vue';
const { panelManager } = useTerminalStore();
// FIXME 全部关闭则关闭
// 卸载清空
onUnmounted(() => {
panelManager.reset();
});
</script>
<style lang="less" scoped>
.terminal-panels-container {
width: 100%;
height: calc(100vh - var(--header-height));
position: relative;
}
</style>

View File

@@ -21,7 +21,7 @@
@save="savePreference" />
<!-- 系统快捷键 -->
<terminal-shortcut-keys-block title="系统快捷键"
:type="TerminalShortcutType.SYSTEM"
:type="TerminalShortcutType.TAB"
:items="shortcutKeys"
@set-editable="setEditableStatus"
@clear-editable="clearEditableStatus"

View File

@@ -1,10 +1,7 @@
<template>
<div class="terminal-container">
<!-- 头部 -->
<div class="terminal-header"
:style="{
background: preference.theme.headerBackgroundColor
}">
<div class="terminal-header">
<!-- 左侧操作 -->
<div class="terminal-header-left">
<!-- 主机地址 -->
@@ -161,7 +158,7 @@
.terminal-container {
width: 100%;
height: calc(100vh - var(--header-height));
height: calc(100vh - var(--header-height) - var(--panel-nav-height));
position: relative;
}
@@ -172,6 +169,7 @@
display: flex;
align-items: center;
justify-content: space-between;
background: var(--color-bg-panel-bar);
&-left, &-right {
display: flex;

View File

@@ -0,0 +1,41 @@
import type { ITerminalPanelManager, TerminalPanelTabItem } from '../types/terminal.type';
import TerminalTabManager from '../handler/terminal-tab-manager';
// 终端面板管理器实现
export default class TerminalPanelManager<T extends TerminalPanelTabItem = TerminalPanelTabItem> implements ITerminalPanelManager<T> {
// 当前面板
active: number;
// 面板列表
panels: Array<TerminalTabManager<T>>;
constructor() {
this.active = 0;
this.panels = [new TerminalTabManager()];
}
// 获取当前面板
getCurrentPanel(): TerminalTabManager<T> {
return this.panels[this.active];
}
// 设置当前面板
setCurrentPanel(active: number): void {
this.active = active;
};
// 获取面板
getPanel(index: number): TerminalTabManager<T> {
return this.panels[index];
};
// 重置
reset() {
for (let panel of this.panels) {
panel.clear();
}
this.active = 0;
this.panels = [new TerminalTabManager()];
};
}

View File

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

View File

@@ -130,7 +130,6 @@
width: var(--sidebar-width);
height: 100%;
background: var(--color-bg-sidebar);
border-top: 1px solid var(--color-bg-content);
overflow: hidden;
}

View File

@@ -52,6 +52,12 @@ export const ExtraSshAuthType = {
CUSTOM_IDENTITY: 'CUSTOM_IDENTITY',
};
// 终端面板 tab 类型
export const TerminalPanelTabType = {
TERMINAL: 'terminal',
SFTP: 'sftp',
};
// 终端状态
export const TerminalStatus = {
// 连接中
@@ -125,7 +131,7 @@ export const ActionBarItems = [
// 终端快捷键操作类型
export const TerminalShortcutType = {
SYSTEM: 1,
TAB: 1,
TERMINAL: 2
};
@@ -146,19 +152,19 @@ export const TerminalShortcutItems: Array<ShortcutKeyItem> = [
{
item: TerminalShortcutKeys.CHANGE_TO_PREV_TAB,
content: '切换为前一个 tab',
type: TerminalShortcutType.SYSTEM
type: TerminalShortcutType.TAB
}, {
item: TerminalShortcutKeys.CHANGE_TO_NEXT_TAB,
content: '切换为后一个 tab',
type: TerminalShortcutType.SYSTEM
type: TerminalShortcutType.TAB
}, {
item: TerminalShortcutKeys.CLOSE_TAB,
content: '关闭当前 tab',
type: TerminalShortcutType.SYSTEM
type: TerminalShortcutType.TAB
}, {
item: TerminalShortcutKeys.OPEN_NEW_CONNECT_TAB,
content: '打开新建连接 tab',
type: TerminalShortcutType.SYSTEM
type: TerminalShortcutType.TAB
}, {
item: 'openCopyTerminalTab',
content: '复制当前终端 tab',

View File

@@ -12,11 +12,19 @@ import type { HostQueryResponse } from '@/api/asset/host';
export interface TerminalTabItem {
key: string;
title: string;
icon?: string;
icon: string;
[key: string]: unknown;
}
// 终端面板 tab 元素
export interface TerminalPanelTabItem extends TerminalTabItem {
seq: number;
hostId: number;
address: string;
type: string;
}
// sidebar 操作类型
export interface SidebarAction {
icon: string;
@@ -90,20 +98,20 @@ export interface TerminalDomRef {
}
// 终端 tab 管理器定义
export interface ITerminalTabManager {
export interface ITerminalTabManager<T extends TerminalTabItem = TerminalTabItem> {
// 当前 tab
active: string;
// 全部 tab
items: Array<TerminalTabItem>;
items: Array<T>;
// 获取当前 tab
getCurrentTab: () => TerminalTabItem | undefined;
getCurrentTab: () => T | undefined;
// 点击 tab
clickTab: (key: string) => void;
// 删除 tab
deleteTab: (key: string) => void;
// 打开 tab
openTab: (tab: TerminalTabItem) => void;
openTab: (tab: T) => void;
// 切换到前一个 tab
changeToPrevTab: () => void;
// 切换到后一个 tab
@@ -114,6 +122,23 @@ export interface ITerminalTabManager {
clear: () => void;
}
// 终端面板管理器定义
export interface ITerminalPanelManager<T extends TerminalPanelTabItem = TerminalPanelTabItem> {
// 当前面板
active: number;
// 面板列表
panels: Array<ITerminalTabManager<T>>;
// 获取当前面板
getCurrentPanel: () => ITerminalTabManager<T>;
// 设置当前面板
setCurrentPanel: (active: number) => void;
// 获取面板
getPanel: (index: number) => ITerminalTabManager<T>;
// 重置
reset: () => void;
}
// 终端会话管理器定义
export interface ITerminalSessionManager {
// 打开终端会话