Improve logging frontend/backend, fix host form being reversed.
This commit is contained in:
@@ -49,7 +49,7 @@ function scheduleSessionCleanup(sessionId: string) {
|
||||
app.post('/ssh/file_manager/ssh/connect', async (req, res) => {
|
||||
const {sessionId, hostId, ip, port, username, password, sshKey, keyPassword, authType, credentialId, userId} = req.body;
|
||||
|
||||
fileLogger.info('File manager SSH connection request received', { operation: 'file_connect', sessionId, hostId, ip, port, username, authType, hasCredentialId: !!credentialId });
|
||||
// Connection request received
|
||||
|
||||
if (!sessionId || !ip || !username || !port) {
|
||||
fileLogger.warn('Missing SSH connection parameters for file manager', { operation: 'file_connect', sessionId, hasIp: !!ip, hasUsername: !!username, hasPort: !!port });
|
||||
@@ -57,14 +57,12 @@ app.post('/ssh/file_manager/ssh/connect', async (req, res) => {
|
||||
}
|
||||
|
||||
if (sshSessions[sessionId]?.isConnected) {
|
||||
fileLogger.info('Cleaning up existing SSH session', { operation: 'file_connect', sessionId });
|
||||
cleanupSession(sessionId);
|
||||
}
|
||||
const client = new SSHClient();
|
||||
|
||||
let resolvedCredentials = {password, sshKey, keyPassword, authType};
|
||||
if (credentialId && hostId && userId) {
|
||||
fileLogger.info('Resolving credentials from database for file manager', { operation: 'file_connect', sessionId, hostId, credentialId, userId });
|
||||
try {
|
||||
const credentials = await db
|
||||
.select()
|
||||
@@ -82,15 +80,14 @@ app.post('/ssh/file_manager/ssh/connect', async (req, res) => {
|
||||
keyPassword: credential.keyPassword,
|
||||
authType: credential.authType
|
||||
};
|
||||
fileLogger.success('Credentials resolved successfully for file manager', { operation: 'file_connect', sessionId, hostId, credentialId, authType: credential.authType });
|
||||
} else {
|
||||
fileLogger.warn('No credentials found in database for file manager', { operation: 'file_connect', sessionId, hostId, credentialId });
|
||||
fileLogger.warn('No credentials found in database for file manager', { operation: 'file_connect', sessionId, hostId, credentialId, userId });
|
||||
}
|
||||
} catch (error) {
|
||||
fileLogger.warn('Failed to resolve credentials from database for file manager', { operation: 'file_connect', sessionId, hostId, credentialId, error: error instanceof Error ? error.message : 'Unknown error' });
|
||||
}
|
||||
} else {
|
||||
fileLogger.info('Using direct credentials for file manager connection', { operation: 'file_connect', sessionId, hostId, authType });
|
||||
} else if (credentialId && hostId) {
|
||||
fileLogger.warn('Missing userId for credential resolution in file manager', { operation: 'file_connect', sessionId, hostId, credentialId, hasUserId: !!userId });
|
||||
}
|
||||
|
||||
const config: any = {
|
||||
@@ -137,7 +134,6 @@ app.post('/ssh/file_manager/ssh/connect', async (req, res) => {
|
||||
};
|
||||
|
||||
if (resolvedCredentials.sshKey && resolvedCredentials.sshKey.trim()) {
|
||||
fileLogger.info('Configuring SSH key authentication for file manager', { operation: 'file_connect', sessionId, hostId, hasKeyPassword: !!resolvedCredentials.keyPassword });
|
||||
try {
|
||||
if (!resolvedCredentials.sshKey.includes('-----BEGIN') || !resolvedCredentials.sshKey.includes('-----END')) {
|
||||
throw new Error('Invalid private key format');
|
||||
@@ -149,13 +145,11 @@ app.post('/ssh/file_manager/ssh/connect', async (req, res) => {
|
||||
|
||||
if (resolvedCredentials.keyPassword) config.passphrase = resolvedCredentials.keyPassword;
|
||||
|
||||
fileLogger.success('SSH key authentication configured successfully for file manager', { operation: 'file_connect', sessionId, hostId });
|
||||
} catch (keyError) {
|
||||
fileLogger.error('SSH key format error for file manager', { operation: 'file_connect', sessionId, hostId, error: keyError.message });
|
||||
return res.status(400).json({error: 'Invalid SSH key format'});
|
||||
}
|
||||
} else if (resolvedCredentials.password && resolvedCredentials.password.trim()) {
|
||||
fileLogger.info('Configuring password authentication for file manager', { operation: 'file_connect', sessionId, hostId });
|
||||
config.password = resolvedCredentials.password;
|
||||
} else {
|
||||
fileLogger.warn('No authentication method provided for file manager', { operation: 'file_connect', sessionId, hostId });
|
||||
@@ -167,7 +161,6 @@ app.post('/ssh/file_manager/ssh/connect', async (req, res) => {
|
||||
client.on('ready', () => {
|
||||
if (responseSent) return;
|
||||
responseSent = true;
|
||||
fileLogger.success('SSH connection established for file manager', { operation: 'file_connect', sessionId, hostId, ip, port, username, authType: resolvedCredentials.authType });
|
||||
sshSessions[sessionId] = {client, isConnected: true, lastActive: Date.now()};
|
||||
res.json({status: 'success', message: 'SSH connection established'});
|
||||
});
|
||||
@@ -377,7 +370,6 @@ app.post('/ssh/file_manager/ssh/writeFile', (req, res) => {
|
||||
writeStream.on('finish', () => {
|
||||
if (hasError || hasFinished) return;
|
||||
hasFinished = true;
|
||||
fileLogger.success(`File written successfully via SFTP: ${filePath}`);
|
||||
if (!res.headersSent) {
|
||||
res.json({message: 'File written successfully', path: filePath});
|
||||
}
|
||||
@@ -386,7 +378,6 @@ app.post('/ssh/file_manager/ssh/writeFile', (req, res) => {
|
||||
writeStream.on('close', () => {
|
||||
if (hasError || hasFinished) return;
|
||||
hasFinished = true;
|
||||
fileLogger.success(`File written successfully via SFTP: ${filePath}`);
|
||||
if (!res.headersSent) {
|
||||
res.json({message: 'File written successfully', path: filePath});
|
||||
}
|
||||
@@ -440,7 +431,6 @@ app.post('/ssh/file_manager/ssh/writeFile', (req, res) => {
|
||||
|
||||
|
||||
if (outputData.includes('SUCCESS')) {
|
||||
fileLogger.success(`File written successfully via fallback: ${filePath}`);
|
||||
if (!res.headersSent) {
|
||||
res.json({message: 'File written successfully', path: filePath});
|
||||
}
|
||||
@@ -536,8 +526,6 @@ app.post('/ssh/file_manager/ssh/uploadFile', (req, res) => {
|
||||
writeStream.on('finish', () => {
|
||||
if (hasError || hasFinished) return;
|
||||
hasFinished = true;
|
||||
|
||||
fileLogger.success(`File uploaded successfully via SFTP: ${fullPath}`);
|
||||
if (!res.headersSent) {
|
||||
res.json({message: 'File uploaded successfully', path: fullPath});
|
||||
}
|
||||
@@ -546,8 +534,6 @@ app.post('/ssh/file_manager/ssh/uploadFile', (req, res) => {
|
||||
writeStream.on('close', () => {
|
||||
if (hasError || hasFinished) return;
|
||||
hasFinished = true;
|
||||
|
||||
fileLogger.success(`File uploaded successfully via SFTP: ${fullPath}`);
|
||||
if (!res.headersSent) {
|
||||
res.json({message: 'File uploaded successfully', path: fullPath});
|
||||
}
|
||||
|
||||
@@ -101,8 +101,6 @@ async function fetchHostById(id: number): Promise<SSHHostWithCredentials | undef
|
||||
|
||||
async function resolveHostCredentials(host: any): Promise<SSHHostWithCredentials | undefined> {
|
||||
try {
|
||||
statsLogger.info('Resolving credentials for host', { operation: 'host_credential_resolve', hostId: host.id, hostName: host.name, hasCredentialId: !!host.credentialId });
|
||||
|
||||
const baseHost: any = {
|
||||
id: host.id,
|
||||
name: host.name,
|
||||
@@ -124,7 +122,6 @@ async function resolveHostCredentials(host: any): Promise<SSHHostWithCredentials
|
||||
};
|
||||
|
||||
if (host.credentialId) {
|
||||
statsLogger.info('Fetching credentials from database', { operation: 'host_credential_resolve', hostId: host.id, credentialId: host.credentialId, userId: host.userId });
|
||||
try {
|
||||
const credentials = await db
|
||||
.select()
|
||||
@@ -152,6 +149,7 @@ async function resolveHostCredentials(host: any): Promise<SSHHostWithCredentials
|
||||
if (credential.keyType) {
|
||||
baseHost.keyType = credential.keyType;
|
||||
}
|
||||
|
||||
} else {
|
||||
statsLogger.warn(`Credential ${host.credentialId} not found for host ${host.id}, using legacy data`);
|
||||
addLegacyCredentials(baseHost, host);
|
||||
@@ -437,23 +435,19 @@ function tcpPing(host: string, port: number, timeoutMs = 5000): Promise<boolean>
|
||||
}
|
||||
|
||||
async function pollStatusesOnce(): Promise<void> {
|
||||
statsLogger.info('Starting status polling for all hosts', { operation: 'status_poll' });
|
||||
const hosts = await fetchAllHosts();
|
||||
if (hosts.length === 0) {
|
||||
statsLogger.warn('No hosts retrieved for status polling', { operation: 'status_poll' });
|
||||
return;
|
||||
}
|
||||
|
||||
statsLogger.info('Polling status for hosts', { operation: 'status_poll', hostCount: hosts.length, hostIds: hosts.map(h => h.id) });
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const checks = hosts.map(async (h) => {
|
||||
statsLogger.info('Checking host status', { operation: 'status_poll', hostId: h.id, hostName: h.name, ip: h.ip, port: h.port });
|
||||
const isOnline = await tcpPing(h.ip, h.port, 5000);
|
||||
const now = new Date().toISOString();
|
||||
const statusEntry: StatusEntry = {status: isOnline ? 'online' : 'offline', lastChecked: now};
|
||||
hostStatuses.set(h.id, statusEntry);
|
||||
statsLogger.info('Host status check completed', { operation: 'status_poll', hostId: h.id, hostName: h.name, status: isOnline ? 'online' : 'offline' });
|
||||
return isOnline;
|
||||
});
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ wss.on('connection', (ws: WebSocket) => {
|
||||
|
||||
|
||||
ws.on('close', () => {
|
||||
sshLogger.info('WebSocket connection closed', { operation: 'websocket_disconnect' });
|
||||
cleanupSSH();
|
||||
});
|
||||
|
||||
@@ -53,7 +52,6 @@ wss.on('connection', (ws: WebSocket) => {
|
||||
break;
|
||||
|
||||
case 'disconnect':
|
||||
sshLogger.info('SSH disconnect requested', { operation: 'ssh_disconnect' });
|
||||
cleanupSSH();
|
||||
break;
|
||||
|
||||
@@ -127,14 +125,14 @@ wss.on('connection', (ws: WebSocket) => {
|
||||
}, 60000);
|
||||
|
||||
let resolvedCredentials = {password, key, keyPassword, keyType, authType};
|
||||
if (credentialId && id) {
|
||||
if (credentialId && id && hostConfig.userId) {
|
||||
try {
|
||||
const credentials = await db
|
||||
.select()
|
||||
.from(sshCredentials)
|
||||
.where(and(
|
||||
eq(sshCredentials.id, credentialId),
|
||||
eq(sshCredentials.userId, hostConfig.userId || '')
|
||||
eq(sshCredentials.userId, hostConfig.userId)
|
||||
));
|
||||
|
||||
if (credentials.length > 0) {
|
||||
@@ -146,15 +144,18 @@ wss.on('connection', (ws: WebSocket) => {
|
||||
keyType: credential.keyType,
|
||||
authType: credential.authType
|
||||
};
|
||||
} else {
|
||||
sshLogger.warn(`No credentials found for host ${id}`, { operation: 'ssh_credentials', hostId: id, credentialId, userId: hostConfig.userId });
|
||||
}
|
||||
} catch (error) {
|
||||
sshLogger.warn(`Failed to resolve credentials for host ${id}`, { operation: 'ssh_credentials', hostId: id, credentialId, error: error instanceof Error ? error.message : 'Unknown error' });
|
||||
}
|
||||
} else if (credentialId && id) {
|
||||
sshLogger.warn('Missing userId for credential resolution in terminal', { operation: 'ssh_credentials', hostId: id, credentialId, hasUserId: !!hostConfig.userId });
|
||||
}
|
||||
|
||||
sshConn.on('ready', () => {
|
||||
clearTimeout(connectionTimeout);
|
||||
sshLogger.success('SSH connection established', { operation: 'ssh_connect', hostId: id, ip, port, username, authType: resolvedCredentials.authType });
|
||||
|
||||
|
||||
sshConn!.shell({
|
||||
@@ -175,7 +176,6 @@ wss.on('connection', (ws: WebSocket) => {
|
||||
});
|
||||
|
||||
stream.on('close', () => {
|
||||
sshLogger.info('SSH stream closed', { operation: 'ssh_stream', hostId: id, ip, port, username });
|
||||
ws.send(JSON.stringify({type: 'disconnected', message: 'Connection lost'}));
|
||||
});
|
||||
|
||||
@@ -219,7 +219,6 @@ wss.on('connection', (ws: WebSocket) => {
|
||||
|
||||
sshConn.on('close', () => {
|
||||
clearTimeout(connectionTimeout);
|
||||
|
||||
cleanupSSH(connectionTimeout);
|
||||
});
|
||||
|
||||
|
||||
@@ -382,7 +382,9 @@ async function connectSSHTunnel(tunnelConfig: TunnelConfig, retryAttempt = 0): P
|
||||
const tunnelName = tunnelConfig.name;
|
||||
const tunnelMarker = getTunnelMarker(tunnelName);
|
||||
|
||||
tunnelLogger.info('SSH tunnel connection attempt started', { operation: 'tunnel_connect', tunnelName, retryAttempt, sourceIP: tunnelConfig.sourceIP, sourcePort: tunnelConfig.sourceSSHPort });
|
||||
if (retryAttempt === 0) {
|
||||
tunnelLogger.info('SSH tunnel connection attempt started', { operation: 'tunnel_connect', tunnelName, sourceIP: tunnelConfig.sourceIP, sourcePort: tunnelConfig.sourceSSHPort });
|
||||
}
|
||||
|
||||
if (manualDisconnects.has(tunnelName)) {
|
||||
tunnelLogger.info('Tunnel connection cancelled due to manual disconnect', { operation: 'tunnel_connect', tunnelName });
|
||||
@@ -394,14 +396,10 @@ async function connectSSHTunnel(tunnelConfig: TunnelConfig, retryAttempt = 0): P
|
||||
if (retryAttempt === 0) {
|
||||
retryExhaustedTunnels.delete(tunnelName);
|
||||
retryCounters.delete(tunnelName);
|
||||
tunnelLogger.info('Reset retry state for tunnel', { operation: 'tunnel_connect', tunnelName });
|
||||
} else {
|
||||
tunnelLogger.warn('Tunnel connection retry attempt', { operation: 'tunnel_connect', tunnelName, retryAttempt });
|
||||
}
|
||||
|
||||
const currentStatus = connectionStatus.get(tunnelName);
|
||||
if (!currentStatus || currentStatus.status !== CONNECTION_STATES.WAITING) {
|
||||
tunnelLogger.info('Broadcasting tunnel connecting status', { operation: 'tunnel_connect', tunnelName, retryAttempt });
|
||||
broadcastTunnelStatus(tunnelName, {
|
||||
connected: false,
|
||||
status: CONNECTION_STATES.CONNECTING,
|
||||
@@ -428,7 +426,6 @@ async function connectSSHTunnel(tunnelConfig: TunnelConfig, retryAttempt = 0): P
|
||||
};
|
||||
|
||||
if (tunnelConfig.sourceCredentialId && tunnelConfig.sourceUserId) {
|
||||
tunnelLogger.info('Resolving source credentials from database', { operation: 'tunnel_connect', tunnelName, credentialId: tunnelConfig.sourceCredentialId, userId: tunnelConfig.sourceUserId });
|
||||
try {
|
||||
const credentials = await db
|
||||
.select()
|
||||
@@ -447,15 +444,12 @@ async function connectSSHTunnel(tunnelConfig: TunnelConfig, retryAttempt = 0): P
|
||||
keyType: credential.keyType,
|
||||
authMethod: credential.authType
|
||||
};
|
||||
tunnelLogger.success('Source credentials resolved successfully', { operation: 'tunnel_connect', tunnelName, credentialId: credential.id, authType: credential.authType });
|
||||
} else {
|
||||
tunnelLogger.warn('No source credentials found in database', { operation: 'tunnel_connect', tunnelName, credentialId: tunnelConfig.sourceCredentialId });
|
||||
}
|
||||
} catch (error) {
|
||||
tunnelLogger.warn('Failed to resolve source credentials from database', { operation: 'tunnel_connect', tunnelName, credentialId: tunnelConfig.sourceCredentialId, error: error instanceof Error ? error.message : 'Unknown error' });
|
||||
}
|
||||
} else {
|
||||
tunnelLogger.info('Using direct source credentials from tunnel config', { operation: 'tunnel_connect', tunnelName, authMethod: tunnelConfig.sourceAuthMethod });
|
||||
}
|
||||
|
||||
// Resolve endpoint credentials if tunnel config has endpointCredentialId
|
||||
@@ -486,10 +480,14 @@ async function connectSSHTunnel(tunnelConfig: TunnelConfig, retryAttempt = 0): P
|
||||
keyType: credential.keyType,
|
||||
authMethod: credential.authType
|
||||
};
|
||||
} else {
|
||||
tunnelLogger.warn('No endpoint credentials found in database', { operation: 'tunnel_connect', tunnelName, credentialId: tunnelConfig.endpointCredentialId });
|
||||
}
|
||||
} catch (error) {
|
||||
tunnelLogger.warn(`Failed to resolve endpoint credentials for tunnel ${tunnelName}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
}
|
||||
} else if (tunnelConfig.endpointCredentialId) {
|
||||
tunnelLogger.warn('Missing userId for endpoint credential resolution', { operation: 'tunnel_connect', tunnelName, credentialId: tunnelConfig.endpointCredentialId, hasUserId: !!tunnelConfig.endpointUserId });
|
||||
}
|
||||
|
||||
const conn = new Client();
|
||||
|
||||
Reference in New Issue
Block a user