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>
133 lines
4.3 KiB
JavaScript
133 lines
4.3 KiB
JavaScript
// services/renderers/mppPhoneFormatters.js
|
|
//
|
|
// Display helpers for MPP phone diagnostics follow-up messages.
|
|
|
|
import { formatMicCertForDisplay } from '../../integrations/cisco-mpp-phone/downloadStatusJson.js';
|
|
|
|
export function shortenFirmware(fw) {
|
|
if (!fw || typeof fw !== 'string') return '?';
|
|
const m = fw.match(/(sip78xx[^\s]+)/i);
|
|
return m ? m[1] : (fw.length > 36 ? `${fw.slice(0, 36)}…` : fw);
|
|
}
|
|
|
|
/** @param {string|null|undefined} elapsedTime */
|
|
export function formatUptime(elapsedTime) {
|
|
if (!elapsedTime || typeof elapsedTime !== 'string') return null;
|
|
const trimmed = elapsedTime.trim();
|
|
if (!trimmed) return null;
|
|
|
|
const dayHour = trimmed.match(/(\d+)\s+days?\s+and\s+(\d+):(\d+):(\d+)/i);
|
|
if (dayHour) {
|
|
const days = Number(dayHour[1]);
|
|
const hours = Number(dayHour[2]);
|
|
if (days > 0) return `${days}d ${hours}h`;
|
|
if (hours > 0) return `${hours}h`;
|
|
return `${Number(dayHour[3])}m`;
|
|
}
|
|
|
|
const hms = trimmed.match(/^(\d+):(\d+):(\d+)$/);
|
|
if (hms) {
|
|
const hours = Number(hms[1]);
|
|
const mins = Number(hms[2]);
|
|
if (hours > 0) return `${hours}h ${mins}m`;
|
|
return `${mins}m`;
|
|
}
|
|
|
|
return trimmed.length > 24 ? `${trimmed.slice(0, 24)}…` : trimmed;
|
|
}
|
|
|
|
/** @param {object} phoneStatus @param {object} neighbor */
|
|
export function formatLinkSpeed(phoneStatus = {}, neighbor = {}) {
|
|
const fromStatus = phoneStatus.linkSpeed || phoneStatus.linkConfig || null;
|
|
if (fromStatus && fromStatus !== 'Disabled') return String(fromStatus).trim();
|
|
const fromLldp = neighbor.portSpeed ? String(neighbor.portSpeed).trim() : null;
|
|
if (fromLldp) {
|
|
const compact = fromLldp.replace(/\s+/g, ' ');
|
|
if (/^\d+F$/i.test(compact)) return compact.replace(/F$/i, 'M Full');
|
|
return compact;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function formatHeaderStatusLine(phoneStatus, neighbor) {
|
|
const uptime = formatUptime(phoneStatus?.elapsedTime);
|
|
const link = formatLinkSpeed(phoneStatus, neighbor);
|
|
const parts = [];
|
|
if (uptime) parts.push(`Uptime ${uptime}`);
|
|
if (link) parts.push(link);
|
|
return parts.length ? parts.join(' · ') : null;
|
|
}
|
|
|
|
export function isLineRegistered(line) {
|
|
const state = String(line?.registrationState || '').toLowerCase();
|
|
if (!state) return false;
|
|
if (state.includes('not registered') || state.includes('fail')) return false;
|
|
return state.includes('registered');
|
|
}
|
|
|
|
export function headerIconForLine(line) {
|
|
if (!line?.registrationState) return '⚠️';
|
|
return isLineRegistered(line) ? '✅' : '❌';
|
|
}
|
|
|
|
export function formatRebootEntry(raw) {
|
|
if (!raw) return null;
|
|
const m = String(raw).match(/^(\w+)\(([^)]+)\)/);
|
|
if (m) return `${m[1]} @ ${m[2]}`;
|
|
return String(raw);
|
|
}
|
|
|
|
export function hostFromUrl(url) {
|
|
if (!url || typeof url !== 'string') return '?';
|
|
try {
|
|
const normalized = url.startsWith('http') ? url : `https://${url}`;
|
|
return new URL(normalized).hostname;
|
|
} catch {
|
|
return url.length > 30 ? `${url.slice(0, 30)}…` : url;
|
|
}
|
|
}
|
|
|
|
export function formatProvisioningResync(entry) {
|
|
if (!entry) return null;
|
|
const host = entry.url ? hostFromUrl(entry.url) : '?';
|
|
const result = entry.succeeded ? 'OK' : (entry.failed ? 'FAILED' : (entry.result || '?'));
|
|
return `Resync: ${host} · ${result}`;
|
|
}
|
|
|
|
export function formatFirmwareUpgrade(entry) {
|
|
if (!entry) return null;
|
|
if (entry.succeeded) return 'Firmware upgrade: OK';
|
|
if (entry.failed) return `Firmware upgrade: ${entry.result || 'FAILED'}`;
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @param {object} mpp buildMppProbeView output
|
|
* @param {object} parsed
|
|
* @returns {string[]}
|
|
*/
|
|
export function collectIssues(mpp, parsed) {
|
|
const issues = [];
|
|
const download = mpp?.download;
|
|
|
|
const mic = formatMicCertForDisplay(download?.micCert);
|
|
if (mic) issues.push(mic);
|
|
|
|
if (download?.latestProvisioning?.failed) {
|
|
const host = download.latestProvisioning.url
|
|
? hostFromUrl(download.latestProvisioning.url)
|
|
: '?';
|
|
issues.push(`Provisioning resync failed — ${host}`);
|
|
}
|
|
|
|
if (download?.latestFirmwareUpgrade?.failed) {
|
|
issues.push(`Firmware upgrade failed — ${download.latestFirmwareUpgrade.result || 'unknown'}`);
|
|
}
|
|
|
|
const line1 = parsed?.lines?.find((l) => l.index === 1) || parsed?.lines?.[0];
|
|
if (line1?.registrationState && !isLineRegistered(line1)) {
|
|
issues.push(`Ext ${line1.index}: ${line1.registrationState}`);
|
|
}
|
|
|
|
return issues;
|
|
}
|