// src/commands/bulkAvSwitchCSV.js import { logger } from '../utils/logger.js'; import { getMerakiNetworks } from '../integrations/meraki/networks.js'; import { fetchAllPages } from '../integrations/meraki/client.js'; import botClient from '../integrations/webex/BotClient.js'; const PROGRESS_INTERVAL = 50; // Progress update every 50 rear switches export async function handleBulkAvSwitchCSV(bot, trigger) { const roomId = trigger.roomId || trigger.message?.roomId; if (!roomId) return; await bot.say('markdown', '🔄 Generating full Rear Switch Port Report (VLAN 340/145)...\nThis may take a few minutes...'); try { const networks = await getMerakiNetworks(); logger('bulk-av-switch', `Loaded ${networks.length} networks`); let csv = 'Switch Name,Port Number,Description,Port State,POE,Port Type,Data VLAN,Voice VLAN,Access Policy,Sticky Assigned,Sticky Allowed,Port Status,Speed,Duplex,Power Used,Errors,Warnings\n'; let totalPortsFound = 0; let rearSwitchCount = 0; let processedSwitches = 0; for (const net of networks) { const devices = await fetchAllPages(`/networks/${net.id}/devices`); const rearSwitches = devices.filter(device => device.model && device.model.startsWith('MS') && device.name && device.name.toUpperCase().includes('R') ); for (const sw of rearSwitches) { rearSwitchCount++; processedSwitches++; // Be nice to the API between switches await new Promise(resolve => setTimeout(resolve, 150)); // 150ms delay // Port configuration const configPorts = await fetchAllPages(`/devices/${sw.serial}/switch/ports`); // Port statuses (operational data) let statusPorts = []; try { statusPorts = await fetchAllPages(`/devices/${sw.serial}/switch/ports/statuses`); } catch (e) { logger('bulk-av-switch', `Statuses failed for ${sw.serial}`); } const statusMap = new Map(); statusPorts.forEach(s => { if (s.portId) statusMap.set(String(s.portId), s); }); for (const port of configPorts) { const vlan = port.vlan || port.dataVlan || null; if (vlan && (vlan === 340 || vlan === 145)) { totalPortsFound++; const stickyAssigned = Array.isArray(port.stickyMacAllowList) ? port.stickyMacAllowList.length : 0; const stickyAllowed = port.stickyMacAllowListLimit || 0; const portState = port.enabled === true ? 'Enabled' : (port.enabled === false ? 'Disabled' : '—'); const statusInfo = statusMap.get(String(port.portId || port.number)) || {}; const portStatus = statusInfo.status || '—'; const speed = statusInfo.speed || '—'; const duplex = statusInfo.duplex || '—'; let powerUsed = '—'; if (statusInfo.powerUsageInWh !== undefined) { powerUsed = statusInfo.powerUsageInWh > 0 ? `${statusInfo.powerUsageInWh} Wh` : '0 Wh'; } const errors = Array.isArray(statusInfo.errors) && statusInfo.errors.length > 0 ? statusInfo.errors.join('; ') : 'None'; const warnings = Array.isArray(statusInfo.warnings) && statusInfo.warnings.length > 0 ? statusInfo.warnings.join('; ') : 'None'; csv += `"${sw.name || '—'}","${port.portId || port.number || '—'}","${port.name || '—'}","${portState}","${port.poeEnabled === true ? 'On' : (port.poeEnabled === false ? 'Off' : '—')}","${port.type || '—'}","${vlan}","${port.voiceVlan || '—'}","${port.accessPolicyType || port.accessPolicy || '—'}","${stickyAssigned}","${stickyAllowed}","${portStatus}","${speed}","${duplex}","${powerUsed}","${errors}","${warnings}"\n`; } } if (processedSwitches % PROGRESS_INTERVAL === 0) { await bot.say('markdown', `✅ Progress: ${processedSwitches} rear switches processed... (${rearSwitchCount} total rear switches so far)`); } } } const buffer = Buffer.from(csv, 'utf8'); const filename = `AV_Rear_Switch_Ports_${new Date().toISOString().slice(0,10)}.csv`; await botClient.sendWithAttachment( roomId, buffer, filename, 'text/csv', `✅ **Rear Switch Port Report Complete**\n` + `• Rear switches processed: **${rearSwitchCount}**\n` + `• Ports with VLAN 340 or 145: **${totalPortsFound}**` ); } catch (err) { logger('bulk-av-switch', `Error: ${err.message}`, 'error'); await bot.say('markdown', `❌ Error generating report: ${err.message}`); } }