From cfa0881b3401db411e84daa27dacc334dea2dca4 Mon Sep 17 00:00:00 2001 From: Baobhan Sith <80159437+Heavrnl@users.noreply.github.com> Date: Tue, 22 Apr 2025 22:36:23 +0800 Subject: [PATCH] update --- .../src/connections/connections.controller.ts | 48 +++-------- .../src/services/connection.service.ts | 86 ++++++++----------- .../backend/src/types/connection.types.ts | 65 ++++++++++++++ .../src/components/StyleCustomizer.vue | 4 +- 4 files changed, 115 insertions(+), 88 deletions(-) create mode 100644 packages/backend/src/types/connection.types.ts diff --git a/packages/backend/src/connections/connections.controller.ts b/packages/backend/src/connections/connections.controller.ts index 8e66e83..5a17dc2 100644 --- a/packages/backend/src/connections/connections.controller.ts +++ b/packages/backend/src/connections/connections.controller.ts @@ -3,9 +3,7 @@ import { Request, Response } from 'express'; import * as ConnectionService from '../services/connection.service'; import * as SshService from '../services/ssh.service'; // 引入 SshService import * as ImportExportService from '../services/import-export.service'; // 引入 ImportExportService -import { AuditLogService } from '../services/audit.service'; // 引入 AuditLogService - -const auditLogService = new AuditLogService(); // 实例化 AuditLogService +// Removed AuditLogService import and instantiation // --- 移除所有不再需要的导入和变量 --- // import { Statement } from 'sqlite3'; @@ -19,26 +17,9 @@ const auditLogService = new AuditLogService(); // 实例化 AuditLogService */ export const createConnection = async (req: Request, res: Response): Promise => { try { - // 基本输入验证(更复杂的验证可以在服务层或使用中间件) - // 移除控制器层对 name 的验证,服务层会处理 - const { host, username, auth_method, password, private_key } = req.body; - if (!host || !username || !auth_method) { // 移除 !name 检查 - res.status(400).json({ message: '缺少必要的连接信息 (host, username, auth_method)。' }); // 更新错误消息 - return; - } - if (auth_method === 'password' && !password) { - res.status(400).json({ message: '密码认证方式需要提供 password。' }); - return; - } - if (auth_method === 'key' && !private_key) { - res.status(400).json({ message: '密钥认证方式需要提供 private_key。' }); - return; - } - - // 将请求体传递给服务层处理 + // Controller performs minimal validation, Service layer handles detailed business logic validation. + // 将请求体传递给服务层处理 (Service layer now handles validation and audit logging) const newConnection = await ConnectionService.createConnection(req.body); - // 记录审计日志 - auditLogService.logAction('CONNECTION_CREATED', { connectionId: newConnection.id, name: newConnection.name, host: newConnection.host }); res.status(201).json({ message: '连接创建成功。', connection: newConnection }); } catch (error: any) { @@ -100,12 +81,7 @@ export const updateConnection = async (req: Request, res: Response): Promise 0; + let updatedFieldsForAudit: string[] = []; // Track fields for audit log if (hasNonTagChanges) { + updatedFieldsForAudit = Object.keys(dataToUpdate); // Get fields before update call const updated = await ConnectionRepository.updateConnection(id, dataToUpdate); if (!updated) { // Should not happen if findFullConnectionById succeeded, but good practice @@ -207,8 +178,18 @@ export const updateConnection = async (id: number, input: UpdateConnectionInput) const validTagIds = input.tag_ids.filter(tagId => typeof tagId === 'number' && tagId > 0); await ConnectionRepository.updateConnectionTags(id, validTagIds); } + // Add 'tag_ids' to audit log if they were updated + if (input.tag_ids !== undefined) { + updatedFieldsForAudit.push('tag_ids'); + } - // 5. Fetch and return the updated connection + + // 5. Log audit action if any changes were made + if (hasNonTagChanges || input.tag_ids !== undefined) { + auditLogService.logAction('CONNECTION_UPDATED', { connectionId: id, updatedFields: updatedFieldsForAudit }); + } + + // 6. Fetch and return the updated connection return getConnectionById(id); }; @@ -217,7 +198,12 @@ export const updateConnection = async (id: number, input: UpdateConnectionInput) * 删除连接 */ export const deleteConnection = async (id: number): Promise => { - return ConnectionRepository.deleteConnection(id); + const deleted = await ConnectionRepository.deleteConnection(id); + if (deleted) { + // Log audit action after successful deletion + auditLogService.logAction('CONNECTION_DELETED', { connectionId: id }); + } + return deleted; }; // Note: testConnection, importConnections, exportConnections logic diff --git a/packages/backend/src/types/connection.types.ts b/packages/backend/src/types/connection.types.ts new file mode 100644 index 0000000..c3351bb --- /dev/null +++ b/packages/backend/src/types/connection.types.ts @@ -0,0 +1,65 @@ +// Centralized types for Connection feature + +export interface ConnectionBase { + id: number; + name: string | null; // Allow name to be null + host: string; + port: number; + username: string; + auth_method: 'password' | 'key'; + proxy_id: number | null; + created_at: number; + updated_at: number; + last_connected_at: number | null; +} + +export interface ConnectionWithTags extends ConnectionBase { + tag_ids: number[]; +} + +// Input type for creating a connection (from controller) +export interface CreateConnectionInput { + name?: string; // Name is now optional + host: string; + port?: number; // Optional, defaults in service/repo + username: string; + auth_method: 'password' | 'key'; + password?: string; // Optional depending on auth_method + private_key?: string; // Optional depending on auth_method + passphrase?: string; // Optional for key auth + proxy_id?: number | null; + tag_ids?: number[]; +} + +// Input type for updating a connection (from controller) +// All fields are optional except potentially auth_method related ones +export interface UpdateConnectionInput { + name?: string; + host?: string; + port?: number; + username?: string; + auth_method?: 'password' | 'key'; + password?: string; + private_key?: string; + passphrase?: string; // Use undefined to signal no change, null/empty string to clear + proxy_id?: number | null; + tag_ids?: number[]; +} + +// Type used within the repository (includes encrypted fields) +// This might stay in the repository or be defined here if needed elsewhere +export interface FullConnectionData { + id: number; + name: string | null; + host: string; + port: number; + username: string; + auth_method: 'password' | 'key'; + encrypted_password: string | null; + encrypted_private_key: string | null; + encrypted_passphrase: string | null; + proxy_id: number | null; + created_at: number; + updated_at: number; + last_connected_at: number | null; +} \ No newline at end of file diff --git a/packages/frontend/src/components/StyleCustomizer.vue b/packages/frontend/src/components/StyleCustomizer.vue index b5bd4d8..7fab51b 100644 --- a/packages/frontend/src/components/StyleCustomizer.vue +++ b/packages/frontend/src/components/StyleCustomizer.vue @@ -5,7 +5,7 @@ import { useAppearanceStore } from '../stores/appearance.store'; // 使用新的 import { storeToRefs } from 'pinia'; import type { ITheme } from 'xterm'; import type { TerminalTheme } from '../../../backend/src/types/terminal-theme.types'; // 引入类型 -import { defaultXtermTheme } from '../stores/default-themes'; // 引入默认主题 +import { defaultXtermTheme } from '../features/appearance/config/default-themes'; // 引入默认主题 const { t } = useI18n(); const appearanceStore = useAppearanceStore(); @@ -73,7 +73,7 @@ brightCyan: #55ffff brightWhite: #ffffff`; // 终端主题编辑器的 placeholder (key: value 格式) // 初始化本地编辑状态 -import { defaultUiTheme } from '../stores/default-themes'; // 确保导入默认主题 +import { defaultUiTheme } from '../features/appearance/config/default-themes'; // 确保导入默认主题 import { safeJsonParse } from '../stores/appearance.store'; // 导入辅助函数 const initializeEditableState = () => {