servchan/tools/fixWiredTV/fixWiredTV.js
jmcqueen c2e98d105e Initial commit: ServChan Webex bot for ServiceChannel work orders.
ServiceChannel webhook processor, proposal approval cards, attachment auto-post,
CollabSupport commands, and Docker deployment configuration.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-24 15:12:08 -04:00

223 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import fs from 'fs';
import path from 'path';
import axios from 'axios';
var merakiNetworks = [];
var config = JSON.parse(fs.readFileSync('./config/config.json'));
var devices = JSON.parse(fs.readFileSync('./enriched-meraki.csv.json'));
async function showTime() {
for (var device of devices) {
if (device.meraki && device.meraki.connection == "Wired" && (device.meraki.vlan == "103" || device.meraki.vlan == "143" || device.meraki.vlan == "145" || device.meraki.vlan == "310" || device.meraki.vlan == "360")) {
console.log(`Working on ${device.device_friendly_name}.`)
await updateSwitchPort(device.meraki.deviceSerial, device.meraki.switchport, {
enabled: false, vlan: 340, accessPolicyType: "Sticky MAC allow list",
stickyMacAllowList: [],
stickyMacAllowListLimit: 1
})
await wait(10)
await updateSwitchPort(device.meraki.deviceSerial, device.meraki.switchport, { enabled: true })
//await sendMDMCommand(device.device_id, {
// "CommandXml": "<dict><key>RequestType</key><string>RestartDevice</string></dict>"
//})
//await wait(30)
//await updateSwitchPort(device.meraki.deviceSerial, device.meraki.switchport, { enabled: true, vlan: 340 })
//await wait(30)
//await sendMDMCommand(device.device_id, {
// "CommandXml": "<dict><key>RequestType</key><string>DeviceInformation</string><key>Queries</key><array><string>Model</string><string>SerialNumber</string><string>OSVersion</string></array></dict>"
//}
//)
}
}
}
async function sendMDMCommand(deviceId, command) {
const tokenResponse = await axios.post(
'https://na.uemauth.workspaceone.com/connect/token',
new URLSearchParams({
grant_type: 'client_credentials',
client_id: '4e43d8a448fd423984776c0b376d9047',
client_secret: '0F858A7DC18563D057176BE1BF11A7B3',
}),
{
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
}
);
const accessToken = tokenResponse.data.access_token;
const baseUrl = `https://as1991.awmdm.com/api`; // e.g., as1991.awmdm.com
const headers = {
Authorization: `Bearer ${accessToken}`,
'aw-tenant-code': 'cMrIjIsQkKSj0N7giRG36o9OgvNHhuG4+dQGkZXm7G8=',
Accept: 'application/json'
};
try {
const response = await axios.post(
`${baseUrl}/mdm/devices/${deviceId}/commands?command=CustomMdmCommand`,
command,
{ headers }
);
console.log(`Command sent successfully: ${response.data}`);
return response.data;
} catch (error) {
console.error('Error sending MDM Query:');
if (error.response) {
console.error('Status:', error.response.status);
console.error('Response:', error.response.data);
} else {
console.error(error.message);
}
//throw error;
}
}
async function updateSwitchPort(serial, portId, settings) {
const baseUrl = 'https://api.meraki.com/api/v1';
try {
const response = await axios.put(
`${baseUrl}/devices/${serial}/switch/ports/${portId}`,
settings,
{
headers: {
'X-Cisco-Meraki-API-Key': config.auth.meraki.apiKey,
'Content-Type': 'application/json'
}
}
);
//console.log('Success:', response.data);
return "Port updated.";
} catch (error) {
console.error('Error updating port:');
console.error(error.response?.data || error.message);
throw error;
}
}
async function findMerakiNetworkId(storeNum) {
var startTime = new Date().getTime();
var networkSearchTerm = Number(storeNum).toString().padStart(5, "0");
try {
// Filter networks by partial name match (case-insensitive)
const matchingNetworks = merakiNetworks.filter(net =>
(net.name || '').toLowerCase().includes(networkSearchTerm.toLowerCase())
);
console.log(matchingNetworks)
if (matchingNetworks.length === 0) {
return null;
}
if (matchingNetworks.length > 1) {
console.log(`Multiple networks match '${networkSearchTerm}':`);
matchingNetworks.forEach(net => console.log(`- ${net.name} (ID: ${net.id})`));
console.log('Using the first match...');
}
return matchingNetworks[0].id;
} catch (error) {
console.error(`Error finding Meraki Network: ${error}`);
return null;
}
}
async function cacheMerakiNetworks() {
var startTime = new Date().getTime();
try {
let allNetworks = [];
let nextUrl = `https://api.meraki.com/api/v1/organizations/${config.auth.meraki.orgId}/networks?perPage=1000`;
while (nextUrl) {
const response = await axios.get(nextUrl, {
headers: {
'X-Cisco-Meraki-API-Key': config.auth.meraki.apiKey,
'Content-Type': 'application/json'
}
});
const pageNetworks = response.data;
allNetworks = allNetworks.concat(pageNetworks);
//logger(`cacheMerakiNetworks()`, `Fetched ${pageNetworks.length} networks (total so far: ${allNetworks.length})`);
// Log raw header
//logger(`cacheMerakiNetworks()`, `Link header raw: ${response.headers.link || '(none)'}`);
// Robust next URL extraction
const linkHeader = response.headers.link;
let foundNext = null;
if (linkHeader) {
//logger(`cacheMerakiNetworks()`, `Full Link header (raw): ${linkHeader}`);
const parts = linkHeader.split(',');
//logger(`cacheMerakiNetworks()`, `Split into ${parts.length} parts`);
for (const part of parts) {
const trimmed = part.trim();
//logger(`cacheMerakiNetworks()`, `Examining part: "${trimmed}"`);
// Forgiving checks: lower case, no quotes required, partial match
const lowerTrimmed = trimmed.toLowerCase();
if (lowerTrimmed.includes('rel=next') || lowerTrimmed.includes('rel="next"') || lowerTrimmed.includes("rel='next'")) {
//logger(`cacheMerakiNetworks()`, `→ Detected rel=next in: "${trimmed}"`);
const urlMatch = trimmed.match(/<([^>]+)>/);
if (urlMatch && urlMatch[1]) {
foundNext = urlMatch[1].trim(); // extra trim just in case
//logger(`cacheMerakiNetworks()`, `→ Extracted next URL: ${foundNext}`);
break;
} else {
//logger(`cacheMerakiNetworks()`, `→ URL match failed on that part`);
}
}
}
}
nextUrl = foundNext;
if (!nextUrl) {
//logger(`cacheMerakiNetworks()`, `No next page detected ending loop`);
} else {
//logger(`cacheMerakiNetworks()`, `Advancing to next URL: ${nextUrl}`);
}
}
merakiNetworks = allNetworks;
logger(`cacheMerakiNetworks()`, `Cached ${merakiNetworks.length} networks. (${new Date().getTime() - startTime}ms)`);
} catch (error) {
console.error('Meraki Networks API error:', error.message);
if (error.response) {
console.error('Status:', error.response.status);
console.error('Data:', error.response.data);
}
}
}
function logger(functionName, message) {
var d = new Date();
var year = d.getFullYear();
var month = (d.getMonth() + 1).toString().padStart(2, "0");
var day = d.getDate().toString().padStart(2, "0");
let logFile = path.join(`./logs/${year}${month}${day}.log`);
console.log(d.toLocaleString() + " " + functionName + ": " + message);
fs.appendFileSync(logFile, d.toLocaleString() + " " + functionName + ": " + message + "\n");
}
function wait(seconds) {
return new Promise(resolve => setTimeout(resolve, seconds * 1000));
}
showTime();