This commit is contained in:
Baobhan Sith
2025-05-07 23:21:18 +08:00
parent 6f4996fcd8
commit 5e1146481a
6 changed files with 121 additions and 27 deletions
@@ -412,10 +412,15 @@ export const getVncSessionToken = async (req: Request, res: Response): Promise<v
return;
}
// 5. 调用 GuacamoleService 获取 VNC 令牌
const guacamoleToken = await GuacamoleService.getVncToken(connection, decryptedPassword);
// 5. 从查询参数中获取可选的 width 和 height
const { width, height } = req.query;
const initialWidth = width ? parseInt(width as string, 10) : undefined;
const initialHeight = height ? parseInt(height as string, 10) : undefined;
console.log(`[Controller:getVncSessionToken] Received Guacamole token via GuacamoleService for VNC connection ${connectionId}`);
// 6. 调用 GuacamoleService 获取 VNC 令牌,传递尺寸信息
const guacamoleToken = await GuacamoleService.getVncToken(connection, decryptedPassword, initialWidth, initialHeight);
console.log(`[Controller:getVncSessionToken] Received Guacamole token via GuacamoleService for VNC connection ${connectionId} with size ${initialWidth}x${initialHeight}`);
// 6. 将 Guacamole 令牌返回给前端
res.status(200).json({ token: guacamoleToken });
@@ -76,7 +76,7 @@ export const getRdpToken = async (connection: ConnectionWithTags, decryptedPassw
* @param decryptedPassword 解密后的密码 (VNC 通常需要密码)
* @returns Guacamole 令牌
*/
export const getVncToken = async (connection: ConnectionWithTags, decryptedPassword?: string): Promise<string> => {
export const getVncToken = async (connection: ConnectionWithTags, decryptedPassword?: string, width?: number, height?: number): Promise<string> => {
if (connection.type !== 'VNC') {
throw new Error('连接类型必须是 VNC。');
}
@@ -95,6 +95,13 @@ export const getVncToken = async (connection: ConnectionWithTags, decryptedPassw
// username: connection.username, // 如果 VNC 服务支持用户名
});
if (width !== undefined) {
vncApiParams.append('width', String(width));
}
if (height !== undefined) {
vncApiParams.append('height', String(height));
}
// 如果 VNC 服务也支持用户名,可以取消注释上面的 username 参数
// 注意:标准的 VNC 协议主要通过密码进行认证,用户名不是标准部分,但某些实现可能支持。
// 这里假设 @nexus-terminal/vnc 的 /api/get-vnc-token 接受这些参数。
@@ -47,6 +47,8 @@ export const settingsController = {
'timezone', // NEW: 添加时区键
'rdpModalWidth', // NEW: 添加 RDP 模态框宽度键
'rdpModalHeight', // NEW: 添加 RDP 模态框高度键
'vncModalWidth', // NEW: 添加 VNC 模态框宽度键
'vncModalHeight', // NEW: 添加 VNC 模态框高度键
'ipBlacklistEnabled', // <-- 添加 IP 黑名单启用键
'layoutLocked', // +++ 添加布局锁定键 +++
'terminalScrollbackLimit', // NEW: 添加终端回滚行数键
+74 -19
View File
@@ -27,8 +27,16 @@ const connectionStatus = ref<'disconnected' | 'connecting' | 'connected' | 'erro
const statusMessage = ref('');
const keyboard = ref<any | null>(null);
const mouse = ref<any | null>(null);
const desiredModalWidth = ref(1024);
const desiredModalHeight = ref(768);
// Initialize desiredModalWidth and desiredModalHeight from store or defaults
const initialStoreWidth = settingsStore.settings.vncModalWidth
? parseInt(settingsStore.settings.vncModalWidth, 10)
: 1024;
const initialStoreHeight = settingsStore.settings.vncModalHeight
? parseInt(settingsStore.settings.vncModalHeight, 10)
: 768;
const desiredModalWidth = ref(Math.max(MIN_MODAL_WIDTH, isNaN(initialStoreWidth) ? MIN_MODAL_WIDTH : initialStoreWidth));
const desiredModalHeight = ref(Math.max(MIN_MODAL_HEIGHT, isNaN(initialStoreHeight) ? MIN_MODAL_HEIGHT : initialStoreHeight));
const isKeyboardDisabledForInput = ref(false);
const MIN_MODAL_WIDTH = 800;
@@ -63,7 +71,7 @@ const handleConnection = async () => {
try {
const connectionsStore = useConnectionsStore();
const token = await connectionsStore.getVncSessionToken(props.connection.id);
const token = await connectionsStore.getVncSessionToken(props.connection.id, desiredModalWidth.value, desiredModalHeight.value);
if (!token) {
throw new Error('VNC Token not found from store action');
}
@@ -105,6 +113,15 @@ const handleConnection = async () => {
if (displayEl && typeof displayEl.focus === 'function') {
displayEl.focus();
}
// Sync size on connect
if (vncDisplayRef.value && guacClient.value) {
const displayWidth = vncDisplayRef.value.offsetWidth;
const displayHeight = vncDisplayRef.value.offsetHeight;
if (displayWidth > 0 && displayHeight > 0) {
console.log(`[VncModal] Initial resize on connect: ${displayWidth}x${displayHeight}`);
guacClient.value.sendSize(displayWidth, displayHeight);
}
}
});
setTimeout(() => {
nextTick(() => {
@@ -302,41 +319,60 @@ const closeModal = () => {
};
watch(desiredModalWidth, (newWidth, oldWidth) => {
if (newWidth === oldWidth) return;
if (newWidth === oldWidth && typeof newWidth === 'number' && typeof oldWidth === 'number') {
// console.log(`[VncModal] 宽度监听触发,但值 (${newWidth}) 未改变。跳过。`);
return;
}
// console.log(`[VncModal] 监听 desiredModalWidth 触发: ${oldWidth} -> ${newWidth}`);
const validatedWidth = Math.max(MIN_MODAL_WIDTH, Number(newWidth) || MIN_MODAL_WIDTH);
if (validatedWidth !== Number(newWidth)) {
nextTick(() => {
desiredModalWidth.value = validatedWidth;
});
}
if (saveWidthTimeout) clearTimeout(saveWidthTimeout);
saveWidthTimeout = setTimeout(() => {
// console.log(`[VncModal] 防抖保存 - 保存宽度: ${validatedWidth}`);
if (String(validatedWidth) !== settingsStore.settings.vncModalWidth) {
settingsStore.updateSetting('vncModalWidth', String(validatedWidth));
} else {
// console.log(`[VncModal] 防抖保存 - 宽度 ${validatedWidth} 与存储值匹配。跳过冗余保存。`);
}
}, DEBOUNCE_DELAY);
});
watch(desiredModalHeight, (newHeight, oldHeight) => {
if (newHeight === oldHeight) return;
if (newHeight === oldHeight && typeof newHeight === 'number' && typeof oldHeight === 'number') {
// console.log(`[VncModal] 高度监听触发,但值 (${newHeight}) 未改变。跳过。`);
return;
}
// console.log(`[VncModal] 监听 desiredModalHeight 触发: ${oldHeight} -> ${newHeight}`);
const validatedHeight = Math.max(MIN_MODAL_HEIGHT, Number(newHeight) || MIN_MODAL_HEIGHT);
if (validatedHeight !== Number(newHeight)) {
nextTick(() => {
desiredModalHeight.value = validatedHeight;
});
}
if (saveHeightTimeout) clearTimeout(saveHeightTimeout);
saveHeightTimeout = setTimeout(() => {
// console.log(`[VncModal] 防抖保存 - 保存高度: ${validatedHeight}`);
if (String(validatedHeight) !== settingsStore.settings.vncModalHeight) {
settingsStore.updateSetting('vncModalHeight', String(validatedHeight));
} else {
// console.log(`[VncModal] 防抖保存 - 高度 ${validatedHeight} 与存储值匹配。跳过冗余保存。`);
}
}, DEBOUNCE_DELAY);
});
watchEffect(() => {
const storeWidth = settingsStore.settings.vncModalWidth;
const storeHeight = settingsStore.settings.vncModalHeight;
console.log(`[VncModal] From store - Width: ${storeWidth}, Height: ${storeHeight}`);
const initialWidth = storeWidth ? parseInt(storeWidth, 10) : desiredModalWidth.value;
const initialHeight = storeHeight ? parseInt(storeHeight, 10) : desiredModalHeight.value;
const finalWidth = Math.max(MIN_MODAL_WIDTH, isNaN(initialWidth) ? MIN_MODAL_WIDTH : initialWidth);
const finalHeight = Math.max(MIN_MODAL_HEIGHT, isNaN(initialHeight) ? MIN_MODAL_HEIGHT : initialHeight);
console.log(`[VncModal] Applied - Width: ${finalWidth}, Height: ${finalHeight}`);
desiredModalWidth.value = finalWidth;
desiredModalHeight.value = finalHeight;
});
// The watchEffect that was here (lines 359-372) is removed as its functionality
// is now covered by the direct initialization of desiredModalWidth/Height from the store
// and the updated watch listeners.
onMounted(() => {
if (props.connection) {
@@ -373,6 +409,25 @@ const computedModalStyle = computed(() => {
height: `${actualHeight}px`,
};
});
watchEffect(() => {
// 依赖 computedModalStyle,当其变化时此 effect 会重新运行
const currentStyle = computedModalStyle.value;
if (guacClient.value && connectionStatus.value === 'connected' && vncDisplayRef.value) {
// 使用 nextTick 确保 DOM 更新完毕,vncDisplayRef 的尺寸已根据 currentStyle 刷新
nextTick(() => {
if (vncDisplayRef.value && guacClient.value) { // 再次检查,因为 nextTick 是异步的
const displayWidth = vncDisplayRef.value.offsetWidth;
const displayHeight = vncDisplayRef.value.offsetHeight;
if (displayWidth > 0 && displayHeight > 0) {
console.log(`[VncModal] Resizing VNC display to: ${displayWidth}x${displayHeight} due to style change.`);
guacClient.value.sendSize(displayWidth, displayHeight);
}
}
});
}
});
</script>
<template>
@@ -286,12 +286,24 @@ export const useConnectionsStore = defineStore('connections', {
},
// +++ 新增:获取 VNC 会话令牌 +++
async getVncSessionToken(connectionId: number): Promise<string | null> {
async getVncSessionToken(connectionId: number, width?: number, height?: number): Promise<string | null> {
// this.isLoading = true; // 考虑是否需要独立的加载状态,或者由调用方处理
// this.error = null;
try {
// 调用后端 API GET /connections/:id/vnc-session
const response = await apiClient.post<{ token: string }>(`/connections/${connectionId}/vnc-session`);
let apiUrl = `/connections/${connectionId}/vnc-session`;
const params = new URLSearchParams();
if (width !== undefined) {
params.append('width', String(width));
}
if (height !== undefined) {
params.append('height', String(height));
}
const queryString = params.toString();
if (queryString) {
apiUrl += `?${queryString}`;
}
// 调用后端 API POST /connections/:id/vnc-session (现在带有可选的 width/height 查询参数)
const response = await apiClient.post<{ token: string }>(apiUrl);
return response.data.token;
} catch (err: any) {
console.error(`获取 VNC 会话令牌失败 (连接 ID: ${connectionId}):`, err);
@@ -50,6 +50,8 @@ interface SettingsState {
timezone?: string; // NEW: 时区设置 (e.g., 'Asia/Shanghai', 'UTC')
rdpModalWidth?: string; // NEW: RDP 模态框宽度
rdpModalHeight?: string; // NEW: RDP 模态框高度
vncModalWidth?: string; // NEW: VNC 模态框宽度
vncModalHeight?: string; // NEW: VNC 模态框高度
ipBlacklistEnabled?: string;
dashboardSortBy?: SortField;
dashboardSortOrder?: SortOrder;
@@ -250,6 +252,13 @@ export const useSettingsStore = defineStore('settings', () => {
if (settings.value.rdpModalHeight === undefined) {
settings.value.rdpModalHeight = '858';
}
// NEW: VNC Modal Size defaults
if (settings.value.vncModalWidth === undefined) {
settings.value.vncModalWidth = '1024'; // 默认宽度
}
if (settings.value.vncModalHeight === undefined) {
settings.value.vncModalHeight = '768'; // 默认高度
}
if (settings.value.dashboardSortBy === undefined) {
settings.value.dashboardSortBy = 'last_connected_at';
@@ -364,6 +373,8 @@ export const useSettingsStore = defineStore('settings', () => {
'timezone', // NEW: 添加时区键
'rdpModalWidth', // NEW: 添加 RDP 模态框宽度键
'rdpModalHeight', // NEW: 添加 RDP 模态框高度键
'vncModalWidth', // NEW: 添加 VNC 模态框宽度键
'vncModalHeight', // NEW: 添加 VNC 模态框高度键
'ipBlacklistEnabled',
'dashboardSortBy',
'dashboardSortOrder',
@@ -451,6 +462,8 @@ export const useSettingsStore = defineStore('settings', () => {
'timezone', // NEW: 添加时区键
'rdpModalWidth', // NEW: 添加 RDP 模态框宽度键
'rdpModalHeight', // NEW: 添加 RDP 模态框高度键
'vncModalWidth', // NEW: 添加 VNC 模态框宽度键
'vncModalHeight', // NEW: 添加 VNC 模态框高度键
'ipBlacklistEnabled',
'dashboardSortBy',
'dashboardSortOrder',