feat: VLAN in FDB, persist vlan, enrich port-devices with hostname, UI columns
Made-with: Cursor
This commit is contained in:
+23
-1
@@ -364,8 +364,30 @@ func (h *Handler) getScanPortDevices(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
limit = n
|
limit = n
|
||||||
}
|
}
|
||||||
|
snmpItems := h.store.ListSNMPResults(id, 200000)
|
||||||
|
ipToName := make(map[string]string, len(snmpItems))
|
||||||
|
for _, s := range snmpItems {
|
||||||
|
if !s.Success {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := strings.TrimSpace(s.SysName)
|
||||||
|
if name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := ipToName[s.IP]; !ok {
|
||||||
|
ipToName[s.IP] = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items := h.store.ListPortDeviceResults(id, ip, ifIndex, limit)
|
||||||
|
for i := range items {
|
||||||
|
if lip := strings.TrimSpace(items[i].LearnedIP); lip != "" {
|
||||||
|
if nm, ok := ipToName[lip]; ok {
|
||||||
|
items[i].Hostname = nm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
writeJSON(w, http.StatusOK, map[string]any{
|
writeJSON(w, http.StatusOK, map[string]any{
|
||||||
"items": h.store.ListPortDeviceResults(id, ip, ifIndex, limit),
|
"items": items,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -542,8 +542,8 @@ func (s *PostgresStore) PurgeAllScanData() error {
|
|||||||
|
|
||||||
func (s *PostgresStore) SavePortDeviceResult(scanID string, result PortDeviceResult) error {
|
func (s *PostgresStore) SavePortDeviceResult(scanID string, result PortDeviceResult) error {
|
||||||
query := `
|
query := `
|
||||||
insert into scan_port_devices (scan_id, ip, if_index, bridge_port, mac, learned_ip, checked_at)
|
insert into scan_port_devices (scan_id, ip, if_index, bridge_port, vlan, mac, learned_ip, checked_at)
|
||||||
values ($1, $2, $3, $4, $5, $6, $7)`
|
values ($1, $2, $3, $4, $5, $6, $7, $8)`
|
||||||
var learnedIP any
|
var learnedIP any
|
||||||
if v := strings.TrimSpace(result.LearnedIP); v != "" {
|
if v := strings.TrimSpace(result.LearnedIP); v != "" {
|
||||||
learnedIP = v
|
learnedIP = v
|
||||||
@@ -557,6 +557,7 @@ values ($1, $2, $3, $4, $5, $6, $7)`
|
|||||||
result.IP,
|
result.IP,
|
||||||
result.IfIndex,
|
result.IfIndex,
|
||||||
result.BridgePort,
|
result.BridgePort,
|
||||||
|
result.Vlan,
|
||||||
result.MAC,
|
result.MAC,
|
||||||
learnedIP,
|
learnedIP,
|
||||||
result.CheckedAt,
|
result.CheckedAt,
|
||||||
@@ -569,12 +570,12 @@ func (s *PostgresStore) ListPortDeviceResults(scanID string, filterIP string, fi
|
|||||||
limit = 20000
|
limit = 20000
|
||||||
}
|
}
|
||||||
query := `
|
query := `
|
||||||
select ip::text, if_index, bridge_port, coalesce(mac, ''), coalesce(learned_ip::text, ''), checked_at
|
select ip::text, if_index, bridge_port, vlan, coalesce(mac, ''), coalesce(learned_ip::text, ''), checked_at
|
||||||
from scan_port_devices
|
from scan_port_devices
|
||||||
where scan_id = $1
|
where scan_id = $1
|
||||||
and ($2 = '' or ip = $2::inet)
|
and ($2 = '' or ip = $2::inet)
|
||||||
and ($3 = 0 or if_index = $3 or bridge_port = $3)
|
and ($3 = 0 or if_index = $3 or bridge_port = $3)
|
||||||
order by if_index asc, mac asc, learned_ip asc
|
order by if_index asc, vlan asc, mac asc, learned_ip asc
|
||||||
limit $4`
|
limit $4`
|
||||||
rows, err := s.db.QueryContext(context.Background(), query, scanID, filterIP, filterIfIndex, limit)
|
rows, err := s.db.QueryContext(context.Background(), query, scanID, filterIP, filterIfIndex, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -585,7 +586,7 @@ limit $4`
|
|||||||
out := make([]PortDeviceResult, 0, 256)
|
out := make([]PortDeviceResult, 0, 256)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var r PortDeviceResult
|
var r PortDeviceResult
|
||||||
if err = rows.Scan(&r.IP, &r.IfIndex, &r.BridgePort, &r.MAC, &r.LearnedIP, &r.CheckedAt); err != nil {
|
if err = rows.Scan(&r.IP, &r.IfIndex, &r.BridgePort, &r.Vlan, &r.MAC, &r.LearnedIP, &r.CheckedAt); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
out = append(out, r)
|
out = append(out, r)
|
||||||
|
|||||||
@@ -266,7 +266,8 @@ func probePortDevices(client *gosnmp.GoSNMP, ip string, checkedAt time.Time) []P
|
|||||||
seen := make(map[string]struct{})
|
seen := make(map[string]struct{})
|
||||||
out := make([]PortDeviceResult, 0, len(combinedFdb))
|
out := make([]PortDeviceResult, 0, len(combinedFdb))
|
||||||
for macTail, bridgePortRaw := range combinedFdb {
|
for macTail, bridgePortRaw := range combinedFdb {
|
||||||
mac := normalizeMACKey(dottedDecimalToMACTail(macTail))
|
vlan, macSix := parseFdbKeyVlanAndMacTail(macTail)
|
||||||
|
mac := normalizeMACKey(dottedDecimalToMACTail(macSix))
|
||||||
if mac == "" {
|
if mac == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -285,7 +286,7 @@ func probePortDevices(client *gosnmp.GoSNMP, ip string, checkedAt time.Time) []P
|
|||||||
|
|
||||||
ipsSet := macToIPs[mac]
|
ipsSet := macToIPs[mac]
|
||||||
if len(ipsSet) == 0 {
|
if len(ipsSet) == 0 {
|
||||||
key := fmt.Sprintf("%d|%s|", ifIndex, mac)
|
key := fmt.Sprintf("%d|%d|%s|", vlan, ifIndex, mac)
|
||||||
if _, ok := seen[key]; ok {
|
if _, ok := seen[key]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -294,6 +295,7 @@ func probePortDevices(client *gosnmp.GoSNMP, ip string, checkedAt time.Time) []P
|
|||||||
IP: ip,
|
IP: ip,
|
||||||
IfIndex: ifIndex,
|
IfIndex: ifIndex,
|
||||||
BridgePort: bridgePort,
|
BridgePort: bridgePort,
|
||||||
|
Vlan: vlan,
|
||||||
MAC: mac,
|
MAC: mac,
|
||||||
LearnedIP: "",
|
LearnedIP: "",
|
||||||
CheckedAt: checkedAt,
|
CheckedAt: checkedAt,
|
||||||
@@ -301,7 +303,7 @@ func probePortDevices(client *gosnmp.GoSNMP, ip string, checkedAt time.Time) []P
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for learnedIP := range ipsSet {
|
for learnedIP := range ipsSet {
|
||||||
key := fmt.Sprintf("%d|%s|%s", ifIndex, mac, learnedIP)
|
key := fmt.Sprintf("%d|%d|%s|%s", vlan, ifIndex, mac, learnedIP)
|
||||||
if _, ok := seen[key]; ok {
|
if _, ok := seen[key]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -310,6 +312,7 @@ func probePortDevices(client *gosnmp.GoSNMP, ip string, checkedAt time.Time) []P
|
|||||||
IP: ip,
|
IP: ip,
|
||||||
IfIndex: ifIndex,
|
IfIndex: ifIndex,
|
||||||
BridgePort: bridgePort,
|
BridgePort: bridgePort,
|
||||||
|
Vlan: vlan,
|
||||||
MAC: mac,
|
MAC: mac,
|
||||||
LearnedIP: learnedIP,
|
LearnedIP: learnedIP,
|
||||||
CheckedAt: checkedAt,
|
CheckedAt: checkedAt,
|
||||||
@@ -321,6 +324,9 @@ func probePortDevices(client *gosnmp.GoSNMP, ip string, checkedAt time.Time) []P
|
|||||||
if out[i].IfIndex != out[j].IfIndex {
|
if out[i].IfIndex != out[j].IfIndex {
|
||||||
return out[i].IfIndex < out[j].IfIndex
|
return out[i].IfIndex < out[j].IfIndex
|
||||||
}
|
}
|
||||||
|
if out[i].Vlan != out[j].Vlan {
|
||||||
|
return out[i].Vlan < out[j].Vlan
|
||||||
|
}
|
||||||
if out[i].MAC != out[j].MAC {
|
if out[i].MAC != out[j].MAC {
|
||||||
return out[i].MAC < out[j].MAC
|
return out[i].MAC < out[j].MAC
|
||||||
}
|
}
|
||||||
@@ -333,6 +339,19 @@ func probePortDevices(client *gosnmp.GoSNMP, ip string, checkedAt time.Time) []P
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseFdbKeyVlanAndMacTail — суффикс OID FDB: dot1q — {vlan}.{6 октетов MAC}; dot1d — только 6 октетов (vlan=0).
|
||||||
|
func parseFdbKeyVlanAndMacTail(macTail string) (vlan int, macSixTail string) {
|
||||||
|
p := strings.Split(strings.TrimSpace(macTail), ".")
|
||||||
|
if len(p) >= 7 {
|
||||||
|
vlan, _ = strconv.Atoi(p[0])
|
||||||
|
return vlan, strings.Join(p[1:7], ".")
|
||||||
|
}
|
||||||
|
if len(p) >= 6 {
|
||||||
|
return 0, strings.Join(p[len(p)-6:], ".")
|
||||||
|
}
|
||||||
|
return 0, ""
|
||||||
|
}
|
||||||
|
|
||||||
// snmpNumericString — значение SNMP как строка (int/float/[]byte) для Atoi.
|
// snmpNumericString — значение SNMP как строка (int/float/[]byte) для Atoi.
|
||||||
func snmpNumericString(v any) string {
|
func snmpNumericString(v any) string {
|
||||||
switch x := v.(type) {
|
switch x := v.(type) {
|
||||||
|
|||||||
@@ -122,8 +122,10 @@ type PortDeviceResult struct {
|
|||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
IfIndex int `json:"if_index"`
|
IfIndex int `json:"if_index"`
|
||||||
BridgePort int `json:"bridge_port,omitempty"`
|
BridgePort int `json:"bridge_port,omitempty"`
|
||||||
|
Vlan int `json:"vlan"`
|
||||||
MAC string `json:"mac"`
|
MAC string `json:"mac"`
|
||||||
LearnedIP string `json:"learned_ip"`
|
LearnedIP string `json:"learned_ip"`
|
||||||
|
Hostname string `json:"hostname,omitempty"` // подставляется при ответе API по sysName скана, не хранится в БД
|
||||||
CheckedAt time.Time `json:"checked_at"`
|
CheckedAt time.Time `json:"checked_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,6 +421,9 @@ func (s *MemoryStore) ListPortDeviceResults(scanID string, filterIP string, filt
|
|||||||
if filtered[i].IfIndex != filtered[j].IfIndex {
|
if filtered[i].IfIndex != filtered[j].IfIndex {
|
||||||
return filtered[i].IfIndex < filtered[j].IfIndex
|
return filtered[i].IfIndex < filtered[j].IfIndex
|
||||||
}
|
}
|
||||||
|
if filtered[i].Vlan != filtered[j].Vlan {
|
||||||
|
return filtered[i].Vlan < filtered[j].Vlan
|
||||||
|
}
|
||||||
if filtered[i].MAC != filtered[j].MAC {
|
if filtered[i].MAC != filtered[j].MAC {
|
||||||
return filtered[i].MAC < filtered[j].MAC
|
return filtered[i].MAC < filtered[j].MAC
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ var scanPortDevicesSQL string
|
|||||||
//go:embed sql/004_scan_port_devices_bridge_port.sql
|
//go:embed sql/004_scan_port_devices_bridge_port.sql
|
||||||
var scanPortDevicesBridgePortSQL string
|
var scanPortDevicesBridgePortSQL string
|
||||||
|
|
||||||
|
//go:embed sql/005_scan_port_devices_vlan.sql
|
||||||
|
var scanPortDevicesVlanSQL string
|
||||||
|
|
||||||
func RunMigrations(db *sql.DB) error {
|
func RunMigrations(db *sql.DB) error {
|
||||||
if _, err := db.Exec(initSQL); err != nil {
|
if _, err := db.Exec(initSQL); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -30,5 +33,8 @@ func RunMigrations(db *sql.DB) error {
|
|||||||
if _, err := db.Exec(scanPortDevicesBridgePortSQL); err != nil {
|
if _, err := db.Exec(scanPortDevicesBridgePortSQL); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if _, err := db.Exec(scanPortDevicesVlanSQL); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ create table if not exists scan_port_devices (
|
|||||||
ip inet not null,
|
ip inet not null,
|
||||||
if_index int not null,
|
if_index int not null,
|
||||||
bridge_port int not null default 0,
|
bridge_port int not null default 0,
|
||||||
|
vlan int not null default 0,
|
||||||
mac text not null,
|
mac text not null,
|
||||||
learned_ip inet null,
|
learned_ip inet null,
|
||||||
checked_at timestamptz not null
|
checked_at timestamptz not null
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
alter table scan_port_devices add column if not exists vlan int not null default 0;
|
||||||
+27
-17
@@ -124,18 +124,20 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div style="font-weight:600; margin-bottom:4px; color:#334155;">Таблица 2. Устройства за портом (MAC/IP по BRIDGE-MIB + ARP)</div>
|
<div style="font-weight:600; margin-bottom:4px; color:#334155;">Таблица 2. Устройства за портом (VLAN, MAC, IP, имя — как MAC table; IP/имя при наличии ARP и строки SNMP в этом scan)</div>
|
||||||
<div class="subtable-wrap">
|
<div class="subtable-wrap">
|
||||||
<table class="device-detail-table" id="deviceLldpTextTable" style="width:100%; border-collapse:collapse;">
|
<table class="device-detail-table" id="deviceLldpTextTable" style="width:100%; border-collapse:collapse;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr style="background:#f1f5f9;">
|
<tr style="background:#f1f5f9;">
|
||||||
<th style="text-align:left;">ifIndex</th>
|
<th style="text-align:left;">ifIndex</th>
|
||||||
|
<th style="text-align:left;">VLAN</th>
|
||||||
<th style="text-align:left;">MAC</th>
|
<th style="text-align:left;">MAC</th>
|
||||||
<th style="text-align:left;">IP</th>
|
<th style="text-align:left;">IP</th>
|
||||||
|
<th style="text-align:left;">Имя (sysName)</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="deviceLldpTextTableBody">
|
<tbody id="deviceLldpTextTableBody">
|
||||||
<tr><td colspan="3" style="padding:10px;color:#64748b;">Выберите устройство и порт в Таблице 1.</td></tr>
|
<tr><td colspan="5" style="padding:10px;color:#64748b;">Выберите устройство и порт в Таблице 1.</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -341,7 +343,7 @@
|
|||||||
deviceLinksTableBody.innerHTML =
|
deviceLinksTableBody.innerHTML =
|
||||||
'<tr><td colspan="9" style="padding:10px;color:#64748b;">Выберите устройство в таблице выше.</td></tr>';
|
'<tr><td colspan="9" style="padding:10px;color:#64748b;">Выберите устройство в таблице выше.</td></tr>';
|
||||||
deviceLldpTextTableBody.innerHTML =
|
deviceLldpTextTableBody.innerHTML =
|
||||||
'<tr><td colspan="3" style="padding:10px;color:#64748b;">Выберите устройство и порт в Таблице 1.</td></tr>';
|
'<tr><td colspan="5" style="padding:10px;color:#64748b;">Выберите устройство и порт в Таблице 1.</td></tr>';
|
||||||
updateDeviceLinksSortIndicators();
|
updateDeviceLinksSortIndicators();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,23 +386,26 @@
|
|||||||
const ifi = Number(p.if_index) || 0;
|
const ifi = Number(p.if_index) || 0;
|
||||||
if (ifi === sortKey || br === sortKey) hits.push(p);
|
if (ifi === sortKey || br === sortKey) hits.push(p);
|
||||||
}
|
}
|
||||||
const byMac = new Map();
|
const byVlanMac = new Map();
|
||||||
for (const p of hits) {
|
for (const p of hits) {
|
||||||
const mk = normalizeMacLocal(p.mac);
|
const mk = normalizeMacLocal(p.mac);
|
||||||
if (!mk) continue;
|
if (!mk) continue;
|
||||||
|
const vlan = Number(p.vlan) || 0;
|
||||||
|
const k = `${vlan}|${mk}`;
|
||||||
const ip = String(p.learned_ip || "").trim();
|
const ip = String(p.learned_ip || "").trim();
|
||||||
const cur = byMac.get(mk);
|
const cur = byVlanMac.get(k);
|
||||||
const macDisp = String(p.mac || "").trim() || mk;
|
const macDisp = String(p.mac || "").trim() || mk;
|
||||||
if (!cur || (ip && !cur.ip)) byMac.set(mk, { mac: macDisp, ip });
|
if (!cur || (ip && !cur.ip)) byVlanMac.set(k, { mac: macDisp, vlan, ip });
|
||||||
}
|
}
|
||||||
const rows = [...byMac.values()];
|
const rows = [...byVlanMac.values()];
|
||||||
const n = rows.length;
|
const n = rows.length;
|
||||||
if (n === 0) return { macText: "—", ipText: "—" };
|
if (n === 0) return { macText: "—", ipText: "—" };
|
||||||
if (n === 1) {
|
if (n === 1) {
|
||||||
const r = rows[0];
|
const r = rows[0];
|
||||||
return { macText: r.mac, ipText: r.ip || "—" };
|
const macShow = r.vlan ? `${r.mac} (VLAN ${r.vlan})` : r.mac;
|
||||||
|
return { macText: macShow, ipText: r.ip || "—" };
|
||||||
}
|
}
|
||||||
return { macText: `несколько MAC (${n})`, ipText: "—" };
|
return { macText: `несколько MAC/VLAN (${n})`, ipText: "—" };
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachFdbSummaries(rows, portItems) {
|
function attachFdbSummaries(rows, portItems) {
|
||||||
@@ -541,10 +546,13 @@
|
|||||||
selectedFdbKey = null;
|
selectedFdbKey = null;
|
||||||
if (!items || items.length === 0) {
|
if (!items || items.length === 0) {
|
||||||
deviceLldpTextTableBody.innerHTML =
|
deviceLldpTextTableBody.innerHTML =
|
||||||
'<tr><td colspan="3" style="padding:10px;color:#64748b;">За этим портом MAC/IP не найдены (или устройство не отдаёт BRIDGE-MIB/ARP).</td></tr>';
|
'<tr><td colspan="5" style="padding:10px;color:#64748b;">За этим портом MAC/IP не найдены (или устройство не отдаёт BRIDGE-MIB/ARP).</td></tr>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const sorted = [...items].sort((a, b) => {
|
const sorted = [...items].sort((a, b) => {
|
||||||
|
const va = Number(a.vlan) || 0;
|
||||||
|
const vb = Number(b.vlan) || 0;
|
||||||
|
if (va !== vb) return va - vb;
|
||||||
const ma = String(a.mac || "").localeCompare(String(b.mac || ""));
|
const ma = String(a.mac || "").localeCompare(String(b.mac || ""));
|
||||||
if (ma !== 0) return ma;
|
if (ma !== 0) return ma;
|
||||||
return String(a.learned_ip || "").localeCompare(String(b.learned_ip || ""));
|
return String(a.learned_ip || "").localeCompare(String(b.learned_ip || ""));
|
||||||
@@ -553,22 +561,24 @@
|
|||||||
.map((row) => {
|
.map((row) => {
|
||||||
const mk = normalizeMacLocal(row.mac);
|
const mk = normalizeMacLocal(row.mac);
|
||||||
const ip = String(row.learned_ip || "").trim();
|
const ip = String(row.learned_ip || "").trim();
|
||||||
const key = `${mk}|${ip}`;
|
const vlan = Number(row.vlan) || 0;
|
||||||
return `<tr class="fdb-row" data-fdb-key="${escapeHtml(key)}"><td>${escapeHtml(row.if_index)}</td><td>${escapeHtml(row.mac || "—")}</td><td>${escapeHtml(row.learned_ip || "—")}</td></tr>`;
|
const key = `${vlan}|${mk}|${ip}`;
|
||||||
|
const host = String(row.hostname || "").trim();
|
||||||
|
return `<tr class="fdb-row" data-fdb-key="${escapeHtml(key)}"><td>${escapeHtml(String(row.if_index ?? ""))}</td><td>${escapeHtml(String(vlan))}</td><td>${escapeHtml(row.mac || "—")}</td><td>${escapeHtml(row.learned_ip || "—")}</td><td>${escapeHtml(host || "—")}</td></tr>`;
|
||||||
})
|
})
|
||||||
.join("");
|
.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPortDevicesForSelectedPort() {
|
function loadPortDevicesForSelectedPort() {
|
||||||
if (!selectedDeviceIP || selectedIfIndex == null) return;
|
if (!selectedDeviceIP || selectedIfIndex == null) return;
|
||||||
deviceLldpTextTableBody.innerHTML = '<tr><td colspan="3" style="padding:10px;color:#64748b;">Загрузка…</td></tr>';
|
deviceLldpTextTableBody.innerHTML = '<tr><td colspan="5" style="padding:10px;color:#64748b;">Загрузка…</td></tr>';
|
||||||
try {
|
try {
|
||||||
const items = lastPortDevItems.filter(
|
const items = lastPortDevItems.filter(
|
||||||
(p) => Number(p.if_index) === selectedIfIndex || Number(p.bridge_port) === selectedIfIndex
|
(p) => Number(p.if_index) === selectedIfIndex || Number(p.bridge_port) === selectedIfIndex
|
||||||
);
|
);
|
||||||
renderFdbTable2Body(items);
|
renderFdbTable2Body(items);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
deviceLldpTextTableBody.innerHTML = `<tr><td colspan="3" style="padding:10px;color:#b91c1c;">${escapeHtml(
|
deviceLldpTextTableBody.innerHTML = `<tr><td colspan="5" style="padding:10px;color:#b91c1c;">${escapeHtml(
|
||||||
e.message
|
e.message
|
||||||
)}</td></tr>`;
|
)}</td></tr>`;
|
||||||
}
|
}
|
||||||
@@ -585,7 +595,7 @@
|
|||||||
renderDeviceTableBody();
|
renderDeviceTableBody();
|
||||||
|
|
||||||
deviceLinksTableBody.innerHTML = '<tr><td colspan="9" style="padding:10px;color:#64748b;">Загрузка…</td></tr>';
|
deviceLinksTableBody.innerHTML = '<tr><td colspan="9" style="padding:10px;color:#64748b;">Загрузка…</td></tr>';
|
||||||
deviceLldpTextTableBody.innerHTML = '<tr><td colspan="3" style="padding:10px;color:#64748b;">Загрузка…</td></tr>';
|
deviceLldpTextTableBody.innerHTML = '<tr><td colspan="5" style="padding:10px;color:#64748b;">Загрузка…</td></tr>';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [linksRes, ifRes, pdRes] = await Promise.all([
|
const [linksRes, ifRes, pdRes] = await Promise.all([
|
||||||
@@ -619,13 +629,13 @@
|
|||||||
selectedIfIndex = null;
|
selectedIfIndex = null;
|
||||||
selectedFdbKey = null;
|
selectedFdbKey = null;
|
||||||
deviceLldpTextTableBody.innerHTML =
|
deviceLldpTextTableBody.innerHTML =
|
||||||
'<tr><td colspan="3" style="padding:10px;color:#64748b;">Выберите строку в Таблице 1, чтобы увидеть MAC/IP за портом.</td></tr>';
|
'<tr><td colspan="5" style="padding:10px;color:#64748b;">Выберите строку в Таблице 1, чтобы увидеть VLAN/MAC/IP за портом.</td></tr>';
|
||||||
setStatus(
|
setStatus(
|
||||||
`Связи для ${ip}: строк табл.1 ${deviceLinksRowsCache.length} (IF-MIB ${ifItems.length}, /links ${myLinks.length}, FDB строк ${lastPortDevItems.length}).`
|
`Связи для ${ip}: строк табл.1 ${deviceLinksRowsCache.length} (IF-MIB ${ifItems.length}, /links ${myLinks.length}, FDB строк ${lastPortDevItems.length}).`
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
deviceLinksTableBody.innerHTML = `<tr><td colspan="9" style="padding:10px;color:#b91c1c;">${escapeHtml(e.message)}</td></tr>`;
|
deviceLinksTableBody.innerHTML = `<tr><td colspan="9" style="padding:10px;color:#b91c1c;">${escapeHtml(e.message)}</td></tr>`;
|
||||||
deviceLldpTextTableBody.innerHTML = `<tr><td colspan="3" style="padding:10px;color:#b91c1c;">${escapeHtml(e.message)}</td></tr>`;
|
deviceLldpTextTableBody.innerHTML = `<tr><td colspan="5" style="padding:10px;color:#b91c1c;">${escapeHtml(e.message)}</td></tr>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user