update
This commit is contained in:
@@ -3,9 +3,7 @@ import { Request, Response } from 'express';
|
|||||||
import * as ConnectionService from '../services/connection.service';
|
import * as ConnectionService from '../services/connection.service';
|
||||||
import * as SshService from '../services/ssh.service'; // 引入 SshService
|
import * as SshService from '../services/ssh.service'; // 引入 SshService
|
||||||
import * as ImportExportService from '../services/import-export.service'; // 引入 ImportExportService
|
import * as ImportExportService from '../services/import-export.service'; // 引入 ImportExportService
|
||||||
import { AuditLogService } from '../services/audit.service'; // 引入 AuditLogService
|
// Removed AuditLogService import and instantiation
|
||||||
|
|
||||||
const auditLogService = new AuditLogService(); // 实例化 AuditLogService
|
|
||||||
|
|
||||||
// --- 移除所有不再需要的导入和变量 ---
|
// --- 移除所有不再需要的导入和变量 ---
|
||||||
// import { Statement } from 'sqlite3';
|
// import { Statement } from 'sqlite3';
|
||||||
@@ -19,26 +17,9 @@ const auditLogService = new AuditLogService(); // 实例化 AuditLogService
|
|||||||
*/
|
*/
|
||||||
export const createConnection = async (req: Request, res: Response): Promise<void> => {
|
export const createConnection = async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
// 基本输入验证(更复杂的验证可以在服务层或使用中间件)
|
// Controller performs minimal validation, Service layer handles detailed business logic validation.
|
||||||
// 移除控制器层对 name 的验证,服务层会处理
|
// 将请求体传递给服务层处理 (Service layer now handles validation and audit logging)
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将请求体传递给服务层处理
|
|
||||||
const newConnection = await ConnectionService.createConnection(req.body);
|
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 });
|
res.status(201).json({ message: '连接创建成功。', connection: newConnection });
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -100,12 +81,7 @@ export const updateConnection = async (req: Request, res: Response): Promise<voi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 基本验证(可选,服务层也会验证)
|
// Controller performs minimal validation, Service layer handles detailed business logic validation.
|
||||||
const { auth_method, password, private_key } = req.body;
|
|
||||||
if (auth_method && auth_method !== 'password' && auth_method !== 'key') {
|
|
||||||
res.status(400).json({ message: '无效的认证方式 (auth_method),必须是 "password" 或 "key"。' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 注意:服务层会处理更复杂的验证,比如切换认证方式时凭证是否提供
|
// 注意:服务层会处理更复杂的验证,比如切换认证方式时凭证是否提供
|
||||||
|
|
||||||
const updatedConnection = await ConnectionService.updateConnection(connectionId, req.body);
|
const updatedConnection = await ConnectionService.updateConnection(connectionId, req.body);
|
||||||
@@ -113,8 +89,7 @@ export const updateConnection = async (req: Request, res: Response): Promise<voi
|
|||||||
if (!updatedConnection) {
|
if (!updatedConnection) {
|
||||||
res.status(404).json({ message: '连接未找到。' });
|
res.status(404).json({ message: '连接未找到。' });
|
||||||
} else {
|
} else {
|
||||||
// 记录审计日志
|
// Audit logging is now handled by the service layer
|
||||||
auditLogService.logAction('CONNECTION_UPDATED', { connectionId, updatedFields: Object.keys(req.body) });
|
|
||||||
res.status(200).json({ message: '连接更新成功。', connection: updatedConnection });
|
res.status(200).json({ message: '连接更新成功。', connection: updatedConnection });
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -144,8 +119,7 @@ export const deleteConnection = async (req: Request, res: Response): Promise<voi
|
|||||||
if (!deleted) {
|
if (!deleted) {
|
||||||
res.status(404).json({ message: '连接未找到。' });
|
res.status(404).json({ message: '连接未找到。' });
|
||||||
} else {
|
} else {
|
||||||
// 记录审计日志
|
// Audit logging is now handled by the service layer
|
||||||
auditLogService.logAction('CONNECTION_DELETED', { connectionId });
|
|
||||||
res.status(200).json({ message: '连接删除成功。' }); // 或使用 204 No Content
|
res.status(200).json({ message: '连接删除成功。' }); // 或使用 204 No Content
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -260,8 +234,9 @@ export const exportConnections = async (req: Request, res: Response): Promise<vo
|
|||||||
const filename = `nexus-terminal-connections-${timestamp}.json`;
|
const filename = `nexus-terminal-connections-${timestamp}.json`;
|
||||||
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
||||||
res.setHeader('Content-Type', 'application/json');
|
res.setHeader('Content-Type', 'application/json');
|
||||||
// 记录审计日志 - 使用数组长度
|
// Audit logging for export/import might still be relevant here or in the service
|
||||||
auditLogService.logAction('CONNECTIONS_EXPORTED', { count: exportedData.length });
|
// For now, let's assume ImportExportService handles its own logging if needed
|
||||||
|
// auditLogService.logAction('CONNECTIONS_EXPORTED', { count: exportedData.length }); // Removed from controller
|
||||||
res.status(200).json(exportedData);
|
res.status(200).json(exportedData);
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -293,8 +268,9 @@ export const importConnections = async (req: Request, res: Response): Promise<vo
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Complete success
|
// Complete success
|
||||||
// 记录审计日志
|
// Audit logging for export/import might still be relevant here or in the service
|
||||||
auditLogService.logAction('CONNECTIONS_IMPORTED', { successCount: result.successCount, failureCount: result.failureCount });
|
// For now, let's assume ImportExportService handles its own logging if needed
|
||||||
|
// auditLogService.logAction('CONNECTIONS_IMPORTED', { successCount: result.successCount, failureCount: result.failureCount }); // Removed from controller
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
message: `导入成功完成。共导入 ${result.successCount} 条连接。`,
|
message: `导入成功完成。共导入 ${result.successCount} 条连接。`,
|
||||||
successCount: result.successCount,
|
successCount: result.successCount,
|
||||||
|
|||||||
@@ -1,55 +1,19 @@
|
|||||||
import * as ConnectionRepository from '../repositories/connection.repository';
|
import * as ConnectionRepository from '../repositories/connection.repository';
|
||||||
import { encrypt, decrypt } from '../utils/crypto';
|
import { encrypt, decrypt } from '../utils/crypto';
|
||||||
|
import { AuditLogService } from '../services/audit.service'; // 导入 AuditLogService
|
||||||
|
import {
|
||||||
|
ConnectionBase,
|
||||||
|
ConnectionWithTags,
|
||||||
|
CreateConnectionInput,
|
||||||
|
UpdateConnectionInput,
|
||||||
|
FullConnectionData // Import FullConnectionData if needed internally or by repo
|
||||||
|
} from '../types/connection.types'; // 从集中类型文件导入
|
||||||
|
|
||||||
// Re-export or define types needed by the controller/service
|
// Re-export types if they need to be available via this service module
|
||||||
// Ideally, these would be in a shared types file, e.g., packages/backend/src/types/connection.types.ts
|
export type { ConnectionBase, ConnectionWithTags, CreateConnectionInput, UpdateConnectionInput };
|
||||||
// For now, let's reuse the interfaces from the repository (adjust as needed)
|
|
||||||
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[];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const auditLogService = new AuditLogService(); // 实例化 AuditLogService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有连接(包含标签)
|
* 获取所有连接(包含标签)
|
||||||
@@ -118,12 +82,17 @@ export const createConnection = async (input: CreateConnectionInput): Promise<Co
|
|||||||
await ConnectionRepository.updateConnectionTags(newConnectionId, tagIds);
|
await ConnectionRepository.updateConnectionTags(newConnectionId, tagIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Fetch and return the newly created connection with tags
|
// 6. Log audit action
|
||||||
|
// Fetch the created connection to get necessary details for logging
|
||||||
const newConnection = await getConnectionById(newConnectionId);
|
const newConnection = await getConnectionById(newConnectionId);
|
||||||
if (!newConnection) {
|
if (!newConnection) {
|
||||||
// This should ideally not happen if creation was successful
|
// This should ideally not happen if creation was successful
|
||||||
|
console.error(`[Audit Log Error] Failed to retrieve connection ${newConnectionId} after creation.`);
|
||||||
throw new Error('创建连接后无法检索到该连接。');
|
throw new Error('创建连接后无法检索到该连接。');
|
||||||
}
|
}
|
||||||
|
auditLogService.logAction('CONNECTION_CREATED', { connectionId: newConnection.id, name: newConnection.name, host: newConnection.host });
|
||||||
|
|
||||||
|
// 7. Return the newly created connection with tags
|
||||||
return newConnection;
|
return newConnection;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -194,7 +163,9 @@ export const updateConnection = async (id: number, input: UpdateConnectionInput)
|
|||||||
|
|
||||||
// 3. Update connection record if there are changes
|
// 3. Update connection record if there are changes
|
||||||
const hasNonTagChanges = Object.keys(dataToUpdate).length > 0;
|
const hasNonTagChanges = Object.keys(dataToUpdate).length > 0;
|
||||||
|
let updatedFieldsForAudit: string[] = []; // Track fields for audit log
|
||||||
if (hasNonTagChanges) {
|
if (hasNonTagChanges) {
|
||||||
|
updatedFieldsForAudit = Object.keys(dataToUpdate); // Get fields before update call
|
||||||
const updated = await ConnectionRepository.updateConnection(id, dataToUpdate);
|
const updated = await ConnectionRepository.updateConnection(id, dataToUpdate);
|
||||||
if (!updated) {
|
if (!updated) {
|
||||||
// Should not happen if findFullConnectionById succeeded, but good practice
|
// 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);
|
const validTagIds = input.tag_ids.filter(tagId => typeof tagId === 'number' && tagId > 0);
|
||||||
await ConnectionRepository.updateConnectionTags(id, validTagIds);
|
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);
|
return getConnectionById(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -217,7 +198,12 @@ export const updateConnection = async (id: number, input: UpdateConnectionInput)
|
|||||||
* 删除连接
|
* 删除连接
|
||||||
*/
|
*/
|
||||||
export const deleteConnection = async (id: number): Promise<boolean> => {
|
export const deleteConnection = async (id: number): Promise<boolean> => {
|
||||||
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
|
// Note: testConnection, importConnections, exportConnections logic
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import { useAppearanceStore } from '../stores/appearance.store'; // 使用新的
|
|||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import type { ITheme } from 'xterm';
|
import type { ITheme } from 'xterm';
|
||||||
import type { TerminalTheme } from '../../../backend/src/types/terminal-theme.types'; // 引入类型
|
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 { t } = useI18n();
|
||||||
const appearanceStore = useAppearanceStore();
|
const appearanceStore = useAppearanceStore();
|
||||||
@@ -73,7 +73,7 @@ brightCyan: #55ffff
|
|||||||
brightWhite: #ffffff`; // 终端主题编辑器的 placeholder (key: value 格式)
|
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'; // 导入辅助函数
|
import { safeJsonParse } from '../stores/appearance.store'; // 导入辅助函数
|
||||||
|
|
||||||
const initializeEditableState = () => {
|
const initializeEditableState = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user