Pulls complete status.xml from each reachable DBS-210 via the relay, renders the full CLI-style dump, and posts per-base adaptive cards for reboot and factory-reset (confirm flow + audit logging). Wired into registry, help, and attachmentAction dispatch.
198 lines
6.4 KiB
JavaScript
198 lines
6.4 KiB
JavaScript
// 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}`);
|
||
}
|