()
| 11791 | const ifs = (d && d.interfaces) || []; |
| 11792 | const cur = sel.value; |
| 11793 | sel.innerHTML = '<option value="">Auto</option>' + ifs.map(i => { |
| 11794 | const bands = (i.bands || []).length ? ' · ' + i.bands.join('/') + 'GHz' : ''; |
| 11795 | const mon = i.type === 'monitor' ? ' • MONITOR' : ''; |
| 11796 | return `<option value="${i.iface}">${i.iface}${bands}${mon}</option>`; |
| 11797 | }).join(''); |
| 11798 | if (cur && ifs.some(i => i.iface === cur)) sel.value = cur; |
| 11799 | } catch (e) { /* leave Auto */ } |
| 11800 | } |
| 11801 | |
| 11802 | function formatThreatBadge(threats) { |
| 11803 | if (!threats || threats.length === 0) return ''; |
| 11804 | const severityColors = { |
| 11805 | critical: 'bg-red-600 text-white', |
| 11806 | high: 'bg-orange-600 text-white', |
| 11807 | medium: 'bg-yellow-600 text-black', |
| 11808 | low: 'bg-blue-600 text-white' |
| 11809 | }; |
| 11810 | const worst = threats.reduce((a, b) => { |
| 11811 | const order = { critical: 0, high: 1, medium: 2, low: 3 }; |
| 11812 | return (order[a.severity] || 3) <= (order[b.severity] || 3) ? a : b; |
| 11813 | }); |
| 11814 | const cls = severityColors[worst.severity] || severityColors.medium; |
| 11815 | const tooltip = threats.map(t => `[${t.severity.toUpperCase()}] ${t.name}: ${t.description}`).join('\n'); |
| 11816 | const label = threats.length === 1 ? worst.name : `${threats.length} threats`; |
| 11817 | return `<span class="px-1.5 py-0.5 rounded text-xs font-bold ${cls} cursor-help" title="${escapeHtml(tooltip)}">⚠ ${escapeHtml(label)}</span>`; |
| 11818 | } |
| 11819 | |
| 11820 | function renderHostRow(normalized) { |
| 11821 | const hostname = normalized.hostname ? escapeHtml(normalized.hostname) : 'Unknown'; |
| 11822 | const mac = normalized.mac ? escapeHtml(normalized.mac) : 'Unknown'; |
| 11823 | const ip = escapeHtml(normalized.ip); |
| 11824 | |
| 11825 | // Add a visual status dot indicator |
| 11826 | const isActive = normalized.statusClass.includes('green'); |
| 11827 | const isInactive = normalized.statusClass.includes('red'); |
| 11828 | const dotColor = isActive ? 'bg-green-500' : (isInactive ? 'bg-red-500' : 'bg-yellow-500'); |
| 11829 | const statusDot = `<span class="inline-block w-2 h-2 rounded-full ${dotColor} mr-1"></span>`; |
| 11830 | const threatBadge = formatThreatBadge(normalized.threats); |
| 11831 | |
| 11832 | return ` |
| 11833 | <td class="py-3 px-4" data-label="Status"> |
| 11834 | <span class="px-2 py-1 rounded text-xs ${normalized.statusClass} flex items-center"> |
| 11835 | ${statusDot}${escapeHtml(normalized.statusText)} |
| 11836 | </span> |
| 11837 | ${threatBadge} |
nothing calls this directly
no test coverage detected