mirror of
https://github.com/siderolabs/talos.git
synced 2025-10-08 22:21:16 +02:00
Instead of doing excessive get/list requests, do a watch per node in an infinite retry. Additionally, refactor the dashboard code to make the various data listener namings more consistent and reorganize the packages. Closes siderolabs/talos#6960. Signed-off-by: Utku Ozdemir <utku.ozdemir@siderolabs.com>
228 lines
5.2 KiB
Go
228 lines
5.2 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package components
|
|
|
|
import (
|
|
"net/netip"
|
|
"strings"
|
|
|
|
"github.com/cosi-project/runtime/pkg/resource"
|
|
"github.com/rivo/tview"
|
|
"github.com/siderolabs/gen/maps"
|
|
"github.com/siderolabs/gen/slices"
|
|
|
|
"github.com/siderolabs/talos/internal/pkg/dashboard/resourcedata"
|
|
"github.com/siderolabs/talos/pkg/machinery/resources/cluster"
|
|
"github.com/siderolabs/talos/pkg/machinery/resources/network"
|
|
)
|
|
|
|
type networkInfoData struct {
|
|
addresses string
|
|
gateway string
|
|
resolvers string
|
|
timeservers string
|
|
|
|
routeStatusMap map[resource.ID]*network.RouteStatus
|
|
memberMap map[resource.ID]*cluster.Member
|
|
}
|
|
|
|
// NetworkInfo represents the network info widget.
|
|
type NetworkInfo struct {
|
|
tview.TextView
|
|
|
|
selectedNode string
|
|
nodeMap map[string]*networkInfoData
|
|
}
|
|
|
|
// NewNetworkInfo initializes NetworkInfo.
|
|
func NewNetworkInfo() *NetworkInfo {
|
|
component := &NetworkInfo{
|
|
TextView: *tview.NewTextView(),
|
|
nodeMap: make(map[string]*networkInfoData),
|
|
}
|
|
|
|
component.SetDynamicColors(true).
|
|
SetText(noData).
|
|
SetBorderPadding(1, 0, 1, 0)
|
|
|
|
return component
|
|
}
|
|
|
|
// OnNodeSelect implements the NodeSelectListener interface.
|
|
func (widget *NetworkInfo) OnNodeSelect(node string) {
|
|
if node != widget.selectedNode {
|
|
widget.selectedNode = node
|
|
|
|
widget.redraw()
|
|
}
|
|
}
|
|
|
|
// OnResourceDataChange implements the ResourceDataListener interface.
|
|
func (widget *NetworkInfo) OnResourceDataChange(data resourcedata.Data) {
|
|
widget.updateNodeData(data)
|
|
|
|
if data.Node == widget.selectedNode {
|
|
widget.redraw()
|
|
}
|
|
}
|
|
|
|
func (widget *NetworkInfo) updateNodeData(data resourcedata.Data) {
|
|
nodeData := widget.getOrCreateNodeData(data.Node)
|
|
|
|
switch res := data.Resource.(type) {
|
|
case *network.ResolverStatus:
|
|
if data.Deleted {
|
|
nodeData.resolvers = notAvailable
|
|
} else {
|
|
nodeData.resolvers = widget.resolvers(res)
|
|
}
|
|
case *network.TimeServerStatus:
|
|
if data.Deleted {
|
|
nodeData.timeservers = notAvailable
|
|
} else {
|
|
nodeData.timeservers = widget.timeservers(res)
|
|
}
|
|
case *network.RouteStatus:
|
|
if data.Deleted {
|
|
delete(nodeData.routeStatusMap, res.Metadata().ID())
|
|
} else {
|
|
nodeData.routeStatusMap[res.Metadata().ID()] = res
|
|
}
|
|
|
|
nodeData.gateway = widget.gateway(maps.Values(nodeData.routeStatusMap))
|
|
case *cluster.Member:
|
|
if data.Deleted {
|
|
delete(nodeData.memberMap, res.Metadata().ID())
|
|
} else {
|
|
nodeData.memberMap[res.Metadata().ID()] = res
|
|
}
|
|
|
|
nodeData.addresses = widget.addresses(data.Node, maps.Values(nodeData.memberMap))
|
|
}
|
|
}
|
|
|
|
func (widget *NetworkInfo) getOrCreateNodeData(node string) *networkInfoData {
|
|
data, ok := widget.nodeMap[node]
|
|
if !ok {
|
|
data = &networkInfoData{
|
|
addresses: notAvailable,
|
|
gateway: notAvailable,
|
|
resolvers: notAvailable,
|
|
timeservers: notAvailable,
|
|
routeStatusMap: make(map[resource.ID]*network.RouteStatus),
|
|
memberMap: make(map[resource.ID]*cluster.Member),
|
|
}
|
|
|
|
widget.nodeMap[node] = data
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
func (widget *NetworkInfo) redraw() {
|
|
data := widget.getOrCreateNodeData(widget.selectedNode)
|
|
|
|
fields := fieldGroup{
|
|
fields: []field{
|
|
{
|
|
Name: "IP",
|
|
Value: data.addresses,
|
|
},
|
|
{
|
|
Name: "GW",
|
|
Value: data.gateway,
|
|
},
|
|
// TODO: enable when implemented
|
|
// {
|
|
// Name: "OUTBOUND",
|
|
// Value: data.outbound,
|
|
// },
|
|
{
|
|
Name: "DNS",
|
|
Value: data.resolvers,
|
|
},
|
|
{
|
|
Name: "NTP",
|
|
Value: data.timeservers,
|
|
},
|
|
},
|
|
}
|
|
|
|
widget.SetText(fields.String())
|
|
}
|
|
|
|
func (widget *NetworkInfo) addresses(node string, members []*cluster.Member) string {
|
|
var currentMember *cluster.Member
|
|
|
|
for _, member := range members {
|
|
for _, address := range member.TypedSpec().Addresses {
|
|
if address.String() == node {
|
|
currentMember = member
|
|
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if currentMember == nil {
|
|
return notAvailable
|
|
}
|
|
|
|
ipStrs := slices.Map(currentMember.TypedSpec().Addresses, func(t netip.Addr) string {
|
|
return t.String()
|
|
})
|
|
|
|
return strings.Join(ipStrs, ", ")
|
|
}
|
|
|
|
func (widget *NetworkInfo) gateway(statuses []*network.RouteStatus) string {
|
|
resultV4 := notAvailable
|
|
resultV6 := notAvailable
|
|
|
|
priorityV4 := uint32(0)
|
|
priorityV6 := uint32(0)
|
|
|
|
for _, status := range statuses {
|
|
gateway := status.TypedSpec().Gateway
|
|
if !gateway.IsValid() {
|
|
continue
|
|
}
|
|
|
|
if gateway.Is4() && status.TypedSpec().Priority > priorityV4 {
|
|
resultV4 = gateway.String()
|
|
priorityV4 = status.TypedSpec().Priority
|
|
} else if gateway.Is6() && status.TypedSpec().Priority > priorityV6 {
|
|
resultV6 = gateway.String()
|
|
priorityV6 = status.TypedSpec().Priority
|
|
}
|
|
}
|
|
|
|
if resultV4 == notAvailable {
|
|
return resultV6
|
|
}
|
|
|
|
return resultV4
|
|
}
|
|
|
|
func (widget *NetworkInfo) resolvers(status *network.ResolverStatus) string {
|
|
strs := slices.Map(status.TypedSpec().DNSServers, func(t netip.Addr) string {
|
|
return t.String()
|
|
})
|
|
|
|
if len(strs) == 0 {
|
|
return none
|
|
}
|
|
|
|
return strings.Join(strs, ", ")
|
|
}
|
|
|
|
func (widget *NetworkInfo) timeservers(status *network.TimeServerStatus) string {
|
|
if len(status.TypedSpec().NTPServers) == 0 {
|
|
return none
|
|
}
|
|
|
|
return strings.Join(status.TypedSpec().NTPServers, ", ")
|
|
}
|