Replace /phonestatus follow-ups with /voicestatus, /wanstatus, /phonediag, and /dectdiag; extend /voicediag with relay probes and section-based MPP diagnostics. Co-authored-by: Cursor <cursoragent@cursor.com>
176 lines
6.3 KiB
JavaScript
176 lines
6.3 KiB
JavaScript
// src/commands/igmpFix.js
|
|
//
|
|
// Adaptive-card inline remediation for Meraki switch multicast policy
|
|
// on a store's network. Not a standalone chat command — the card is
|
|
// emitted by `/voicestatus` (see commands/voiceStatus.js) whenever the
|
|
// collector reports `data.multicast.needsFix`. Confirm and Cancel
|
|
// flow through the framework's `attachmentAction` event dispatched
|
|
// from index.js, which follows the same pending-card / one-shot /
|
|
// censor pattern as /webexhost and /offboarduser.
|
|
//
|
|
// End-state target (per plan): every store network's switch multicast
|
|
// settings pinned to:
|
|
// defaultSettings.igmpSnoopingEnabled = false
|
|
// defaultSettings.floodUnknownMulticastTrafficEnabled = true
|
|
// overrides = []
|
|
//
|
|
// The target is a hardcoded constant. We do NOT read-then-mutate the
|
|
// current snapshot at PUT time — the multicast object schema has only
|
|
// these two fields, so there's nothing else to preserve, and a
|
|
// constant PUT sidesteps read/write races.
|
|
|
|
import { logger } from '../utils/logger.js';
|
|
import { describeRequester } from '../utils/requester.js';
|
|
import { setSwitchMulticastSettings } from '../integrations/meraki/switches.js';
|
|
|
|
// Exported so tests can regression-guard the payload shape and so the
|
|
// applyDectSafeMulticast function has a single source of truth.
|
|
export const DECT_SAFE_MULTICAST_PAYLOAD = Object.freeze({
|
|
defaultSettings: {
|
|
igmpSnoopingEnabled: false,
|
|
floodUnknownMulticastTrafficEnabled: true,
|
|
},
|
|
overrides: [],
|
|
});
|
|
|
|
/**
|
|
* Build the adaptive-card object shown in-chat under a /voicestatus
|
|
* reply when multicast policy deviates from DECT-safe defaults. The
|
|
* card renders whatever the summarizer flagged as deviating so the
|
|
* clicker knows exactly what will change.
|
|
*
|
|
* @param {object} args
|
|
* @param {string} args.storeNum
|
|
* @param {string} args.networkName
|
|
* @param {object} args.summary data.multicast summary shape
|
|
* @param {string} args.cardId uuid stashed in pendingIgmpFixes
|
|
* @returns {object} Adaptive Card 1.3 JSON.
|
|
*/
|
|
export function buildIgmpFixCard({ storeNum, networkName, summary, cardId }) {
|
|
const facts = [];
|
|
if (summary.defaultSnoopOn) {
|
|
facts.push({ title: 'Default IGMP snooping', value: 'ON → will change to OFF' });
|
|
} else {
|
|
facts.push({ title: 'Default IGMP snooping', value: 'OFF (no change)' });
|
|
}
|
|
if (summary.defaultFloodOff) {
|
|
facts.push({ title: 'Default flood-unknown multicast', value: 'OFF → will change to ON' });
|
|
} else {
|
|
facts.push({ title: 'Default flood-unknown multicast', value: 'ON (no change)' });
|
|
}
|
|
const overrideCount = summary.deviatingOverrides?.length || 0;
|
|
facts.push({
|
|
title: 'Switch/stack overrides',
|
|
value: overrideCount > 0
|
|
? `${overrideCount} deviating → all overrides cleared`
|
|
: 'none (no change)',
|
|
});
|
|
|
|
return {
|
|
type: 'AdaptiveCard',
|
|
$schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
|
|
version: '1.3',
|
|
body: [
|
|
{
|
|
type: 'TextBlock',
|
|
size: 'Medium',
|
|
weight: 'Bolder',
|
|
text: `Apply DECT-safe multicast on ${networkName || `store ${storeNum}`}?`,
|
|
wrap: true,
|
|
},
|
|
{
|
|
type: 'TextBlock',
|
|
text:
|
|
`This will PUT the Meraki switch multicast settings for this network so IGMP snooping is OFF, ` +
|
|
`flood-unknown-multicast is ON, and all per-switch overrides are cleared. Intended to unblock DECT ` +
|
|
`handset registration for store ${storeNum}.`,
|
|
wrap: true,
|
|
spacing: 'Small',
|
|
},
|
|
{
|
|
type: 'FactSet',
|
|
spacing: 'Medium',
|
|
facts,
|
|
},
|
|
],
|
|
actions: [
|
|
{
|
|
type: 'Action.Submit',
|
|
title: '✅ Apply DECT-safe defaults',
|
|
data: { action: 'confirm_igmp_fix', cardId },
|
|
},
|
|
{
|
|
type: 'Action.Submit',
|
|
title: '❌ Cancel',
|
|
data: { action: 'cancel_igmp_fix', cardId },
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Confirm handler — PUTs the constant DECT-safe payload to the
|
|
* network's multicast endpoint. Called from index.js after the
|
|
* pending-card lookup + one-shot delete + card censor.
|
|
*
|
|
* Signature matches applyHostAssignConfirmation from /webexhost so the
|
|
* dispatch in index.js stays uniform across all inline-card actions.
|
|
*
|
|
* @param {object} bot bot-framework per-room bot
|
|
* @param {object} data pending-card payload (see pendingIgmpFixes)
|
|
* @param {string} _roomId unused; bot is already room-scoped
|
|
* @param {object} requester from extractRequester(trigger)
|
|
*/
|
|
export async function applyDectSafeMulticast(bot, data, _roomId, requester) {
|
|
const { networkId, networkName, storeNum, summary } = data;
|
|
|
|
logger(
|
|
'igmp:audit',
|
|
`CONFIRMED DECT-safe multicast on ${networkName} (${networkId}) for store ${storeNum} ` +
|
|
`by ${describeRequester(requester)} — was: snoop=${summary?.defaultSnoopOn ? 'on' : 'off'}, ` +
|
|
`flood=${summary?.defaultFloodOff ? 'off' : 'on'}, overrides=${summary?.deviatingOverrides?.length ?? 0}`,
|
|
);
|
|
|
|
try {
|
|
await setSwitchMulticastSettings(networkId, DECT_SAFE_MULTICAST_PAYLOAD);
|
|
} catch (err) {
|
|
logger(
|
|
'igmp:audit',
|
|
`FAILED DECT-safe multicast on ${networkName} for store ${storeNum}: ${err.message}`,
|
|
'error',
|
|
);
|
|
await bot.say(
|
|
'markdown',
|
|
`❌ Failed to update multicast config on **${networkName || `store ${storeNum}`}**: ${err.message}`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
await bot.say(
|
|
'markdown',
|
|
`✅ Multicast pinned to DECT-safe defaults on **${networkName || `store ${storeNum}`}**: ` +
|
|
`IGMP snoop=OFF, flood-unknown=ON, all switch overrides cleared. ` +
|
|
`Re-run \`/voicestatus ${storeNum}\` to verify.`,
|
|
);
|
|
logger(
|
|
'igmp:audit',
|
|
`COMPLETED DECT-safe multicast on ${networkName} for store ${storeNum}`,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Cancel handler — logs the cancel and posts a friendly line. No
|
|
* mutation. Signature matches cancelHostAssignCard for uniform
|
|
* dispatch in index.js.
|
|
*/
|
|
export async function cancelIgmpFixCard(bot, data, _roomId, requester) {
|
|
const { networkName, storeNum } = data;
|
|
await bot.say(
|
|
'markdown',
|
|
`❌ Multicast change cancelled for **${networkName || `store ${storeNum}`}**. No changes were made.`,
|
|
);
|
|
logger(
|
|
'igmp:audit',
|
|
`CANCELLED DECT-safe multicast on ${networkName} for store ${storeNum} by ${describeRequester(requester)}`,
|
|
);
|
|
}
|