()
| 7271 | const bad = (i.status === 'hijacked' || i.status === 'new'); |
| 7272 | const st = bad ? `<span class="text-red-300">${escapeHtml(i.status)}</span>` : `<span class="text-gray-500">${escapeHtml(i.status)}</span>`; |
| 7273 | return `<tr class="border-t border-slate-800"> |
| 7274 | <td class="px-2 py-1 text-gray-400">${escapeHtml(i.proto)}</td> |
| 7275 | <td class="px-2 py-1 font-mono">${i.vlan}</td> |
| 7276 | <td class="px-2 py-1 font-mono ${bad ? 'text-red-300' : ''}">${i.root_prio == null ? '—' : i.root_prio}.${escapeHtml(i.root_mac || '?')}</td> |
| 7277 | <td class="px-2 py-1 font-mono text-gray-400">${escapeHtml(i.advertised_by || '—')}</td> |
| 7278 | <td class="px-2 py-1">${st}</td> |
| 7279 | </tr>`; |
| 7280 | }).join('') + |
| 7281 | '</tbody></table>'; |
| 7282 | } |
| 7283 | if ((d.bridges || []).length) { |
| 7284 | html += '<p class="text-xs uppercase text-gray-400 mt-2 mb-1">Bridges seen (' + d.bridges.length + ')</p>' + |
| 7285 | '<table class="min-w-full text-xs text-gray-300 whitespace-nowrap"><thead>' + |
| 7286 | '<tr class="text-left text-gray-500"><th class="px-2 py-1">Bridge MAC</th><th class="px-2 py-1">Proto</th><th class="px-2 py-1">Role(s)</th><th class="px-2 py-1">Status</th></tr>' + |
| 7287 | '</thead><tbody>' + |
| 7288 | d.bridges.map(b => { |
| 7289 | const badge = b.baseline ? '<span class="text-green-400">✓ known</span>' : '<span class="text-amber-300">? new</span>'; |
| 7290 | return `<tr class="border-t border-slate-800"> |
| 7291 | <td class="px-2 py-1 font-mono ${b.baseline ? '' : 'text-amber-300'}">${escapeHtml(b.mac)}</td> |
| 7292 | <td class="px-2 py-1 text-gray-400">${escapeHtml(b.proto)}</td> |
| 7293 | <td class="px-2 py-1 text-gray-400">${escapeHtml((b.roles || []).join(', ') || '—')}</td> |
| 7294 | <td class="px-2 py-1">${badge}</td> |
| 7295 | </tr>`; |
| 7296 | }).join('') + |
| 7297 | '</tbody></table>'; |
| 7298 | } |
| 7299 | if (d.reasons && d.reasons.length) { |
| 7300 | html += '<ul class="text-xs text-gray-400 mt-2 list-disc pl-5">' + |
| 7301 | d.reasons.map(r => '<li>' + escapeHtml(r) + '</li>').join('') + '</ul>'; |
| 7302 | } |
| 7303 | (d.advisories || []).forEach(a => { |
| 7304 | html += `<div class="mt-2 px-3 py-2 rounded border text-xs text-gray-400 border-slate-700 bg-slate-900/40">${escapeHtml(a)}</div>`; |
| 7305 | }); |
| 7306 | out.innerHTML = html; |
| 7307 | } catch (e) { |
| 7308 | out.innerHTML = '<p class="text-sm text-red-400">Failed: ' + escapeHtml(e.message) + '</p>'; |
| 7309 | } finally { |
| 7310 | _ndBusy(btn, false); |
| 7311 | } |
| 7312 | } |
| 7313 | async function stpTrustBaseline() { |
| 7314 | try { |
| 7315 | await postAPI('/api/net/stp-baseline', { action: 'reset' }); |
| 7316 | addConsoleMessage('STP baseline reset — re-learning current root(s) & bridges', 'info'); |
| 7317 | await runStpWatch(); |
| 7318 | } catch (e) { |
| 7319 | addConsoleMessage('Failed to reset STP baseline: ' + e.message, 'error'); |
| 7320 | } |
| 7321 | } |
no test coverage detected