🔨 优化会话工具栏.
This commit is contained in:
@@ -110,7 +110,10 @@ public class SessionStores {
|
||||
}
|
||||
}
|
||||
// 超时时间
|
||||
session.timeout(config.getTimeout());
|
||||
Integer timeout = config.getTimeout();
|
||||
if (timeout != null) {
|
||||
session.timeout(timeout);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ public class HostSshConfigDTO implements GenericsDataModel, UpdatePasswordAction
|
||||
private Long keyId;
|
||||
|
||||
@NotNull
|
||||
@Min(value = 1)
|
||||
@Min(value = 0)
|
||||
@Max(value = 100000)
|
||||
@Schema(description = "连接超时时间")
|
||||
private Integer connectTimeout;
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<a-textarea class="action-bar-clipboard"
|
||||
v-model="clipboardData"
|
||||
:ref="setAutoFocus"
|
||||
placeholder="远程剪切板"
|
||||
allow-clear />
|
||||
<!-- 按钮 -->
|
||||
<a-space class="action-bar-content-footer">
|
||||
<a-button size="small" @click="clearClipboardData">
|
||||
清空
|
||||
</a-button>
|
||||
<a-button type="primary"
|
||||
size="small"
|
||||
:disabled="!clipboardData"
|
||||
@click="sendClipboardData">
|
||||
发送
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'clipboardAction'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { IGuacdSession } from '@/views/terminal/interfaces';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { setAutoFocus } from '@/utils/dom';
|
||||
import { readText } from '@/hooks/copy';
|
||||
|
||||
const props = defineProps<{
|
||||
session: IGuacdSession;
|
||||
}>();
|
||||
const emits = defineEmits(['close']);
|
||||
|
||||
const clipboardData = ref('');
|
||||
|
||||
// 发送剪切板数据
|
||||
const sendClipboardData = () => {
|
||||
props.session.paste(clipboardData.value);
|
||||
emits('close');
|
||||
};
|
||||
|
||||
// 清空剪切板数据
|
||||
const clearClipboardData = () => {
|
||||
clipboardData.value = '';
|
||||
};
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
readText(false)
|
||||
.then(s => clipboardData.value = s)
|
||||
.catch(() => clipboardData.value = '');
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<a-row :gutter="[12, 12]" wrap>
|
||||
<a-col v-for="item in GuacdCombinationKeyItems"
|
||||
:key="item.name"
|
||||
:span="12"
|
||||
class="combination-key-item"
|
||||
@click="triggerCombinationKey(item.keys)">
|
||||
<span>{{ item.name }}</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'combinationKeyAction'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { IGuacdSession } from '@/views/terminal/interfaces';
|
||||
import { GuacdCombinationKeyItems } from '@/views/terminal/types/const';
|
||||
|
||||
const props = defineProps<{
|
||||
session: IGuacdSession;
|
||||
}>();
|
||||
const emits = defineEmits(['close']);
|
||||
|
||||
// 触发组合键
|
||||
const triggerCombinationKey = (keys: Array<number>) => {
|
||||
props.session.sendKeys(keys);
|
||||
emits('close');
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<!-- 分辨率 -->
|
||||
<a-space>
|
||||
<span class="display-size-label">分辨率</span>
|
||||
<a-select v-model="displaySize"
|
||||
class="display-size-input"
|
||||
placeholder="请选择分辨率"
|
||||
:options="toOptions(screenResolutionKey)"
|
||||
allow-create />
|
||||
</a-space>
|
||||
<!-- 按钮 -->
|
||||
<a-space class="action-bar-content-footer">
|
||||
<a-button type="primary"
|
||||
size="small"
|
||||
@click="fitOnce">
|
||||
临时自适应
|
||||
</a-button>
|
||||
<a-button type="primary"
|
||||
size="small"
|
||||
@click="setDisplaySize">
|
||||
设置
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'displayAction'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { IGuacdSession } from '@/views/terminal/interfaces';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useDictStore } from '@/store';
|
||||
import { getDisplaySize } from '@/views/terminal/types/utils';
|
||||
import { screenResolutionKey, fitDisplayValue } from '@/views/terminal/types/const';
|
||||
|
||||
const { toOptions, getDictValue } = useDictStore();
|
||||
|
||||
const props = defineProps<{
|
||||
session: IGuacdSession;
|
||||
}>();
|
||||
const emits = defineEmits(['close']);
|
||||
|
||||
const displaySize = ref(fitDisplayValue);
|
||||
|
||||
// 临时自适应
|
||||
const fitOnce = () => {
|
||||
props.session.displayHandler?.fit(true);
|
||||
emits('close');
|
||||
};
|
||||
|
||||
// 设置显示大小
|
||||
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;
|
||||
}
|
||||
}
|
||||
emits('close');
|
||||
};
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
if (props.session.displayHandler?.autoFit) {
|
||||
displaySize.value = fitDisplayValue;
|
||||
} else {
|
||||
displaySize.value = `${props.session.displayHandler?.displayWidth || 0}x${props.session.displayHandler?.displayHeight || 0}`;
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<a-descriptions size="large"
|
||||
:label-style="{ display: 'flex', width: '48px' }"
|
||||
:column="1">
|
||||
<!-- 主机id -->
|
||||
<a-descriptions-item label="主机id">
|
||||
{{ session.info.hostId }}
|
||||
</a-descriptions-item>
|
||||
<!-- 主机名称 -->
|
||||
<a-descriptions-item label="主机名称">
|
||||
{{ session.info.name }}
|
||||
</a-descriptions-item>
|
||||
<!-- 主机地址 -->
|
||||
<a-descriptions-item label="主机地址">
|
||||
<span class="text-copy" @click="copy(session.info.address, true)">
|
||||
{{ session.info.address }}
|
||||
</span>
|
||||
</a-descriptions-item>
|
||||
<!-- 主机端口 -->
|
||||
<a-descriptions-item label="主机端口">
|
||||
{{ session.info.port }}
|
||||
</a-descriptions-item>
|
||||
<!-- 用户名 -->
|
||||
<a-descriptions-item v-if="session.info.username" label="用户名">
|
||||
{{ session.info.username }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'infoAction'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { IGuacdSession } from '@/views/terminal/interfaces';
|
||||
import { copy } from '@/hooks/copy';
|
||||
|
||||
const props = defineProps<{
|
||||
session: IGuacdSession;
|
||||
}>();
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
</style>
|
||||
@@ -25,9 +25,9 @@
|
||||
:show-arrow="false"
|
||||
:content="action.content">
|
||||
<a-button class="action-bar-button"
|
||||
:disabled="action.disabled"
|
||||
:disabled="!action.enabled()"
|
||||
:type="action.active ? 'primary' : 'secondary'"
|
||||
@click="toggleClickAction(action.item)">
|
||||
@click="toggleAction(action.item)">
|
||||
<template #icon>
|
||||
<component :is="action.icon" />
|
||||
</template>
|
||||
@@ -35,65 +35,28 @@
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-space>
|
||||
<!-- 连接信息 -->
|
||||
<div v-if="current === GuacdActionItemKeys.INFO" class="action-bar-content">
|
||||
<info-action :session="session" />
|
||||
</div>
|
||||
<!-- 显示设置 -->
|
||||
<div v-if="current === GuacdActionItemKeys.DISPLAY" class="action-bar-content">
|
||||
<!-- 分辨率 -->
|
||||
<a-space>
|
||||
<span class="display-size-label">分辨率</span>
|
||||
<a-select v-model="displaySize"
|
||||
class="display-size-input"
|
||||
placeholder="请选择分辨率"
|
||||
:options="toOptions(screenResolutionKey)"
|
||||
allow-create />
|
||||
</a-space>
|
||||
<!-- 按钮 -->
|
||||
<a-space class="action-bar-content-footer">
|
||||
<a-button type="primary"
|
||||
size="small"
|
||||
@click="fitOnce">
|
||||
临时自适应
|
||||
</a-button>
|
||||
<a-button type="primary"
|
||||
size="small"
|
||||
@click="setDisplaySize">
|
||||
设置
|
||||
</a-button>
|
||||
</a-space>
|
||||
<div v-else-if="current === GuacdActionItemKeys.DISPLAY" class="action-bar-content">
|
||||
<display-action :session="session" @close="close" />
|
||||
</div>
|
||||
<!-- 组合键 -->
|
||||
<div v-else-if="current === GuacdActionItemKeys.COMBINATION_KEY" class="action-bar-content">
|
||||
<a-row :gutter="[12, 12]" wrap>
|
||||
<a-col v-for="item in GuacdCombinationKeyItems"
|
||||
:key="item.name"
|
||||
:span="12"
|
||||
class="combination-key-item"
|
||||
@click="triggerCombinationKey(item.keys)">
|
||||
<span>{{ item.name }}</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<combination-key-action :session="session" @close="close" />
|
||||
</div>
|
||||
<!-- 触发键 -->
|
||||
<div v-else-if="current === GuacdActionItemKeys.TRIGGER_KEY" class="action-bar-content">
|
||||
<trigger-key-action :session="session" />
|
||||
</div>
|
||||
<!-- 剪切板 -->
|
||||
<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="action-bar-content-footer">
|
||||
<a-button size="small" @click="clearClipboardData">
|
||||
清空
|
||||
</a-button>
|
||||
<a-button type="primary"
|
||||
size="small"
|
||||
:disabled="!clipboardData"
|
||||
@click="sendClipboardData">
|
||||
发送
|
||||
</a-button>
|
||||
</a-space>
|
||||
<clipboard-action :session="session" @close="close" />
|
||||
</div>
|
||||
<!-- 文件上传 -->
|
||||
<div v-else-if="current === GuacdActionItemKeys.UPLOAD" class="action-bar-content">
|
||||
<!-- RDP 文件上传 -->
|
||||
<div v-else-if="current === GuacdActionItemKeys.RDP_UPLOAD" class="action-bar-content">
|
||||
<a-upload class="action-bar-upload"
|
||||
v-model:file-list="fileList"
|
||||
:auto-upload="false"
|
||||
@@ -116,6 +79,8 @@
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<!-- sftp 上传框 -->
|
||||
<sftp-upload-modal ref="sftpUploadModalRef" :session="session" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -130,44 +95,31 @@
|
||||
import type { IRdpSession } from '@/views/terminal/interfaces';
|
||||
import {
|
||||
TerminalStatus,
|
||||
GuacdCombinationKeyItems,
|
||||
GuacdActionItemKeys,
|
||||
RdpActionBarItems,
|
||||
screenResolutionKey,
|
||||
fitDisplayValue, ActionBarPosition
|
||||
ActionBarPosition, TerminalSessionTypes
|
||||
} 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 useGuacdActionBar from '@/views/terminal/types/use-guacd-action-bar';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import useVisible from '@/hooks/visible';
|
||||
import InfoAction from '../guacd/actions/info-action.vue';
|
||||
import DisplayAction from '../guacd/actions/display-action.vue';
|
||||
import TriggerKeyAction from '../guacd/actions/trigger-key-action.vue';
|
||||
import ClipboardAction from '../guacd/actions/clipboard-action.vue';
|
||||
import CombinationKeyAction from '../guacd/actions/combination-key-action.vue';
|
||||
import SftpUploadModal from '@/views/terminal/components/view/sftp/sftp-upload-modal.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
session: IRdpSession;
|
||||
direction: string;
|
||||
}>();
|
||||
|
||||
const { preference, transferManager } = useTerminalStore();
|
||||
const { toOptions, getDictValue } = useDictStore();
|
||||
const { hosts, preference, openSession, reOpenSession, transferManager } = useTerminalStore();
|
||||
const { visible, setVisible } = useVisible();
|
||||
|
||||
const {
|
||||
displaySize,
|
||||
clipboardData,
|
||||
fitOnce,
|
||||
setDisplaySize,
|
||||
triggerCombinationKey,
|
||||
sendClipboardData,
|
||||
clearClipboardData,
|
||||
disconnect,
|
||||
} = useGuacdActionBar({
|
||||
session: props.session,
|
||||
setVisible,
|
||||
});
|
||||
|
||||
const current = ref('');
|
||||
const sftpUploadModalRef = ref();
|
||||
const fileList = ref<FileItem[]>([]);
|
||||
|
||||
const actions = computed(() => {
|
||||
@@ -178,7 +130,17 @@
|
||||
return {
|
||||
...item,
|
||||
active: current.value === key,
|
||||
disabled: (key === GuacdActionItemKeys.DISPLAY || key === GuacdActionItemKeys.SAVE_RDP || GuacdActionItemKeys.DISCONNECT || key === GuacdActionItemKeys.CLOSE) ? false : !props.session.isWriteable(),
|
||||
enabled: () => {
|
||||
if (key === GuacdActionItemKeys.DISPLAY || key === GuacdActionItemKeys.DISCONNECT || key === GuacdActionItemKeys.RECONNECT || key === GuacdActionItemKeys.SAVE_RDP || key === GuacdActionItemKeys.CLOSE) {
|
||||
return true;
|
||||
} else if (key === GuacdActionItemKeys.OPEN_SFTP || key === GuacdActionItemKeys.SFTP_UPLOAD) {
|
||||
// 支持 SFTP 协议
|
||||
if (!hosts.hostList.find(s => s.id === props.session.info.hostId)?.types?.includes?.(TerminalSessionTypes.SFTP.protocol)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return props.session.isWriteable();
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -189,31 +151,22 @@
|
||||
return;
|
||||
}
|
||||
// 重新触发点击
|
||||
toggleClickAction(current.value);
|
||||
toggleAction(current.value);
|
||||
});
|
||||
|
||||
// 触发 action
|
||||
const toggleClickAction = (key: string) => {
|
||||
if (key === GuacdActionItemKeys.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 === GuacdActionItemKeys.COMBINATION_KEY) {
|
||||
// 组合键
|
||||
current.value = GuacdActionItemKeys.COMBINATION_KEY;
|
||||
} else if (key === GuacdActionItemKeys.CLIPBOARD) {
|
||||
// 剪切板
|
||||
current.value = GuacdActionItemKeys.CLIPBOARD;
|
||||
readText(false)
|
||||
.then(s => clipboardData.value = s)
|
||||
.catch(() => clipboardData.value = '');
|
||||
} else if (key === GuacdActionItemKeys.UPLOAD) {
|
||||
// 文件上传
|
||||
current.value = GuacdActionItemKeys.UPLOAD;
|
||||
const toggleAction = (key: string) => {
|
||||
if (key === GuacdActionItemKeys.OPEN_SFTP) {
|
||||
// 打开 SFTP 会话
|
||||
openSession(hosts.hostList.find(s => s.id === props.session.info.hostId) as any, TerminalSessionTypes.SFTP);
|
||||
setVisible(false);
|
||||
} else if (key === GuacdActionItemKeys.SFTP_UPLOAD) {
|
||||
// 打开 SFTP 上传
|
||||
sftpUploadModalRef.value.open('/');
|
||||
setVisible(false);
|
||||
} else if (key === GuacdActionItemKeys.RDP_UPLOAD) {
|
||||
// RDP 文件上传
|
||||
current.value = GuacdActionItemKeys.RDP_UPLOAD;
|
||||
fileList.value = [];
|
||||
} else if (key === GuacdActionItemKeys.SAVE_RDP) {
|
||||
// 保存 rdp 文件
|
||||
@@ -221,9 +174,14 @@
|
||||
} else if (key === GuacdActionItemKeys.DISCONNECT) {
|
||||
// 断开连接
|
||||
disconnect();
|
||||
} else if (key === GuacdActionItemKeys.RECONNECT) {
|
||||
// 重新连接
|
||||
reconnect();
|
||||
} else if (key === GuacdActionItemKeys.CLOSE) {
|
||||
// 关闭工具栏
|
||||
setVisible(false);
|
||||
} else {
|
||||
current.value = key;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -256,6 +214,29 @@
|
||||
fileList.value = [];
|
||||
};
|
||||
|
||||
// 关闭会话
|
||||
const disconnect = () => {
|
||||
props.session.disconnect();
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
// 关闭会话
|
||||
const reconnect = () => {
|
||||
const session = props.session;
|
||||
// 断开连接
|
||||
session.disconnect();
|
||||
// 重新连接
|
||||
if (session.state.canReconnect) {
|
||||
reOpenSession(session.sessionKey);
|
||||
}
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
// 关闭
|
||||
const close = () => {
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
// 设置选中
|
||||
onMounted(() => {
|
||||
if (actions.value?.length) {
|
||||
|
||||
@@ -25,9 +25,9 @@
|
||||
:show-arrow="false"
|
||||
:content="action.content">
|
||||
<a-button class="action-bar-button"
|
||||
:disabled="action.disabled"
|
||||
:disabled="!action.enabled()"
|
||||
:type="action.active ? 'primary' : 'secondary'"
|
||||
@click="toggleClickAction(action.item)">
|
||||
@click="toggleAction(action.item)">
|
||||
<template #icon>
|
||||
<component :is="action.icon" />
|
||||
</template>
|
||||
@@ -35,65 +35,35 @@
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-space>
|
||||
<!-- 连接信息 -->
|
||||
<div v-if="current === GuacdActionItemKeys.INFO" class="action-bar-content">
|
||||
<info-action :session="session" />
|
||||
</div>
|
||||
<!-- 显示设置 -->
|
||||
<div v-if="current === GuacdActionItemKeys.DISPLAY" class="action-bar-content">
|
||||
<!-- 分辨率 -->
|
||||
<a-space>
|
||||
<span class="display-size-label">分辨率</span>
|
||||
<a-select v-model="displaySize"
|
||||
class="display-size-input"
|
||||
placeholder="请选择分辨率"
|
||||
:options="toOptions(screenResolutionKey)"
|
||||
allow-create />
|
||||
</a-space>
|
||||
<!-- 按钮 -->
|
||||
<a-space class="action-bar-content-footer">
|
||||
<a-button type="primary"
|
||||
size="small"
|
||||
@click="fitOnce">
|
||||
临时自适应
|
||||
</a-button>
|
||||
<a-button type="primary"
|
||||
size="small"
|
||||
@click="setDisplaySize">
|
||||
设置
|
||||
</a-button>
|
||||
</a-space>
|
||||
<div v-else-if="current === GuacdActionItemKeys.DISPLAY" class="action-bar-content">
|
||||
<display-action ref="display"
|
||||
:session="session"
|
||||
@close="close" />
|
||||
</div>
|
||||
<!-- 组合键 -->
|
||||
<div v-else-if="current === GuacdActionItemKeys.COMBINATION_KEY" class="action-bar-content">
|
||||
<a-row :gutter="[12, 12]" wrap>
|
||||
<a-col v-for="item in GuacdCombinationKeyItems"
|
||||
:key="item.name"
|
||||
:span="12"
|
||||
class="combination-key-item"
|
||||
@click="triggerCombinationKey(item.keys)">
|
||||
<span>{{ item.name }}</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<combination-key-action :session="session"
|
||||
@close="close" />
|
||||
</div>
|
||||
<!-- 触发键 -->
|
||||
<div v-else-if="current === GuacdActionItemKeys.TRIGGER_KEY" class="action-bar-content">
|
||||
<trigger-key-action :session="session" />
|
||||
</div>
|
||||
<!-- 剪切板 -->
|
||||
<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="action-bar-content-footer">
|
||||
<a-button size="small" @click="clearClipboardData">
|
||||
清空
|
||||
</a-button>
|
||||
<a-button type="primary"
|
||||
size="small"
|
||||
:disabled="!clipboardData"
|
||||
@click="sendClipboardData">
|
||||
发送
|
||||
</a-button>
|
||||
</a-space>
|
||||
<clipboard-action ref="clipboard"
|
||||
:session="session"
|
||||
@close="close" />
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<!-- sftp 上传框 -->
|
||||
<sftp-upload-modal ref="sftpUploadModalRef" :session="session" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -107,43 +77,31 @@
|
||||
import type { IVncSession } from '@/views/terminal/interfaces';
|
||||
import {
|
||||
TerminalStatus,
|
||||
GuacdCombinationKeyItems,
|
||||
GuacdActionItemKeys,
|
||||
VncActionBarItems,
|
||||
screenResolutionKey,
|
||||
fitDisplayValue, ActionBarPosition,
|
||||
ActionBarPosition,
|
||||
TerminalSessionTypes,
|
||||
} from '@/views/terminal/types/const';
|
||||
import { computed, ref, watch, onMounted } from 'vue';
|
||||
import { setAutoFocus } from '@/utils/dom';
|
||||
import { readText } from '@/hooks/copy';
|
||||
import { useTerminalStore, useDictStore } from '@/store';
|
||||
import useGuacdActionBar from '@/views/terminal/types/use-guacd-action-bar';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import useVisible from '@/hooks/visible';
|
||||
import InfoAction from '../guacd/actions/info-action.vue';
|
||||
import DisplayAction from '../guacd/actions/display-action.vue';
|
||||
import ClipboardAction from '../guacd/actions/clipboard-action.vue';
|
||||
import TriggerKeyAction from '../guacd/actions/trigger-key-action.vue';
|
||||
import CombinationKeyAction from '../guacd/actions/combination-key-action.vue';
|
||||
import SftpUploadModal from '@/views/terminal/components/view/sftp/sftp-upload-modal.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
session: IVncSession;
|
||||
direction: string;
|
||||
}>();
|
||||
|
||||
const { preference } = useTerminalStore();
|
||||
const { toOptions, getDictValue } = useDictStore();
|
||||
const { hosts, preference, openSession, reOpenSession } = useTerminalStore();
|
||||
const { visible, setVisible } = useVisible();
|
||||
|
||||
const {
|
||||
displaySize,
|
||||
clipboardData,
|
||||
fitOnce,
|
||||
setDisplaySize,
|
||||
triggerCombinationKey,
|
||||
sendClipboardData,
|
||||
clearClipboardData,
|
||||
disconnect,
|
||||
} = useGuacdActionBar({
|
||||
session: props.session,
|
||||
setVisible,
|
||||
});
|
||||
|
||||
const current = ref('');
|
||||
const sftpUploadModalRef = ref();
|
||||
|
||||
const actions = computed(() => {
|
||||
return VncActionBarItems.filter(item => {
|
||||
@@ -153,7 +111,16 @@
|
||||
return {
|
||||
...item,
|
||||
active: current.value === key,
|
||||
disabled: (key === GuacdActionItemKeys.DISPLAY || GuacdActionItemKeys.DISCONNECT || key === GuacdActionItemKeys.CLOSE) ? false : !props.session.isWriteable(),
|
||||
enabled: () => {
|
||||
if (key === GuacdActionItemKeys.DISPLAY || key === GuacdActionItemKeys.DISCONNECT || key === GuacdActionItemKeys.RECONNECT || key === GuacdActionItemKeys.CLOSE) {
|
||||
return true;
|
||||
} else if (key === GuacdActionItemKeys.OPEN_SFTP || key === GuacdActionItemKeys.SFTP_UPLOAD) {
|
||||
if (!hosts.hostList.find(s => s.id === props.session.info.hostId)?.types?.includes?.(TerminalSessionTypes.SFTP.protocol)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return props.session.isWriteable();
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -163,38 +130,57 @@
|
||||
if (!val) {
|
||||
return;
|
||||
}
|
||||
// 重新触发点击
|
||||
toggleClickAction(current.value);
|
||||
// 重新触发
|
||||
toggleAction(current.value);
|
||||
});
|
||||
|
||||
// 触发 action
|
||||
const toggleClickAction = (key: string) => {
|
||||
if (key === GuacdActionItemKeys.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 === GuacdActionItemKeys.COMBINATION_KEY) {
|
||||
// 组合键
|
||||
current.value = GuacdActionItemKeys.COMBINATION_KEY;
|
||||
} else if (key === GuacdActionItemKeys.CLIPBOARD) {
|
||||
// 剪切板
|
||||
current.value = GuacdActionItemKeys.CLIPBOARD;
|
||||
readText(false)
|
||||
.then(s => clipboardData.value = s)
|
||||
.catch(() => clipboardData.value = '');
|
||||
const toggleAction = (key: string) => {
|
||||
if (key === GuacdActionItemKeys.OPEN_SFTP) {
|
||||
// 打开 SFTP 会话
|
||||
openSession(hosts.hostList.find(s => s.id === props.session.info.hostId) as any, TerminalSessionTypes.SFTP);
|
||||
setVisible(false);
|
||||
} else if (key === GuacdActionItemKeys.SFTP_UPLOAD) {
|
||||
// 打开 SFTP 上传
|
||||
sftpUploadModalRef.value.open('/');
|
||||
setVisible(false);
|
||||
} else if (key === GuacdActionItemKeys.DISCONNECT) {
|
||||
// 断开连接
|
||||
disconnect();
|
||||
} else if (key === GuacdActionItemKeys.RECONNECT) {
|
||||
// 重新连接
|
||||
reconnect();
|
||||
} else if (key === GuacdActionItemKeys.CLOSE) {
|
||||
// 关闭工具栏
|
||||
setVisible(false);
|
||||
} else {
|
||||
current.value = key;
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭会话
|
||||
const disconnect = () => {
|
||||
props.session.disconnect();
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
// 关闭会话
|
||||
const reconnect = () => {
|
||||
const session = props.session;
|
||||
// 断开连接
|
||||
session.disconnect();
|
||||
// 重新连接
|
||||
if (session.state.canReconnect) {
|
||||
reOpenSession(session.sessionKey);
|
||||
}
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
// 关闭
|
||||
const close = () => {
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
// 设置选中
|
||||
onMounted(() => {
|
||||
if (actions.value?.length) {
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
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