Adds three new SD-WAN checks (wanAppRtpMos/Loss/Jitter) that measure
REAL voice-traffic quality on actual RTP frames via Prisma DPI, not
synthetic link probes. Graded against the WORST 5-minute window so
transient degradation the 24h link-probe averages smooth away
actually surfaces.
Voice-app selection is tenant-configurable via PRISMA_APP_ID_VOICE +
PRISMA_APP_NAME_VOICE (Webex_Calling_RTP recommended for Webex
Calling shops — the Webex-specific DPI signature excludes non-Webex
UDP noise). Legacy PRISMA_APP_ID_RTP_BASE still honored with a
one-time deprecation warning.
Widens the default WAN look-back from 24h to 7 days: per-app metrics
only get datapoints when calls actually happen, so sporadic Webex
Calling stores (3-4 calls/day) need a wider window for worst-window
statistics to be meaningful. Interval picker snaps 7d to 1hour
buckets (168 pts) to keep payloads bounded while preserving
worst-hour granularity. Hard-capped at 7d — beyond that Prisma
downsamples to 1-day buckets and the signal collapses.
Also:
- Client-side concurrency limiter (PRISMA_MAX_INFLIGHT, default 3)
to prevent 429 cascades when /voicediag fans out 10+ parallel
metric fetches
- "View in Prisma UI" deep links in both /phonestatus WAN follow-up
and /voicediag details, threading through a new
integrations/paloalto/urls.js builder
- humanizeMetricUnit maps raw API unit strings ("percentage",
"milliseconds") to display symbols ("%", "ms") to fix
"11.83percentage" leaking to the UI
- getAppAudio envelope distinguishes not-configured / fetch-failed /
no-traffic states so misleading "set env var" messages don't fire
when the real problem is a 429
Co-authored-by: Cursor <cursoragent@cursor.com>
115 lines
4.6 KiB
JavaScript
115 lines
4.6 KiB
JavaScript
// src/services/voiceDiag/checks/index.js
|
|
//
|
|
// Ordered registry of every /voicediag check. Order matters — the
|
|
// renderer walks this list to produce the output, so put the checks a
|
|
// human operator would notice first (DND, forwarding) at the top,
|
|
// then the deeper feature-config ones, then finally the online-device
|
|
// summary which is often the last thing they want to see.
|
|
//
|
|
// Adding a new check
|
|
// 1. Drop a new file next to this one (e.g. `myNewCheck.js`) that
|
|
// exports the check descriptor as its default export or a named
|
|
// export.
|
|
// 2. Add it to CHECKS below in the position you want it to render.
|
|
// 3. If it exports a `remediations: {actionId: handler}` map, the
|
|
// voiceDiagService remediation registry will pick it up
|
|
// automatically. Action ids must be globally unique across all
|
|
// checks — a collision throws at startup.
|
|
//
|
|
// Every check must export an object matching:
|
|
//
|
|
// {
|
|
// id: string, // stable, snake_case, globally unique
|
|
// label: string, // human-readable heading
|
|
// requires: string[], // subset of ['personId','phoneStatus',
|
|
// // 'telephonyProfile'] — runner skips
|
|
// // the check if any are missing on ctx
|
|
// scope: string, // primary Webex scope needed; surfaced
|
|
// // in the skipped message on 401/403/404
|
|
// run: async (ctx) => CheckResult,
|
|
// remediations?: { [actionId: string]: async (bot, data, requester) => void }
|
|
// }
|
|
|
|
import { dndCheck } from './dnd.js';
|
|
import { callForwardingCheck } from './callForwarding.js';
|
|
import { callWaitingCheck } from './callWaiting.js';
|
|
import { voicemailCheck } from './voicemail.js';
|
|
import { callInterceptCheck } from './callIntercept.js';
|
|
import { hotelingCheck } from './hoteling.js';
|
|
import { executiveAssistantCheck } from './executiveAssistant.js';
|
|
import { outgoingPermissionCheck } from './outgoingPermission.js';
|
|
import { phoneOnlineCheck } from './phoneOnline.js';
|
|
import { portTypeCheck } from './port/portType.js';
|
|
import { portVlanCheck } from './port/portVlan.js';
|
|
import { portPoeCheck } from './port/portPoe.js';
|
|
import { portEnabledCheck } from './port/portEnabled.js';
|
|
import { wanSiteCheck } from './wan/wanSite.js';
|
|
import { wanHealthscoreCheck } from './wan/wanHealthscore.js';
|
|
import { wanLinkStateCheck } from './wan/wanLinkState.js';
|
|
import { wanLatencyCheck } from './wan/wanLatency.js';
|
|
import { wanJitterCheck } from './wan/wanJitter.js';
|
|
import { wanLossCheck } from './wan/wanLoss.js';
|
|
import { wanMosCheck } from './wan/wanMos.js';
|
|
import { wanAppRtpMosCheck } from './wan/wanAppRtpMos.js';
|
|
import { wanAppRtpLossCheck } from './wan/wanAppRtpLoss.js';
|
|
import { wanAppRtpJitterCheck } from './wan/wanAppRtpJitter.js';
|
|
import { wanAlarmsCheck } from './wan/wanAlarms.js';
|
|
|
|
// Order: user-facing feature signals first (things an operator can
|
|
// see from the phone UI), then LAN-side port hygiene, then the WAN
|
|
// bucket (Prisma SD-WAN — the "network edge" one hop out from the
|
|
// LAN), then the broad-brush online summary last so it acts as a
|
|
// reachability closer.
|
|
export const CHECKS = [
|
|
dndCheck,
|
|
callForwardingCheck,
|
|
callInterceptCheck,
|
|
callWaitingCheck,
|
|
voicemailCheck,
|
|
hotelingCheck,
|
|
executiveAssistantCheck,
|
|
outgoingPermissionCheck,
|
|
portTypeCheck,
|
|
portVlanCheck,
|
|
portPoeCheck,
|
|
portEnabledCheck,
|
|
wanSiteCheck,
|
|
wanHealthscoreCheck,
|
|
wanLinkStateCheck,
|
|
wanLatencyCheck,
|
|
wanJitterCheck,
|
|
wanLossCheck,
|
|
wanMosCheck,
|
|
// Per-app (DPI) audio-quality checks. These are graded against the
|
|
// WORST 5-min window in the series rather than the average, so they
|
|
// catch transient degradation the LQM per-link averages smooth over.
|
|
// Feature-gated on the PRISMA_APP_ID_VOICE env var (with
|
|
// PRISMA_APP_ID_RTP_BASE honored for backwards compat) — skipped
|
|
// with an actionable message when not configured.
|
|
wanAppRtpMosCheck,
|
|
wanAppRtpLossCheck,
|
|
wanAppRtpJitterCheck,
|
|
wanAlarmsCheck,
|
|
phoneOnlineCheck,
|
|
];
|
|
|
|
const _byId = new Map();
|
|
for (const c of CHECKS) {
|
|
if (!c || typeof c !== 'object') {
|
|
throw new Error('voicediag: encountered non-object check in registry');
|
|
}
|
|
if (!c.id || typeof c.id !== 'string') {
|
|
throw new Error('voicediag: check is missing required "id" string');
|
|
}
|
|
const key = c.id.toLowerCase();
|
|
if (_byId.has(key)) {
|
|
throw new Error(`voicediag: duplicate check id "${c.id}"`);
|
|
}
|
|
_byId.set(key, c);
|
|
}
|
|
|
|
/** Case-insensitive lookup so `--only DND` and `--only dnd` both work. */
|
|
export function getCheckById(id) {
|
|
if (!id) return null;
|
|
return _byId.get(String(id).toLowerCase().trim()) || null;
|
|
}
|