Improve logging frontend/backend, fix host form being reversed.

This commit is contained in:
LukeGus
2025-09-09 15:38:29 -05:00
parent 67dd87fc55
commit 4c33b43a0f
21 changed files with 658 additions and 669 deletions

View File

@@ -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});
}

View File

@@ -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;
});

View File

@@ -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);
});

View File

@@ -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();