diff --git a/.gitignore b/.gitignore index 3a1e5e1..5d6c39d 100644 --- a/.gitignore +++ b/.gitignore @@ -134,3 +134,4 @@ dist *.db /packages/data *.db +*.db diff --git a/packages/backend/src/connections/connections.controller.ts b/packages/backend/src/connections/connections.controller.ts index 7506b29..2a26d8b 100644 --- a/packages/backend/src/connections/connections.controller.ts +++ b/packages/backend/src/connections/connections.controller.ts @@ -20,9 +20,10 @@ const auditLogService = new AuditLogService(); // 实例化 AuditLogService export const createConnection = async (req: Request, res: Response): Promise => { try { // 基本输入验证(更复杂的验证可以在服务层或使用中间件) - const { name, host, username, auth_method, password, private_key } = req.body; - if (!name || !host || !username || !auth_method) { - res.status(400).json({ message: '缺少必要的连接信息 (name, host, username, auth_method)。' }); + // 移除控制器层对 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) { diff --git a/packages/backend/src/migrations.ts b/packages/backend/src/migrations.ts index 71598ca..215f25a 100644 --- a/packages/backend/src/migrations.ts +++ b/packages/backend/src/migrations.ts @@ -88,7 +88,7 @@ CREATE TABLE IF NOT EXISTS proxies ( const createConnectionsTableSQL = ` CREATE TABLE IF NOT EXISTS connections ( id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, + name TEXT NULL, -- 允许 name 为空 host TEXT NOT NULL, port INTEGER NOT NULL, username TEXT NOT NULL, diff --git a/packages/backend/src/repositories/connection.repository.ts b/packages/backend/src/repositories/connection.repository.ts index ef583be..b409d5a 100644 --- a/packages/backend/src/repositories/connection.repository.ts +++ b/packages/backend/src/repositories/connection.repository.ts @@ -7,7 +7,7 @@ const db = getDb(); // 注意:这里不包含加密字段,因为 Repository 不应处理解密 interface ConnectionBase { id: number; - name: string; + name: string | null; // 允许 name 为 null host: string; port: number; username: string; @@ -126,15 +126,17 @@ export const findFullConnectionById = async (id: number): Promise => /** * 创建新连接 */ -export const createConnection = async (data: Omit): Promise => { +// Update function signature to accept name as string | null +export const createConnection = async (data: Omit & { name: string | null }): Promise => { return new Promise((resolve, reject) => { const now = Math.floor(Date.now() / 1000); const stmt = db.prepare( `INSERT INTO connections (name, host, port, username, auth_method, encrypted_password, encrypted_private_key, encrypted_passphrase, proxy_id, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` ); stmt.run( - data.name, data.host, data.port, data.username, data.auth_method, + data.name ?? null, // Ensure null is passed if name is null/undefined + data.host, data.port, data.username, data.auth_method, data.encrypted_password ?? null, data.encrypted_private_key ?? null, data.encrypted_passphrase ?? null, data.proxy_id ?? null, now, now, @@ -153,7 +155,8 @@ export const createConnection = async (data: Omit>): Promise => { +// Update function signature to accept name as string | null | undefined +export const updateConnection = async (id: number, data: Partial & { name?: string | null }>): Promise => { const fieldsToUpdate: { [key: string]: any } = { ...data }; const params: any[] = []; diff --git a/packages/backend/src/services/connection.service.ts b/packages/backend/src/services/connection.service.ts index a69a582..f4b35e7 100644 --- a/packages/backend/src/services/connection.service.ts +++ b/packages/backend/src/services/connection.service.ts @@ -6,7 +6,7 @@ import { encrypt, decrypt } from '../utils/crypto'; // For now, let's reuse the interfaces from the repository (adjust as needed) export interface ConnectionBase { id: number; - name: string; + name: string | null; // Allow name to be null host: string; port: number; username: string; @@ -23,7 +23,7 @@ export interface ConnectionWithTags extends ConnectionBase { // Input type for creating a connection (from controller) export interface CreateConnectionInput { - name: string; + name?: string; // Name is now optional host: string; port?: number; // Optional, defaults in service/repo username: string; @@ -70,8 +70,9 @@ export const getConnectionById = async (id: number): Promise => { // 1. Validate input (basic validation, more complex validation can be added) - if (!input.name || !input.host || !input.username || !input.auth_method) { - throw new Error('缺少必要的连接信息 (name, host, username, auth_method)。'); + // Removed name validation: if (!input.name || !input.host || !input.username || !input.auth_method) { + if (!input.host || !input.username || !input.auth_method) { // Validate required fields except name + throw new Error('缺少必要的连接信息 (host, username, auth_method)。'); } if (input.auth_method === 'password' && !input.password) { throw new Error('密码认证方式需要提供 password。'); @@ -97,7 +98,7 @@ export const createConnection = async (input: CreateConnectionInput): Promise 解析后的网络统计信息或 null */ private async parseProcNetDev(sshClient: Client): Promise { + let output: string; + try { + // 将命令执行放入 try...catch + output = await this.executeSshCommand(sshClient, 'cat /proc/net/dev'); + } catch (error) { + // 如果命令失败,记录警告并返回 null + console.warn("[StatusMonitor] Failed to execute 'cat /proc/net/dev':", error); + return null; + } + // 如果命令成功,继续解析 try { - const output = await this.executeSshCommand(sshClient, 'cat /proc/net/dev'); const lines = output.split('\n').slice(2); // Skip header lines const stats: NetworkStats = {}; for (const line of lines) { @@ -238,8 +247,9 @@ export class StatusMonitorService { } } return Object.keys(stats).length > 0 ? stats : null; - } catch (error) { - console.error("[StatusMonitor] Error parsing /proc/net/dev:", error); + } catch (parseError) { + // 如果解析失败,记录错误并返回 null + console.error("[StatusMonitor] Error parsing /proc/net/dev output:", parseError); return null; } } @@ -253,14 +263,18 @@ export class StatusMonitorService { try { // 使用 ip route 命令查找默认路由对应的接口 const output = await this.executeSshCommand(sshClient, "ip route get 1.1.1.1 | grep -oP 'dev\\s+\\K\\S+'"); - return output.trim() || null; + const interfaceName = output.trim(); + if (interfaceName) return interfaceName; + // 如果 ip route 没返回有效接口名,也尝试 fallback + console.warn("[StatusMonitor] 'ip route' did not return a valid interface name. Falling back..."); + } catch (error) { - console.warn("[StatusMonitor] Failed to get default interface using 'ip route':", error); - // Fallback: 尝试查找第一个非 lo 接口 - try { - const netDevOutput = await this.executeSshCommand(sshClient, 'cat /proc/net/dev'); - const lines = netDevOutput.split('\n').slice(2); - for (const line of lines) { + console.warn("[StatusMonitor] Failed to get default interface using 'ip route', falling back:", error); + // Fallback: 尝试查找第一个非 lo 接口 + try { + const netDevOutput = await this.executeSshCommand(sshClient, 'cat /proc/net/dev'); + const lines = netDevOutput.split('\n').slice(2); + for (const line of lines) { const iface = line.trim().split(':')[0]; if (iface && iface !== 'lo') { return iface; @@ -269,8 +283,12 @@ export class StatusMonitorService { } catch (fallbackError) { console.error("[StatusMonitor] Failed to fallback to /proc/net/dev for interface:", fallbackError); } + // Ensure null is returned if both primary and fallback fail within the outer catch return null; } + // This part should ideally not be reached if the first try succeeded or the catch block returned. + // Adding a final return null for safety and to satisfy TS if logic paths are complex. + return null; } /** diff --git a/packages/data/nexus-terminal.db b/packages/data/nexus-terminal.db deleted file mode 100644 index 6073a75..0000000 Binary files a/packages/data/nexus-terminal.db and /dev/null differ diff --git a/packages/frontend/src/App.vue b/packages/frontend/src/App.vue index bd32442..ebc3930 100644 --- a/packages/frontend/src/App.vue +++ b/packages/frontend/src/App.vue @@ -19,6 +19,7 @@ const handleLogout = () => {