diff --git a/internal/webui/index.html b/internal/webui/index.html index 1be51ed..ba5a91d 100644 --- a/internal/webui/index.html +++ b/internal/webui/index.html @@ -60,7 +60,7 @@
Найденные устройства - отвечают на ping; клик по строке — связи этого устройства в таблицах ниже. Граф — «Загрузить». + отвечают на ping; колонка LLDP: при отсутствии соседей в MIB — пометка «No LLDP data» (часто для коммутаторов без LLDP или без успешного SNMP). Клик по строке — связи ниже.
@@ -71,10 +71,11 @@ Ping Имя (SNMP) SNMP + LLDP - Укажите scan_id или создайте scan. + Укажите scan_id или создайте scan.
@@ -173,7 +174,7 @@ let lastTopology = null; let lastHighlight = null; let lastDiff = null; - /** @type {{ip:string,ping:string,name:string,snmp:string}[]} */ + /** @type {{ip:string,ping:string,name:string,snmp:string,lldp:string,lldpCount:number}[]} */ let deviceRowsCache = []; let deviceSort = { key: "ip", dir: 1 }; let selectedDeviceIP = null; @@ -264,6 +265,8 @@ cmp = String(a.name).localeCompare(String(b.name), "ru", { sensitivity: "base" }); } else if (key === "snmp") { cmp = String(a.snmp).localeCompare(String(b.snmp), "ru", { sensitivity: "base" }); + } else if (key === "lldp") { + cmp = (a.lldpCount || 0) - (b.lldpCount || 0); } return cmp; } @@ -281,14 +284,16 @@ function renderDeviceTableBody() { updateDeviceSortIndicators(); if (deviceRowsCache.length === 0) { - deviceTableBody.innerHTML = 'Нет хостов с ping «вверх» для этого scan (или скан ещё не дошёл до них).'; + deviceTableBody.innerHTML = 'Нет хостов с ping «вверх» для этого scan (или скан ещё не дошёл до них).'; return; } const sorted = [...deviceRowsCache].sort((a, b) => deviceSort.dir * compareRows(a, b, deviceSort.key)); deviceTableBody.innerHTML = sorted .map((r) => { const dip = escapeHtml(r.ip); - return `${dip}${escapeHtml(r.ping)}${escapeHtml(r.name)}${escapeHtml(r.snmp)}`; + const noLldp = (r.lldpCount || 0) === 0; + const lldpStyle = noLldp ? ' style="color:#9a3412;font-weight:500;"' : ""; + return `${dip}${escapeHtml(r.ping)}${escapeHtml(r.name)}${escapeHtml(r.snmp)}${escapeHtml(r.lldp)}`; }) .join(""); deviceTableBody.querySelectorAll("tr.device-row").forEach((tr) => { @@ -405,38 +410,49 @@ if (!scanId) { deviceRowsCache = []; clearDeviceDetailPanel(); - deviceTableBody.innerHTML = 'Нет scan_id.'; + deviceTableBody.innerHTML = 'Нет scan_id.'; updateDeviceSortIndicators(); return; } clearDeviceDetailPanel(); - deviceTableBody.innerHTML = 'Загрузка…'; + deviceTableBody.innerHTML = 'Загрузка…'; try { - const [hRes, sRes] = await Promise.all([ + const [hRes, sRes, lRes] = await Promise.all([ fetch(`/api/scans/${encodeURIComponent(scanId)}/hosts?limit=5000`), fetch(`/api/scans/${encodeURIComponent(scanId)}/snmp?limit=5000`), + fetch(`/api/scans/${encodeURIComponent(scanId)}/lldp?limit=10000`), ]); if (!hRes.ok) throw new Error(await hRes.text()); if (!sRes.ok) throw new Error(await sRes.text()); + if (!lRes.ok) throw new Error((await lRes.text()) || "LLDP"); const hData = await hRes.json(); const sData = await sRes.json(); + const lData = await lRes.json(); const snmpByIp = new Map(); for (const r of sData.items || []) { snmpByIp.set(r.ip, r); } + const lldpCountByIp = new Map(); + for (const row of lData.items || []) { + const ipk = row.ip; + if (!ipk) continue; + lldpCountByIp.set(ipk, (lldpCountByIp.get(ipk) || 0) + 1); + } deviceRowsCache = []; for (const h of hData.items || []) { if (!h.is_up) continue; const s = snmpByIp.get(h.ip); const name = s && s.success ? s.sys_name || "(пусто)" : "—"; const snmpCell = s ? (s.success ? "OK" : s.error || "нет ответа") : "не опрашивался"; - deviceRowsCache.push({ ip: h.ip, ping: "да", name, snmp: snmpCell }); + const lldpCount = lldpCountByIp.get(h.ip) || 0; + const lldpCell = lldpCount === 0 ? "No LLDP data" : `${lldpCount} записей`; + deviceRowsCache.push({ ip: h.ip, ping: "да", name, snmp: snmpCell, lldp: lldpCell, lldpCount }); } renderDeviceTableBody(); } catch (e) { deviceRowsCache = []; clearDeviceDetailPanel(); - deviceTableBody.innerHTML = `Ошибка: ${escapeHtml(e.message)}`; + deviceTableBody.innerHTML = `Ошибка: ${escapeHtml(e.message)}`; updateDeviceSortIndicators(); } }