diff --git a/errors.go b/errors.go index 39eba5d..9ff539a 100644 --- a/errors.go +++ b/errors.go @@ -2,6 +2,7 @@ package tendrils import ( "fmt" + "sort" "sync" "time" ) @@ -245,6 +246,12 @@ func (e *ErrorTracker) GetErrors() []*Error { for _, err := range e.errors { errors = append(errors, err) } + sort.Slice(errors, func(i, j int) bool { + if errors[i].NodeName != errors[j].NodeName { + return errors[i].NodeName < errors[j].NodeName + } + return errors[i].Port < errors[j].Port + }) return errors } diff --git a/http.go b/http.go index f4e945e..590d315 100644 --- a/http.go +++ b/http.go @@ -115,7 +115,7 @@ func ensureCert() error { } func (t *Tendrils) handleAPIStatus(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") + w.Header().Set("Content-Type", "application/json; charset=utf-8") data, err := t.GetStatusJSON() if err != nil { log.Printf("[ERROR] failed to encode status: %v", err) @@ -178,7 +178,7 @@ func (t *Tendrils) handleAPIStatusStream(w http.ResponseWriter, r *http.Request) return } - w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Content-Type", "text/event-stream; charset=utf-8") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") w.Header().Set("Access-Control-Allow-Origin", "*") diff --git a/nodes.go b/nodes.go index 2b81d35..bc2937d 100644 --- a/nodes.go +++ b/nodes.go @@ -171,13 +171,28 @@ func (n *Nodes) updateNodeIPs(node *Node, ips []net.IP) []string { } } n.ipIndex[ipKey] = node - iface, exists := node.Interfaces[ipKey] - if !exists { - iface = &Interface{IPs: IPSet{}} - node.Interfaces[ipKey] = iface + + var targetIface *Interface + for _, iface := range node.Interfaces { + if iface.MAC != "" { + targetIface = iface + break + } + } + if targetIface != nil { + if !targetIface.IPs.Has(ipKey) { + targetIface.IPs.Add(ip) + added = append(added, "ip="+ipKey) + } + } else { + iface, exists := node.Interfaces[ipKey] + if !exists { + iface = &Interface{IPs: IPSet{}} + node.Interfaces[ipKey] = iface + } + iface.IPs.Add(ip) + added = append(added, "ip="+ipKey) } - iface.IPs.Add(ip) - added = append(added, "ip="+ipKey) go n.t.requestARP(ip) } return added @@ -281,8 +296,13 @@ func (n *Nodes) updateNodeInterface(node *Node, mac net.HardwareAddr, ips []net. iface.IPs.Add(ip) n.ipIndex[ipKey] = node - if ipOnlyIface, exists := node.Interfaces[ipKey]; exists && ipOnlyIface != iface { - delete(node.Interfaces, ipKey) + for key, other := range node.Interfaces { + if other != iface && other.IPs.Has(ipKey) { + delete(other.IPs, ipKey) + if len(other.IPs) == 0 && other.MAC == "" { + delete(node.Interfaces, key) + } + } } } diff --git a/static/index.html b/static/index.html index fa64132..7f966aa 100644 --- a/static/index.html +++ b/static/index.html @@ -1992,9 +1992,21 @@ const sacnUniverseInputs = new Map(); const sacnUniverseOutputs = new Map(); + function getSacnInputsFromMulticast(node) { + const groups = node.multicast_groups || []; + const inputs = []; + groups.forEach(g => { + if (typeof g === 'string' && g.startsWith('sacn:')) { + const u = parseInt(g.substring(5), 10); + if (!isNaN(u)) inputs.push(u); + } + }); + return inputs; + } + nodes.forEach(node => { const name = getShortLabel(node); - (node.sacn_inputs || []).forEach(u => { + getSacnInputsFromMulticast(node).forEach(u => { if (!sacnUniverseInputs.has(u)) sacnUniverseInputs.set(u, []); sacnUniverseInputs.get(u).push(name); }); @@ -2012,7 +2024,7 @@ nodes.forEach(node => { const nodeId = node.id; - const sacnInputs = node.sacn_inputs || []; + const sacnInputs = getSacnInputsFromMulticast(node); const sacnOutputs = node.sacn_outputs || []; if (sacnInputs.length === 0 && sacnOutputs.length === 0) return;