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>
114 lines
3.9 KiB
JavaScript
114 lines
3.9 KiB
JavaScript
// integrations/cisco-mpp-phone/downloadStatusJson.js
|
|
//
|
|
// Parser for /Download%20Status.json (provisioning + firmware history).
|
|
|
|
import { coerceRows, fieldsFromRows, extractIndexed } from './jsonRows.js';
|
|
|
|
/**
|
|
* @param {string|Array} raw
|
|
* @returns {object}
|
|
*/
|
|
export function parseDownloadStatusJson(raw) {
|
|
const rows = coerceRows(raw);
|
|
const fields = fieldsFromRows(rows);
|
|
|
|
const firmwareUpgrades = extractIndexed(fields, /^Firmware Upgrade Status \d+$/i);
|
|
const provisioning = extractIndexed(fields, /^Provisioning Status \d+$/i);
|
|
const transitionAuth = extractIndexed(fields, /^Transition Authorization Status \d+$/i);
|
|
const micCertStatus = fields['MIC Cert Provisioning Status'] || null;
|
|
const micCertInfo = fields['MIC Cert Info'] || null;
|
|
|
|
return {
|
|
fields,
|
|
latestFirmwareUpgrade: parseHistoryEntry(firmwareUpgrades[0]),
|
|
latestProvisioning: parseHistoryEntry(provisioning[0]),
|
|
latestTransitionAuth: parseHistoryEntry(transitionAuth[0]),
|
|
firmwareUpgrades: firmwareUpgrades.map(parseHistoryEntry).filter(Boolean),
|
|
provisioningHistory: provisioning.map(parseHistoryEntry).filter(Boolean),
|
|
micCert: {
|
|
provisioningStatus: micCertStatus,
|
|
info: micCertInfo,
|
|
failed: !!(micCertStatus && /fail/i.test(micCertStatus)),
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Parse "[timestamp][url]Result." or "[seq][timestamp][url]Result." entries.
|
|
* @param {string|null} raw
|
|
*/
|
|
export function parseHistoryEntry(raw) {
|
|
if (!raw || typeof raw !== 'string') return null;
|
|
const trimmed = raw.trim();
|
|
if (!trimmed) return null;
|
|
|
|
const triple = trimmed.match(/^\[([^\]]*)\]\[([^\]]*)\]\[([^\]]*)\](.+)$/);
|
|
if (triple && looksLikeUrl(triple[3])) {
|
|
return buildEntry(trimmed, triple[2], triple[3], triple[4]);
|
|
}
|
|
|
|
const double = trimmed.match(/^\[([^\]]*)\]\[([^\]]*)\](.+)$/);
|
|
if (double) {
|
|
if (looksLikeUrl(double[2])) {
|
|
return buildEntry(trimmed, double[1], double[2], double[3]);
|
|
}
|
|
return buildEntry(trimmed, null, double[1], double[2]);
|
|
}
|
|
|
|
return buildEntry(trimmed, null, null, trimmed);
|
|
}
|
|
|
|
function looksLikeUrl(s) {
|
|
return typeof s === 'string' && (/^https?:\/\//i.test(s) || /\.[a-z]{2,}/i.test(s));
|
|
}
|
|
|
|
function buildEntry(raw, timestamp, url, resultPart) {
|
|
const result = (resultPart || '').trim();
|
|
return {
|
|
raw,
|
|
timestamp: timestamp || null,
|
|
url: url || null,
|
|
result: result || null,
|
|
succeeded: /succeed/i.test(result),
|
|
failed: /fail/i.test(result),
|
|
};
|
|
}
|
|
|
|
export function downloadStatusWarnings(download) {
|
|
const warnings = [];
|
|
if (!download) return warnings;
|
|
if (download.micCert?.failed) {
|
|
warnings.push('mic_cert_failed');
|
|
}
|
|
if (download.latestProvisioning?.failed) {
|
|
warnings.push('provisioning_failed');
|
|
}
|
|
if (download.latestFirmwareUpgrade?.failed) {
|
|
warnings.push('firmware_upgrade_failed');
|
|
}
|
|
return warnings;
|
|
}
|
|
|
|
/**
|
|
* Human-readable MIC cert issue for chat rendering (no raw URLs).
|
|
* @param {{ provisioningStatus?: string|null, info?: string|null, failed?: boolean }|null} micCert
|
|
* @returns {string|null}
|
|
*/
|
|
export function formatMicCertForDisplay(micCert) {
|
|
if (!micCert?.failed && !micCert?.provisioningStatus) return null;
|
|
const entry = parseHistoryEntry(micCert.provisioningStatus || micCert.info);
|
|
const at = entry?.timestamp || null;
|
|
const reason = shortenMicReason(entry?.result || micCert.provisioningStatus || 'download failed');
|
|
const atSuffix = at ? ` (${at})` : '';
|
|
return `MIC cert download failed — ${reason}${atSuffix}`;
|
|
}
|
|
|
|
function shortenMicReason(raw) {
|
|
if (!raw || typeof raw !== 'string') return 'unknown error';
|
|
const reasonMatch = raw.match(/Reason:\s*(.+?)\.?$/i);
|
|
if (reasonMatch) return reasonMatch[1].trim();
|
|
const failedMatch = raw.match(/MIC Cert Download Failed\.?\s*(.*)$/i);
|
|
if (failedMatch && failedMatch[1].trim()) return failedMatch[1].replace(/^Reason:\s*/i, '').trim();
|
|
if (/fail/i.test(raw)) return 'download failed';
|
|
return raw.length > 80 ? `${raw.slice(0, 77)}…` : raw;
|
|
}
|