🔨 添加 vnc 会话.
This commit is contained in:
@@ -619,5 +619,189 @@ body[terminal-theme='dark'] .arco-modal-container {
|
||||
font-size: 16px;
|
||||
margin: 0 8px 0 4px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// guacd 容器
|
||||
.guacd-container {
|
||||
|
||||
// guacd 视口
|
||||
.guacd-viewport {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
:deep(> div) {
|
||||
position: relative;
|
||||
z-index: 8;
|
||||
}
|
||||
}
|
||||
|
||||
// guacd 状态遮罩
|
||||
.guacd-status-mask {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
backdrop-filter: blur(10px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
// guacd 工具栏
|
||||
.guacd-action-bar {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
z-index: 9998;
|
||||
|
||||
&.top {
|
||||
top: 4px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
&.right {
|
||||
right: 4px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
// 工具栏触发器
|
||||
.action-bar-trigger {
|
||||
display: flex;
|
||||
border-radius: 8px;
|
||||
transition: .3s all;
|
||||
background: var(--color-bg-rdp-toolbar);
|
||||
filter: contrast(50%) brightness(50%);
|
||||
|
||||
&.top {
|
||||
width: 240px;
|
||||
height: 8px;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(2px);
|
||||
}
|
||||
}
|
||||
|
||||
&.right {
|
||||
width: 8px;
|
||||
height: 228px;
|
||||
|
||||
&:hover {
|
||||
transform: translateX(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--color-bg-rdp-toolbar-hover);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// guacd 工具栏
|
||||
@guacd-action-size: 42px;
|
||||
|
||||
.guacd-action-bar-popover {
|
||||
--actions-width: calc(var(--action-count) * @guacd-action-size + (var(--action-count) - 1) * 16px);
|
||||
background: var(--color-bg-2);
|
||||
|
||||
.arco-popover-content {
|
||||
margin-top: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.action-bar-button {
|
||||
width: @guacd-action-size !important;
|
||||
height: @guacd-action-size !important;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.action-bar-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.action-bar-content-footer {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.display-size-label {
|
||||
padding-right: 6px;
|
||||
user-select: none;
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.display-size-input {
|
||||
width: 198px;
|
||||
}
|
||||
|
||||
.action-bar-upload, .action-bar-clipboard {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.combination-key-item {
|
||||
span {
|
||||
display: block;
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
background: var(--color-fill-1);
|
||||
border-radius: 2px;
|
||||
user-select: none;
|
||||
transition: 0.2s ALL;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-fill-2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.guacd-action-bar-popover.top {
|
||||
.arco-popover-content {
|
||||
flex-direction: column;
|
||||
width: var(--actions-width);
|
||||
}
|
||||
|
||||
.action-bar-content {
|
||||
margin-top: 16px;
|
||||
max-height: 224px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.action-bar-upload, .action-bar-clipboard {
|
||||
height: 186px;
|
||||
}
|
||||
}
|
||||
|
||||
.guacd-action-bar-popover.right {
|
||||
.arco-popover-content {
|
||||
flex-direction: row-reverse;
|
||||
height: var(--actions-width);
|
||||
}
|
||||
|
||||
.action-bar-content {
|
||||
margin-right: 16px;
|
||||
width: 344px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.action-bar-upload, .action-bar-clipboard {
|
||||
height: calc(var(--actions-width) - 40px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ import type {
|
||||
TerminalSshDisplaySetting,
|
||||
TerminalSshInteractSetting,
|
||||
TerminalSshPluginsSetting,
|
||||
TerminalState
|
||||
TerminalState,
|
||||
TerminalVncActionBarSetting,
|
||||
TerminalVncGraphSetting
|
||||
} from './types';
|
||||
import type {
|
||||
IDomViewportHandler,
|
||||
@@ -56,8 +58,12 @@ export const TerminalPreferenceItem = {
|
||||
RDP_GRAPH_SETTING: 'rdpGraphSetting',
|
||||
// rdp 操作栏设置
|
||||
RDP_ACTION_BAR_SETTING: 'rdpActionBarSetting',
|
||||
// 会话设置
|
||||
// rdp 会话设置
|
||||
RDP_SESSION_SETTING: 'rdpSessionSetting',
|
||||
// vnc 图形化设置
|
||||
VNC_GRAPH_SETTING: 'vncGraphSetting',
|
||||
// vnc 工具栏设置
|
||||
VNC_ACTION_BAR_SETTING: 'vncActionBarSetting',
|
||||
// 快捷键设置
|
||||
SHORTCUT_SETTING: 'shortcutSetting',
|
||||
};
|
||||
@@ -77,6 +83,8 @@ export default defineStore('terminal', {
|
||||
rdpGraphSetting: {} as TerminalRdpGraphSetting,
|
||||
rdpSessionSetting: {} as TerminalRdpSessionSetting,
|
||||
rdpActionBarSetting: {} as TerminalRdpActionBarSetting,
|
||||
vncGraphSetting: {} as TerminalVncGraphSetting,
|
||||
vncActionBarSetting: {} as TerminalVncActionBarSetting,
|
||||
shortcutSetting: {
|
||||
enabled: false,
|
||||
keys: []
|
||||
|
||||
@@ -23,6 +23,8 @@ export interface TerminalPreference {
|
||||
rdpGraphSetting: TerminalRdpGraphSetting;
|
||||
rdpActionBarSetting: TerminalRdpActionBarSetting;
|
||||
rdpSessionSetting: TerminalRdpSessionSetting;
|
||||
vncGraphSetting: TerminalVncGraphSetting;
|
||||
vncActionBarSetting: TerminalVncActionBarSetting;
|
||||
shortcutSetting: TerminalShortcutSetting;
|
||||
}
|
||||
|
||||
@@ -68,6 +70,7 @@ export interface TerminalSshInteractSetting {
|
||||
wordSeparator: string;
|
||||
terminalEmulationType: string;
|
||||
scrollBackLine: number;
|
||||
replaceBackspace: boolean;
|
||||
}
|
||||
|
||||
// RDP 图形化设置
|
||||
@@ -111,6 +114,31 @@ export interface TerminalRdpSessionSetting {
|
||||
driveMountMode?: string;
|
||||
}
|
||||
|
||||
// VNC 图形化设置
|
||||
export interface TerminalVncGraphSetting {
|
||||
displaySize?: string;
|
||||
displayWidth?: number;
|
||||
displayHeight?: number;
|
||||
colorDepth?: number;
|
||||
forceLossless?: boolean;
|
||||
swapRedBlue?: boolean;
|
||||
cursor?: string;
|
||||
compressLevel?: number;
|
||||
qualityLevel?: number;
|
||||
}
|
||||
|
||||
// VNC 操作栏设置
|
||||
export interface TerminalVncActionBarSetting {
|
||||
position?: string;
|
||||
display?: boolean;
|
||||
combinationKey?: boolean;
|
||||
clipboard?: boolean;
|
||||
disconnect?: boolean;
|
||||
close?: boolean;
|
||||
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
// 终端快捷键设置
|
||||
export interface TerminalShortcutSetting {
|
||||
enabled: boolean;
|
||||
|
||||
@@ -31,45 +31,21 @@
|
||||
<icon-right />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<!-- 打开 SSH -->
|
||||
<a-tooltip v-if="handler.host?.types?.includes(TerminalSessionTypes.SSH.type)"
|
||||
position="top"
|
||||
:mini="true"
|
||||
:auto-fix-position="false"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="打开 SSH">
|
||||
<a-button class="combined-handler-action icon-button"
|
||||
@click="openSession(handler.host as any, TerminalSessionTypes.SSH)">
|
||||
<icon-thunderbolt />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<!-- 打开 SFTP -->
|
||||
<a-tooltip v-if="handler.host?.types?.includes(TerminalSessionTypes.SSH.type)"
|
||||
position="top"
|
||||
:mini="true"
|
||||
:auto-fix-position="false"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="打开 SFTP">
|
||||
<a-button class="combined-handler-action icon-button"
|
||||
@click="openSession(handler.host as any, TerminalSessionTypes.SFTP)">
|
||||
<icon-folder />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<!-- 打开 RDP -->
|
||||
<a-tooltip v-if="handler.host?.types?.includes(TerminalSessionTypes.RDP.type)"
|
||||
position="top"
|
||||
:mini="true"
|
||||
:auto-fix-position="false"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="打开 RDP">
|
||||
<a-button class="combined-handler-action icon-button"
|
||||
@click="openSession(handler.host as any, TerminalSessionTypes.RDP)">
|
||||
<icon-computer />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<!-- 打开会话 -->
|
||||
<template v-for="type in TerminalSessionTypes">
|
||||
<template v-if="handler.host?.types?.includes(type.protocol)">
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
:auto-fix-position="false"
|
||||
:content="`打开 ${type.type}`"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content">
|
||||
<a-button class="combined-handler-action icon-button" @click="openSession(handler.host as any, type)">
|
||||
<component :is="type.connectIcon || type.icon" />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,45 +40,21 @@
|
||||
</div>
|
||||
<!-- 操作 -->
|
||||
<div class="host-item-actions">
|
||||
<!-- 打开 SSH -->
|
||||
<a-tooltip v-if="item.types?.includes(TerminalSessionTypes.SSH.type)"
|
||||
position="top"
|
||||
:mini="true"
|
||||
:auto-fix-position="false"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="打开 SSH">
|
||||
<a-button class="host-item-action icon-button"
|
||||
@click="clickHost(item, TerminalSessionTypes.SSH)">
|
||||
<icon-thunderbolt />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<!-- 打开 SFTP -->
|
||||
<a-tooltip v-if="item.types?.includes(TerminalSessionTypes.SSH.type)"
|
||||
position="top"
|
||||
:mini="true"
|
||||
:auto-fix-position="false"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="打开 SFTP">
|
||||
<a-button class="host-item-action icon-button"
|
||||
@click="clickHost(item, TerminalSessionTypes.SFTP)">
|
||||
<icon-folder />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<!-- 打开 RDP -->
|
||||
<a-tooltip v-if="item.types?.includes(TerminalSessionTypes.RDP.type)"
|
||||
position="top"
|
||||
:mini="true"
|
||||
:auto-fix-position="false"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="打开 RDP">
|
||||
<a-button class="host-item-action icon-button"
|
||||
@click="clickHost(item, TerminalSessionTypes.RDP)">
|
||||
<icon-computer />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<!-- 打开会话 -->
|
||||
<template v-for="type in TerminalSessionTypes">
|
||||
<template v-if="item.types?.includes(type.protocol)">
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
:auto-fix-position="false"
|
||||
:content="`打开 ${type.type}`"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content">
|
||||
<a-button class="host-item-action icon-button" @click="openHostSession(item, type)">
|
||||
<component :is="type.connectIcon || type.icon" />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
@@ -144,7 +120,7 @@
|
||||
defineExpose({ open });
|
||||
|
||||
// 打开终端
|
||||
const clickHost = (item: HostQueryResponse, tab: TerminalSessionType) => {
|
||||
const openHostSession = (item: HostQueryResponse, tab: TerminalSessionType) => {
|
||||
openSession(item, tab, panelIndex.value);
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
@@ -111,48 +111,23 @@
|
||||
</div>
|
||||
<!-- 操作 -->
|
||||
<div class="host-item-right-actions">
|
||||
<!-- 打开 SSH -->
|
||||
<a-tooltip v-if="item.types?.includes(TerminalSessionTypes.SSH.type)"
|
||||
position="top"
|
||||
:mini="true"
|
||||
:auto-fix-position="false"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="打开 SSH">
|
||||
<div class="terminal-sidebar-icon-wrapper">
|
||||
<a-button class="terminal-sidebar-icon" @click="openSession(item, TerminalSessionTypes.SSH)">
|
||||
<icon-thunderbolt />
|
||||
</a-button>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<!-- 打开 SFTP -->
|
||||
<a-tooltip v-if="item.types?.includes(TerminalSessionTypes.SSH.type)"
|
||||
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">
|
||||
<a-button class="terminal-sidebar-icon" @click="openSession(item, TerminalSessionTypes.SFTP)">
|
||||
<icon-folder />
|
||||
</a-button>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<!-- 打开 RDP -->
|
||||
<a-tooltip v-if="item.types?.includes(TerminalSessionTypes.RDP.type)"
|
||||
position="top"
|
||||
:mini="true"
|
||||
:auto-fix-position="false"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="打开 RDP">
|
||||
<div class="terminal-sidebar-icon-wrapper">
|
||||
<a-button class="terminal-sidebar-icon" @click="openSession(item, TerminalSessionTypes.RDP)">
|
||||
<icon-computer />
|
||||
</a-button>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<!-- 打开会话 -->
|
||||
<template v-for="type in TerminalSessionTypes">
|
||||
<template v-if="item.types?.includes(type.protocol)">
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
:auto-fix-position="false"
|
||||
:content="`打开 ${type.type}`"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content">
|
||||
<div class="terminal-sidebar-icon-wrapper">
|
||||
<a-button class="terminal-sidebar-icon" @click="openSession(item, type)">
|
||||
<component :is="type.connectIcon || type.icon" />
|
||||
</a-button>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 主机设置 -->
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { SelectOptionData } from '@arco-design/web-vue';
|
||||
import { onBeforeMount, ref } from 'vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { NewConnectionType, newConnectionTypeKey } from '../../types/const';
|
||||
import { useDictStore, useTerminalStore } from '@/store';
|
||||
import { TerminalPreferenceItem } from '@/store/modules/terminal';
|
||||
@@ -86,7 +86,7 @@
|
||||
const filterOptions = ref<Array<SelectOptionData>>([]);
|
||||
|
||||
// 初始化过滤器项
|
||||
onBeforeMount(() => {
|
||||
onMounted(() => {
|
||||
filterOptions.value = getAuthorizedHostOptions(hosts.hostList);
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
dot />
|
||||
<!-- 会话关闭 -->
|
||||
<a-card v-if="session.state.connectStatus === TerminalStatus.CLOSED"
|
||||
class="rdp-status-wrapper"
|
||||
class="status-wrapper"
|
||||
title="会话已关闭">
|
||||
<!-- 错误信息 -->
|
||||
<a-descriptions size="large"
|
||||
@@ -51,12 +51,12 @@
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'rdpStatus'
|
||||
name: 'guacdStatus'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { IRdpSession } from '@/views/terminal/interfaces';
|
||||
import type { IGuacdSession } from '@/views/terminal/interfaces';
|
||||
import { TerminalStatus, TerminalCloseCode, TerminalMessages } from '@/views/terminal/types/const';
|
||||
import { copy } from '@/hooks/copy';
|
||||
import { dateFormat } from '@/utils';
|
||||
@@ -64,7 +64,7 @@
|
||||
import useVisible from '@/hooks/visible';
|
||||
|
||||
const props = defineProps<{
|
||||
session: IRdpSession;
|
||||
session: IGuacdSession;
|
||||
}>();
|
||||
|
||||
const { visible, setVisible } = useVisible(true);
|
||||
@@ -73,7 +73,7 @@
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.rdp-status-wrapper {
|
||||
.status-wrapper {
|
||||
width: 520px;
|
||||
min-height: 180px;
|
||||
border-radius: 8px;
|
||||
@@ -4,27 +4,27 @@
|
||||
<a-popover v-model:popup-visible="visible"
|
||||
:title="undefined"
|
||||
trigger="click"
|
||||
:content-class="['tool-bar-popover', direction]"
|
||||
:content-style="{ '--action-count': Math.max(actions.length, 6) }"
|
||||
:position="direction === 'right' ? 'left' : 'bottom'"
|
||||
:content-class="['guacd-action-bar-popover', direction]"
|
||||
:content-style="{ '--action-count': Math.max(actions.length, direction === ActionBarPosition.RIGHT ? 5 : 6) }"
|
||||
:position="direction === ActionBarPosition.RIGHT ? 'left' : 'bottom'"
|
||||
:show-arrow="false"
|
||||
:auto-fix-position="false">
|
||||
<!-- 触发器 -->
|
||||
<div class="tool-bar" :class="[direction === 'right' ? 'right' : 'top']" />
|
||||
<div class="action-bar-trigger" :class="[direction === ActionBarPosition.RIGHT ? 'right' : 'top']" />
|
||||
<!-- 工具内容 -->
|
||||
<template #content>
|
||||
<!-- 按钮 -->
|
||||
<a-space class="tool-bar-actions"
|
||||
:direction="direction === 'right' ? 'vertical' : 'horizontal'"
|
||||
<a-space class="action-bar-actions"
|
||||
:direction="direction === ActionBarPosition.RIGHT ? 'vertical' : 'horizontal'"
|
||||
:size="16">
|
||||
<div v-for="action in actions" :key="action.item">
|
||||
<a-tooltip :mini="true"
|
||||
:auto-fix-position="false"
|
||||
:position="direction === 'right' ? 'left' : 'bottom'"
|
||||
:position="direction === ActionBarPosition.RIGHT ? 'left' : 'bottom'"
|
||||
content-class="terminal-tooltip-content"
|
||||
:show-arrow="false"
|
||||
:content="action.content">
|
||||
<a-button class="tool-bar-button"
|
||||
<a-button class="action-bar-button"
|
||||
:disabled="action.disabled"
|
||||
:type="action.active ? 'primary' : 'secondary'"
|
||||
@click="toggleClickAction(action.item)">
|
||||
@@ -36,7 +36,7 @@
|
||||
</div>
|
||||
</a-space>
|
||||
<!-- 显示设置 -->
|
||||
<div v-if="current === RdpActionItemKeys.DISPLAY" class="tool-bar-content">
|
||||
<div v-if="current === GuacdActionItemKeys.DISPLAY" class="action-bar-content">
|
||||
<!-- 分辨率 -->
|
||||
<a-space>
|
||||
<span class="display-size-label">分辨率</span>
|
||||
@@ -47,7 +47,7 @@
|
||||
allow-create />
|
||||
</a-space>
|
||||
<!-- 按钮 -->
|
||||
<a-space class="tool-bar-content-footer">
|
||||
<a-space class="action-bar-content-footer">
|
||||
<a-button type="primary"
|
||||
size="small"
|
||||
@click="fitOnce">
|
||||
@@ -61,9 +61,9 @@
|
||||
</a-space>
|
||||
</div>
|
||||
<!-- 组合键 -->
|
||||
<div v-else-if="current === RdpActionItemKeys.COMBINATION_KEY" class="tool-bar-content">
|
||||
<div v-else-if="current === GuacdActionItemKeys.COMBINATION_KEY" class="action-bar-content">
|
||||
<a-row :gutter="[12, 12]" wrap>
|
||||
<a-col v-for="item in RdpCombinationKeyItems"
|
||||
<a-col v-for="item in GuacdCombinationKeyItems"
|
||||
:key="item.name"
|
||||
:span="12"
|
||||
class="combination-key-item"
|
||||
@@ -73,14 +73,14 @@
|
||||
</a-row>
|
||||
</div>
|
||||
<!-- 剪切板 -->
|
||||
<div v-else-if="current === RdpActionItemKeys.CLIPBOARD" class="tool-bar-content">
|
||||
<a-textarea class="tool-bar-clipboard"
|
||||
<div v-else-if="current === GuacdActionItemKeys.CLIPBOARD" class="action-bar-content">
|
||||
<a-textarea class="action-bar-clipboard"
|
||||
v-model="clipboardData"
|
||||
:ref="setAutoFocus"
|
||||
placeholder="远程剪切板"
|
||||
allow-clear />
|
||||
<!-- 按钮 -->
|
||||
<a-space class="tool-bar-content-footer">
|
||||
<a-space class="action-bar-content-footer">
|
||||
<a-button size="small" @click="clearClipboardData">
|
||||
清空
|
||||
</a-button>
|
||||
@@ -93,8 +93,8 @@
|
||||
</a-space>
|
||||
</div>
|
||||
<!-- 文件上传 -->
|
||||
<div v-else-if="current === RdpActionItemKeys.UPLOAD" class="tool-bar-content">
|
||||
<a-upload class="tool-bar-upload"
|
||||
<div v-else-if="current === GuacdActionItemKeys.UPLOAD" class="action-bar-content">
|
||||
<a-upload class="action-bar-upload"
|
||||
v-model:file-list="fileList"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
@@ -102,7 +102,7 @@
|
||||
:tip="fileList.length ? fileList[0]?.name : '选择文件后会自动上传至驱动目录'"
|
||||
@change="onSelectFile" />
|
||||
<!-- 按钮 -->
|
||||
<a-space class="tool-bar-content-footer">
|
||||
<a-space class="action-bar-content-footer">
|
||||
<a-button size="small" @click="clearUploadFile">
|
||||
清空
|
||||
</a-button>
|
||||
@@ -130,18 +130,18 @@
|
||||
import type { IRdpSession } from '@/views/terminal/interfaces';
|
||||
import {
|
||||
TerminalStatus,
|
||||
RdpCombinationKeyItems,
|
||||
RdpActionItemKeys,
|
||||
GuacdCombinationKeyItems,
|
||||
GuacdActionItemKeys,
|
||||
RdpActionBarItems,
|
||||
screenResolutionKey,
|
||||
fitDisplayValue
|
||||
fitDisplayValue, ActionBarPosition
|
||||
} from '@/views/terminal/types/const';
|
||||
import { computed, ref, watch, onMounted } from 'vue';
|
||||
import { setAutoFocus } from '@/utils/dom';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { readText } from '@/hooks/copy';
|
||||
import { useTerminalStore, useDictStore } from '@/store';
|
||||
import { getDisplaySize } from '@/views/terminal/types/utils';
|
||||
import useGuacdActionBar from '@/views/terminal/types/use-guacd-action-bar';
|
||||
import useVisible from '@/hooks/visible';
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -153,9 +153,21 @@
|
||||
const { toOptions, getDictValue } = useDictStore();
|
||||
const { visible, setVisible } = useVisible();
|
||||
|
||||
const {
|
||||
displaySize,
|
||||
clipboardData,
|
||||
fitOnce,
|
||||
setDisplaySize,
|
||||
triggerCombinationKey,
|
||||
sendClipboardData,
|
||||
clearClipboardData,
|
||||
disconnect,
|
||||
} = useGuacdActionBar({
|
||||
session: props.session,
|
||||
setVisible,
|
||||
});
|
||||
|
||||
const current = ref('');
|
||||
const displaySize = ref(fitDisplayValue);
|
||||
const clipboardData = ref('');
|
||||
const fileList = ref<FileItem[]>([]);
|
||||
|
||||
const actions = computed(() => {
|
||||
@@ -166,7 +178,7 @@
|
||||
return {
|
||||
...item,
|
||||
active: current.value === key,
|
||||
disabled: (key === RdpActionItemKeys.DISPLAY || key === RdpActionItemKeys.SAVE_RDP || RdpActionItemKeys.DISCONNECT || key === RdpActionItemKeys.CLOSE) ? false : !props.session.isWriteable(),
|
||||
disabled: (key === GuacdActionItemKeys.DISPLAY || key === GuacdActionItemKeys.SAVE_RDP || GuacdActionItemKeys.DISCONNECT || key === GuacdActionItemKeys.CLOSE) ? false : !props.session.isWriteable(),
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -182,70 +194,39 @@
|
||||
|
||||
// 触发 action
|
||||
const toggleClickAction = (key: string) => {
|
||||
if (key === RdpActionItemKeys.DISPLAY) {
|
||||
if (key === GuacdActionItemKeys.DISPLAY) {
|
||||
// 显示设置
|
||||
current.value = RdpActionItemKeys.DISPLAY;
|
||||
current.value = GuacdActionItemKeys.DISPLAY;
|
||||
if (props.session.displayHandler?.autoFit) {
|
||||
displaySize.value = fitDisplayValue;
|
||||
} else {
|
||||
displaySize.value = `${props.session.displayHandler?.displayWidth || 0}x${props.session.displayHandler?.displayHeight || 0}`;
|
||||
}
|
||||
} else if (key === RdpActionItemKeys.COMBINATION_KEY) {
|
||||
} else if (key === GuacdActionItemKeys.COMBINATION_KEY) {
|
||||
// 组合键
|
||||
current.value = RdpActionItemKeys.COMBINATION_KEY;
|
||||
} else if (key === RdpActionItemKeys.CLIPBOARD) {
|
||||
current.value = GuacdActionItemKeys.COMBINATION_KEY;
|
||||
} else if (key === GuacdActionItemKeys.CLIPBOARD) {
|
||||
// 剪切板
|
||||
current.value = RdpActionItemKeys.CLIPBOARD;
|
||||
current.value = GuacdActionItemKeys.CLIPBOARD;
|
||||
readText(false)
|
||||
.then(s => clipboardData.value = s)
|
||||
.catch(() => clipboardData.value = '');
|
||||
} else if (key === RdpActionItemKeys.UPLOAD) {
|
||||
} else if (key === GuacdActionItemKeys.UPLOAD) {
|
||||
// 文件上传
|
||||
current.value = RdpActionItemKeys.UPLOAD;
|
||||
current.value = GuacdActionItemKeys.UPLOAD;
|
||||
fileList.value = [];
|
||||
} else if (key === RdpActionItemKeys.SAVE_RDP) {
|
||||
} else if (key === GuacdActionItemKeys.SAVE_RDP) {
|
||||
// 保存 rdp 文件
|
||||
saveRdpFile();
|
||||
} else if (key === RdpActionItemKeys.DISCONNECT) {
|
||||
} else if (key === GuacdActionItemKeys.DISCONNECT) {
|
||||
// 断开连接
|
||||
disconnect();
|
||||
} else if (key === RdpActionItemKeys.CLOSE) {
|
||||
} else if (key === GuacdActionItemKeys.CLOSE) {
|
||||
// 关闭工具栏
|
||||
setVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 临时自适应
|
||||
const fitOnce = () => {
|
||||
props.session.displayHandler?.fit(true);
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
// 设置显示大小
|
||||
const setDisplaySize = () => {
|
||||
const displayHandler = props.session.displayHandler;
|
||||
if (!displayHandler) {
|
||||
return;
|
||||
}
|
||||
if (displaySize.value === fitDisplayValue) {
|
||||
// 设置自适应
|
||||
displayHandler.autoFit = true;
|
||||
displayHandler.fit(true);
|
||||
} else {
|
||||
try {
|
||||
// 获取大小
|
||||
const [width, height] = getDisplaySize(displaySize.value, true);
|
||||
// 取消自适应
|
||||
displayHandler.autoFit = false;
|
||||
// 设置大小
|
||||
displayHandler.resize(width, height);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
// 保存 rdp 文件
|
||||
const saveRdpFile = () => {
|
||||
const address = props.session.info.address;
|
||||
@@ -255,24 +236,6 @@
|
||||
saveAs(new Blob([content], { type: 'text/plain;charset=utf-8' }), `${address}.rdp`);
|
||||
};
|
||||
|
||||
// 触发组合键
|
||||
const triggerCombinationKey = (keys: Array<number>) => {
|
||||
props.session.sendKeys(keys);
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
// 发送剪切板数据
|
||||
const sendClipboardData = () => {
|
||||
// 粘贴
|
||||
props.session.paste(clipboardData.value);
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
// 清空剪切板数据
|
||||
const clearClipboardData = () => {
|
||||
clipboardData.value = '';
|
||||
};
|
||||
|
||||
// 上传文件
|
||||
const uploadFile = () => {
|
||||
const file = fileList.value[0].file as File;
|
||||
@@ -293,12 +256,6 @@
|
||||
fileList.value = [];
|
||||
};
|
||||
|
||||
// 关闭会话
|
||||
const disconnect = () => {
|
||||
props.session.disconnect();
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
// 设置选中
|
||||
onMounted(() => {
|
||||
if (actions.value?.length) {
|
||||
@@ -309,127 +266,4 @@
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tool-bar {
|
||||
display: flex;
|
||||
border-radius: 8px;
|
||||
transition: .3s all;
|
||||
background: var(--color-bg-rdp-toolbar);
|
||||
filter: contrast(50%) brightness(50%);
|
||||
|
||||
&.top {
|
||||
width: 240px;
|
||||
height: 8px;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(2px);
|
||||
}
|
||||
}
|
||||
|
||||
&.right {
|
||||
width: 8px;
|
||||
height: 228px;
|
||||
|
||||
&:hover {
|
||||
transform: translateX(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--color-bg-rdp-toolbar-hover);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
@action-size: 42px;
|
||||
|
||||
.tool-bar-popover.top {
|
||||
.arco-popover-content {
|
||||
flex-direction: column;
|
||||
width: var(--actions-width);
|
||||
}
|
||||
|
||||
.tool-bar-content {
|
||||
margin-top: 16px;
|
||||
max-height: var(--actions-width);
|
||||
}
|
||||
|
||||
.tool-bar-upload, .tool-bar-clipboard {
|
||||
height: 186px;
|
||||
}
|
||||
}
|
||||
|
||||
.tool-bar-popover.right {
|
||||
.arco-popover-content {
|
||||
flex-direction: row-reverse;
|
||||
max-height: var(--actions-width);
|
||||
}
|
||||
|
||||
.tool-bar-content {
|
||||
margin-right: 16px;
|
||||
width: var(--actions-width);
|
||||
}
|
||||
|
||||
.tool-bar-upload, .tool-bar-clipboard {
|
||||
height: calc(var(--actions-width) - 40px);
|
||||
}
|
||||
}
|
||||
|
||||
.tool-bar-popover {
|
||||
--actions-width: calc(var(--action-count) * @action-size + (var(--action-count) - 1) * 16px);
|
||||
background: var(--color-bg-2);
|
||||
|
||||
.arco-popover-content {
|
||||
margin-top: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tool-bar-button {
|
||||
width: @action-size !important;
|
||||
height: @action-size !important;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.tool-bar-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tool-bar-content-footer {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.display-size-label {
|
||||
padding-right: 6px;
|
||||
user-select: none;
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.display-size-input {
|
||||
width: 198px;
|
||||
}
|
||||
|
||||
.tool-bar-upload, .tool-bar-clipboard {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.combination-key-item {
|
||||
span {
|
||||
display: block;
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
background: var(--color-fill-1);
|
||||
border-radius: 2px;
|
||||
user-select: none;
|
||||
transition: 0.2s ALL;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-fill-2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div class="rdp-container">
|
||||
<div class="guacd-container">
|
||||
<!-- 状态 -->
|
||||
<rdp-status v-if="session"
|
||||
class="rdp-status-mask"
|
||||
:session="session" />
|
||||
<guacd-status v-if="session"
|
||||
class="guacd-status-mask"
|
||||
:session="session" />
|
||||
<!-- 工具栏 -->
|
||||
<rdp-action-bar v-if="session"
|
||||
class="rdp-action-bar"
|
||||
:class="[toolbarDirection === 'right' ? 'right' : 'top']"
|
||||
class="guacd-action-bar"
|
||||
:class="[actionBarDirection === 'right' ? 'right' : 'top']"
|
||||
:session="session"
|
||||
:direction="toolbarDirection" />
|
||||
<!-- rdp 视口 -->
|
||||
<div class="rdp-viewport" ref="viewport" />
|
||||
:direction="actionBarDirection" />
|
||||
<!-- 视口 -->
|
||||
<div class="guacd-viewport" ref="viewport" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -25,8 +25,9 @@
|
||||
import type { TerminalSessionTabItem, IRdpSession } from '@/views/terminal/interfaces';
|
||||
import { onMounted, ref, onUnmounted } from 'vue';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import RdpStatus from './rdp-status.vue';
|
||||
import { ActionBarPosition } from '@/views/terminal/types/const';
|
||||
import RdpActionBar from './rdp-action-bar.vue';
|
||||
import GuacdStatus from '../guacd/guacd-status.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
item: TerminalSessionTabItem;
|
||||
@@ -34,7 +35,7 @@
|
||||
|
||||
const { preference, sessionManager } = useTerminalStore();
|
||||
|
||||
const toolbarDirection = ref('top');
|
||||
const actionBarDirection = ref(ActionBarPosition.TOP);
|
||||
|
||||
const viewport = ref();
|
||||
const session = ref<IRdpSession>();
|
||||
@@ -42,7 +43,7 @@
|
||||
// 初始化会话
|
||||
onMounted(async () => {
|
||||
// 工具栏方向
|
||||
toolbarDirection.value = preference.rdpActionBarSetting.position || 'top';
|
||||
actionBarDirection.value = preference.rdpActionBarSetting.position || ActionBarPosition.TOP;
|
||||
// 创建终端会话
|
||||
session.value = sessionManager.createSession<IRdpSession>(props.item);
|
||||
// 打开终端会话
|
||||
@@ -60,53 +61,5 @@
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.rdp-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.rdp-viewport {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
:deep(> div) {
|
||||
position: relative;
|
||||
z-index: 8;
|
||||
}
|
||||
}
|
||||
|
||||
.rdp-status-mask {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
backdrop-filter: blur(10px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.rdp-action-bar {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
z-index: 9998;
|
||||
|
||||
&.top {
|
||||
top: 4px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
&.right {
|
||||
right: 4px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="sftp-container">
|
||||
<div>
|
||||
<a-split class="split-view"
|
||||
v-model:size="splitSize"
|
||||
:min="0.3"
|
||||
@@ -69,7 +69,7 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { ISftpSession, SftpFile, TerminalSessionTabItem } from '@/views/terminal/interfaces';
|
||||
import type { ISftpSession, ISftpSessionHandler, SftpFile, TerminalSessionTabItem } from '@/views/terminal/interfaces';
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
@@ -297,8 +297,7 @@
|
||||
}
|
||||
// 创建终端会话
|
||||
session.value = sessionManager.createSession<ISftpSession>(props.item);
|
||||
// 打开终端会话
|
||||
await sessionManager.openSftp(props.item, {
|
||||
const handler: ISftpSessionHandler = {
|
||||
setLoading: setTableLoading,
|
||||
connectCallback,
|
||||
onClose,
|
||||
@@ -311,7 +310,8 @@
|
||||
resolveDownloadFlatDirectory,
|
||||
resolveSftpGetContent,
|
||||
resolveSftpSetContent,
|
||||
});
|
||||
};
|
||||
await sessionManager.openSftp(props.item, handler);
|
||||
});
|
||||
|
||||
// 关闭会话
|
||||
@@ -326,15 +326,9 @@
|
||||
<style lang="less" scoped>
|
||||
@sftp-table-header-height: 32px + 8px;
|
||||
|
||||
.sftp-container {
|
||||
.split-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
.split-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.sftp-table-container, .sftp-editor-container {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="ssh-container">
|
||||
<div>
|
||||
<!-- 头部 -->
|
||||
<ssh-header :session="session" @handle="doTerminalHandle" />
|
||||
<!-- 终端右键菜单 -->
|
||||
@@ -94,12 +94,6 @@
|
||||
<style lang="less" scoped>
|
||||
@ssh-header-height: 36px;
|
||||
|
||||
.ssh-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ssh-wrapper {
|
||||
width: 100%;
|
||||
height: calc(100% - @ssh-header-height);
|
||||
|
||||
@@ -79,8 +79,8 @@ export interface ISftpSessionHandler {
|
||||
resolveSftpSetContent: (result: string, msg: string, token: string) => void;
|
||||
}
|
||||
|
||||
// rdp 会话视图处理器定义
|
||||
export interface IRdpSessionDisplayHandler {
|
||||
// guacd 会话视图处理器定义
|
||||
export interface IGuacdSessionDisplayHandler {
|
||||
displayWidth: number;
|
||||
displayHeight: number;
|
||||
displayDpi: number;
|
||||
@@ -101,8 +101,8 @@ export interface IRdpSessionDisplayHandler {
|
||||
setDisplaySize: (width: number, height: number) => void;
|
||||
}
|
||||
|
||||
// rdp 会话剪切板处理器定义
|
||||
export interface IRdpSessionClipboardHandler {
|
||||
// guacd 会话剪切板处理器定义
|
||||
export interface IGuacdSessionClipboardHandler {
|
||||
// 发送数据到远程剪切板
|
||||
sendDataToRemoteClipboard: (data: string | File | Blob) => void;
|
||||
// 接收远程剪切板数据
|
||||
|
||||
@@ -3,8 +3,8 @@ import type { Terminal } from '@xterm/xterm';
|
||||
import type { ISearchOptions } from '@xterm/addon-search';
|
||||
import type Guacamole from 'guacamole-common-js';
|
||||
import type {
|
||||
IRdpSessionClipboardHandler,
|
||||
IRdpSessionDisplayHandler,
|
||||
IGuacdSessionClipboardHandler,
|
||||
IGuacdSessionDisplayHandler,
|
||||
ISftpSessionHandler,
|
||||
ISshSessionHandler,
|
||||
TerminalSessionTabItem
|
||||
@@ -108,6 +108,9 @@ export interface ITerminalSession<State extends ReactiveSessionState = ReactiveS
|
||||
setConnected: () => void;
|
||||
// 设置已关闭
|
||||
setClosed: () => void;
|
||||
|
||||
// 是否可写
|
||||
isWriteable: () => boolean;
|
||||
}
|
||||
|
||||
// SSH 会话定义
|
||||
@@ -160,29 +163,32 @@ export interface ISftpSession extends ITerminalSession {
|
||||
export interface IGuacdSession extends ITerminalSession<GuacdReactiveSessionStatus>, IDomViewportHandler {
|
||||
// guacd 客户端
|
||||
client: Guacamole.Client;
|
||||
// FIXME VNC 可以再抽象
|
||||
// 会话配置
|
||||
config: GuacdInitConfig;
|
||||
// 视图处理器
|
||||
displayHandler: IGuacdSessionDisplayHandler;
|
||||
// 剪切板处理器
|
||||
clipboardHandler: IGuacdSessionClipboardHandler;
|
||||
|
||||
// 初始化
|
||||
init: (config: GuacdInitConfig) => Promise<void>;
|
||||
|
||||
// 发送键
|
||||
sendKeys: (keys: Array<number>) => void;
|
||||
// 粘贴
|
||||
paste: (data: string) => void;
|
||||
}
|
||||
|
||||
// RDP 会话定义
|
||||
export interface IRdpSession extends IGuacdSession {
|
||||
fileSystemName: string;
|
||||
// 会话配置
|
||||
config: GuacdInitConfig;
|
||||
// 视图处理器
|
||||
displayHandler: IRdpSessionDisplayHandler;
|
||||
// 剪切板处理器
|
||||
clipboardHandler: IRdpSessionClipboardHandler;
|
||||
|
||||
// 初始化
|
||||
init: (config: GuacdInitConfig) => Promise<void>;
|
||||
// 文件系统事件
|
||||
onFileSystemEvent: (event: Record<string, any>) => void;
|
||||
// 发送键
|
||||
sendKeys: (keys: Array<number>) => void;
|
||||
// 粘贴
|
||||
paste: (data: string) => void;
|
||||
// 是否可写
|
||||
isWriteable: () => boolean;
|
||||
}
|
||||
|
||||
// VNC 会话定义
|
||||
export interface IVncSession extends IGuacdSession {
|
||||
}
|
||||
|
||||
// sftp 文件
|
||||
@@ -210,6 +216,8 @@ export interface ITerminalSessionManager {
|
||||
openSftp: (item: TerminalSessionTabItem, handler: ISftpSessionHandler) => Promise<void>;
|
||||
// 打开 rdp 会话
|
||||
openRdp: (item: TerminalSessionTabItem, config: GuacdInitConfig) => Promise<void>;
|
||||
// 打开 vnc 会话
|
||||
openVnc: (item: TerminalSessionTabItem, config: GuacdInitConfig) => Promise<void>;
|
||||
// 重新打开会话
|
||||
reOpenSession: (sessionKey: string) => Promise<void>;
|
||||
// 创建终端会话
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Reactive } from 'vue';
|
||||
import { reactive } from 'vue';
|
||||
import type { ITerminalChannel, ITerminalSession, ReactiveSessionState, SessionHostInfo, TerminalSessionTabItem } from '@/views/terminal/interfaces';
|
||||
import type { ITerminalChannel, ITerminalSession, ReactiveSessionState, TerminalSessionTabItem, SessionHostInfo } from '@/views/terminal/interfaces';
|
||||
import { TerminalStatus } from '@/views/terminal/types/const';
|
||||
|
||||
// 会话基类
|
||||
@@ -86,4 +86,9 @@ export default abstract class BaseSession<State extends ReactiveSessionState, Ch
|
||||
this.state.connectStatus = TerminalStatus.CLOSED;
|
||||
}
|
||||
|
||||
// 是否可写
|
||||
isWriteable(): boolean {
|
||||
return this.state.connected && this.state.canWrite;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { ref } from 'vue';
|
||||
import type { IGuacdSession } from '../interfaces';
|
||||
import { fitDisplayValue } from './const';
|
||||
import { getDisplaySize } from './utils';
|
||||
|
||||
// guacd 工具栏配置
|
||||
export interface UseGuacdActionBarOptions {
|
||||
session: IGuacdSession;
|
||||
setVisible: (visible: boolean) => void;
|
||||
}
|
||||
|
||||
// 使用主机配置表单
|
||||
export default function useGuacdActionBar(options: UseGuacdActionBarOptions) {
|
||||
const { session, setVisible } = options;
|
||||
|
||||
const displaySize = ref(fitDisplayValue);
|
||||
const clipboardData = ref('');
|
||||
|
||||
// 临时自适应
|
||||
const fitOnce = () => {
|
||||
session.displayHandler?.fit(true);
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
// 设置显示大小
|
||||
const setDisplaySize = () => {
|
||||
const displayHandler = session.displayHandler;
|
||||
if (!displayHandler) {
|
||||
return;
|
||||
}
|
||||
if (displaySize.value === fitDisplayValue) {
|
||||
// 设置自适应
|
||||
displayHandler.autoFit = true;
|
||||
displayHandler.fit(true);
|
||||
} else {
|
||||
try {
|
||||
// 获取大小
|
||||
const [width, height] = getDisplaySize(displaySize.value, true);
|
||||
// 取消自适应
|
||||
displayHandler.autoFit = false;
|
||||
// 设置大小
|
||||
displayHandler.resize(width, height);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
// 触发组合键
|
||||
const triggerCombinationKey = (keys: Array<number>) => {
|
||||
session.sendKeys(keys);
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
// 发送剪切板数据
|
||||
const sendClipboardData = () => {
|
||||
// 粘贴
|
||||
session.paste(clipboardData.value);
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
// 清空剪切板数据
|
||||
const clearClipboardData = () => {
|
||||
clipboardData.value = '';
|
||||
};
|
||||
|
||||
// 关闭会话
|
||||
const disconnect = () => {
|
||||
session.disconnect();
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
return {
|
||||
displaySize,
|
||||
clipboardData,
|
||||
fitOnce,
|
||||
setDisplaySize,
|
||||
triggerCombinationKey,
|
||||
sendClipboardData,
|
||||
clearClipboardData,
|
||||
disconnect,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user