UI: LLDP column in device list with No LLDP data marker.
Made-with: Cursor
This commit is contained in:
+26
-10
@@ -60,7 +60,7 @@
|
||||
<div class="panel">
|
||||
<div class="row" style="margin-bottom: 6px;">
|
||||
<strong>Найденные устройства</strong>
|
||||
<span class="hint" style="margin:0;">отвечают на ping; клик по строке — связи этого устройства в таблицах ниже. Граф — «Загрузить».</span>
|
||||
<span class="hint" style="margin:0;">отвечают на ping; колонка LLDP: при отсутствии соседей в MIB — пометка «No LLDP data» (часто для коммутаторов без LLDP или без успешного SNMP). Клик по строке — связи ниже.</span>
|
||||
<button type="button" id="refreshDevicesBtn">Обновить список</button>
|
||||
</div>
|
||||
<div style="overflow:auto; max-height: 240px; border: 1px solid #e5e7eb; border-radius: 8px;">
|
||||
@@ -71,10 +71,11 @@
|
||||
<th class="sortable-th" data-sort="ping" style="text-align:left;">Ping <span class="sort-ind"></span></th>
|
||||
<th class="sortable-th" data-sort="name" style="text-align:left;">Имя (SNMP) <span class="sort-ind"></span></th>
|
||||
<th class="sortable-th" data-sort="snmp" style="text-align:left;">SNMP <span class="sort-ind"></span></th>
|
||||
<th class="sortable-th" data-sort="lldp" style="text-align:left;">LLDP <span class="sort-ind"></span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="deviceTableBody">
|
||||
<tr><td colspan="4" style="padding:10px;color:#64748b;">Укажите scan_id или создайте scan.</td></tr>
|
||||
<tr><td colspan="5" style="padding:10px;color:#64748b;">Укажите scan_id или создайте scan.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -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 = '<tr><td colspan="4" style="padding:10px;color:#64748b;">Нет хостов с ping «вверх» для этого scan (или скан ещё не дошёл до них).</td></tr>';
|
||||
deviceTableBody.innerHTML = '<tr><td colspan="5" style="padding:10px;color:#64748b;">Нет хостов с ping «вверх» для этого scan (или скан ещё не дошёл до них).</td></tr>';
|
||||
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 `<tr class="device-row" data-ip="${dip}" title="Показать связи этого устройства"><td>${dip}</td><td>${escapeHtml(r.ping)}</td><td>${escapeHtml(r.name)}</td><td>${escapeHtml(r.snmp)}</td></tr>`;
|
||||
const noLldp = (r.lldpCount || 0) === 0;
|
||||
const lldpStyle = noLldp ? ' style="color:#9a3412;font-weight:500;"' : "";
|
||||
return `<tr class="device-row" data-ip="${dip}" title="Показать связи этого устройства"><td>${dip}</td><td>${escapeHtml(r.ping)}</td><td>${escapeHtml(r.name)}</td><td>${escapeHtml(r.snmp)}</td><td${lldpStyle}>${escapeHtml(r.lldp)}</td></tr>`;
|
||||
})
|
||||
.join("");
|
||||
deviceTableBody.querySelectorAll("tr.device-row").forEach((tr) => {
|
||||
@@ -405,38 +410,49 @@
|
||||
if (!scanId) {
|
||||
deviceRowsCache = [];
|
||||
clearDeviceDetailPanel();
|
||||
deviceTableBody.innerHTML = '<tr><td colspan="4" style="padding:10px;color:#64748b;">Нет scan_id.</td></tr>';
|
||||
deviceTableBody.innerHTML = '<tr><td colspan="5" style="padding:10px;color:#64748b;">Нет scan_id.</td></tr>';
|
||||
updateDeviceSortIndicators();
|
||||
return;
|
||||
}
|
||||
clearDeviceDetailPanel();
|
||||
deviceTableBody.innerHTML = '<tr><td colspan="4" style="padding:10px;color:#64748b;">Загрузка…</td></tr>';
|
||||
deviceTableBody.innerHTML = '<tr><td colspan="5" style="padding:10px;color:#64748b;">Загрузка…</td></tr>';
|
||||
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 = `<tr><td colspan="4" style="padding:10px;color:#b91c1c;">Ошибка: ${escapeHtml(e.message)}</td></tr>`;
|
||||
deviceTableBody.innerHTML = `<tr><td colspan="5" style="padding:10px;color:#b91c1c;">Ошибка: ${escapeHtml(e.message)}</td></tr>`;
|
||||
updateDeviceSortIndicators();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user