Refactor every /voicediag check to declare a top-level `standards` object so the desired state is legible without reading run() logic and can drive a documented reference table. Upgrade callForwarding to error severity, tighten voicemail with three send-to-VM error paths + a `stop_sending_to_voicemail` remediation, and add a `disable_hoteling` remediation. Add a port-hygiene check bucket under services/voiceDiag/checks/port (portType, portVlan, portPoe, portEnabled) that reuses the phone- status snapshot to enforce switchport standards. Configurable via VOICE_STANDARD_PHONE_VLAN (default 102) and VOICE_STANDARD_ENABLED (kill-switch). Preserve Meraki `portType`/`voiceVlan`/`dataVlan` through the enrichment chain so the checks have clean data to read. Add an "apply all N fixes" combined card that shows up when 2+ remediations are available. New confirm_voicediag_all / cancel_voicediag_all actions run each fix in sequence (readable audit trail, no per-person write-throttle stacking), accumulate individual failures into a summary rather than aborting. Adds regression tests asserting every check exposes .standards, plus coverage for port checks, kill-switch, and combined-card iteration. 63 tests in the checks file, 188 total, all green. Co-authored-by: Cursor <cursoragent@cursor.com>
77 lines
3.2 KiB
JavaScript
77 lines
3.2 KiB
JavaScript
// src/services/voiceDiag/checks/port/_helpers.js
|
|
//
|
|
// Shared helpers for the port-hygiene check bucket. Each of the port
|
|
// checks (portType, portVlan, portPoe, portEnabled) walks the same
|
|
// per-phone / per-DECT-base list out of ctx.phoneStatus and reports
|
|
// a per-device verdict. Centralising the walk + labelling here keeps
|
|
// the individual check modules focused on their single signal.
|
|
|
|
/**
|
|
* Collect every device that could plausibly have a wired port. This
|
|
* is desk phones + DECT basestations. DECT handsets are excluded —
|
|
* they're radio-connected to their base, they don't have their own
|
|
* switchport. Wireless desk phones aren't filtered out here; the
|
|
* per-check `isWireless(d)` guard decides how to render them.
|
|
*
|
|
* @param {object} phoneStatus collectPhoneStatus() result
|
|
* @returns {Array<object>} each entry retains its full shape so
|
|
* checks can reach `.meraki.*` freely.
|
|
*/
|
|
export function collectPortDevices(phoneStatus) {
|
|
if (!phoneStatus) return [];
|
|
const phones = Array.isArray(phoneStatus?.phones?.data) ? phoneStatus.phones.data : [];
|
|
const dectBases = Array.isArray(phoneStatus?.dectBasestations) ? phoneStatus.dectBasestations : [];
|
|
const tag = (kind) => (d) => ({ ...d, _voiceDiagKind: kind });
|
|
return [
|
|
...phones.map(tag('phone')),
|
|
...dectBases.map(tag('dect-base')),
|
|
];
|
|
}
|
|
|
|
/** True when the device is on wireless — no switchport to inspect. */
|
|
export function isWireless(dev) {
|
|
const conn = String(dev?.meraki?.connectionType || '').toLowerCase();
|
|
if (conn === 'wireless') return true;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Global kill-switch for the port-hygiene bucket. Env
|
|
* `VOICE_STANDARD_ENABLED=false` returns a short "port checks are
|
|
* currently disabled" skip result that every port check can return
|
|
* verbatim. Feature-config checks (DND, forwarding, etc.) are
|
|
* intentionally *not* gated by this — those are unconditional
|
|
* standards. Only the switchport bucket bows out.
|
|
*
|
|
* The check descriptor is passed in so the returned result carries
|
|
* the caller's id / label without callers repeating themselves.
|
|
*/
|
|
export function maybeSkippedByKillSwitch(check) {
|
|
const raw = String(process.env.VOICE_STANDARD_ENABLED ?? 'true').toLowerCase().trim();
|
|
const enabled = !(raw === 'false' || raw === '0' || raw === 'no' || raw === 'off');
|
|
if (enabled) return null;
|
|
return {
|
|
status: 'skipped',
|
|
message:
|
|
`${check.label} skipped — VOICE_STANDARD_ENABLED=false (port-hygiene checks are silenced).`,
|
|
details: { killSwitch: 'VOICE_STANDARD_ENABLED', value: raw },
|
|
remediation: null,
|
|
};
|
|
}
|
|
|
|
/** Human labels used in per-device drilldowns. Kept short so
|
|
* aggregate messages don't blow past Webex's chat readability. */
|
|
export function portLabelFor(dev) {
|
|
const kind = dev?._voiceDiagKind || 'device';
|
|
const deviceLabel = dev?.name || dev?.mac || (kind === 'dect-base' ? 'DECT base' : 'Phone');
|
|
const switchName = dev?.meraki?.switchName || dev?.meraki?.deviceName || '—';
|
|
const portName = dev?.meraki?.portName || dev?.meraki?.port || '—';
|
|
return {
|
|
kind,
|
|
deviceLabel,
|
|
switchName,
|
|
portName,
|
|
portLabel: `${switchName}:${portName}`,
|
|
mac: dev?.mac || null,
|
|
};
|
|
}
|