ServiceChannel webhook processor, proposal approval cards, attachment auto-post, CollabSupport commands, and Docker deployment configuration. Co-authored-by: Cursor <cursoragent@cursor.com>
223 lines
8.3 KiB
JavaScript
223 lines
8.3 KiB
JavaScript
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();
|
||
|