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();
}
}