From d7bee1138317480187e15c5ae3c96bb6cc41f56c Mon Sep 17 00:00:00 2001 From: Baobhan Sith <80159437+Heavrnl@users.noreply.github.com> Date: Sun, 11 May 2025 13:02:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=80=9A=E7=9F=A5?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E8=87=AA=E5=AE=9A=E4=B9=89telegram=E5=9F=9F?= =?UTF-8?q?=E5=90=8D=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/appearance/appearance.controller.ts | 2 +- .../src/appearance/appearance.routes.ts | 2 +- packages/backend/src/auth/auth.routes.ts | 2 +- .../src/repositories/appearance.repository.ts | 6 ++--- .../src/services/notification.service.ts | 25 ++++++++++++++++-- .../senders/telegram.sender.service.ts | 16 ++++++++++-- packages/backend/src/services/sftp.service.ts | 6 ++--- .../backend/src/types/appearance.types.ts | 2 +- .../backend/src/types/notification.types.ts | 1 + .../src/websocket/handlers/rdp.handler.ts | 2 +- packages/backend/src/websocket/types.ts | 8 +++--- packages/frontend/src/App.vue | 2 +- .../src/components/CommandInputBar.vue | 2 +- .../src/components/ConnectionList.vue | 8 +++--- .../src/components/FileEditorContainer.vue | 8 +++--- .../src/components/LayoutRenderer.vue | 4 +-- .../components/NotificationSettingForm.vue | 26 ++++++++++++++----- .../src/components/StyleCustomizer.vue | 4 +-- .../file-manager/useFileManagerDragAndDrop.ts | 6 ++--- .../src/composables/useSshTerminal.ts | 4 +-- packages/frontend/src/locales/en-US.json | 1 + packages/frontend/src/locales/ja-JP.json | 1 + packages/frontend/src/locales/zh-CN.json | 1 + packages/frontend/src/router/index.ts | 10 +++---- .../frontend/src/stores/appearance.store.ts | 2 +- packages/frontend/src/stores/auth.store.ts | 16 ++++++------ packages/frontend/src/stores/session.store.ts | 2 +- .../stores/session/actions/sessionActions.ts | 4 +-- .../frontend/src/stores/settings.store.ts | 2 +- .../frontend/src/types/appearance.types.ts | 2 +- packages/frontend/src/types/server.types.ts | 1 + packages/frontend/src/views/LoginView.vue | 2 +- packages/frontend/src/views/SettingsView.vue | 4 +-- packages/frontend/src/views/SetupView.vue | 8 +++--- 34 files changed, 121 insertions(+), 71 deletions(-) diff --git a/packages/backend/src/appearance/appearance.controller.ts b/packages/backend/src/appearance/appearance.controller.ts index 7542e91..fd88001 100644 --- a/packages/backend/src/appearance/appearance.controller.ts +++ b/packages/backend/src/appearance/appearance.controller.ts @@ -121,7 +121,7 @@ export const uploadTerminalBackgroundController = async (req: Request, res: Resp }; /** - * 新增:获取背景图片文件 + * 获取背景图片文件 */ export const getBackgroundFileController = async (req: Request, res: Response): Promise => { const filename = req.params.filename; diff --git a/packages/backend/src/appearance/appearance.routes.ts b/packages/backend/src/appearance/appearance.routes.ts index 1b7848e..ba218f0 100644 --- a/packages/backend/src/appearance/appearance.routes.ts +++ b/packages/backend/src/appearance/appearance.routes.ts @@ -27,7 +27,7 @@ router.post( appearanceController.uploadTerminalBackgroundController ); -// 新增:GET /api/v1/appearance/background/file/:filename - 获取背景图片文件 +// GET /api/v1/appearance/background/file/:filename - 获取背景图片文件 router.get('/background/file/:filename', appearanceController.getBackgroundFileController); // DELETE /api/v1/appearance/background/page - 删除页面背景图片 diff --git a/packages/backend/src/auth/auth.routes.ts b/packages/backend/src/auth/auth.routes.ts index 85548cd..902b5f9 100644 --- a/packages/backend/src/auth/auth.routes.ts +++ b/packages/backend/src/auth/auth.routes.ts @@ -19,7 +19,7 @@ import { // 新的 Passkey 管理处理器 listUserPasskeysHandler, deleteUserPasskeyHandler, - updateUserPasskeyNameHandler, // 新增:更新 Passkey 名称的处理器 + updateUserPasskeyNameHandler, // 更新 Passkey 名称的处理器 checkHasPasskeys } from './auth.controller'; import { isAuthenticated } from './auth.middleware'; diff --git a/packages/backend/src/repositories/appearance.repository.ts b/packages/backend/src/repositories/appearance.repository.ts index 26eb1d7..a11c248 100644 --- a/packages/backend/src/repositories/appearance.repository.ts +++ b/packages/backend/src/repositories/appearance.repository.ts @@ -18,7 +18,7 @@ interface DbAppearanceSettingsRow { const mapRowsToAppearanceSettings = (rows: DbAppearanceSettingsRow[]): AppearanceSettings => { const settings: Partial = {}; let latestUpdatedAt = 0; - let terminalBackgroundEnabledFound = false; // 新增:标记是否在数据库中找到该设置 + let terminalBackgroundEnabledFound = false; // 标记是否在数据库中找到该设置 for (const row of rows) { // 更新 latestUpdatedAt @@ -85,7 +85,7 @@ const getDefaultAppearanceSettings = (): Omit => { editorFontSize: 14, terminalBackgroundImage: undefined, pageBackgroundImage: undefined, - terminalBackgroundEnabled: true, // 新增:默认启用 + terminalBackgroundEnabled: true, // 默认启用 updatedAt: Date.now(), // 提供默认时间戳 }; }; @@ -249,7 +249,7 @@ const updateAppearanceSettingsInternal = async (db: sqlite3.Database, settingsDt dbValue = key === 'activeTerminalThemeId' ? 'null' : ''; // 主题 ID 特殊存储为 'null' } else if (typeof value === 'object') { dbValue = JSON.stringify(value); - } else if (typeof value === 'boolean') { // 新增:处理布尔值 + } else if (typeof value === 'boolean') { // 处理布尔值 dbValue = value ? 'true' : 'false'; } else { dbValue = String(value); diff --git a/packages/backend/src/services/notification.service.ts b/packages/backend/src/services/notification.service.ts index 7ddf306..b532e3a 100644 --- a/packages/backend/src/services/notification.service.ts +++ b/packages/backend/src/services/notification.service.ts @@ -356,7 +356,17 @@ export class NotificationService { ); console.log(`[通知测试 - Telegram] 渲染的消息文本:`, messageText); - const telegramApiUrl = `https://api.telegram.org/bot${config.botToken}/sendMessage`; + let baseApiUrl = "https://api.telegram.org"; + if (config.customDomain) { + try { + const url = new URL(config.customDomain); + baseApiUrl = `${url.protocol}//${url.host}`; + console.log(`[通知测试 - Telegram] 使用自定义域名: ${baseApiUrl}`); + } catch (e) { + console.warn(`[通知测试 - Telegram] 无效的自定义域名 URL: ${config.customDomain}。将回退到默认 Telegram API。`); + } + } + const telegramApiUrl = `${baseApiUrl}/bot${config.botToken}/sendMessage`; try { console.log( @@ -802,7 +812,18 @@ export class NotificationService { } console.log(`[_sendTelegram] Final message text to send:`, messageText); - const telegramApiUrl = `https://api.telegram.org/bot${config.botToken}/sendMessage`; + let baseApiUrlSend = "https://api.telegram.org"; + if (config.customDomain) { + try { + const url = new URL(config.customDomain); + baseApiUrlSend = `${url.protocol}//${url.host}`; + console.log(`[_sendTelegram] 使用自定义域名: ${baseApiUrlSend} (事件: ${payload.event})`); + } catch (e) { + console.warn(`[_sendTelegram] 无效的自定义域名 URL: ${config.customDomain} (事件: ${payload.event})。将回退到默认 Telegram API。`); + } + } + const telegramApiUrl = `${baseApiUrlSend}/bot${config.botToken}/sendMessage`; + try { console.log( `[通知] 发送 Telegram 消息到聊天 ID ${config.chatId} (事件: ${payload.event})` diff --git a/packages/backend/src/services/senders/telegram.sender.service.ts b/packages/backend/src/services/senders/telegram.sender.service.ts index 75b51b9..5a8f71f 100644 --- a/packages/backend/src/services/senders/telegram.sender.service.ts +++ b/packages/backend/src/services/senders/telegram.sender.service.ts @@ -6,7 +6,7 @@ import { TelegramConfig } from "../../types/notification.types"; class TelegramSenderService implements INotificationSender { async send(notification: ProcessedNotification): Promise { const config = notification.config as TelegramConfig; - const { botToken, chatId } = config; + const { botToken, chatId, customDomain } = config; // Destructure customDomain const messageBody = notification.body; if (!botToken || !chatId) { @@ -18,7 +18,19 @@ class TelegramSenderService implements INotificationSender { ); } - const apiUrl = `https://api.telegram.org/bot${botToken}/sendMessage`; + let baseApiUrl = "https://api.telegram.org"; + if (customDomain) { + try { + const url = new URL(customDomain); // Validate and parse the custom domain + baseApiUrl = `${url.protocol}//${url.host}`; // Use protocol and host from customDomain + console.log(`[TelegramSender] Using custom domain: ${baseApiUrl}`); + } catch (e) { + console.warn(`[TelegramSender] Invalid customDomain URL: ${customDomain}. Falling back to default Telegram API.`); + // Optionally, you could throw an error here or decide to proceed with the default + } + } + + const apiUrl = `${baseApiUrl}/bot${botToken}/sendMessage`; try { console.log( diff --git a/packages/backend/src/services/sftp.service.ts b/packages/backend/src/services/sftp.service.ts index f926dde..032b6cd 100644 --- a/packages/backend/src/services/sftp.service.ts +++ b/packages/backend/src/services/sftp.service.ts @@ -753,7 +753,7 @@ export class SftpService { } try { - // --- 新增:移动前检查目标是否存在 --- + // --- 移动前检查目标是否存在 --- let targetExists = false; try { await this.getStats(sftp, newPath); @@ -1011,7 +1011,7 @@ export class SftpService { console.log(`[SFTP Upload ${uploadId}] Starting upload for ${remotePath} (${totalSize} bytes) in session ${sessionId}`); try { - // --- 新增:在创建流之前确保目录存在 --- + // --- 在创建流之前确保目录存在 --- if (relativePath) { const targetDirectory = pathModule.dirname(remotePath).replace(/\\/g, '/'); console.log(`[SFTP Upload ${uploadId}] Ensuring directory exists: ${targetDirectory}`); @@ -1029,7 +1029,7 @@ export class SftpService { } // --- 结束新增 --- - // --- 新增:预检查文件是否可写 --- + // --- 预检查文件是否可写 --- console.log(`[SFTP Upload ${uploadId}] Pre-checking writability for: ${remotePath}`); try { // 确保 state.sftp 存在 diff --git a/packages/backend/src/types/appearance.types.ts b/packages/backend/src/types/appearance.types.ts index 021709b..a3146a2 100644 --- a/packages/backend/src/types/appearance.types.ts +++ b/packages/backend/src/types/appearance.types.ts @@ -16,7 +16,7 @@ export interface AppearanceSettings { terminalBackgroundImage?: string; // 终端背景图片 URL 或路径 pageBackgroundImage?: string; // 页面背景图片 URL 或路径 editorFontSize?: number; // 编辑器字体大小 (px) - terminalBackgroundEnabled?: boolean; // 新增:终端背景是否启用 + terminalBackgroundEnabled?: boolean; // 终端背景是否启用 updatedAt?: number; } diff --git a/packages/backend/src/types/notification.types.ts b/packages/backend/src/types/notification.types.ts index 90a71f4..ed4ed8e 100644 --- a/packages/backend/src/types/notification.types.ts +++ b/packages/backend/src/types/notification.types.ts @@ -40,6 +40,7 @@ export interface TelegramConfig { botToken: string; // Consider storing this securely, maybe encrypted or via env vars chatId: string; // Target chat ID messageTemplate?: string; // Optional message template + customDomain?: string; // 允许用户自定义 Telegram API 域名 } export type NotificationChannelConfig = WebhookConfig | EmailConfig | TelegramConfig; diff --git a/packages/backend/src/websocket/handlers/rdp.handler.ts b/packages/backend/src/websocket/handlers/rdp.handler.ts index 64dbf73..72c4a99 100644 --- a/packages/backend/src/websocket/handlers/rdp.handler.ts +++ b/packages/backend/src/websocket/handlers/rdp.handler.ts @@ -16,7 +16,7 @@ export function handleRdpProxyConnection( const rdpWidthStr = (request as any).rdpWidth; // Get as string first const rdpHeightStr = (request as any).rdpHeight; // Get as string first - // --- 新增:参数验证和 DPI 计算 --- + // --- 参数验证和 DPI 计算 --- if (!rdpToken || !rdpWidthStr || !rdpHeightStr) { // Check string presence console.error(`WebSocket: RDP Proxy connection for ${ws.username} missing required parameters (token, width, height).`); ws.send(JSON.stringify({ type: 'rdp:error', payload: 'Missing RDP connection parameters (token, width, height).' })); diff --git a/packages/backend/src/websocket/types.ts b/packages/backend/src/websocket/types.ts index 8561af4..fd2669f 100644 --- a/packages/backend/src/websocket/types.ts +++ b/packages/backend/src/websocket/types.ts @@ -20,10 +20,10 @@ export interface ClientState { // 导出以便 Service 可以导入 statusIntervalId?: NodeJS.Timeout; // 添加状态轮询 ID (由 StatusMonitorService 管理) dockerStatusIntervalId?: NodeJS.Timeout; // NEW: Docker 状态轮询 ID ipAddress?: string; // 添加 IP 地址字段 - isShellReady?: boolean; // 新增:标记 Shell 是否已准备好处理输入和调整大小 - isSuspendedByService?: boolean; // 新增:标记此会话是否已被 SshSuspendService 接管 - isMarkedForSuspend?: boolean; // 新增:标记此会话是否已被用户请求挂起(等待断开连接) - suspendLogPath?: string; // 新增:如果标记挂起,则存储日志路径 (基于原始 sessionId) + isShellReady?: boolean; // 标记 Shell 是否已准备好处理输入和调整大小 + isSuspendedByService?: boolean; // 标记此会话是否已被 SshSuspendService 接管 + isMarkedForSuspend?: boolean; // 标记此会话是否已被用户请求挂起(等待断开连接) + suspendLogPath?: string; // 如果标记挂起,则存储日志路径 (基于原始 sessionId) // suspendLogWritableStream?: NodeJS.WritableStream; // 移除,将直接使用 temporaryLogStorageService.writeToLog } diff --git a/packages/frontend/src/App.vue b/packages/frontend/src/App.vue index 0dfeec6..7d1c4fe 100644 --- a/packages/frontend/src/App.vue +++ b/packages/frontend/src/App.vue @@ -85,7 +85,7 @@ onUnmounted(() => { }); -// *** 新增:计算属性,判断是否在 workspace 路由 *** +// *** 计算属性,判断是否在 workspace 路由 *** const isWorkspaceRoute = computed(() => route.path === '/workspace'); watch(route, () => { diff --git a/packages/frontend/src/components/CommandInputBar.vue b/packages/frontend/src/components/CommandInputBar.vue index 12d4231..ed71f30 100644 --- a/packages/frontend/src/components/CommandInputBar.vue +++ b/packages/frontend/src/components/CommandInputBar.vue @@ -193,7 +193,7 @@ const handleCommandInputKeydown = (event: KeyboardEvent) => { event.preventDefault(); // Prevent default if needed, e.g., form submission sendCommand(); // Call the existing sendCommand function } else { - // --- 新增:处理其他按键,取消列表选中状态 --- + // --- 处理其他按键,取消列表选中状态 --- // 检查按下的键是否是普通输入键或删除键等,而不是导航键或修饰键 if (!['ArrowUp', 'ArrowDown', 'Enter', 'Shift', 'Control', 'Alt', 'Meta', 'Tab', 'Escape'].includes(event.key)) { const target = commandInputSyncTarget.value; diff --git a/packages/frontend/src/components/ConnectionList.vue b/packages/frontend/src/components/ConnectionList.vue index 90102ac..2eac9be 100644 --- a/packages/frontend/src/components/ConnectionList.vue +++ b/packages/frontend/src/components/ConnectionList.vue @@ -22,7 +22,7 @@ const props = defineProps<{ // 定义组件发出的事件 (添加 edit-connection) const emit = defineEmits(['edit-connection']); -// 新增:用于跟踪每个连接测试状态的响应式对象 +// 用于跟踪每个连接测试状态的响应式对象 const testingState = reactive>({}); // 组件挂载时获取标签列表 (连接列表由父组件传入) @@ -54,7 +54,7 @@ const getConnectionTagNames = (conn: ConnectionInfo): string[] => { .filter((name): name is string => !!name); // 过滤掉未找到的标签并确保类型为 string }; - // 新增:计算按标签分组的连接 + // 计算按标签分组的连接 const groupedConnections = computed(() => { const groups: { [key: string]: ConnectionInfo[] } = {}; const untaggedKey = '_untagged_'; // 特殊键,用于未标记的连接 @@ -118,7 +118,7 @@ const formatTimestamp = (timestamp: number | null): string => { return new Date(timestamp * 1000).toLocaleString(); // 乘以 1000 转换为毫秒 }; -// 新增:处理删除连接的方法 +// 处理删除连接的方法 const handleDelete = async (conn: ConnectionInfo) => { // 在函数内部获取 store 实例 const connectionsStore = useConnectionsStore(); @@ -135,7 +135,7 @@ const handleDelete = async (conn: ConnectionInfo) => { } }; - // 新增:处理测试连接的方法 + // 处理测试连接的方法 const handleTestConnection = async (connectionId: number) => { const connectionsStore = useConnectionsStore(); // 获取 store 实例 testingState[connectionId] = true; // 设置为正在测试状态 diff --git a/packages/frontend/src/components/FileEditorContainer.vue b/packages/frontend/src/components/FileEditorContainer.vue index 4032603..427748e 100644 --- a/packages/frontend/src/components/FileEditorContainer.vue +++ b/packages/frontend/src/components/FileEditorContainer.vue @@ -207,10 +207,10 @@ const handleEncodingChange = (event: Event) => { // const handleCloseContainer = () => { ... }; // const handleMinimizeContainer = () => { ... }; -// 新增:Monaco Editor 组件的引用 +// Monaco Editor 组件的引用 const monacoEditorRef = ref | null>(null); -// 新增:聚焦活动编辑器的方法 +// 聚焦活动编辑器的方法 const focusActiveEditor = (): boolean => { if (monacoEditorRef.value) { monacoEditorRef.value.focus(); @@ -219,7 +219,7 @@ const focusActiveEditor = (): boolean => { return false; // 聚焦失败 }; -// 新增:暴露聚焦方法 +// 暴露聚焦方法 defineExpose({ focusActiveEditor }); // +++ 注册/注销自定义聚焦动作 +++ @@ -296,7 +296,7 @@ const handleKeyDown = (event: KeyboardEvent) => { *
- +
+
+ + +