- Initialize git repository - Add comprehensive .gitignore (protects .env and secrets) - Fix package.json (correct main entry, add metadata) - Expand .env.example with all required variables and comments - Add README.md with architecture, setup, and commands - Clean up empty scaffolding directories (logs removed, agents/models marked) - Backup previous .env file locally This establishes a safe foundation before further development.
219 lines
No EOL
9.4 KiB
JavaScript
219 lines
No EOL
9.4 KiB
JavaScript
const { getStoreLocation } = require('../services/siw');
|
|
const { findMerakiNetwork, getMerakiDeviceAvailabilities, getMerakiClients } = require('../services/meraki');
|
|
const { getMDMDevices } = require('../services/mdm');
|
|
|
|
async function getStoreHealth(storeNumber) {
|
|
console.log(`🔍 Running health analysis for store ${storeNumber}`);
|
|
|
|
let locationData, merakiNetwork, merakiClients = [], mdmDevices = [], paymentTerminals = [], printers = [];
|
|
|
|
try {
|
|
[locationData, merakiNetwork] = await Promise.all([
|
|
getStoreLocation(storeNumber),
|
|
findMerakiNetwork(storeNumber)
|
|
]);
|
|
|
|
if (merakiNetwork) {
|
|
[merakiClients, mdmDevices, paymentTerminals, printers] = await Promise.all([
|
|
getMerakiClients(merakiNetwork.id),
|
|
getMDMDevices(storeNumber),
|
|
require('../services/siw').getStorePaymentTerminals(storeNumber),
|
|
require('../services/siw').getStorePrinters(storeNumber)
|
|
]);
|
|
}
|
|
} catch (err) {
|
|
console.error('❌ Health analysis error:', err.message);
|
|
}
|
|
|
|
let summary = `**📊 Store ${storeNumber} Health Summary - ${locationData?.name || 'Unknown Store'}**\n\n`;
|
|
let overallScore = 100;
|
|
const issues = [];
|
|
|
|
// === Network Infrastructure ===
|
|
summary += `**🌐 Network Infrastructure**\n`;
|
|
if (merakiNetwork) {
|
|
summary += `- Meraki Network: ✅ [${merakiNetwork.name}](${merakiNetwork.url})\n`;
|
|
|
|
try {
|
|
const avail = await getMerakiDeviceAvailabilities(merakiNetwork.id);
|
|
const switches = avail.filter(d => d.productType === 'switch' && d.status !== 'dormant');
|
|
const aps = avail.filter(d => d.productType === 'wireless' && d.status !== 'dormant');
|
|
|
|
const swOnline = switches.filter(d => d.status === 'online').length;
|
|
const apOnline = aps.filter(d => d.status === 'online').length;
|
|
const apAlerting = aps.filter(d => d.status === 'alerting').length;
|
|
|
|
summary += `- Switching: ${swOnline}/${switches.length} online\n`;
|
|
summary += `- Access Points: ${apOnline}/${aps.length} online`;
|
|
|
|
if (apAlerting > 0) {
|
|
summary += ` (${apAlerting} alerting)`;
|
|
}
|
|
summary += `\n\n`;
|
|
|
|
// Scoring & Issues
|
|
if (swOnline < switches.length) {
|
|
const offlineSw = switches.length - swOnline;
|
|
overallScore -= offlineSw * 15;
|
|
issues.push(`${offlineSw} switch(es) offline`);
|
|
}
|
|
|
|
if (apOnline < aps.length || apAlerting > 0) {
|
|
const totalApIssues = aps.length - apOnline;
|
|
|
|
|
|
if (apAlerting > 0) {
|
|
overallScore -= totalApIssues * 3;
|
|
issues.push(`${apAlerting} access point(s) alerting (check cabling/power)`);
|
|
} else {
|
|
overallScore -= totalApIssues * 5;
|
|
issues.push(`${totalApIssues} access point(s) offline`);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error('Meraki availability error:', e.message);
|
|
}
|
|
} else {
|
|
summary += `- Meraki Network: ❌ Not Found\n\n`;
|
|
overallScore -= 50;
|
|
issues.push("No Meraki network found");
|
|
}
|
|
|
|
// === Core Systems ===
|
|
summary += `**🖥️ POS Systems**\n`;
|
|
|
|
if (mdmDevices && mdmDevices.length > 0) {
|
|
const servers = mdmDevices.filter(d => (d.UserName || d.DeviceFriendlyName || '').includes('SRV'));
|
|
const mobileRegs = mdmDevices.filter(d => (d.UserName || d.DeviceFriendlyName || '').includes('MR'));
|
|
const custDisplays = mdmDevices.filter(d => (d.UserName || d.DeviceFriendlyName || '').includes('CD'));
|
|
const iphones = mdmDevices.filter(d => (d.UserName || d.DeviceFriendlyName || '').includes('IPH'));
|
|
|
|
// Server (heavy penalty)
|
|
let serverStatus = '❓ Unknown';
|
|
if (servers.length > 0) {
|
|
const match = merakiClients.find(c =>
|
|
c?.description && servers.some(s =>
|
|
(s.UserName || s.DeviceFriendlyName || '').toLowerCase().includes((c.description || '').toLowerCase())
|
|
)
|
|
);
|
|
serverStatus = match && match.status === 'Online' ? '✅ Online' : '❌ Offline';
|
|
if (serverStatus === '❌ Offline') {
|
|
overallScore -= 35;
|
|
issues.push(`Store Server ${servers[0]?.UserName} is offline`);
|
|
}
|
|
}
|
|
summary += `- Store Server: ${serverStatus}\n`;
|
|
|
|
// Mobile Registers
|
|
let mobileOnline = 0;
|
|
mobileRegs.forEach(reg => {
|
|
const name = (reg.UserName || reg.DeviceFriendlyName || '').trim();
|
|
const match = merakiClients.find(c =>
|
|
c?.description && c.description.toLowerCase().includes(name.toLowerCase()) ||
|
|
(c.user && c.user.toLowerCase().includes(name.toLowerCase()))
|
|
);
|
|
const status = match && match.status === 'Online' ? '✅ Online' : '❌ Offline';
|
|
if (status === '✅ Online') mobileOnline++;
|
|
if (status === '❌ Offline') issues.push(`Mobile Register ${name} is offline`);
|
|
});
|
|
summary += `- Mobile Registers: ${mobileOnline}/${mobileRegs.length} online\n`;
|
|
|
|
// Customer Displays & iPhones (lighter penalty)
|
|
let cdOnline = 0;
|
|
custDisplays.forEach(cd => {
|
|
const name = (cd.UserName || cd.DeviceFriendlyName || '').trim();
|
|
const match = merakiClients.find(c =>
|
|
c?.description && c.description.toLowerCase().includes(name.toLowerCase()) ||
|
|
(c.user && c.user.toLowerCase().includes(name.toLowerCase()))
|
|
);
|
|
if (match && match.status === 'Online') cdOnline++;
|
|
if (match && match.status !== 'Online') issues.push(`Customer Display ${name} is offline`);
|
|
});
|
|
summary += `- Customer Displays: ${cdOnline}/${custDisplays.length} online\n`;
|
|
|
|
let iphoneOnline = 0;
|
|
iphones.forEach(phone => {
|
|
const name = (phone.UserName || phone.DeviceFriendlyName || '').trim();
|
|
const match = merakiClients.find(c =>
|
|
c?.description && c.description.toLowerCase().includes(name.toLowerCase()) ||
|
|
(c.user && c.user.toLowerCase().includes(name.toLowerCase()))
|
|
);
|
|
if (match && match.status === 'Online') iphoneOnline++;
|
|
if (match && match.status !== 'Online') issues.push(`iPhone ${name} is offline`);
|
|
});
|
|
summary += `- Store iPhones: ${iphoneOnline}/${iphones.length} online\n\n`;
|
|
} else {
|
|
summary += `- No MDM data available\n\n`;
|
|
overallScore -= 20;
|
|
}
|
|
|
|
// === POS & Peripherals (with per-device penalty) ===
|
|
summary += `**💳 POS Peripherals**\n`;
|
|
|
|
// Payment Terminals
|
|
if (paymentTerminals && paymentTerminals.length > 0) {
|
|
let onlinePayments = 0;
|
|
paymentTerminals.forEach(term => {
|
|
const name = (term.device_name || term.adyen_device_name || '').trim().toLowerCase();
|
|
const cleanIp = (term.ip_address || '').split('.')[0].toLowerCase();
|
|
|
|
const match = merakiClients.find(c =>
|
|
c?.description && (
|
|
c.description.toLowerCase().includes(name) ||
|
|
c.description.toLowerCase().includes(cleanIp)
|
|
)
|
|
);
|
|
|
|
const status = match && match.status === 'Online' ? '✅ Online' : '❌ Offline';
|
|
if (status === '✅ Online') onlinePayments++;
|
|
if (status === '❌ Offline') {
|
|
overallScore -= 5; // penalty per offline terminal
|
|
issues.push(`Payment Terminal ${name} is offline`);
|
|
}
|
|
});
|
|
|
|
summary += `- Payment Terminals: ${onlinePayments}/${paymentTerminals.length} online\n`;
|
|
}
|
|
|
|
// Printers
|
|
const filteredPrinters = (printers || []).filter(p =>
|
|
(p.connection_type_name || '').toLowerCase() !== 'usb'
|
|
);
|
|
|
|
if (filteredPrinters.length > 0) {
|
|
let onlinePrinters = 0;
|
|
filteredPrinters.forEach(printer => {
|
|
const name = (printer.printer_name || '').trim().toLowerCase();
|
|
const match = merakiClients.find(c =>
|
|
c?.description && c.description.toLowerCase().includes(name)
|
|
);
|
|
const status = match && match.status === 'Online' ? '✅ Online' : '❌ Offline';
|
|
if (status === '✅ Online') onlinePrinters++;
|
|
if (status === '❌ Offline') {
|
|
overallScore -= 4; // penalty per offline printer
|
|
issues.push(`Printer ${name} is offline`);
|
|
}
|
|
});
|
|
|
|
summary += `- Printers: ${onlinePrinters}/${filteredPrinters.length} online\n`;
|
|
}
|
|
|
|
// === Overall Status ===
|
|
overallScore = Math.max(0, Math.round(overallScore));
|
|
|
|
let statusEmoji = overallScore >= 90 ? '🟢' : overallScore >= 70 ? '🟡' : '🔴';
|
|
let statusText = overallScore >= 90 ? 'Good' : overallScore >= 70 ? 'Fair' : 'Needs Attention';
|
|
|
|
summary += `\n**Overall Status**: ${statusEmoji} ${statusText} (${overallScore}% healthy)\n\n`;
|
|
|
|
if (issues.length > 0) {
|
|
summary += `**⚠️ Issues Detected**\n`;
|
|
issues.forEach(issue => summary += `- ${issue}\n`);
|
|
} else {
|
|
summary += `**✅ No major issues detected**\n`;
|
|
}
|
|
|
|
return { summary };
|
|
}
|
|
|
|
module.exports = { getStoreHealth }; |