()
| 7032 | <td class="px-2 py-1">${a.writes ? '<span class="text-red-300">RW</span>' : '<span class="text-gray-500">—</span>'}</td> |
| 7033 | <td class="px-2 py-1">${a.baseline ? '<span class="text-gray-500">known</span>' : '<span class="text-amber-300">NEW</span>'}</td> |
| 7034 | </tr>`; |
| 7035 | }).join('') + '</tbody></table>'; |
| 7036 | } |
| 7037 | const comms = d.communities || []; |
| 7038 | if (comms.length) { |
| 7039 | html += '<p class="text-xs uppercase text-gray-400 mt-2 mb-1">Community strings exposed (' + comms.length + ')</p>' + |
| 7040 | '<table class="min-w-full text-xs text-gray-300 whitespace-nowrap"><thead>' + |
| 7041 | '<tr class="text-left text-gray-500"><th class="px-2 py-1">Community</th><th class="px-2 py-1">Version</th><th class="px-2 py-1">Seen</th><th class="px-2 py-1">Notes</th></tr>' + |
| 7042 | '</thead><tbody>' + |
| 7043 | comms.map(c => { |
| 7044 | const notes = []; |
| 7045 | if (c.default) notes.push('<span class="text-amber-300">default/guessable</span>'); |
| 7046 | if (c.writes) notes.push('<span class="text-red-300">write</span>'); |
| 7047 | return `<tr class="border-t border-slate-800"> |
| 7048 | <td class="px-2 py-1 font-mono text-red-300">"${escapeHtml(c.community)}"</td> |
| 7049 | <td class="px-2 py-1 text-gray-400">${escapeHtml((c.versions || []).join(', '))}</td> |
| 7050 | <td class="px-2 py-1 text-gray-400">×${c.count}</td> |
| 7051 | <td class="px-2 py-1">${notes.join(', ') || '—'}</td> |
| 7052 | </tr>`; |
| 7053 | }).join('') + '</tbody></table>'; |
| 7054 | } |
| 7055 | if (d.reasons && d.reasons.length) { |
| 7056 | html += '<ul class="text-xs text-gray-400 mt-2 list-disc pl-5">' + |
| 7057 | d.reasons.map(r => '<li>' + escapeHtml(r) + '</li>').join('') + '</ul>'; |
| 7058 | } |
| 7059 | if (d.advisories && d.advisories.length) { |
| 7060 | html += '<ul class="text-xs text-cyan-400/80 mt-2 list-disc pl-5">' + |
| 7061 | d.advisories.map(a => '<li>' + escapeHtml(a) + '</li>').join('') + '</ul>'; |
| 7062 | } |
| 7063 | out.innerHTML = html; |
| 7064 | } catch (e) { |
| 7065 | out.innerHTML = '<p class="text-sm text-red-400">Failed: ' + escapeHtml(e.message) + '</p>'; |
| 7066 | } finally { |
| 7067 | _ndBusy(btn, false); |
| 7068 | } |
| 7069 | } |
| 7070 | async function snmpTrustBaseline() { |
| 7071 | try { |
| 7072 | await postAPI('/api/net/snmp-baseline', { action: 'reset' }); |
| 7073 | addConsoleMessage('SNMP baseline reset — re-learning current agents / community strings', 'info'); |
| 7074 | await runSnmpWatch(); |
| 7075 | } catch (e) { |
| 7076 | addConsoleMessage('Failed to reset SNMP baseline: ' + e.message, 'error'); |
| 7077 | } |
| 7078 | } |
| 7079 | |
| 7080 | // ---- Cert Watch (active certificate & TLS hygiene checker) ------------------ |
| 7081 | const _CERT_VERDICT_STYLE = { |
| 7082 | valid: ['bg-green-950/40 border-green-900 text-green-400', '✓ valid'], |
| 7083 | clean: ['bg-green-950/40 border-green-900 text-green-400', '✓ All certificates valid, trusted and in-date'], |
| 7084 | expiring: ['bg-amber-950/50 border-amber-800 text-amber-300', '⚠ expiring soon'], |
| 7085 | 'deprecated-tls': ['bg-amber-950/50 border-amber-800 text-amber-300', '⚠ deprecated TLS'], |
| 7086 | 'weak-crypto': ['bg-amber-950/50 border-amber-800 text-amber-300', '⚠ weak crypto'], |
| 7087 | 'hostname-mismatch':['bg-red-950/60 border-red-800 text-red-300', '🛑 hostname mismatch'], |
| 7088 | untrusted: ['bg-red-950/60 border-red-800 text-red-300', '🛑 untrusted chain'], |
| 7089 | 'self-signed': ['bg-red-950/60 border-red-800 text-red-300', '🛑 self-signed'], |
nothing calls this directly
no test coverage detected