collabSupport/integrations/cisco-dect/statusXmlInventory.js
jmcqueen 2b8c4e06aa Improve /dectstatus with handset RF context and cleaner base cards.
Surface handset registrations and RSSI, tighten reboot health to 7 days,
and consolidate Base / Handsets & RF / Network & RTP sections.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 09:02:55 -04:00

91 lines
2.5 KiB
JavaScript

// integrations/cisco-dect/statusXmlInventory.js
//
// Walk a parsed status.xml tree and list every leaf path with a short
// preview. Used by collect-raw tooling to spot populated sections
// (RSSI_List, Device_Presence, SIP_Identity_Status, etc.) without
// hand-reading XML.
import { xmlToObject } from './statusXml.js';
const HANDSET_RELATED = /rssi|presence|sip_identity|device_line|statistics|rpn|mac/i;
/**
* @typedef {object} SectionInventoryEntry
* @property {string} path dot-separated path from root
* @property {boolean} isEmpty true when leaf is absent or whitespace-only
* @property {string|null} preview truncated leaf value (null for empty containers)
* @property {boolean} handsetRelated heuristic flag for operator summaries
*/
/**
* Build a flat inventory of leaf paths from raw status.xml text.
*
* @param {string} xml
* @returns {SectionInventoryEntry[]}
*/
export function inventoryStatusXml(xml) {
if (typeof xml !== 'string' || !xml.trim()) return [];
const tree = xmlToObject(xml);
const entries = [];
walk(tree, '', entries);
return entries;
}
/**
* Summarize non-empty handset-related paths for console output.
*
* @param {SectionInventoryEntry[]} inventory
* @returns {{ nonEmpty: number, handsetRelated: number, paths: string[] }}
*/
export function summarizeInventory(inventory) {
const list = Array.isArray(inventory) ? inventory : [];
const nonEmpty = list.filter((e) => !e.isEmpty);
const handsetPaths = nonEmpty
.filter((e) => e.handsetRelated)
.map((e) => e.path);
return {
nonEmpty: nonEmpty.length,
handsetRelated: handsetPaths.length,
paths: handsetPaths,
};
}
function walk(node, prefix, out) {
if (node == null) {
pushLeaf(prefix, '', out);
return;
}
if (typeof node === 'string') {
pushLeaf(prefix, node, out);
return;
}
if (typeof node !== 'object') {
pushLeaf(prefix, String(node), out);
return;
}
const keys = Object.keys(node);
if (keys.length === 0) {
pushLeaf(prefix, '', out);
return;
}
for (const key of keys) {
const path = prefix ? `${prefix}.${key}` : key;
walk(node[key], path, out);
}
}
function pushLeaf(path, value, out) {
const str = value == null ? '' : String(value).trim();
const isEmpty = str === '';
out.push({
path,
isEmpty,
preview: isEmpty ? null : truncate(str, 120),
handsetRelated: HANDSET_RELATED.test(path),
});
}
function truncate(s, max) {
if (s.length <= max) return s;
return `${s.slice(0, max - 1)}`;
}