Security / Python Security (bandit) (push) Successful in 1m50s
Test / Python Tests (pytest) (push) Successful in 1m52s
Lint / Python (flake8) (pull_request) Successful in 1m20s
Lint / Python (flake8) (push) Successful in 1m16s
Lint / JS (eslint) (pull_request) Successful in 10s
Lint / JS (eslint) (push) Successful in 15s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m5s
Test / Python Tests (pytest) (pull_request) Successful in 1m7s
Ports were keyed by their UniFi name, so when two ports shared a name the later one replaced the earlier. The 24-port switch lost port 13 this way (ports 13 and 14 are both "Office TV"). A repeated name is now kept as "<name> (Port N)". Traffic rates are now matched to the previous poll by port number rather than name. Names are re-read from UniFi every poll, so a port renamed there keeps its rates instead of losing them for a cycle. Collect what UniFi reports about each SFP cage: whether a module is present, its type (DAC or optic), part, serial, fault flags, and DOM readings when the module has them. The faceplate draws empty cages, optics, DACs and unidentified modules differently and outlines a faulted module in red. The port panel gets an SFP Module section. Both ends of a DAC report the same cable serial, so each end links to the other. Closes #7 Closes #8 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>