From 4c983945dbb7e81461c4cf53e608657863a2f3cf Mon Sep 17 00:00:00 2001 From: Baobhan Sith <80159437+Heavrnl@users.noreply.github.com> Date: Mon, 5 May 2025 17:06:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=BB=88=E7=AB=AF?= =?UTF-8?q?=E5=9B=9E=E6=BB=9A=E8=A1=8C=E6=95=B0=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/repositories/settings.repository.ts | 1 + .../src/settings/settings.controller.ts | 3 +- packages/frontend/src/components/Terminal.vue | 15 ++++- packages/frontend/src/locales/en-US.json | 13 +++++ packages/frontend/src/locales/ja-JP.json | 13 +++++ packages/frontend/src/locales/zh-CN.json | 13 +++++ .../frontend/src/stores/settings.store.ts | 25 +++++++- packages/frontend/src/views/SettingsView.vue | 58 +++++++++++++++++++ 8 files changed, 136 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/repositories/settings.repository.ts b/packages/backend/src/repositories/settings.repository.ts index 5e7db2d..de46f17 100644 --- a/packages/backend/src/repositories/settings.repository.ts +++ b/packages/backend/src/repositories/settings.repository.ts @@ -268,6 +268,7 @@ export const ensureDefaultSettingsExist = async (db: sqlite3.Database): Promise< [SIDEBAR_CONFIG_KEY]: JSON.stringify(defaultSidebarPanesStructure), [CAPTCHA_CONFIG_KEY]: JSON.stringify(defaultCaptchaSettings), timezone: 'UTC', // NEW: 添加时区默认值 + terminalScrollbackLimit: '5000', // NEW: 添加终端回滚行数默认值 }; 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 ea36bcb..82842ca 100644 --- a/packages/backend/src/settings/settings.controller.ts +++ b/packages/backend/src/settings/settings.controller.ts @@ -48,7 +48,8 @@ export const settingsController = { 'rdpModalWidth', // NEW: 添加 RDP 模态框宽度键 'rdpModalHeight', // NEW: 添加 RDP 模态框高度键 'ipBlacklistEnabled', // <-- 添加 IP 黑名单启用键 - 'layoutLocked' // +++ 添加布局锁定键 +++ + 'layoutLocked', // +++ 添加布局锁定键 +++ + 'terminalScrollbackLimit' // NEW: 添加终端回滚行数键 ]; const filteredSettings: Record = {}; for (const key in settingsToUpdate) { diff --git a/packages/frontend/src/components/Terminal.vue b/packages/frontend/src/components/Terminal.vue index 45216f5..a35e64e 100644 --- a/packages/frontend/src/components/Terminal.vue +++ b/packages/frontend/src/components/Terminal.vue @@ -46,7 +46,10 @@ const { // --- Settings Store --- const settingsStore = useSettingsStore(); // +++ 实例化设置 store +++ -const { autoCopyOnSelectBoolean } = storeToRefs(settingsStore); // +++ 获取选中即复制状态 +++ +const { + autoCopyOnSelectBoolean, + terminalScrollbackLimitNumber // NEW: Import scrollback limit getter +} = storeToRefs(settingsStore); // +++ 获取选中即复制状态 +++ // 防抖函数 const debounce = (func: Function, delay: number) => { @@ -119,6 +122,14 @@ const debouncedSetTerminalFontSize = debounce(async (size: number) => { } }, 500); // 500ms 防抖延迟,可以调整 +// NEW: 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 + } + return Math.max(0, limit); // Ensure non-negative, return the number otherwise +}; + // 初始化终端 onMounted(() => { if (terminalRef.value) { @@ -132,7 +143,7 @@ onMounted(() => { allowTransparency: true, disableStdin: false, convertEol: true, - scrollback: 1000, // 减少可滚动历史行数 + scrollback: getScrollbackValue(terminalScrollbackLimitNumber.value), // NEW: Use setting from store scrollOnUserInput: true, // 输入时滚动到底部 ...props.options, // 合并外部传入的选项 }); diff --git a/packages/frontend/src/locales/en-US.json b/packages/frontend/src/locales/en-US.json index eabecc1..1637e5f 100644 --- a/packages/frontend/src/locales/en-US.json +++ b/packages/frontend/src/locales/en-US.json @@ -640,6 +640,19 @@ "showQuickCommandTagsLabel": "Show tags in quick command list", "showQuickCommandTagsDescription": "Disable to hide tags in the quick command list and exclude them from search." }, + "terminalScrollback": { + "title": "Terminal Scrollback Limit", + "limitLabel": "Maximum Lines", + "limitHint": "Set the maximum number of output lines the terminal keeps. 0 means unlimited (uses default 5000). This setting takes effect the next time a terminal is opened.", + "saveButton": "Save", + "success": { + "saved": "Terminal scrollback limit setting saved successfully." + }, + "error": { + "saveFailed": "Failed to save terminal scrollback limit setting.", + "invalidInput": "Please enter a valid non-negative integer." + } + }, "ipBlacklist": { "title": "IP Blacklist Management", "description": "Configure login attempt limits and automatic ban duration. Local addresses (127.0.0.1, ::1) will not be banned.", diff --git a/packages/frontend/src/locales/ja-JP.json b/packages/frontend/src/locales/ja-JP.json index effe49a..4c45d4b 100644 --- a/packages/frontend/src/locales/ja-JP.json +++ b/packages/frontend/src/locales/ja-JP.json @@ -874,6 +874,19 @@ "showQuickCommandTagsLabel": "クイックコマンドリストにタグを表示", "showQuickCommandTagsDescription": "無効にすると、クイックコマンドリストのタグが非表示になり、検索から除外されます。" }, + "terminalScrollback": { + "title": "ターミナルスクロールバック制限", + "limitLabel": "最大行数", + "limitHint": "ターミナルが保持する最大出力行数を設定します。0 は無制限を意味します (デフォルト値 5000 を使用)。この設定は、次にターミナルを開いたときに有効になります。", + "saveButton": "保存", + "success": { + "saved": "ターミナルスクロールバック制限の設定が正常に保存されました。" + }, + "error": { + "saveFailed": "ターミナルスクロールバック制限の設定の保存に失敗しました。", + "invalidInput": "有効な非負整数を入力してください。" + } + }, "about": { "version": "バージョン", "checkingUpdate": "更新を確認中...", diff --git a/packages/frontend/src/locales/zh-CN.json b/packages/frontend/src/locales/zh-CN.json index 449b173..e27cc62 100644 --- a/packages/frontend/src/locales/zh-CN.json +++ b/packages/frontend/src/locales/zh-CN.json @@ -640,6 +640,19 @@ "showQuickCommandTagsLabel": "在快捷指令列表中显示标签", "showQuickCommandTagsDescription": "关闭后将隐藏快捷指令列表中的标签,并从搜索中排除标签。" }, + "terminalScrollback": { + "title": "终端回滚行数", + "limitLabel": "最大行数", + "limitHint": "设置终端保留的最大输出行数。0 表示无限制 (使用默认值 5000)。此设置将在下次打开终端时生效。", + "saveButton": "保存", + "success": { + "saved": "终端回滚行数设置已保存。" + }, + "error": { + "saveFailed": "保存终端回滚行数设置失败。", + "invalidInput": "请输入一个有效的非负整数。" + } + }, "ipBlacklist": { "title": "IP 黑名单管理", "description": "配置登录失败次数限制和自动封禁时长。本地地址 (127.0.0.1, ::1) 不会被封禁。", diff --git a/packages/frontend/src/stores/settings.store.ts b/packages/frontend/src/stores/settings.store.ts index 2582381..de4d7c4 100644 --- a/packages/frontend/src/stores/settings.store.ts +++ b/packages/frontend/src/stores/settings.store.ts @@ -56,6 +56,7 @@ interface SettingsState { 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) [key: string]: string | undefined; } @@ -270,6 +271,11 @@ export const useSettingsStore = defineStore('settings', () => { } else { console.log(`[SettingsStore] layoutLocked found in fetched settings: ${settings.value.layoutLocked}`); } + // NEW: 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}`); + } // --- 语言设置 --- @@ -357,7 +363,8 @@ export const useSettingsStore = defineStore('settings', () => { 'dashboardSortOrder', 'showConnectionTags', // NEW 'showQuickCommandTags', // NEW - 'layoutLocked' // NEW + 'layoutLocked', // NEW + 'terminalScrollbackLimit' // NEW ]; if (!allowedKeys.includes(key)) { console.error(`[SettingsStore] 尝试更新不允许的设置键: ${key}`); @@ -442,7 +449,8 @@ export const useSettingsStore = defineStore('settings', () => { 'dashboardSortOrder', 'showConnectionTags', // NEW 'showQuickCommandTags', // NEW - 'layoutLocked' // NEW + 'layoutLocked', // NEW + 'terminalScrollbackLimit' // NEW ]; const filteredUpdates: Partial = {}; let languageUpdate: string | undefined = undefined; @@ -717,6 +725,18 @@ export const useSettingsStore = defineStore('settings', () => { return settings.value.layoutLocked === 'true'; }); + // NEW: 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() === '') { + return 5000; // Default value if not set or empty + } + const val = parseInt(valStr, 10); + if (isNaN(val) || val < 0) { + return 5000; // Default value if invalid number or negative + } + return val; // Return 0 if it's 0, or the positive number + }); return { settings, // 只包含通用设置 @@ -758,5 +778,6 @@ export const useSettingsStore = defineStore('settings', () => { showQuickCommandTagsBoolean, // NEW: Expose layout locked getter layoutLockedBoolean, + terminalScrollbackLimitNumber, // NEW: Expose terminal scrollback limit getter }; }); diff --git a/packages/frontend/src/views/SettingsView.vue b/packages/frontend/src/views/SettingsView.vue index 9d53848..ba05369 100644 --- a/packages/frontend/src/views/SettingsView.vue +++ b/packages/frontend/src/views/SettingsView.vue @@ -484,6 +484,26 @@ +
+ +
+

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

+
+
+ + + {{ t('settings.terminalScrollback.limitHint', '设置终端保留的最大输出行数。0 或留空表示无限制 (使用默认值 5000)。此设置将在下次打开终端时生效。') }} +
+
+ +

{{ terminalScrollbackLimitMessage }}

+
+
+
@@ -666,6 +686,7 @@ const { ipBlacklistEnabledBoolean, // <-- Import IP Blacklist enabled getter showConnectionTagsBoolean, // NEW: Import connection tag visibility getter showQuickCommandTagsBoolean, // NEW: Import quick command tag visibility getter + terminalScrollbackLimitNumber, // NEW: Import terminal scrollback limit getter } = storeToRefs(settingsStore); // Removed Passkey state import from authStore @@ -692,6 +713,7 @@ const commandInputSyncTargetLocal = ref<'none' | 'quickCommands' | 'commandHisto const ipBlacklistEnabled = ref(true); // <-- Local state for IP Blacklist switch const showConnectionTagsLocal = ref(true); // NEW: Local state for connection tags switch const showQuickCommandTagsLocal = ref(true); // NEW: Local state for quick command tags switch +const terminalScrollbackLimitLocal = ref(null); // NEW: Local state for terminal scrollback limit input (allow null for empty input) // --- Local UI feedback state --- const ipWhitelistLoading = ref(false); @@ -742,6 +764,9 @@ const showConnectionTagsSuccess = ref(false); // NEW const showQuickCommandTagsLoading = ref(false); // NEW const showQuickCommandTagsMessage = ref(''); // NEW const showQuickCommandTagsSuccess = ref(false); // NEW +const terminalScrollbackLimitLoading = ref(false); // NEW +const terminalScrollbackLimitMessage = ref(''); // NEW +const terminalScrollbackLimitSuccess = ref(false); // NEW // CAPTCHA Form State const captchaForm = reactive({ // Use reactive for the form object enabled: false, @@ -792,6 +817,8 @@ watch(settings, (newSettings, oldSettings) => { ipBlacklistEnabled.value = ipBlacklistEnabledBoolean.value; // <-- Sync IP Blacklist enabled state showConnectionTagsLocal.value = showConnectionTagsBoolean.value; // NEW: Sync connection tags state showQuickCommandTagsLocal.value = showQuickCommandTagsBoolean.value; // NEW: Sync quick command tags state + // NEW: Directly sync terminal scrollback limit from store getter to local state + terminalScrollbackLimitLocal.value = terminalScrollbackLimitNumber.value; }, { deep: true, immediate: true }); // immediate: true to run on initial load @@ -816,6 +843,9 @@ watch(captchaSettings, (newCaptchaSettings) => { }, { immediate: true }); // immediate: true to run on initial load +// Watcher specifically for terminalScrollbackLimitNumber is redundant if the main settings watcher handles it. +// Let's remove the specific watcher for simplicity, the main one should suffice. + // --- Popup Editor setting method --- const handleUpdatePopupEditorSetting = async () => { popupEditorLoading.value = true; @@ -1020,6 +1050,34 @@ const handleUpdateShowQuickCommandTags = async () => { } }; +// --- Terminal Scrollback Limit setting method --- +const handleUpdateTerminalScrollbackLimit = async () => { + terminalScrollbackLimitLoading.value = true; + terminalScrollbackLimitMessage.value = ''; + terminalScrollbackLimitSuccess.value = false; + try { + const limitValue = terminalScrollbackLimitLocal.value; + + // Validate: must be a non-negative integer or null/undefined (treat null/undefined as default 5000) + if (limitValue !== null && limitValue !== undefined && (isNaN(limitValue) || !Number.isInteger(limitValue) || limitValue < 0)) { + throw new Error(t('settings.terminalScrollback.error.invalidInput', '请输入一个有效的非负整数。')); + } + + // If input is empty (null/undefined), save the default value '5000'. Otherwise, save the entered number as string. + const valueToSave = (limitValue === null || limitValue === undefined) ? '5000' : String(limitValue); + + await settingsStore.updateSetting('terminalScrollbackLimit', valueToSave); + terminalScrollbackLimitMessage.value = t('settings.terminalScrollback.success.saved', '终端回滚行数设置已保存。'); + terminalScrollbackLimitSuccess.value = true; + } catch (error: any) { + console.error('更新终端回滚行数设置失败:', error); + terminalScrollbackLimitMessage.value = error.message || t('settings.terminalScrollback.error.saveFailed', '保存终端回滚行数设置失败。'); + terminalScrollbackLimitSuccess.value = false; + } finally { + terminalScrollbackLimitLoading.value = false; + } +}; + // --- 外观设置 --- const openStyleCustomizer = () => { appearanceStore.toggleStyleCustomizer(true);