diff --git a/packages/backend/src/repositories/settings.repository.ts b/packages/backend/src/repositories/settings.repository.ts index a307024..9aee8fe 100644 --- a/packages/backend/src/repositories/settings.repository.ts +++ b/packages/backend/src/repositories/settings.repository.ts @@ -48,13 +48,9 @@ export const settingsRepository = { updated_at = excluded.updated_at`; const params = [key, value, now, now]; - // console.log(`[仓库] 尝试设置设置项。键: ${key}, 值: ${value}`); - // console.log(`[仓库] 执行 SQL: ${sql},参数: ${JSON.stringify(params)}`); - try { const db = await getDbInstance(); const result = await runDb(db, sql, params); - // console.log(`[仓库] 成功设置键为 ${key} 的设置项。影响行数: ${result.changes}`); } catch (err: any) { console.error(`[Repository] 设置设置项 ${key} 时出错:`, err.message); throw new Error(`设置设置项 ${key} 失败`); @@ -265,8 +261,9 @@ export const ensureDefaultSettingsExist = async (db: sqlite3.Database): Promise< statusMonitorIntervalSeconds: '3', [SIDEBAR_CONFIG_KEY]: JSON.stringify(defaultSidebarPanesStructure), [CAPTCHA_CONFIG_KEY]: JSON.stringify(defaultCaptchaSettings), - timezone: 'UTC', // NEW: 添加时区默认值 - terminalScrollbackLimit: '5000', // NEW: 添加终端回滚行数默认值 + timezone: 'UTC', // 添加时区默认值 + terminalScrollbackLimit: '5000', // 添加终端回滚行数默认值 + terminalEnableRightClickPaste: 'true', // 添加终端右键粘贴默认值 }; const nowSeconds = Math.floor(Date.now() / 1000); const sqlInsertOrIgnore = `INSERT OR IGNORE INTO settings (key, value, created_at, updated_at) VALUES (?, ?, ?, ?)`; diff --git a/packages/backend/src/settings/settings.controller.ts b/packages/backend/src/settings/settings.controller.ts index 71b2b3e..8107242 100644 --- a/packages/backend/src/settings/settings.controller.ts +++ b/packages/backend/src/settings/settings.controller.ts @@ -45,15 +45,16 @@ export const settingsController = { 'fileManagerRowSizeMultiplier', // +++ 添加文件管理器行大小键 +++ 'fileManagerColWidths', // +++ 添加文件管理器列宽键 +++ 'commandInputSyncTarget', // +++ 添加命令输入同步目标键 +++ - 'timezone', // NEW: 添加时区键 - 'rdpModalWidth', // NEW: 添加 RDP 模态框宽度键 - 'rdpModalHeight', // NEW: 添加 RDP 模态框高度键 - 'vncModalWidth', // NEW: 添加 VNC 模态框宽度键 - 'vncModalHeight', // NEW: 添加 VNC 模态框高度键 + 'timezone', // 添加时区键 + 'rdpModalWidth', // 添加 RDP 模态框宽度键 + 'rdpModalHeight', // 添加 RDP 模态框高度键 + 'vncModalWidth', // 添加 VNC 模态框宽度键 + 'vncModalHeight', // 添加 VNC 模态框高度键 'ipBlacklistEnabled', // <-- 添加 IP 黑名单启用键 'layoutLocked', // +++ 添加布局锁定键 +++ - 'terminalScrollbackLimit', // NEW: 添加终端回滚行数键 - 'fileManagerShowDeleteConfirmation' // NEW: 添加文件管理器删除确认键 + 'terminalScrollbackLimit', // 添加终端回滚行数键 + 'fileManagerShowDeleteConfirmation', // 添加文件管理器删除确认键 + 'terminalEnableRightClickPaste' // 添加终端右键粘贴键 ]; const filteredSettings: Record = {}; for (const key in settingsToUpdate) { @@ -64,7 +65,7 @@ export const settingsController = { if (Object.keys(filteredSettings).length > 0) { await settingsService.setMultipleSettings(filteredSettings); - } + } const updatedKeys = Object.keys(filteredSettings); if (updatedKeys.length > 0) { diff --git a/packages/backend/src/websocket/types.ts b/packages/backend/src/websocket/types.ts index fd2669f..41f8ff8 100644 --- a/packages/backend/src/websocket/types.ts +++ b/packages/backend/src/websocket/types.ts @@ -18,7 +18,7 @@ export interface ClientState { // 导出以便 Service 可以导入 connectionName?: string; // 添加连接名称字段 sftp?: SFTPWrapper; // 添加 sftp 实例 (由 SftpService 管理) statusIntervalId?: NodeJS.Timeout; // 添加状态轮询 ID (由 StatusMonitorService 管理) - dockerStatusIntervalId?: NodeJS.Timeout; // NEW: Docker 状态轮询 ID + dockerStatusIntervalId?: NodeJS.Timeout; // Docker 状态轮询 ID ipAddress?: string; // 添加 IP 地址字段 isShellReady?: boolean; // 标记 Shell 是否已准备好处理输入和调整大小 isSuspendedByService?: boolean; // 标记此会话是否已被 SshSuspendService 接管 diff --git a/packages/frontend/src/components/CommandInputBar.vue b/packages/frontend/src/components/CommandInputBar.vue index fc8aeb7..2d60782 100644 --- a/packages/frontend/src/components/CommandInputBar.vue +++ b/packages/frontend/src/components/CommandInputBar.vue @@ -114,7 +114,7 @@ watch(searchTerm, (newValue) => { } }); -// NEW: Watch currentSessionCommandInput and sync searchTerm based on settings +// Watch currentSessionCommandInput and sync searchTerm based on settings watch(currentSessionCommandInput, (newValue) => { // 监听计算属性 const target = commandInputSyncTarget.value; if (target === 'quickCommands') { @@ -213,7 +213,7 @@ const handleCommandInputKeydown = (event: KeyboardEvent) => { } }; -// NEW: Handle blur event on command input +// Handle blur event on command input const handleCommandInputBlur = () => { // Reset selection in the target store when input loses focus const target = commandInputSyncTarget.value; diff --git a/packages/frontend/src/components/Terminal.vue b/packages/frontend/src/components/Terminal.vue index b8f41da..c8d9315 100644 --- a/packages/frontend/src/components/Terminal.vue +++ b/packages/frontend/src/components/Terminal.vue @@ -47,8 +47,9 @@ const { const settingsStore = useSettingsStore(); // +++ 实例化设置 store +++ const { autoCopyOnSelectBoolean, - terminalScrollbackLimitNumber // NEW: Import scrollback limit getter -} = storeToRefs(settingsStore); // +++ 获取选中即复制状态 +++ + terminalScrollbackLimitNumber, // Import scrollback limit getter + terminalEnableRightClickPasteBoolean, // Import right-click paste setting getter +} = storeToRefs(settingsStore); // +++ 获取设置状态 +++ // 防抖函数 const debounce = (func: Function, delay: number) => { @@ -121,7 +122,7 @@ const debouncedSetTerminalFontSize = debounce(async (size: number) => { } }, 500); // 500ms 防抖延迟,可以调整 -// NEW: Helper function to convert setting value to xterm scrollback value +// Helper function to convert setting value to xterm scrollback value const getScrollbackValue = (limit: number): number => { if (limit === 0) { return Infinity; // 0 means unlimited for xterm @@ -129,6 +130,36 @@ const getScrollbackValue = (limit: number): number => { return Math.max(0, limit); // Ensure non-negative, return the number otherwise }; +// --- 右键粘贴功能 --- +const handleContextMenuPaste = async (event: MouseEvent) => { + event.preventDefault(); // 阻止默认右键菜单 + try { + const text = await navigator.clipboard.readText(); + if (text && terminal) { + // 将粘贴的文本发送到后端 + emitWorkspaceEvent('terminal:input', { sessionId: props.sessionId, data: text }); + console.log('[Terminal] Pasted via Right Click'); + } + } catch (err) { + console.error('[Terminal] Failed to paste via Right Click:', err); + } +}; + +const addContextMenuListener = () => { + if (terminalRef.value) { + terminalRef.value.addEventListener('contextmenu', handleContextMenuPaste); + console.log(`[Terminal ${props.sessionId}] Right-click paste listener added.`); + } +}; + +const removeContextMenuListener = () => { + if (terminalRef.value) { + terminalRef.value.removeEventListener('contextmenu', handleContextMenuPaste); + console.log(`[Terminal ${props.sessionId}] Right-click paste listener removed.`); + } +}; +// --- 右键粘贴功能结束 --- + // 初始化终端 onMounted(() => { if (terminalRef.value) { @@ -142,7 +173,7 @@ onMounted(() => { allowTransparency: true, disableStdin: false, convertEol: true, - scrollback: getScrollbackValue(terminalScrollbackLimitNumber.value), // NEW: Use setting from store + scrollback: getScrollbackValue(terminalScrollbackLimitNumber.value), // Use setting from store scrollOnUserInput: true, // 输入时滚动到底部 ...props.options, // 合并外部传入的选项 }); @@ -402,24 +433,20 @@ onMounted(() => { }); } - - // --- 添加右键粘贴功能 --- - if (terminalRef.value) { - terminalRef.value.addEventListener('contextmenu', async (event: MouseEvent) => { - event.preventDefault(); // 阻止默认右键菜单 - try { - const text = await navigator.clipboard.readText(); - if (text && terminal) { - // 将粘贴的文本发送到后端 - emitWorkspaceEvent('terminal:input', { sessionId: props.sessionId, data: text }); - console.log('[Terminal] Pasted via Right Click'); - } - } catch (err) { - console.error('[Terminal] Failed to paste via Right Click:', err); - } - }); + // 根据初始设置添加监听器 + if (terminalEnableRightClickPasteBoolean.value) { + addContextMenuListener(); } + // 监听设置变化 + watch(terminalEnableRightClickPasteBoolean, (newValue) => { + if (newValue) { + addContextMenuListener(); + } else { + removeContextMenuListener(); + } + }); + // 重新添加鼠标滚轮缩放功能 if (terminalRef.value) { @@ -506,11 +533,14 @@ onBeforeUnmount(() => { selectionListenerDisposable.dispose(); } - if (terminalRef.value) { - - } -}); - + + // 确保在卸载时移除右键监听器 + removeContextMenuListener(); + + if (terminalRef.value) { + + } + }); // 暴露 write 方法给父组件 (可选) const write = (data: string | Uint8Array) => { terminal?.write(data); diff --git a/packages/frontend/src/components/settings/WorkspaceSettingsSection.vue b/packages/frontend/src/components/settings/WorkspaceSettingsSection.vue index 3bb5327..b28b987 100644 --- a/packages/frontend/src/components/settings/WorkspaceSettingsSection.vue +++ b/packages/frontend/src/components/settings/WorkspaceSettingsSection.vue @@ -79,7 +79,7 @@ -
+

{{ $t('settings.commandInputSync.title', '命令输入同步') }}

@@ -104,7 +104,7 @@
-
+

{{ $t('settings.workspace.showConnectionTagsTitle', '显示连接标签') }}

@@ -124,7 +124,7 @@
-
+

{{ $t('settings.workspace.showQuickCommandTagsTitle', '显示快捷指令标签') }}

@@ -144,7 +144,7 @@
-
+

{{ t('settings.terminalScrollback.title', '终端回滚行数') }}

@@ -164,7 +164,7 @@
-
+

{{ $t('settings.workspace.fileManagerDeleteConfirmTitle', '文件管理器删除确认') }}

@@ -183,6 +183,26 @@
+
+ +
+

{{ $t('settings.workspace.terminalRightClickPasteTitle', '终端右键粘贴') }}

+
+
+ + +
+

{{ $t('settings.workspace.terminalEnableRightClickPasteDescription', '允许在终端区域内使用鼠标右键粘贴剪贴板内容。') }}

+
+ +

{{ terminalEnableRightClickPasteMessage }}

+
+
+
@@ -243,6 +263,11 @@ const { fileManagerShowDeleteConfirmationMessage, fileManagerShowDeleteConfirmationSuccess, handleUpdateFileManagerDeleteConfirmation, + terminalEnableRightClickPasteLocal, // NEW + terminalEnableRightClickPasteLoading, // NEW (Not used in template, but available) + terminalEnableRightClickPasteMessage, // NEW + terminalEnableRightClickPasteSuccess, // NEW + handleUpdateTerminalRightClickPasteSetting, // NEW } = useWorkspaceSettings(); diff --git a/packages/frontend/src/composables/settings/useWorkspaceSettings.ts b/packages/frontend/src/composables/settings/useWorkspaceSettings.ts index d3da3c1..c14af9e 100644 --- a/packages/frontend/src/composables/settings/useWorkspaceSettings.ts +++ b/packages/frontend/src/composables/settings/useWorkspaceSettings.ts @@ -17,6 +17,7 @@ export function useWorkspaceSettings() { showQuickCommandTagsBoolean, terminalScrollbackLimitNumber, fileManagerShowDeleteConfirmationBoolean, + terminalEnableRightClickPasteBoolean, // NEW } = storeToRefs(settingsStore); // --- Popup Editor --- @@ -236,6 +237,30 @@ export function useWorkspaceSettings() { } }; + // --- Terminal Right Click Paste --- + const terminalEnableRightClickPasteLocal = ref(true); + const terminalEnableRightClickPasteLoading = ref(false); + const terminalEnableRightClickPasteMessage = ref(''); + const terminalEnableRightClickPasteSuccess = ref(false); + + const handleUpdateTerminalRightClickPasteSetting = async () => { + terminalEnableRightClickPasteLoading.value = true; + terminalEnableRightClickPasteMessage.value = ''; + terminalEnableRightClickPasteSuccess.value = false; + try { + const valueToSave = terminalEnableRightClickPasteLocal.value ? 'true' : 'false'; + await settingsStore.updateSetting('terminalEnableRightClickPaste', valueToSave); + terminalEnableRightClickPasteMessage.value = t('settings.workspace.terminalRightClickPasteSuccess', '终端右键粘贴设置已保存。'); + terminalEnableRightClickPasteSuccess.value = true; + } catch (error: any) { + console.error('更新终端右键粘贴设置失败:', error); + terminalEnableRightClickPasteMessage.value = error.message || t('settings.workspace.terminalRightClickPasteError', '保存终端右键粘贴设置失败。'); + terminalEnableRightClickPasteSuccess.value = false; + } finally { + terminalEnableRightClickPasteLoading.value = false; + } + }; + // Watchers to sync local state with store state watch(showPopupFileEditorBoolean, (newValue) => { popupEditorEnabled.value = newValue; }, { immediate: true }); watch(shareFileEditorTabsBoolean, (newValue) => { shareTabsEnabled.value = newValue; }, { immediate: true }); @@ -246,6 +271,7 @@ export function useWorkspaceSettings() { watch(showQuickCommandTagsBoolean, (newValue) => { showQuickCommandTagsLocal.value = newValue; }, { immediate: true }); watch(terminalScrollbackLimitNumber, (newValue) => { terminalScrollbackLimitLocal.value = newValue; }, { immediate: true }); watch(fileManagerShowDeleteConfirmationBoolean, (newValue) => { fileManagerShowDeleteConfirmationLocal.value = newValue; }, { immediate: true }); + watch(terminalEnableRightClickPasteBoolean, (newValue) => { terminalEnableRightClickPasteLocal.value = newValue; }, { immediate: true }); // NEW return { @@ -302,5 +328,11 @@ export function useWorkspaceSettings() { fileManagerShowDeleteConfirmationMessage, fileManagerShowDeleteConfirmationSuccess, handleUpdateFileManagerDeleteConfirmation, + + terminalEnableRightClickPasteLocal, // NEW + terminalEnableRightClickPasteLoading, // NEW + terminalEnableRightClickPasteMessage, // NEW + terminalEnableRightClickPasteSuccess, // NEW + handleUpdateTerminalRightClickPasteSetting, // NEW }; } \ No newline at end of file diff --git a/packages/frontend/src/stores/auth.store.ts b/packages/frontend/src/stores/auth.store.ts index 5731a6d..66c134f 100644 --- a/packages/frontend/src/stores/auth.store.ts +++ b/packages/frontend/src/stores/auth.store.ts @@ -62,10 +62,10 @@ interface AuthState { total: number; }; needsSetup: boolean; // 是否需要初始设置 - publicCaptchaConfig: PublicCaptchaConfig | null; // NEW: Public CAPTCHA config - passkeys: PasskeyInfo[] | null; // NEW: Store for user's passkeys - passkeysLoading: boolean; // NEW: Loading state for passkeys - hasPasskeysAvailable: boolean; // NEW: Indicates if passkeys are available for login + publicCaptchaConfig: PublicCaptchaConfig | null; // Public CAPTCHA config + passkeys: PasskeyInfo[] | null; // Store for user's passkeys + passkeysLoading: boolean; // Loading state for passkeys + hasPasskeysAvailable: boolean; // Indicates if passkeys are available for login } export const useAuthStore = defineStore('auth', { @@ -77,7 +77,7 @@ export const useAuthStore = defineStore('auth', { loginRequires2FA: false, // 初始为不需要 ipBlacklist: { entries: [], total: 0 }, // 初始化黑名单状态 needsSetup: false, // 初始假设不需要设置 - publicCaptchaConfig: null, // NEW: Initialize CAPTCHA config as null + publicCaptchaConfig: null, // Initialize CAPTCHA config as null passkeys: null, // Initialize passkeys as null passkeysLoading: false, // Initialize passkeysLoading as false hasPasskeysAvailable: false, // Initialize as false @@ -322,7 +322,7 @@ export const useAuthStore = defineStore('auth', { } }, - // NEW: 获取公共 CAPTCHA 配置 (修改为从 /settings/captcha 获取) + // 获取公共 CAPTCHA 配置 (修改为从 /settings/captcha 获取) async fetchCaptchaConfig() { console.log('[AuthStore] fetchCaptchaConfig called. Forcing refetch.'); // 更新日志,表明强制刷新 diff --git a/packages/frontend/src/stores/commandHistory.store.ts b/packages/frontend/src/stores/commandHistory.store.ts index abe4c52..8b88e41 100644 --- a/packages/frontend/src/stores/commandHistory.store.ts +++ b/packages/frontend/src/stores/commandHistory.store.ts @@ -21,7 +21,7 @@ export const useCommandHistoryStore = defineStore('commandHistory', () => { const isLoading = ref(false); const error = ref(null); const uiNotificationsStore = useUiNotificationsStore(); - const selectedIndex = ref(-1); // NEW: Index of the selected command in the filtered list + const selectedIndex = ref(-1); // Index of the selected command in the filtered list // --- Getters --- @@ -38,7 +38,7 @@ export const useCommandHistoryStore = defineStore('commandHistory', () => { // --- Actions --- - // NEW: Action to select the next command in the filtered list + // Action to select the next command in the filtered list const selectNextCommand = () => { const history = filteredHistory.value; if (history.length === 0) { @@ -48,7 +48,7 @@ export const useCommandHistoryStore = defineStore('commandHistory', () => { selectedIndex.value = (selectedIndex.value + 1) % history.length; }; - // NEW: Action to select the previous command in the filtered list + // Action to select the previous command in the filtered list const selectPreviousCommand = () => { const history = filteredHistory.value; if (history.length === 0) { @@ -110,7 +110,7 @@ export const useCommandHistoryStore = defineStore('commandHistory', () => { // 添加命令到历史记录 (由 CommandInputBar 调用, 添加后清除缓存) const addCommand = async (command: string) => { - // NEW: Filter out Ctrl+C signal (\x03) from being added to history + // Filter out Ctrl+C signal (\x03) from being added to history if (command === '\x03') { console.log('[CmdHistoryStore] Ignoring Ctrl+C signal for history.'); return; @@ -172,7 +172,7 @@ export const useCommandHistoryStore = defineStore('commandHistory', () => { selectedIndex.value = -1; // Reset selection when search term changes }; - // NEW: Action to reset the selection (Moved before return) + // Action to reset the selection (Moved before return) const resetSelection = () => { selectedIndex.value = -1; }; @@ -183,14 +183,14 @@ export const useCommandHistoryStore = defineStore('commandHistory', () => { isLoading, error, filteredHistory, - selectedIndex, // NEW: Expose selected index + selectedIndex, // Expose selected index fetchHistory, addCommand, // 导出 addCommand deleteCommand, clearAllHistory, setSearchTerm, - selectNextCommand, // NEW: Expose action - selectPreviousCommand, // NEW: Expose action + selectNextCommand, // Expose action + selectPreviousCommand, // Expose action resetSelection, // Ensure resetSelection is exported }; diff --git a/packages/frontend/src/stores/quickCommands.store.ts b/packages/frontend/src/stores/quickCommands.store.ts index 64a697b..1e1c321 100644 --- a/packages/frontend/src/stores/quickCommands.store.ts +++ b/packages/frontend/src/stores/quickCommands.store.ts @@ -398,7 +398,7 @@ export const useQuickCommandsStore = defineStore('quickCommands', () => { } }; - // NEW: Action to reset the selection + // Action to reset the selection const resetSelection = () => { selectedIndex.value = -1; }; diff --git a/packages/frontend/src/stores/settings.store.ts b/packages/frontend/src/stores/settings.store.ts index c396acc..d709ff9 100644 --- a/packages/frontend/src/stores/settings.store.ts +++ b/packages/frontend/src/stores/settings.store.ts @@ -39,27 +39,28 @@ interface SettingsState { shareFileEditorTabs?: string; // 'true' or 'false' ipWhitelistEnabled?: string; // 添加 IP 白名单启用状态 'true' or 'false' autoCopyOnSelect?: string; // 'true' or 'false' - 终端选中自动复制 - dockerStatusIntervalSeconds?: string; // NEW: Docker 状态刷新间隔 (秒) - dockerDefaultExpand?: string; // NEW: Docker 默认展开详情 'true' or 'false' - statusMonitorIntervalSeconds?: string; // NEW: 状态监控轮询间隔 (秒) - workspaceSidebarPersistent?: string; // NEW: 工作区侧边栏是否固定 'true' or 'false' - sidebarPaneWidths?: string; // NEW: 存储各侧边栏组件宽度的 JSON 字符串 - fileManagerRowSizeMultiplier?: string; // NEW: 文件管理器行大小乘数 (e.g., '1.0') - fileManagerColWidths?: string; // NEW: 文件管理器列宽 JSON 字符串 (e.g., '{"name": 300, "size": 100}') - commandInputSyncTarget?: 'quickCommands' | 'commandHistory' | 'none'; // NEW: 命令输入同步目标 - timezone?: string; // NEW: 时区设置 (e.g., 'Asia/Shanghai', 'UTC') - rdpModalWidth?: string; // NEW: RDP 模态框宽度 - rdpModalHeight?: string; // NEW: RDP 模态框高度 - vncModalWidth?: string; // NEW: VNC 模态框宽度 - vncModalHeight?: string; // NEW: VNC 模态框高度 + dockerStatusIntervalSeconds?: string; // Docker 状态刷新间隔 (秒) + dockerDefaultExpand?: string; // Docker 默认展开详情 'true' or 'false' + statusMonitorIntervalSeconds?: string; // 状态监控轮询间隔 (秒) + workspaceSidebarPersistent?: string; // 工作区侧边栏是否固定 'true' or 'false' + sidebarPaneWidths?: string; // 存储各侧边栏组件宽度的 JSON 字符串 + fileManagerRowSizeMultiplier?: string; // 文件管理器行大小乘数 (e.g., '1.0') + fileManagerColWidths?: string; // 文件管理器列宽 JSON 字符串 (e.g., '{"name": 300, "size": 100}') + commandInputSyncTarget?: 'quickCommands' | 'commandHistory' | 'none'; // 命令输入同步目标 + timezone?: string; // 时区设置 (e.g., 'Asia/Shanghai', 'UTC') + rdpModalWidth?: string; // RDP 模态框宽度 + rdpModalHeight?: string; // RDP 模态框高度 + vncModalWidth?: string; // VNC 模态框宽度 + vncModalHeight?: string; // VNC 模态框高度 ipBlacklistEnabled?: string; dashboardSortBy?: SortField; dashboardSortOrder?: SortOrder; showConnectionTags?: string; // 'true' or 'false' showQuickCommandTags?: string; // 'true' or 'false' layoutLocked?: string; // 'true' or 'false' - NEW: 布局锁定状态 - terminalScrollbackLimit?: string; // NEW: 终端回滚行数上限 (e.g., '5000', '0' for unlimited) - fileManagerShowDeleteConfirmation?: string; // NEW: 'true' or 'false' - 文件管理器删除确认提示 + terminalScrollbackLimit?: string; // 终端回滚行数上限 (e.g., '5000', '0' for unlimited) + fileManagerShowDeleteConfirmation?: string; // 'true' or 'false' - 文件管理器删除确认提示 + terminalEnableRightClickPaste?: string; // 'true' or 'false' - 终端右键粘贴 [key: string]: string | undefined; } @@ -69,9 +70,9 @@ export const useSettingsStore = defineStore('settings', () => { // --- State --- const settings = ref>({}); // 通用设置状态 - const parsedSidebarPaneWidths = ref>({}); // NEW: 解析后的侧边栏宽度对象 - const parsedFileManagerColWidths = ref>({}); // NEW: 解析后的文件管理器列宽对象 - const captchaSettings = ref(null); // NEW: CAPTCHA 设置状态 + const parsedSidebarPaneWidths = ref>({}); // 解析后的侧边栏宽度对象 + const parsedFileManagerColWidths = ref>({}); // 解析后的文件管理器列宽对象 + const captchaSettings = ref(null); // CAPTCHA 设置状态 const isLoading = ref(false); const error = ref(null); // 移除外观相关状态: isStyleCustomizerVisible, currentUiTheme, currentXtermTheme @@ -127,7 +128,7 @@ export const useSettingsStore = defineStore('settings', () => { } - // NEW: IP Blacklist enabled default + // IP Blacklist enabled default if (settings.value.ipBlacklistEnabled === undefined) { settings.value.ipBlacklistEnabled = 'true'; // 默认启用 IP 黑名单 } @@ -135,22 +136,22 @@ export const useSettingsStore = defineStore('settings', () => { if (settings.value.autoCopyOnSelect === undefined) { settings.value.autoCopyOnSelect = 'false'; // 默认禁用选中即复制 } - // NEW: Docker setting defaults + // Docker setting defaults if (settings.value.dockerStatusIntervalSeconds === undefined) { settings.value.dockerStatusIntervalSeconds = '2'; // 默认 2 秒 } if (settings.value.dockerDefaultExpand === undefined) { settings.value.dockerDefaultExpand = 'false'; // 默认不展开 } - // NEW: Status Monitor interval default + // Status Monitor interval default if (settings.value.statusMonitorIntervalSeconds === undefined) { settings.value.statusMonitorIntervalSeconds = '3'; // 默认 3 秒 } - // NEW: Workspace sidebar persistent default + // Workspace sidebar persistent default if (settings.value.workspaceSidebarPersistent === undefined) { settings.value.workspaceSidebarPersistent = 'false'; // 默认不固定 } - // NEW: Load and parse sidebar pane widths + // Load and parse sidebar pane widths const defaultPaneWidth = '350px'; // +++ Ensure PaneName type is available or define it here +++ const knownPanes: PaneName[] = ['connections', 'fileManager', 'editor', 'statusMonitor', 'commandHistory', 'quickCommands', 'dockerManager']; // Add all possible sidebar panes @@ -178,7 +179,7 @@ export const useSettingsStore = defineStore('settings', () => { // await updateSetting('sidebarPaneWidths', JSON.stringify(finalWidths)); // } - // NEW: Load and parse file manager layout settings + // Load and parse file manager layout settings const defaultFileManagerRowMultiplier = '1.0'; const defaultFileManagerColWidths = { type: 50, name: 300, size: 100, permissions: 120, modified: 180 }; @@ -237,22 +238,22 @@ export const useSettingsStore = defineStore('settings', () => { // await updateSetting('fileManagerColWidths', finalFmWidthsString); // } - // NEW: Command Input Sync Target default + // Command Input Sync Target default if (settings.value.commandInputSyncTarget === undefined) { settings.value.commandInputSyncTarget = 'none'; // 默认不同步 } - // NEW: Timezone default + // Timezone default if (settings.value.timezone === undefined) { settings.value.timezone = 'UTC'; // 默认 UTC } - // NEW: RDP Modal Size defaults + // RDP Modal Size defaults if (settings.value.rdpModalWidth === undefined) { settings.value.rdpModalWidth = '1064'; // 默认宽度 (1024 + 40 padding) } if (settings.value.rdpModalHeight === undefined) { settings.value.rdpModalHeight = '858'; } - // NEW: VNC Modal Size defaults + // VNC Modal Size defaults if (settings.value.vncModalWidth === undefined) { settings.value.vncModalWidth = '1024'; // 默认宽度 } @@ -267,30 +268,38 @@ export const useSettingsStore = defineStore('settings', () => { settings.value.dashboardSortOrder = 'desc'; } - // NEW: Tag visibility defaults + // Tag visibility defaults if (settings.value.showConnectionTags === undefined) { settings.value.showConnectionTags = 'true'; // 默认显示 } if (settings.value.showQuickCommandTags === undefined) { settings.value.showQuickCommandTags = 'true'; // 默认显示 } // +++ Add missing closing brace +++ - // NEW: Layout locked default - Only set if not provided by backend + // Layout locked default - Only set if not provided by backend if (settings.value.layoutLocked === undefined) { settings.value.layoutLocked = 'false'; // 默认不锁定 console.log('[SettingsStore] layoutLocked not found in fetched settings, set to default: false'); } else { console.log(`[SettingsStore] layoutLocked found in fetched settings: ${settings.value.layoutLocked}`); } - // NEW: Terminal scrollback limit default + // Terminal scrollback limit default if (settings.value.terminalScrollbackLimit === undefined) { settings.value.terminalScrollbackLimit = '5000'; // 默认 5000 行 console.log(`[SettingsStore] terminalScrollbackLimit not found, set to default: ${settings.value.terminalScrollbackLimit}`); } - // NEW: File Manager Delete Confirmation default + // File Manager Delete Confirmation default if (settings.value.fileManagerShowDeleteConfirmation === undefined) { settings.value.fileManagerShowDeleteConfirmation = 'true'; // 默认显示删除确认 console.log(`[SettingsStore] fileManagerShowDeleteConfirmation not found, set to default: ${settings.value.fileManagerShowDeleteConfirmation}`); } + // Terminal Right Click Paste default + // --- 添加日志:打印从后端获取的原始值 --- + console.log(`[SettingsStore DEBUG] Raw terminalEnableRightClickPaste from backend: '${settings.value.terminalEnableRightClickPaste}' (type: ${typeof settings.value.terminalEnableRightClickPaste})`); + // --- 日志结束 --- + if (settings.value.terminalEnableRightClickPaste === undefined) { + settings.value.terminalEnableRightClickPaste = 'true'; // 默认启用右键粘贴 + console.log(`[SettingsStore] terminalEnableRightClickPaste not found, set to default: ${settings.value.terminalEnableRightClickPaste}`); + } // --- 语言设置 --- @@ -370,11 +379,11 @@ export const useSettingsStore = defineStore('settings', () => { 'fileManagerRowSizeMultiplier', // +++ 添加文件管理器行大小键 +++ 'fileManagerColWidths', // +++ 添加文件管理器列宽键 +++ 'commandInputSyncTarget', // +++ 添加命令输入同步目标键 +++ - 'timezone', // NEW: 添加时区键 - 'rdpModalWidth', // NEW: 添加 RDP 模态框宽度键 - 'rdpModalHeight', // NEW: 添加 RDP 模态框高度键 - 'vncModalWidth', // NEW: 添加 VNC 模态框宽度键 - 'vncModalHeight', // NEW: 添加 VNC 模态框高度键 + 'timezone', // 添加时区键 + 'rdpModalWidth', // 添加 RDP 模态框宽度键 + 'rdpModalHeight', // 添加 RDP 模态框高度键 + 'vncModalWidth', // 添加 VNC 模态框宽度键 + 'vncModalHeight', // 添加 VNC 模态框高度键 'ipBlacklistEnabled', 'dashboardSortBy', 'dashboardSortOrder', @@ -382,7 +391,8 @@ export const useSettingsStore = defineStore('settings', () => { 'showQuickCommandTags', // NEW 'layoutLocked', // NEW 'terminalScrollbackLimit', // NEW - 'fileManagerShowDeleteConfirmation' // NEW + 'fileManagerShowDeleteConfirmation', // NEW + 'terminalEnableRightClickPaste' // NEW ]; if (!allowedKeys.includes(key)) { console.error(`[SettingsStore] 尝试更新不允许的设置键: ${key}`); @@ -405,6 +415,11 @@ export const useSettingsStore = defineStore('settings', () => { console.log(`[SettingsStore] Attempting to update boolean setting via specific endpoint - Key: ${key}, Value: ${value}, Endpoint: ${endpoint}`); apiPromise = apiClient.put(endpoint, { enabled: value }); } else if (typeof value === 'string') { + // --- 添加针对 terminalEnableRightClickPaste 的特定日志 --- + if (key === 'terminalEnableRightClickPaste') { + console.log(`[SettingsStore DEBUG] Updating terminalEnableRightClickPaste. Value type: ${typeof value}, Value: '${value}'`); + } + // --- 日志结束 --- console.log(`[SettingsStore] Attempting to update general setting - Key: ${key}, Value: ${value}`); const payload = { [key]: value }; console.log('[SettingsStore] Sending PUT request to /settings with payload:', payload); @@ -459,11 +474,11 @@ export const useSettingsStore = defineStore('settings', () => { 'fileManagerRowSizeMultiplier', // +++ 添加文件管理器行大小键 +++ 'fileManagerColWidths', // +++ 添加文件管理器列宽键 +++ 'commandInputSyncTarget', // +++ 添加命令输入同步目标键 +++ - 'timezone', // NEW: 添加时区键 - 'rdpModalWidth', // NEW: 添加 RDP 模态框宽度键 - 'rdpModalHeight', // NEW: 添加 RDP 模态框高度键 - 'vncModalWidth', // NEW: 添加 VNC 模态框宽度键 - 'vncModalHeight', // NEW: 添加 VNC 模态框高度键 + 'timezone', // 添加时区键 + 'rdpModalWidth', // 添加 RDP 模态框宽度键 + 'rdpModalHeight', // 添加 RDP 模态框高度键 + 'vncModalWidth', // 添加 VNC 模态框宽度键 + 'vncModalHeight', // 添加 VNC 模态框高度键 'ipBlacklistEnabled', 'dashboardSortBy', 'dashboardSortOrder', @@ -471,7 +486,8 @@ export const useSettingsStore = defineStore('settings', () => { 'showQuickCommandTags', // NEW 'layoutLocked', // NEW 'terminalScrollbackLimit', // NEW - 'fileManagerShowDeleteConfirmation' // NEW + 'fileManagerShowDeleteConfirmation', // NEW + 'terminalEnableRightClickPaste' // NEW ]; const filteredUpdates: Partial = {}; let languageUpdate: string | undefined = undefined; @@ -667,12 +683,12 @@ export const useSettingsStore = defineStore('settings', () => { return settings.value.autoCopyOnSelect === 'true'; }); - // NEW: Getter for workspace sidebar persistent setting, returning boolean + // Getter for workspace sidebar persistent setting, returning boolean const workspaceSidebarPersistentBoolean = computed(() => { return settings.value.workspaceSidebarPersistent === 'true'; }); - // NEW: Getter to get width for a specific sidebar pane + // Getter to get width for a specific sidebar pane const getSidebarPaneWidth = computed(() => (paneName: PaneName | null): string => { const defaultWidth = '350px'; if (!paneName) return defaultWidth; @@ -681,30 +697,30 @@ export const useSettingsStore = defineStore('settings', () => { return widths[paneName] || defaultWidth; }); - // NEW: Getter for Docker default expand setting, returning boolean + // Getter for Docker default expand setting, returning boolean const dockerDefaultExpandBoolean = computed(() => { return settings.value.dockerDefaultExpand === 'true'; }); - // NEW: Getter for Status Monitor interval, returning number + // Getter for Status Monitor interval, returning number const statusMonitorIntervalSecondsNumber = computed(() => { const val = parseInt(settings.value.statusMonitorIntervalSeconds || '3', 10); return isNaN(val) || val <= 0 ? 3 : val; // Fallback to 3 if invalid }); - // NEW: Getter for File Manager row size multiplier, returning number + // Getter for File Manager row size multiplier, returning number const fileManagerRowSizeMultiplierNumber = computed(() => { const val = parseFloat(settings.value.fileManagerRowSizeMultiplier || '1.0'); return isNaN(val) || val <= 0 ? 1.0 : val; // Fallback to 1.0 if invalid }); - // NEW: Getter for File Manager column widths, returning object + // Getter for File Manager column widths, returning object const fileManagerColWidthsObject = computed(() => { // Return the reactive ref directly, which is updated during load and save return parsedFileManagerColWidths.value; }); - // NEW: Getter for command input sync target + // Getter for command input sync target const commandInputSyncTarget = computed(() => { const target = settings.value.commandInputSyncTarget; if (target === 'quickCommands' || target === 'commandHistory') { @@ -713,7 +729,7 @@ export const useSettingsStore = defineStore('settings', () => { return 'none'; // Default to 'none' if invalid or not set }); - // NEW: Getter for timezone setting + // Getter for timezone setting const timezone = computed(() => settings.value.timezone || 'UTC'); const dashboardSortBy = computed((): SortField => { @@ -733,7 +749,7 @@ export const useSettingsStore = defineStore('settings', () => { const recaptchaSiteKey = computed(() => captchaSettings.value?.recaptchaSiteKey ?? ''); // DO NOT expose secret keys via getters - // NEW: Getters for tag visibility + // Getters for tag visibility const showConnectionTagsBoolean = computed(() => { return settings.value.showConnectionTags !== 'false'; // Default to true }); @@ -741,12 +757,12 @@ export const useSettingsStore = defineStore('settings', () => { return settings.value.showQuickCommandTags !== 'false'; // Default to true }); - // NEW: Getter for layout locked status + // Getter for layout locked status const layoutLockedBoolean = computed(() => { return settings.value.layoutLocked === 'true'; }); - // NEW: Getter for terminal scrollback limit, returning number (0 means Infinity for xterm) + // Getter for terminal scrollback limit, returning number (0 means Infinity for xterm) const terminalScrollbackLimitNumber = computed(() => { const valStr = settings.value.terminalScrollbackLimit; if (valStr === null || valStr === undefined || valStr.trim() === '') { @@ -759,10 +775,15 @@ export const useSettingsStore = defineStore('settings', () => { return val; // Return 0 if it's 0, or the positive number }); - // NEW: Getter for File Manager delete confirmation, returning boolean + // Getter for File Manager delete confirmation, returning boolean const fileManagerShowDeleteConfirmationBoolean = computed(() => { return settings.value.fileManagerShowDeleteConfirmation !== 'false'; // Default to true }); + + // Getter for Terminal Right Click Paste, returning boolean + const terminalEnableRightClickPasteBoolean = computed(() => { + return settings.value.terminalEnableRightClickPaste !== 'false'; // Default to true + }); return { settings, // 只包含通用设置 @@ -799,12 +820,13 @@ export const useSettingsStore = defineStore('settings', () => { dashboardSortBy, dashboardSortOrder, saveDashboardSortPreference, - // NEW: Expose tag visibility getters + // Expose tag visibility getters showConnectionTagsBoolean, showQuickCommandTagsBoolean, - // NEW: Expose layout locked getter + // Expose layout locked getter layoutLockedBoolean, - terminalScrollbackLimitNumber, // NEW: Expose terminal scrollback limit getter - fileManagerShowDeleteConfirmationBoolean, // NEW: Expose file manager delete confirmation getter + terminalScrollbackLimitNumber, // Expose terminal scrollback limit getter + fileManagerShowDeleteConfirmationBoolean, // Expose file manager delete confirmation getter + terminalEnableRightClickPasteBoolean, // Expose terminal right click paste getter }; }); diff --git a/packages/frontend/src/views/LoginView.vue b/packages/frontend/src/views/LoginView.vue index 682cbca..919ba32 100644 --- a/packages/frontend/src/views/LoginView.vue +++ b/packages/frontend/src/views/LoginView.vue @@ -19,9 +19,9 @@ const credentials = reactive({ }); const twoFactorToken = ref(''); // 用于存储 2FA 验证码 const rememberMe = ref(false); // 记住我状态,默认为 false -const captchaToken = ref(null); // NEW: Store CAPTCHA token -const captchaError = ref(null); // NEW: Store CAPTCHA specific error -const hcaptchaWidget = ref | null>(null); // NEW: Ref for hCaptcha component instance +const captchaToken = ref(null); // Store CAPTCHA token +const captchaError = ref(null); // Store CAPTCHA specific error +const hcaptchaWidget = ref | null>(null); // Ref for hCaptcha component instance const recaptchaWidget = ref | null>(null); // 更新 Ref 类型以匹配新导入 // --- reCAPTCHA v3 Initialization ---