// src/services/renderers/dectStatusRenderer.js // // Full CLI-style DECT base dump for `/dectstatus`. Complements the // compact follow-up from renderDectDiagnosticsMarkdown (phonestatus): // that one is "exceptions only"; this one is the complete status.xml // picture (device, firmware, reboot log, network, RTP, security, // emergency numbers, health verdict). // // Input is the same collectAll() result array used by the compact // renderer. Pure — no I/O, no env. import { formatDisplayTime } from '../../utils/time.js'; /** * @param {Array} results collectAll() output * @param {object} opts * @param {string} opts.storeNum * @param {boolean} [opts.footer=true] * @param {object} [opts.relay] optional hub.status() snapshot * @param {Array} [opts.discoveryWarnings] * @returns {string} */ export function renderDectStatusMarkdown(results, opts = {}) { const { storeNum, footer = true, relay = null, discoveryWarnings = [], } = opts; const list = Array.isArray(results) ? results : []; const lines = [`**DECT Status — Store ${storeNum}**`, '']; if (relay) { if (relay.connected) { const agent = relay.agent?.hostname || relay.agent?.version || 'connected'; lines.push(`_Relay: online (${agent})_`); } else { lines.push('_Relay: **offline** — base collect will fail until dect-relay-agent reconnects_'); } lines.push(''); } if (list.length === 0) { lines.push('_No reachable DECT basestations discovered for this store._'); if (discoveryWarnings.length > 0) { lines.push(''); lines.push('**Discovery notes:**'); for (const w of discoveryWarnings) { const who = w.mac || w.ip || 'base'; lines.push(`- ${who}: ${w.reason}`); } } if (footer) { lines.push(''); lines.push(`_Pulled at ${formatDisplayTime()}._`); } return lines.join('\n').trim(); } for (let i = 0; i < list.length; i++) { if (i > 0) lines.push('', '---', ''); lines.push(...renderOneBaseFull(list[i])); } if (discoveryWarnings.length > 0) { lines.push('', '**Discovery notes (skipped bases):**'); for (const w of discoveryWarnings) { const who = w.mac || w.ip || 'base'; lines.push(`- ${who}: ${w.reason}`); } } if (footer) { lines.push(''); lines.push( `_Full base dump at ${formatDisplayTime()} via the DECT relay. ` + `Use the action cards below for reboot / factory-reset (chat only)._`, ); } return lines.join('\n').trim(); } function renderOneBaseFull(r) { const label = r.base?.name || `Basestation ${r.base?.mac || '?'}`; const ip = r.base?.ip || '?'; const mac = r.base?.mac || '?'; const lines = []; if (!r.ok) { lines.push(`⚠️ **${label}**`); lines.push(`- IP: \`${ip}\` · MAC: \`${mac}\``); lines.push(`- Collect failed: ${r.error?.message || 'unknown error'}`); if (r.error?.hint) lines.push(`- _${r.error.hint}_`); if (r.elapsedMs != null) lines.push(`- Elapsed: ${r.elapsedMs}ms`); return lines; } const p = r.data || {}; const verdict = r.verdict || {}; const icon = verdict.healthy ? '✅' : '⚠️'; lines.push(`${icon} **${label}**`); // ── Device ── lines.push('', '**Device**'); row(lines, 'Model', p.device?.model); row(lines, 'System type', p.device?.systemType); row(lines, 'Unit', [p.device?.unitName, p.device?.unitIndex].filter(Boolean).join(' · ') || null); row(lines, 'MAC', p.device?.macAddress || mac); row(lines, 'IP', p.device?.ipAddress || ip); row(lines, 'RFPI', p.device?.rfpiAddress); row(lines, 'RF band', p.device?.rfBand); row(lines, 'Multi-cell', p.multiCell?.role || p.multiCell?.raw); row(lines, 'Base status', p.baseStatus); row(lines, 'Conflict', p.conflictInfo); // ── Firmware ── lines.push('', '**Firmware**'); row(lines, 'Version', p.firmware?.version); row(lines, 'Update server', p.firmware?.updateServer); row(lines, 'Update path', p.firmware?.updatePath); // ── Time ── lines.push('', '**Time / uptime**'); row(lines, 'Local time', p.time?.currentLocalTime); row(lines, 'Uptime', p.time?.operatingTime); if (r.elapsedMs != null) row(lines, 'Collect latency', `${r.elapsedMs}ms`); // ── Reboot log ── lines.push('', '**Reboot log** (newest first)'); const log = Array.isArray(p.rebootLog) ? p.rebootLog : []; if (log.length === 0) { lines.push('- _(none)_'); } else { for (const entry of log) { if (entry.unrecognized) { lines.push(`- ??? ${entry.raw || ''}`); continue; } const tag = entry.reasonCode === 80 ? '⚡' : '•'; lines.push( `- ${tag} #${entry.sequence} ${entry.at} **${entry.reasonName}** (${entry.reasonCode}) fw=${entry.firmwareAtBoot || '?'}`, ); } } // ── Network ── lines.push('', '**Network stats** (since boot)'); row(lines, 'Tx packets', p.network?.txPackets); row(lines, 'Tx dropped', p.network?.txDropped); row(lines, 'Tx errors', p.network?.txErrors); row(lines, 'Rx packets', p.network?.rxPackets); row(lines, 'Rx dropped', p.network?.rxDropped); row(lines, 'Rx errors', p.network?.rxErrors); row(lines, 'Rx broadcasts', p.network?.rxBroadcasts); // ── RTP ── lines.push('', '**RTP**'); row(lines, 'Total since boot', p.rtp?.total); row(lines, 'Current active', p.rtp?.current); row(lines, 'Current local', p.rtp?.currentLocal); row(lines, 'Current relay', p.rtp?.currentRelay); // ── Security ── lines.push('', '**Security**'); row( lines, 'Custom CA', p.security?.customCa?.installed ? (p.security.customCa.info || 'installed') : 'Not installed', ); row(lines, '802.1X protocol', p.security?.dot1x?.protocol); row(lines, '802.1X status', p.security?.dot1x?.transactionStatus); // ── Emergency ── const emerg = Array.isArray(p.emergencyNumbers) ? p.emergencyNumbers : []; lines.push('', '**Emergency numbers**'); lines.push(emerg.length ? `- ${emerg.join(', ')}` : '- _(none configured)_'); // ── Verdict ── lines.push('', '**Health verdict**'); lines.push(`- healthy: **${verdict.healthy ? 'YES' : 'NO'}**`); for (const w of verdict.warnings || []) { lines.push(`- ⚠️ ${w}`); } for (const i of verdict.info || []) { lines.push(`- ℹ️ ${i}`); } return lines; } function row(lines, label, value) { if (value == null || value === '') return; lines.push(`- **${label}:** ${value}`); }