n8n-nodes-arubacentral
Version:
n8n community node for Aruba Central API integration with comprehensive monitoring, configuration, and management capabilities
274 lines (230 loc) • 8.37 kB
text/typescript
// api/monitoring/client/operations/getClients.methods.ts
import { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow';
import { logger } from '../../../../helpers/logger';
import { handleApiError } from '../../../../helpers/errorHandler';
import { handlePagination } from '../../../../helpers/pagination';
import { apiRequest } from '../../../../helpers/apiRequest';
/**
* Pagination using the last_client_mac cursor strategy required by the
* Aruba Central v2 wireless client API. The standard offset-based approach
* causes a 500 error when the offset reaches 10,000, so subsequent pages must
* be fetched by passing the last_client_mac returned in the previous response
* while keeping offset=0 throughout.
*/
async function handleWirelessClientPagination(
context: IExecuteFunctions,
endpoint: string,
qs: IDataObject,
limit: number,
pageSize: number = 100,
): Promise<INodeExecutionData[]> {
const allItems: IDataObject[] = [];
// Build the base query — offset stays 0 for every call
const pageQs: IDataObject = { ...qs, offset: 0, limit: pageSize };
delete pageQs.last_client_mac; // ensure we start fresh
while (true) {
logger.debug(
'monitoring:client:wirelessPagination',
`Fetching page, last_client_mac=${pageQs.last_client_mac ?? 'none'}, collected=${allItems.length}`,
);
const response = await apiRequest.call(context, 'GET', endpoint, {}, pageQs);
const items: IDataObject[] = (response as IDataObject)?.clients as IDataObject[];
if (!Array.isArray(items) || items.length === 0) {
break;
}
if (limit > 0) {
const remaining = limit - allItems.length;
if (remaining <= 0) break;
allItems.push(...items.slice(0, remaining));
if (allItems.length >= limit) break;
} else {
allItems.push(...items);
}
const lastMac = (response as IDataObject)?.last_client_mac;
// null (or missing) signals the final page
if (lastMac === null || lastMac === undefined || lastMac === '') {
break;
}
pageQs.last_client_mac = lastMac as string;
}
logger.debug(
'monitoring:client:wirelessPagination',
`Complete. Total items retrieved: ${allItems.length}`,
);
return allItems.map((item) => ({ json: item }));
}
/**
* Valid client types for unified client operations
*/
const CLIENT_TYPES = {
WIRELESS: 'WIRELESS',
WIRED: 'WIRED',
} as const;
/**
* Valid client statuses for unified client operations
*/
const CLIENT_STATUSES = {
CONNECTED: 'CONNECTED',
FAILED_TO_CONNECT: 'FAILED_TO_CONNECT',
} as const;
/**
* Validates that only one of the mutually exclusive parameters is provided
*/
function validateMutuallyExclusiveParams(
params: Record<string, any>,
exclusiveParams: string[],
): string | null {
const providedParams = exclusiveParams.filter(
(param) => params[param] && params[param].toString().trim(),
);
if (providedParams.length > 1) {
return `You can only specify one of: ${exclusiveParams.join(', ')}. Found: ${providedParams.join(', ')}`;
}
return null;
}
/**
* Validates client type and status combination
*/
function validateClientTypeAndStatus(clientType: string, clientStatus: string): string | null {
if (clientStatus === CLIENT_STATUSES.FAILED_TO_CONNECT && clientType !== CLIENT_TYPES.WIRELESS) {
return 'Failed to connect status is not supported for wired clients';
}
return null;
}
/**
* Get a list of Connected wireless clients from Aruba Central
*
* @param this The n8n execution context
* @returns Formatted list of wireless clients
*/
export async function getWirelessClients(this: IExecuteFunctions): Promise<INodeExecutionData[]> {
try {
logger.debug('monitoring:client:getWirelessClients', 'Getting wireless clients');
const returnAll = this.getNodeParameter('returnAll', 0, false) as boolean;
const limit = this.getNodeParameter('limit', 0, 50) as number;
const additionalFields = this.getNodeParameter('additionalFields', 0, {}) as IDataObject;
const qs: IDataObject = { ...additionalFields };
logger.debug('monitoring:client:getWirelessClients', `Query parameters: ${JSON.stringify(qs)}`);
return await handlePagination.call(
this,
'/monitoring/v1/clients/wireless',
'GET',
{},
qs,
{ path: ['clients'], fallbackPaths: [['data'], ['items']] },
returnAll,
returnAll ? 0 : limit,
limit,
);
} catch (error) {
return handleApiError.call(this, error, 'Failed to get wireless clients');
}
}
/**
* Get a list of Connected wired clients from Aruba Central
*
* @param this The n8n execution context
* @returns Formatted list of wired clients
*/
export async function getWiredClients(this: IExecuteFunctions): Promise<INodeExecutionData[]> {
try {
logger.debug('monitoring:client:getWiredClients', 'Getting wired clients');
const returnAll = this.getNodeParameter('returnAll', 0, false) as boolean;
const limit = this.getNodeParameter('limit', 0, 50) as number;
const additionalFields = this.getNodeParameter('additionalFields', 0, {}) as IDataObject;
const qs: IDataObject = { ...additionalFields };
logger.debug('monitoring:client:getWiredClients', `Query parameters: ${JSON.stringify(qs)}`);
return await handlePagination.call(
this,
'/monitoring/v1/clients/wired',
'GET',
{},
qs,
{ path: ['clients'], fallbackPaths: [['data'], ['items']] },
returnAll,
returnAll ? 0 : limit,
limit,
);
} catch (error) {
return handleApiError.call(this, error, 'Failed to get wired clients');
}
}
/**
* Get a list of unified clients from Aruba Central
* This is a unified form of the wired and wireless client APIs
*
* @param this The n8n execution context
* @returns Formatted list of unified clients
*/
export async function getUnifiedClients(this: IExecuteFunctions): Promise<INodeExecutionData[]> {
try {
logger.debug('monitoring:client:getUnifiedClients', 'Getting unified clients');
const returnAll = this.getNodeParameter('returnAll', 0, false) as boolean;
const limit = this.getNodeParameter('limit', 0, 50) as number;
const clientType = this.getNodeParameter('client_type', 0) as string;
const clientStatus = this.getNodeParameter('client_status', 0) as string;
const timerange = this.getNodeParameter('timerange', 0) as string;
// Validate client type and status combination
const statusError = validateClientTypeAndStatus(clientType, clientStatus);
if (statusError) {
throw new Error(statusError);
}
const additionalFields = this.getNodeParameter('additionalFields', 0, {}) as IDataObject;
// Validate mutually exclusive parameters
const exclusiveError = validateMutuallyExclusiveParams(additionalFields as Record<string, any>, [
'group',
'swarm_id',
'cluster_id',
'network',
'site',
'label',
]);
if (exclusiveError) {
throw new Error(exclusiveError);
}
// Validate wireless-only parameters
if (clientType !== CLIENT_TYPES.WIRELESS) {
if ((additionalFields.band as string)?.trim()) {
throw new Error('Band filter is only supported for wireless clients');
}
if (additionalFields.show_signal_db !== undefined) {
throw new Error('Show signal DB is only supported for wireless clients');
}
}
// Validate wired-only parameters
if (clientType !== CLIENT_TYPES.WIRED) {
if ((additionalFields.stack_id as string)?.trim()) {
throw new Error('Stack ID filter is only supported for wired clients');
}
}
const qs: IDataObject = {
client_type: clientType,
client_status: clientStatus,
timerange,
...additionalFields,
};
logger.debug(
'monitoring:client:getUnifiedClients',
`Getting ${clientType.toLowerCase()} clients with status ${clientStatus}: ${JSON.stringify(qs)}`,
);
// The v2 wireless client endpoint returns a 500 error when the offset
// reaches 10,000. Use the last_client_mac cursor strategy instead of
// standard offset pagination when fetching all wireless clients.
if (clientType === CLIENT_TYPES.WIRELESS && returnAll) {
return await handleWirelessClientPagination(this, '/monitoring/v2/clients', qs, 0, limit);
}
return await handlePagination.call(
this,
'/monitoring/v2/clients',
'GET',
{},
qs,
{ path: ['clients'], fallbackPaths: [['data'], ['items']] },
returnAll,
returnAll ? 0 : limit,
limit,
);
} catch (error) {
return handleApiError.call(this, error, 'Failed to get unified clients');
}
}