Track unreachable nodes separately for diagram highlighting

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ian Gulliver
2026-01-25 19:05:13 -08:00
parent 1eef7319cc
commit b2ec349c51
3 changed files with 46 additions and 23 deletions

View File

@@ -35,20 +35,22 @@ type portErrorBaseline struct {
} }
type ErrorTracker struct { type ErrorTracker struct {
mu sync.RWMutex mu sync.RWMutex
errors map[string]*PortError errors map[string]*PortError
baselines map[string]*portErrorBaseline baselines map[string]*portErrorBaseline
suppressedUnreachable map[string]bool suppressedUnreachable map[string]bool
nextID int unreachableNodes map[string]bool
t *Tendrils nextID int
t *Tendrils
} }
func NewErrorTracker(t *Tendrils) *ErrorTracker { func NewErrorTracker(t *Tendrils) *ErrorTracker {
return &ErrorTracker{ return &ErrorTracker{
errors: map[string]*PortError{}, errors: map[string]*PortError{},
baselines: map[string]*portErrorBaseline{}, baselines: map[string]*portErrorBaseline{},
suppressedUnreachable: map[string]bool{}, suppressedUnreachable: map[string]bool{},
t: t, unreachableNodes: map[string]bool{},
t: t,
} }
} }
@@ -193,6 +195,17 @@ func (e *ErrorTracker) GetErrors() []*PortError {
return errors return errors
} }
func (e *ErrorTracker) GetUnreachableNodes() []string {
e.mu.RLock()
defer e.mu.RUnlock()
nodes := make([]string, 0, len(e.unreachableNodes))
for nodeTypeID := range e.unreachableNodes {
nodes = append(nodes, nodeTypeID)
}
return nodes
}
func (e *ErrorTracker) SetUnreachable(node *Node, ip string) { func (e *ErrorTracker) SetUnreachable(node *Node, ip string) {
changed := e.setUnreachableLocked(node, ip) changed := e.setUnreachableLocked(node, ip)
if changed { if changed {
@@ -206,12 +219,15 @@ func (e *ErrorTracker) setUnreachableLocked(node *Node, ip string) bool {
key := "unreachable:" + node.TypeID + ":" + ip key := "unreachable:" + node.TypeID + ":" + ip
wasUnreachable := e.unreachableNodes[node.TypeID]
e.unreachableNodes[node.TypeID] = true
if e.suppressedUnreachable[key] { if e.suppressedUnreachable[key] {
return false return !wasUnreachable
} }
if _, exists := e.errors[key]; exists { if _, exists := e.errors[key]; exists {
return false return !wasUnreachable
} }
now := time.Now() now := time.Now()
@@ -243,9 +259,12 @@ func (e *ErrorTracker) clearUnreachableLocked(node *Node, ip string) bool {
delete(e.suppressedUnreachable, key) delete(e.suppressedUnreachable, key)
wasUnreachable := e.unreachableNodes[node.TypeID]
delete(e.unreachableNodes, node.TypeID)
if _, exists := e.errors[key]; exists { if _, exists := e.errors[key]; exists {
delete(e.errors, key) delete(e.errors, key)
return true return true
} }
return false return wasUnreachable
} }

26
http.go
View File

@@ -25,12 +25,13 @@ const (
) )
type StatusResponse struct { type StatusResponse struct {
Nodes []*Node `json:"nodes"` Nodes []*Node `json:"nodes"`
Links []*Link `json:"links"` Links []*Link `json:"links"`
MulticastGroups []*MulticastGroupMembers `json:"multicast_groups"` MulticastGroups []*MulticastGroupMembers `json:"multicast_groups"`
ArtNetNodes []*ArtNetNode `json:"artnet_nodes"` ArtNetNodes []*ArtNetNode `json:"artnet_nodes"`
DanteFlows []*DanteFlow `json:"dante_flows"` DanteFlows []*DanteFlow `json:"dante_flows"`
PortErrors []*PortError `json:"port_errors"` PortErrors []*PortError `json:"port_errors"`
UnreachableNodes []string `json:"unreachable_nodes"`
} }
func (t *Tendrils) startHTTPServer() { func (t *Tendrils) startHTTPServer() {
@@ -132,12 +133,13 @@ func (t *Tendrils) handleAPIConfig(w http.ResponseWriter, r *http.Request) {
func (t *Tendrils) GetStatus() *StatusResponse { func (t *Tendrils) GetStatus() *StatusResponse {
return &StatusResponse{ return &StatusResponse{
Nodes: t.getNodes(), Nodes: t.getNodes(),
Links: t.getLinks(), Links: t.getLinks(),
MulticastGroups: t.getMulticastGroups(), MulticastGroups: t.getMulticastGroups(),
ArtNetNodes: t.getArtNetNodes(), ArtNetNodes: t.getArtNetNodes(),
DanteFlows: t.getDanteFlows(), DanteFlows: t.getDanteFlows(),
PortErrors: t.errors.GetErrors(), PortErrors: t.errors.GetErrors(),
UnreachableNodes: t.errors.GetUnreachableNodes(),
} }
} }

View File

@@ -822,7 +822,9 @@
const links = data.links || []; const links = data.links || [];
portErrors = data.port_errors || []; portErrors = data.port_errors || [];
const unreachableNodes = new Set(data.unreachable_nodes || []);
const errorNodeIds = new Set(portErrors.map(e => e.node_typeid)); const errorNodeIds = new Set(portErrors.map(e => e.node_typeid));
unreachableNodes.forEach(id => errorNodeIds.add(id));
const locationTree = buildLocationTree(config.locations || [], null); const locationTree = buildLocationTree(config.locations || [], null);