Move the port inspector onto device pages
Lint / JS (eslint) (push) Successful in 1m14s
Lint / Python (flake8) (push) Successful in 2m12s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m3s
Test / Python Tests (pytest) (push) Successful in 1m11s
Lint / Python (flake8) (pull_request) Successful in 1m1s
Lint / JS (eslint) (pull_request) Successful in 10s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 56s
Test / Python Tests (pytest) (pull_request) Successful in 1m5s

Replace the all-switches Inspector page with a per-device front panel,
port table and detail panel. /inspector now redirects to the dashboard,
and /device/<name>#port-<n> links directly to a port.

Restore neighbor names after UniFi's LLDP change: read local_port_idx
and resolve neighbors by MAC using Prometheus NIC data, UniFi devices
and the wired client table. This also brings back Run Diagnostics on
server ports, which depends on a named neighbor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-05 10:11:11 -05:00
co-authored by Claude Opus 5.5
parent e32499b7f6
commit bdcfb76c25
13 changed files with 484 additions and 226 deletions
+41
View File
@@ -373,3 +373,44 @@ class TestUnifiDeviceDetail:
c = monitor.UnifiClient({'controller': 'https://u.test', 'api_key': 'k'})
c.session.post = MagicMock(side_effect=OSError('boom'))
assert c.get_system_events(0, modem_api.WAN_LOG_KEYS) is None
class TestLldpCollection:
def _client_with(self, entry):
c = monitor.UnifiClient({'controller': 'https://u.test', 'api_key': 'k'})
resp = MagicMock()
resp.json.return_value = {'data': [{
'type': 'usw', 'name': 'sw', 'lldp_table': [entry],
'port_table': [{'port_idx': 3, 'name': 'Port 3'}],
}]}
c.session.get = MagicMock(return_value=resp)
return c
def test_current_unifi_field_name(self):
ports = self._client_with({'local_port_idx': 3, 'chassis_id': 'aa'}).get_switch_ports()['sw']['ports']
assert ports['Port 3']['lldp']['chassis_id'] == 'aa'
def test_older_unifi_field_name(self):
ports = self._client_with({'lldp_port_idx': 3, 'chassis_id': 'aa'}).get_switch_ports()['sw']['ports']
assert ports['Port 3']['lldp']['chassis_id'] == 'aa'
def test_bond_does_not_hide_its_member_nic(self):
collector = monitor.LinkStatsCollector.__new__(monitor.LinkStatsCollector)
collector.prom = MagicMock()
collector.unifi = MagicMock()
collector.unifi.get_wired_clients.return_value = []
mac = 'aa:bb:cc:dd:ee:ff'
collector.prom.query.return_value = [
{'metric': {'device': 'bond0', 'address': mac, 'instance': '10.0.0.1:9100'}},
{'metric': {'device': 'eth0', 'address': mac, 'instance': '10.0.0.1:9100'}},
{'metric': {'device': 'vmbr0', 'address': mac, 'instance': '10.0.0.1:9100'}},
]
switches = {'sw': {'ports': {'p': {'lldp': {'chassis_id': 'x', 'port_id': mac.upper()}}}}}
collector._resolve_neighbors(switches, {}, {'10.0.0.1:9100': 'alpha'})
lldp = switches['sw']['ports']['p']['lldp']
assert (lldp['system_name'], lldp['port_id']) == ('alpha', 'eth0')
def test_client_list_failure_is_empty(self):
c = monitor.UnifiClient({'controller': 'https://u.test', 'api_key': 'k'})
c.session.get = MagicMock(side_effect=OSError('boom'))
assert c.get_wired_clients() == []