UNPKG

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
// 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'); } }