n8n-nodes-netdevices
Version:
n8n node to interact with network devices (Cisco, Juniper, Palo Alto PAN-OS, Ciena SAOS, Linux, etc.)
451 lines • 21.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetDevices = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const index_1 = require("./utils/index");
let Logger;
try {
const { LoggerProxy } = require('n8n-workflow');
Logger = LoggerProxy;
}
catch (error) {
Logger = {
debug: console.log,
info: console.log,
warn: console.warn,
error: console.error
};
}
class NetDevices {
constructor() {
this.description = {
displayName: 'Net Devices',
name: 'netDevices',
icon: 'file:netdevices-icon.svg',
group: ['transform'],
version: 1,
description: 'Manage network devices via SSH',
defaults: {
name: 'Net Devices',
},
usableAsTool: true,
inputs: ['main'],
outputs: ['main'],
credentials: [
{
displayName: 'netDevicesApi',
name: 'netDevicesApi',
required: true,
}
],
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
required: true,
options: [
{
name: 'Get Running Config',
value: 'getRunningConfig',
description: 'Get the current running configuration',
action: 'Get the current running configuration',
},
{
name: 'Reboot Device',
value: 'rebootDevice',
description: 'Reboot the network device',
action: 'Reboot the network device',
},
{
name: 'Save Config',
value: 'saveConfig',
description: 'Save the current configuration',
action: 'Save the current configuration',
},
{
name: 'Send Command',
value: 'sendCommand',
description: 'Send a command to the device and get the response',
action: 'Send a command to the device',
},
{
name: 'Send Config',
value: 'sendConfig',
description: 'Send configuration commands to the device',
action: 'Send configuration commands to the device',
},
],
default: 'sendCommand',
},
{
displayName: 'Command',
name: 'command',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
operation: ['sendCommand'],
},
},
description: 'The command to send to the device',
placeholder: 'show version',
},
{
displayName: 'Configuration Commands',
name: 'configCommands',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
operation: ['sendConfig'],
},
},
description: 'Configuration commands to send (one per line)',
placeholder: 'interface GigabitEthernet1/0/1\ndescription Test Interface\nno shutdown',
typeOptions: {
rows: 5,
},
},
{
displayName: 'Advanced Options',
name: 'advancedOptions',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Auto Disconnect',
name: 'autoDisconnect',
type: 'boolean',
default: true,
description: 'Whether to automatically disconnect after command execution',
},
{
displayName: 'Command Retry Count',
name: 'commandRetryCount',
type: 'number',
default: 2,
description: 'Number of retry attempts for command failures',
typeOptions: {
minValue: 1,
maxValue: 5,
},
},
{
displayName: 'Command Timeout',
name: 'commandTimeout',
type: 'number',
default: 10,
description: 'Timeout for command execution in seconds',
typeOptions: {
minValue: 2,
maxValue: 300,
},
},
{
displayName: 'Connection Pooling',
name: 'connectionPooling',
type: 'boolean',
default: false,
description: 'Whether to enable connection pooling for better performance',
},
{
displayName: 'Connection Retry Count',
name: 'connectionRetryCount',
type: 'number',
default: 3,
description: 'Number of retry attempts for connection failures',
typeOptions: {
minValue: 1,
maxValue: 10,
},
},
{
displayName: 'Connection Timeout',
name: 'connectionTimeout',
type: 'number',
default: 15,
description: 'Timeout for establishing connection in seconds',
typeOptions: {
minValue: 3,
maxValue: 300,
},
},
{
displayName: 'Fail on Error',
name: 'failOnError',
type: 'boolean',
default: true,
description: 'Whether to fail the workflow on command errors',
},
{
displayName: 'Fast Mode',
name: 'fastMode',
type: 'boolean',
default: false,
description: 'Whether to enable fast mode for simple commands (skips setup steps)',
},
{
displayName: 'Retry Delay',
name: 'retryDelay',
type: 'number',
default: 2,
description: 'Delay between retry attempts in seconds',
typeOptions: {
minValue: 1,
maxValue: 60,
},
},
{
displayName: 'Reuse Connection',
name: 'reuseConnection',
type: 'boolean',
default: false,
description: 'Whether to reuse existing connections when possible',
},
],
},
]
};
}
async execute() {
const items = this.getInputData();
const returnData = [];
const credentials = await this.getCredentials('netDevicesApi');
const operation = this.getNodeParameter('operation', 0);
Logger.debug('NetDevices node execution started', {
operation,
itemCount: items.length,
host: credentials.host,
username: credentials.username,
authMethod: credentials.authMethod,
deviceType: credentials.deviceType,
hasPassword: !!credentials.password,
hasPrivateKey: !!credentials.privateKey,
hasPassphrase: !!credentials.passphrase,
passphraseValue: credentials.passphrase ? `"${credentials.passphrase}"` : 'undefined',
passphraseLength: credentials.passphrase ? String(credentials.passphrase).length : 0
});
for (let i = 0; i < items.length; i++) {
const startTime = Date.now();
let connection = null;
const advancedOptions = this.getNodeParameter('advancedOptions', i, {});
const autoDisconnect = advancedOptions.autoDisconnect !== false;
const connectionRetryCount = Math.max(1, advancedOptions.connectionRetryCount || 3);
const commandRetryCount = Math.max(1, advancedOptions.commandRetryCount || 2);
const retryDelay = Math.max(1, advancedOptions.retryDelay || 2) * 1000;
const connectionTimeout = Math.max(5, advancedOptions.connectionTimeout || 15) * 1000;
const commandTimeout = Math.max(5, advancedOptions.commandTimeout || 10) * 1000;
const failOnError = advancedOptions.failOnError !== false;
const fastMode = advancedOptions.fastMode || false;
const connectionPooling = advancedOptions.connectionPooling || false;
const reuseConnection = advancedOptions.reuseConnection || false;
Logger.debug('Processing item with advanced options', {
itemIndex: i,
operation,
autoDisconnect,
connectionRetryCount,
commandRetryCount,
connectionTimeout: connectionTimeout / 1000,
commandTimeout: commandTimeout / 1000,
fastMode,
connectionPooling,
reuseConnection
});
const deviceCredentials = {
host: credentials.host,
port: credentials.port,
username: credentials.username,
authMethod: credentials.authMethod,
deviceType: credentials.deviceType,
timeout: connectionTimeout / 1000,
keepAlive: true,
fastMode: fastMode,
commandTimeout: commandTimeout,
connectionPooling: connectionPooling,
reuseConnection: reuseConnection,
};
if (deviceCredentials.authMethod === 'privateKey') {
deviceCredentials.privateKey = credentials.privateKey;
if (credentials.passphrase && String(credentials.passphrase).trim() !== '') {
deviceCredentials.passphrase = credentials.passphrase;
}
}
else {
deviceCredentials.password = credentials.password;
}
if (credentials.enablePassword) {
deviceCredentials.enablePassword = credentials.enablePassword;
}
if (credentials.useJumpHost) {
deviceCredentials.useJumpHost = true;
deviceCredentials.jumpHostHost = credentials.jumpHostHost;
deviceCredentials.jumpHostPort = credentials.jumpHostPort;
deviceCredentials.jumpHostUsername = credentials.jumpHostUsername;
deviceCredentials.jumpHostAuthMethod = credentials.jumpHostAuthMethod;
deviceCredentials.jumpHostPassword = credentials.jumpHostPassword;
deviceCredentials.jumpHostPrivateKey = credentials.jumpHostPrivateKey;
deviceCredentials.jumpHostPassphrase = credentials.jumpHostPassphrase;
}
Logger.debug('Configured device credentials', {
host: deviceCredentials.host,
port: deviceCredentials.port,
username: deviceCredentials.username,
authMethod: deviceCredentials.authMethod,
deviceType: deviceCredentials.deviceType,
hasPassword: !!deviceCredentials.password,
hasPrivateKey: !!deviceCredentials.privateKey,
hasPassphrase: !!deviceCredentials.passphrase,
passphraseLength: deviceCredentials.passphrase ? deviceCredentials.passphrase.length : 0,
timeout: deviceCredentials.timeout,
fastMode: deviceCredentials.fastMode,
connectionPooling: deviceCredentials.connectionPooling,
reuseConnection: deviceCredentials.reuseConnection
});
try {
let connectionError = null;
for (let attempt = 1; attempt <= connectionRetryCount; attempt++) {
try {
connection = (0, index_1.ConnectHandler)(deviceCredentials);
const connectPromise = connection.connect();
const timeoutPromise = new Promise((_, reject) => {
global.setTimeout(() => {
reject(new Error(`Connection timeout after ${connectionTimeout / 1000} seconds`));
}, connectionTimeout);
});
await Promise.race([connectPromise, timeoutPromise]);
connectionError = null;
break;
}
catch (error) {
connectionError = error instanceof Error ? error : new Error(String(error));
if (attempt === connectionRetryCount) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to connect to device ${credentials.host} after ${connectionRetryCount} attempts. Last error: ${connectionError.message}`, { itemIndex: i, description: `Connection attempts: ${attempt}/${connectionRetryCount}` });
}
const delay = retryDelay * Math.pow(2, attempt - 1);
await new Promise(resolve => global.setTimeout(resolve, delay));
}
}
let result;
let commandError = null;
for (let attempt = 1; attempt <= commandRetryCount; attempt++) {
try {
const executeCommand = async () => {
switch (operation) {
case 'sendCommand':
const command = this.getNodeParameter('command', i);
if (!command) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Command parameter is required for sendCommand operation', { itemIndex: i });
}
return await connection.sendCommand(command);
case 'sendConfig':
const configCommands = this.getNodeParameter('configCommands', i);
if (!configCommands) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Configuration commands are required for sendConfig operation', { itemIndex: i });
}
const commands = configCommands.split('\n')
.map(cmd => cmd.trim())
.filter(cmd => cmd.length > 0);
return await connection.sendConfig(commands);
case 'getRunningConfig':
return await connection.getCurrentConfig();
case 'saveConfig':
return await connection.saveConfig();
case 'rebootDevice':
return await connection.rebootDevice();
default:
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `The operation "${operation}" is not supported!`, { itemIndex: i });
}
};
const commandPromise = executeCommand();
const timeoutPromise = new Promise((_, reject) => {
global.setTimeout(() => {
reject(new Error(`Command timeout after ${commandTimeout / 1000} seconds`));
}, commandTimeout);
});
result = await Promise.race([commandPromise, timeoutPromise]);
if (!result.success) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), result.error || 'Command execution failed', { itemIndex: i });
}
commandError = null;
break;
}
catch (error) {
commandError = error instanceof Error ? error : new Error(String(error));
if (attempt === commandRetryCount) {
if (failOnError) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Command execution failed after ${commandRetryCount} attempts: ${commandError.message}`, { itemIndex: i, description: `Command attempts: ${attempt}/${commandRetryCount}` });
}
else {
result = {
command: operation,
output: '',
success: false,
error: commandError.message,
};
break;
}
}
await new Promise(resolve => global.setTimeout(resolve, retryDelay));
}
}
const executionTime = Date.now() - startTime;
const outputData = {
success: result.success,
command: result.command,
output: result.output,
deviceType: connection.getDeviceType(),
host: connection.getHost(),
timestamp: new Date().toISOString(),
executionTime: executionTime,
connectionRetries: connectionRetryCount,
commandRetries: commandRetryCount,
};
if (!result.success && result.error) {
outputData.error = result.error;
}
const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(outputData), { itemData: { item: i } });
returnData.push(...executionData);
}
catch (error) {
if (connection && autoDisconnect) {
try {
await connection.disconnect();
}
catch (disconnectError) {
}
}
if (this.continueOnFail()) {
const executionTime = Date.now() - startTime;
const errorData = {
error: error instanceof Error ? error.message : 'Unknown error',
success: false,
timestamp: new Date().toISOString(),
executionTime: executionTime,
host: credentials.host,
operation: operation,
};
returnData.push({ json: errorData });
continue;
}
throw error;
}
finally {
if (connection && autoDisconnect && !deviceCredentials.reuseConnection) {
try {
await connection.disconnect();
}
catch (disconnectError) {
}
}
}
}
return [returnData];
}
}
exports.NetDevices = NetDevices;
//# sourceMappingURL=NetDevices.node.js.map