🔨 优化会话工具栏.
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;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ public class HostSshConfigDTO implements GenericsDataModel, UpdatePasswordAction
|
|||||||
private Long keyId;
|
private Long keyId;
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Min(value = 1)
|
@Min(value = 0)
|
||||||
@Max(value = 100000)
|
@Max(value = 100000)
|
||||||
@Schema(description = "连接超时时间")
|
@Schema(description = "连接超时时间")
|
||||||
private Integer connectTimeout;
|
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"
|
:show-arrow="false"
|
||||||
:content="action.content">
|
:content="action.content">
|
||||||
<a-button class="action-bar-button"
|
<a-button class="action-bar-button"
|
||||||
:disabled="action.disabled"
|
:disabled="!action.enabled()"
|
||||||
:type="action.active ? 'primary' : 'secondary'"
|
:type="action.active ? 'primary' : 'secondary'"
|
||||||
@click="toggleClickAction(action.item)">
|
@click="toggleAction(action.item)">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<component :is="action.icon" />
|
<component :is="action.icon" />
|
||||||
</template>
|
</template>
|
||||||
@@ -35,65 +35,28 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</a-space>
|
</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">
|
<div v-else-if="current === GuacdActionItemKeys.DISPLAY" class="action-bar-content">
|
||||||
<!-- 分辨率 -->
|
<display-action :session="session" @close="close" />
|
||||||
<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>
|
</div>
|
||||||
<!-- 组合键 -->
|
<!-- 组合键 -->
|
||||||
<div v-else-if="current === GuacdActionItemKeys.COMBINATION_KEY" class="action-bar-content">
|
<div v-else-if="current === GuacdActionItemKeys.COMBINATION_KEY" class="action-bar-content">
|
||||||
<a-row :gutter="[12, 12]" wrap>
|
<combination-key-action :session="session" @close="close" />
|
||||||
<a-col v-for="item in GuacdCombinationKeyItems"
|
</div>
|
||||||
:key="item.name"
|
<!-- 触发键 -->
|
||||||
:span="12"
|
<div v-else-if="current === GuacdActionItemKeys.TRIGGER_KEY" class="action-bar-content">
|
||||||
class="combination-key-item"
|
<trigger-key-action :session="session" />
|
||||||
@click="triggerCombinationKey(item.keys)">
|
|
||||||
<span>{{ item.name }}</span>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 剪切板 -->
|
<!-- 剪切板 -->
|
||||||
<div v-else-if="current === GuacdActionItemKeys.CLIPBOARD" class="action-bar-content">
|
<div v-else-if="current === GuacdActionItemKeys.CLIPBOARD" class="action-bar-content">
|
||||||
<a-textarea class="action-bar-clipboard"
|
<clipboard-action :session="session" @close="close" />
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 文件上传 -->
|
<!-- RDP 文件上传 -->
|
||||||
<div v-else-if="current === GuacdActionItemKeys.UPLOAD" class="action-bar-content">
|
<div v-else-if="current === GuacdActionItemKeys.RDP_UPLOAD" class="action-bar-content">
|
||||||
<a-upload class="action-bar-upload"
|
<a-upload class="action-bar-upload"
|
||||||
v-model:file-list="fileList"
|
v-model:file-list="fileList"
|
||||||
:auto-upload="false"
|
:auto-upload="false"
|
||||||
@@ -116,6 +79,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
|
<!-- sftp 上传框 -->
|
||||||
|
<sftp-upload-modal ref="sftpUploadModalRef" :session="session" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -130,44 +95,31 @@
|
|||||||
import type { IRdpSession } from '@/views/terminal/interfaces';
|
import type { IRdpSession } from '@/views/terminal/interfaces';
|
||||||
import {
|
import {
|
||||||
TerminalStatus,
|
TerminalStatus,
|
||||||
GuacdCombinationKeyItems,
|
|
||||||
GuacdActionItemKeys,
|
GuacdActionItemKeys,
|
||||||
RdpActionBarItems,
|
RdpActionBarItems,
|
||||||
screenResolutionKey,
|
ActionBarPosition, TerminalSessionTypes
|
||||||
fitDisplayValue, ActionBarPosition
|
|
||||||
} from '@/views/terminal/types/const';
|
} from '@/views/terminal/types/const';
|
||||||
import { computed, ref, watch, onMounted } from 'vue';
|
import { computed, ref, watch, onMounted } from 'vue';
|
||||||
import { setAutoFocus } from '@/utils/dom';
|
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
import { readText } from '@/hooks/copy';
|
import { useTerminalStore } from '@/store';
|
||||||
import { useTerminalStore, useDictStore } from '@/store';
|
|
||||||
import useGuacdActionBar from '@/views/terminal/types/use-guacd-action-bar';
|
|
||||||
import useVisible from '@/hooks/visible';
|
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<{
|
const props = defineProps<{
|
||||||
session: IRdpSession;
|
session: IRdpSession;
|
||||||
direction: string;
|
direction: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { preference, transferManager } = useTerminalStore();
|
const { hosts, preference, openSession, reOpenSession, transferManager } = useTerminalStore();
|
||||||
const { toOptions, getDictValue } = useDictStore();
|
|
||||||
const { visible, setVisible } = useVisible();
|
const { visible, setVisible } = useVisible();
|
||||||
|
|
||||||
const {
|
|
||||||
displaySize,
|
|
||||||
clipboardData,
|
|
||||||
fitOnce,
|
|
||||||
setDisplaySize,
|
|
||||||
triggerCombinationKey,
|
|
||||||
sendClipboardData,
|
|
||||||
clearClipboardData,
|
|
||||||
disconnect,
|
|
||||||
} = useGuacdActionBar({
|
|
||||||
session: props.session,
|
|
||||||
setVisible,
|
|
||||||
});
|
|
||||||
|
|
||||||
const current = ref('');
|
const current = ref('');
|
||||||
|
const sftpUploadModalRef = ref();
|
||||||
const fileList = ref<FileItem[]>([]);
|
const fileList = ref<FileItem[]>([]);
|
||||||
|
|
||||||
const actions = computed(() => {
|
const actions = computed(() => {
|
||||||
@@ -178,7 +130,17 @@
|
|||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
active: current.value === key,
|
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;
|
return;
|
||||||
}
|
}
|
||||||
// 重新触发点击
|
// 重新触发点击
|
||||||
toggleClickAction(current.value);
|
toggleAction(current.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 触发 action
|
// 触发 action
|
||||||
const toggleClickAction = (key: string) => {
|
const toggleAction = (key: string) => {
|
||||||
if (key === GuacdActionItemKeys.DISPLAY) {
|
if (key === GuacdActionItemKeys.OPEN_SFTP) {
|
||||||
// 显示设置
|
// 打开 SFTP 会话
|
||||||
current.value = GuacdActionItemKeys.DISPLAY;
|
openSession(hosts.hostList.find(s => s.id === props.session.info.hostId) as any, TerminalSessionTypes.SFTP);
|
||||||
if (props.session.displayHandler?.autoFit) {
|
setVisible(false);
|
||||||
displaySize.value = fitDisplayValue;
|
} else if (key === GuacdActionItemKeys.SFTP_UPLOAD) {
|
||||||
} else {
|
// 打开 SFTP 上传
|
||||||
displaySize.value = `${props.session.displayHandler?.displayWidth || 0}x${props.session.displayHandler?.displayHeight || 0}`;
|
sftpUploadModalRef.value.open('/');
|
||||||
}
|
setVisible(false);
|
||||||
} else if (key === GuacdActionItemKeys.COMBINATION_KEY) {
|
} else if (key === GuacdActionItemKeys.RDP_UPLOAD) {
|
||||||
// 组合键
|
// RDP 文件上传
|
||||||
current.value = GuacdActionItemKeys.COMBINATION_KEY;
|
current.value = GuacdActionItemKeys.RDP_UPLOAD;
|
||||||
} 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;
|
|
||||||
fileList.value = [];
|
fileList.value = [];
|
||||||
} else if (key === GuacdActionItemKeys.SAVE_RDP) {
|
} else if (key === GuacdActionItemKeys.SAVE_RDP) {
|
||||||
// 保存 rdp 文件
|
// 保存 rdp 文件
|
||||||
@@ -221,9 +174,14 @@
|
|||||||
} else if (key === GuacdActionItemKeys.DISCONNECT) {
|
} else if (key === GuacdActionItemKeys.DISCONNECT) {
|
||||||
// 断开连接
|
// 断开连接
|
||||||
disconnect();
|
disconnect();
|
||||||
|
} else if (key === GuacdActionItemKeys.RECONNECT) {
|
||||||
|
// 重新连接
|
||||||
|
reconnect();
|
||||||
} else if (key === GuacdActionItemKeys.CLOSE) {
|
} else if (key === GuacdActionItemKeys.CLOSE) {
|
||||||
// 关闭工具栏
|
// 关闭工具栏
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
|
} else {
|
||||||
|
current.value = key;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -256,6 +214,29 @@
|
|||||||
fileList.value = [];
|
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(() => {
|
onMounted(() => {
|
||||||
if (actions.value?.length) {
|
if (actions.value?.length) {
|
||||||
|
|||||||
@@ -25,9 +25,9 @@
|
|||||||
:show-arrow="false"
|
:show-arrow="false"
|
||||||
:content="action.content">
|
:content="action.content">
|
||||||
<a-button class="action-bar-button"
|
<a-button class="action-bar-button"
|
||||||
:disabled="action.disabled"
|
:disabled="!action.enabled()"
|
||||||
:type="action.active ? 'primary' : 'secondary'"
|
:type="action.active ? 'primary' : 'secondary'"
|
||||||
@click="toggleClickAction(action.item)">
|
@click="toggleAction(action.item)">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<component :is="action.icon" />
|
<component :is="action.icon" />
|
||||||
</template>
|
</template>
|
||||||
@@ -35,65 +35,35 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</a-space>
|
</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">
|
<div v-else-if="current === GuacdActionItemKeys.DISPLAY" class="action-bar-content">
|
||||||
<!-- 分辨率 -->
|
<display-action ref="display"
|
||||||
<a-space>
|
:session="session"
|
||||||
<span class="display-size-label">分辨率</span>
|
@close="close" />
|
||||||
<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>
|
</div>
|
||||||
<!-- 组合键 -->
|
<!-- 组合键 -->
|
||||||
<div v-else-if="current === GuacdActionItemKeys.COMBINATION_KEY" class="action-bar-content">
|
<div v-else-if="current === GuacdActionItemKeys.COMBINATION_KEY" class="action-bar-content">
|
||||||
<a-row :gutter="[12, 12]" wrap>
|
<combination-key-action :session="session"
|
||||||
<a-col v-for="item in GuacdCombinationKeyItems"
|
@close="close" />
|
||||||
:key="item.name"
|
</div>
|
||||||
:span="12"
|
<!-- 触发键 -->
|
||||||
class="combination-key-item"
|
<div v-else-if="current === GuacdActionItemKeys.TRIGGER_KEY" class="action-bar-content">
|
||||||
@click="triggerCombinationKey(item.keys)">
|
<trigger-key-action :session="session" />
|
||||||
<span>{{ item.name }}</span>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 剪切板 -->
|
<!-- 剪切板 -->
|
||||||
<div v-else-if="current === GuacdActionItemKeys.CLIPBOARD" class="action-bar-content">
|
<div v-else-if="current === GuacdActionItemKeys.CLIPBOARD" class="action-bar-content">
|
||||||
<a-textarea class="action-bar-clipboard"
|
<clipboard-action ref="clipboard"
|
||||||
v-model="clipboardData"
|
:session="session"
|
||||||
:ref="setAutoFocus"
|
@close="close" />
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
|
<!-- sftp 上传框 -->
|
||||||
|
<sftp-upload-modal ref="sftpUploadModalRef" :session="session" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -107,43 +77,31 @@
|
|||||||
import type { IVncSession } from '@/views/terminal/interfaces';
|
import type { IVncSession } from '@/views/terminal/interfaces';
|
||||||
import {
|
import {
|
||||||
TerminalStatus,
|
TerminalStatus,
|
||||||
GuacdCombinationKeyItems,
|
|
||||||
GuacdActionItemKeys,
|
GuacdActionItemKeys,
|
||||||
VncActionBarItems,
|
VncActionBarItems,
|
||||||
screenResolutionKey,
|
ActionBarPosition,
|
||||||
fitDisplayValue, ActionBarPosition,
|
TerminalSessionTypes,
|
||||||
} from '@/views/terminal/types/const';
|
} from '@/views/terminal/types/const';
|
||||||
import { computed, ref, watch, onMounted } from 'vue';
|
import { computed, ref, watch, onMounted } from 'vue';
|
||||||
import { setAutoFocus } from '@/utils/dom';
|
import { useTerminalStore } from '@/store';
|
||||||
import { readText } from '@/hooks/copy';
|
|
||||||
import { useTerminalStore, useDictStore } from '@/store';
|
|
||||||
import useGuacdActionBar from '@/views/terminal/types/use-guacd-action-bar';
|
|
||||||
import useVisible from '@/hooks/visible';
|
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<{
|
const props = defineProps<{
|
||||||
session: IVncSession;
|
session: IVncSession;
|
||||||
direction: string;
|
direction: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { preference } = useTerminalStore();
|
const { hosts, preference, openSession, reOpenSession } = useTerminalStore();
|
||||||
const { toOptions, getDictValue } = useDictStore();
|
|
||||||
const { visible, setVisible } = useVisible();
|
const { visible, setVisible } = useVisible();
|
||||||
|
|
||||||
const {
|
|
||||||
displaySize,
|
|
||||||
clipboardData,
|
|
||||||
fitOnce,
|
|
||||||
setDisplaySize,
|
|
||||||
triggerCombinationKey,
|
|
||||||
sendClipboardData,
|
|
||||||
clearClipboardData,
|
|
||||||
disconnect,
|
|
||||||
} = useGuacdActionBar({
|
|
||||||
session: props.session,
|
|
||||||
setVisible,
|
|
||||||
});
|
|
||||||
|
|
||||||
const current = ref('');
|
const current = ref('');
|
||||||
|
const sftpUploadModalRef = ref();
|
||||||
|
|
||||||
const actions = computed(() => {
|
const actions = computed(() => {
|
||||||
return VncActionBarItems.filter(item => {
|
return VncActionBarItems.filter(item => {
|
||||||
@@ -153,7 +111,16 @@
|
|||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
active: current.value === key,
|
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) {
|
if (!val) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 重新触发点击
|
// 重新触发
|
||||||
toggleClickAction(current.value);
|
toggleAction(current.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 触发 action
|
// 触发 action
|
||||||
const toggleClickAction = (key: string) => {
|
const toggleAction = (key: string) => {
|
||||||
if (key === GuacdActionItemKeys.DISPLAY) {
|
if (key === GuacdActionItemKeys.OPEN_SFTP) {
|
||||||
// 显示设置
|
// 打开 SFTP 会话
|
||||||
current.value = GuacdActionItemKeys.DISPLAY;
|
openSession(hosts.hostList.find(s => s.id === props.session.info.hostId) as any, TerminalSessionTypes.SFTP);
|
||||||
if (props.session.displayHandler?.autoFit) {
|
setVisible(false);
|
||||||
displaySize.value = fitDisplayValue;
|
} else if (key === GuacdActionItemKeys.SFTP_UPLOAD) {
|
||||||
} else {
|
// 打开 SFTP 上传
|
||||||
displaySize.value = `${props.session.displayHandler?.displayWidth || 0}x${props.session.displayHandler?.displayHeight || 0}`;
|
sftpUploadModalRef.value.open('/');
|
||||||
}
|
setVisible(false);
|
||||||
} 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.DISCONNECT) {
|
} else if (key === GuacdActionItemKeys.DISCONNECT) {
|
||||||
// 断开连接
|
// 断开连接
|
||||||
disconnect();
|
disconnect();
|
||||||
|
} else if (key === GuacdActionItemKeys.RECONNECT) {
|
||||||
|
// 重新连接
|
||||||
|
reconnect();
|
||||||
} else if (key === GuacdActionItemKeys.CLOSE) {
|
} else if (key === GuacdActionItemKeys.CLOSE) {
|
||||||
// 关闭工具栏
|
// 关闭工具栏
|
||||||
setVisible(false);
|
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(() => {
|
onMounted(() => {
|
||||||
if (actions.value?.length) {
|
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