fix: apply small fixes on dashboard
* Clear the input form and switch to summary tab after the network config is saved. * Use nodeaddress resource for detecting and displaying IPs. Improve the IP filtering logic. * Fix the logic of gateway detection. Display all gateways instead of a single one. * Use hostnamestatus resource to detect the hostname instead of an API call. * Add hostname entry to the network info section on summary tab (as `HOST`). * Enable `OUTBOUND` entry in network info section on summary tab. * Display only the physical network interfaces in the interface dropdown on network config tab. * Improve form input handling. * Additional minor fixes & improvements. Closes siderolabs/talos#6992. Signed-off-by: Utku Ozdemir <utku.ozdemir@siderolabs.com>
This commit is contained in:
parent
188560a334
commit
aa662ff635
@ -91,27 +91,6 @@ func (source *Source) gather() *Data {
|
||||
var resultLock sync.Mutex
|
||||
|
||||
gatherFuncs := []func() error{
|
||||
func() error {
|
||||
resp, err := source.MachineClient.Hostname(source.ctx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resultLock.Lock()
|
||||
defer resultLock.Unlock()
|
||||
|
||||
for _, msg := range resp.GetMessages() {
|
||||
node := msg.GetMetadata().GetHostname()
|
||||
|
||||
if _, ok := result.Nodes[node]; !ok {
|
||||
result.Nodes[node] = &Node{}
|
||||
}
|
||||
|
||||
result.Nodes[node].Hostname = msg
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func() error {
|
||||
resp, err := source.MachineClient.LoadAvg(source.ctx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
|
@ -13,17 +13,37 @@ import (
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"github.com/siderolabs/talos/internal/pkg/dashboard/apidata"
|
||||
"github.com/siderolabs/talos/internal/pkg/dashboard/resourcedata"
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
const noHostname = "(no hostname)"
|
||||
|
||||
type headerData struct {
|
||||
hostname string
|
||||
version string
|
||||
uptime string
|
||||
numCPUs string
|
||||
cpuFreq string
|
||||
totalMem string
|
||||
numProcesses string
|
||||
cpuUsagePercent string
|
||||
memUsagePercent string
|
||||
}
|
||||
|
||||
// Header represents the top bar with host info.
|
||||
type Header struct {
|
||||
tview.TextView
|
||||
|
||||
selectedNode string
|
||||
nodeMap map[string]*headerData
|
||||
}
|
||||
|
||||
// NewHeader initializes Header.
|
||||
func NewHeader() *Header {
|
||||
header := &Header{
|
||||
TextView: *tview.NewTextView(),
|
||||
nodeMap: make(map[string]*headerData),
|
||||
}
|
||||
|
||||
header.SetDynamicColors(true).SetText(noData)
|
||||
@ -31,67 +51,42 @@ func NewHeader() *Header {
|
||||
return header
|
||||
}
|
||||
|
||||
// OnNodeSelect implements the NodeSelectListener interface.
|
||||
func (widget *Header) OnNodeSelect(node string) {
|
||||
if node != widget.selectedNode {
|
||||
widget.selectedNode = node
|
||||
|
||||
widget.redraw()
|
||||
}
|
||||
}
|
||||
|
||||
// OnResourceDataChange implements the ResourceDataListener interface.
|
||||
func (widget *Header) OnResourceDataChange(data resourcedata.Data) {
|
||||
nodeData := widget.getOrCreateNodeData(data.Node)
|
||||
|
||||
switch res := data.Resource.(type) { //nolint:gocritic
|
||||
case *network.HostnameStatus:
|
||||
if data.Deleted {
|
||||
nodeData.hostname = noHostname
|
||||
} else {
|
||||
nodeData.hostname = res.TypedSpec().Hostname
|
||||
}
|
||||
}
|
||||
|
||||
if data.Node == widget.selectedNode {
|
||||
widget.redraw()
|
||||
}
|
||||
}
|
||||
|
||||
// OnAPIDataChange implements the APIDataListener interface.
|
||||
func (widget *Header) OnAPIDataChange(node string, data *apidata.Data) {
|
||||
nodeData := data.Nodes[node]
|
||||
_ = nodeData
|
||||
nodeAPIData := data.Nodes[node]
|
||||
|
||||
if nodeData == nil {
|
||||
widget.SetText(notAvailable)
|
||||
widget.updateNodeAPIData(node, nodeAPIData)
|
||||
|
||||
return
|
||||
if node == widget.selectedNode {
|
||||
widget.redraw()
|
||||
}
|
||||
|
||||
hostname := notAvailable
|
||||
uptime := notAvailable
|
||||
version := notAvailable
|
||||
numCPUs := notAvailable
|
||||
cpuFreq := notAvailable
|
||||
totalMem := notAvailable
|
||||
numProcesses := notAvailable
|
||||
|
||||
cpuUsagePercent := fmt.Sprintf("%.1f%%", nodeData.CPUUsageByName("usage")*100.0)
|
||||
memUsagePercent := fmt.Sprintf("%.1f%%", nodeData.MemUsage()*100.0)
|
||||
|
||||
if nodeData.Hostname != nil {
|
||||
hostname = nodeData.Hostname.GetHostname()
|
||||
}
|
||||
|
||||
if nodeData.Version != nil {
|
||||
version = nodeData.Version.GetVersion().GetTag()
|
||||
}
|
||||
|
||||
if nodeData.SystemStat != nil {
|
||||
uptime = time.Since(time.Unix(int64(nodeData.SystemStat.GetBootTime()), 0)).Round(time.Second).String()
|
||||
}
|
||||
|
||||
if nodeData.CPUsInfo != nil {
|
||||
numCPUs = fmt.Sprintf("%d", len(nodeData.CPUsInfo.GetCpuInfo()))
|
||||
cpuFreq = widget.humanizeCPUFrequency(nodeData.CPUsInfo.GetCpuInfo()[0].GetCpuMhz())
|
||||
}
|
||||
|
||||
if nodeData.Processes != nil {
|
||||
numProcesses = fmt.Sprintf("%d", len(nodeData.Processes.GetProcesses()))
|
||||
}
|
||||
|
||||
if nodeData.Memory != nil {
|
||||
totalMem = humanize.IBytes(nodeData.Memory.GetMeminfo().GetMemtotal() << 10)
|
||||
}
|
||||
|
||||
text := fmt.Sprintf(
|
||||
"[yellow::b]%s[-:-:-] (%s): uptime %s, %sx%s, %s RAM, PROCS %s, CPU %s, RAM %s",
|
||||
hostname,
|
||||
version,
|
||||
uptime,
|
||||
numCPUs,
|
||||
cpuFreq,
|
||||
totalMem,
|
||||
numProcesses,
|
||||
cpuUsagePercent,
|
||||
memUsagePercent,
|
||||
)
|
||||
|
||||
widget.SetText(text)
|
||||
}
|
||||
|
||||
func (widget *Header) humanizeCPUFrequency(mhz float64) string {
|
||||
@ -106,3 +101,79 @@ func (widget *Header) humanizeCPUFrequency(mhz float64) string {
|
||||
|
||||
return fmt.Sprintf("%s%s", humanize.Ftoa(value), unit)
|
||||
}
|
||||
|
||||
func (widget *Header) redraw() {
|
||||
data := widget.getOrCreateNodeData(widget.selectedNode)
|
||||
|
||||
text := fmt.Sprintf(
|
||||
"[yellow::b]%s[-:-:-] (%s): uptime %s, %sx%s, %s RAM, PROCS %s, CPU %s, RAM %s",
|
||||
data.hostname,
|
||||
data.version,
|
||||
data.uptime,
|
||||
data.numCPUs,
|
||||
data.cpuFreq,
|
||||
data.totalMem,
|
||||
data.numProcesses,
|
||||
data.cpuUsagePercent,
|
||||
data.memUsagePercent,
|
||||
)
|
||||
|
||||
widget.SetText(text)
|
||||
}
|
||||
|
||||
func (widget *Header) updateNodeAPIData(node string, data *apidata.Node) {
|
||||
sss := widget.getOrCreateNodeData(node)
|
||||
|
||||
if data == nil {
|
||||
return
|
||||
}
|
||||
|
||||
sss.cpuUsagePercent = fmt.Sprintf("%.1f%%", data.CPUUsageByName("usage")*100.0)
|
||||
sss.memUsagePercent = fmt.Sprintf("%.1f%%", data.MemUsage()*100.0)
|
||||
|
||||
if data.Hostname != nil {
|
||||
sss.hostname = data.Hostname.GetHostname()
|
||||
}
|
||||
|
||||
if data.Version != nil {
|
||||
sss.version = data.Version.GetVersion().GetTag()
|
||||
}
|
||||
|
||||
if data.SystemStat != nil {
|
||||
sss.uptime = time.Since(time.Unix(int64(data.SystemStat.GetBootTime()), 0)).Round(time.Second).String()
|
||||
}
|
||||
|
||||
if data.CPUsInfo != nil {
|
||||
sss.numCPUs = fmt.Sprintf("%d", len(data.CPUsInfo.GetCpuInfo()))
|
||||
sss.cpuFreq = widget.humanizeCPUFrequency(data.CPUsInfo.GetCpuInfo()[0].GetCpuMhz())
|
||||
}
|
||||
|
||||
if data.Processes != nil {
|
||||
sss.numProcesses = fmt.Sprintf("%d", len(data.Processes.GetProcesses()))
|
||||
}
|
||||
|
||||
if data.Memory != nil {
|
||||
sss.totalMem = humanize.IBytes(data.Memory.GetMeminfo().GetMemtotal() << 10)
|
||||
}
|
||||
}
|
||||
|
||||
func (widget *Header) getOrCreateNodeData(node string) *headerData {
|
||||
data, ok := widget.nodeMap[node]
|
||||
if !ok {
|
||||
data = &headerData{
|
||||
hostname: notAvailable,
|
||||
version: notAvailable,
|
||||
uptime: notAvailable,
|
||||
numCPUs: notAvailable,
|
||||
cpuFreq: notAvailable,
|
||||
totalMem: notAvailable,
|
||||
numProcesses: notAvailable,
|
||||
cpuUsagePercent: notAvailable,
|
||||
memUsagePercent: notAvailable,
|
||||
}
|
||||
|
||||
widget.nodeMap[node] = data
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ func (widget *KubernetesInfo) OnNodeSelect(node string) {
|
||||
func (widget *KubernetesInfo) OnResourceDataChange(data resourcedata.Data) {
|
||||
widget.updateNodeData(data)
|
||||
|
||||
if data.Node != widget.selectedNode {
|
||||
if data.Node == widget.selectedNode {
|
||||
widget.redraw()
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package components
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
@ -14,18 +15,27 @@ import (
|
||||
"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/k8s"
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
var (
|
||||
zeroPrefix = netip.Prefix{}
|
||||
routedNoK8sID = network.FilteredNodeAddressID(network.NodeAddressRoutedID, k8s.NodeAddressFilterNoK8s)
|
||||
)
|
||||
|
||||
type networkInfoData struct {
|
||||
addresses string
|
||||
hostname string
|
||||
gateway string
|
||||
outbound string
|
||||
resolvers string
|
||||
timeservers string
|
||||
|
||||
addresses string
|
||||
nodeAddressRouted *network.NodeAddress
|
||||
nodeAddressRoutedNoK8s *network.NodeAddress
|
||||
|
||||
routeStatusMap map[resource.ID]*network.RouteStatus
|
||||
memberMap map[resource.ID]*cluster.Member
|
||||
}
|
||||
|
||||
// NetworkInfo represents the network info widget.
|
||||
@ -68,6 +78,7 @@ func (widget *NetworkInfo) OnResourceDataChange(data resourcedata.Data) {
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (widget *NetworkInfo) updateNodeData(data resourcedata.Data) {
|
||||
nodeData := widget.getOrCreateNodeData(data.Node)
|
||||
|
||||
@ -84,6 +95,18 @@ func (widget *NetworkInfo) updateNodeData(data resourcedata.Data) {
|
||||
} else {
|
||||
nodeData.timeservers = widget.timeservers(res)
|
||||
}
|
||||
case *network.Status:
|
||||
if data.Deleted {
|
||||
nodeData.outbound = notAvailable
|
||||
} else {
|
||||
nodeData.outbound = widget.outbound(res)
|
||||
}
|
||||
case *network.HostnameStatus:
|
||||
if data.Deleted {
|
||||
nodeData.hostname = notAvailable
|
||||
} else {
|
||||
nodeData.hostname = res.TypedSpec().Hostname
|
||||
}
|
||||
case *network.RouteStatus:
|
||||
if data.Deleted {
|
||||
delete(nodeData.routeStatusMap, res.Metadata().ID())
|
||||
@ -92,14 +115,8 @@ func (widget *NetworkInfo) updateNodeData(data resourcedata.Data) {
|
||||
}
|
||||
|
||||
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))
|
||||
case *network.NodeAddress:
|
||||
widget.setAddresses(data, res)
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,12 +124,13 @@ func (widget *NetworkInfo) getOrCreateNodeData(node string) *networkInfoData {
|
||||
data, ok := widget.nodeMap[node]
|
||||
if !ok {
|
||||
data = &networkInfoData{
|
||||
hostname: notAvailable,
|
||||
addresses: notAvailable,
|
||||
gateway: notAvailable,
|
||||
outbound: notAvailable,
|
||||
resolvers: notAvailable,
|
||||
timeservers: notAvailable,
|
||||
routeStatusMap: make(map[resource.ID]*network.RouteStatus),
|
||||
memberMap: make(map[resource.ID]*cluster.Member),
|
||||
}
|
||||
|
||||
widget.nodeMap[node] = data
|
||||
@ -126,6 +144,10 @@ func (widget *NetworkInfo) redraw() {
|
||||
|
||||
fields := fieldGroup{
|
||||
fields: []field{
|
||||
{
|
||||
Name: "HOST",
|
||||
Value: data.hostname,
|
||||
},
|
||||
{
|
||||
Name: "IP",
|
||||
Value: data.addresses,
|
||||
@ -134,11 +156,10 @@ func (widget *NetworkInfo) redraw() {
|
||||
Name: "GW",
|
||||
Value: data.gateway,
|
||||
},
|
||||
// TODO: enable when implemented
|
||||
// {
|
||||
// Name: "OUTBOUND",
|
||||
// Value: data.outbound,
|
||||
// },
|
||||
{
|
||||
Name: "OUTBOUND",
|
||||
Value: data.outbound,
|
||||
},
|
||||
{
|
||||
Name: "DNS",
|
||||
Value: data.resolvers,
|
||||
@ -153,57 +174,74 @@ func (widget *NetworkInfo) redraw() {
|
||||
widget.SetText(fields.String())
|
||||
}
|
||||
|
||||
func (widget *NetworkInfo) addresses(node string, members []*cluster.Member) string {
|
||||
var currentMember *cluster.Member
|
||||
func (widget *NetworkInfo) setAddresses(data resourcedata.Data, nodeAddress *network.NodeAddress) {
|
||||
nodeData := widget.getOrCreateNodeData(data.Node)
|
||||
|
||||
for _, member := range members {
|
||||
for _, address := range member.TypedSpec().Addresses {
|
||||
if address.String() == node {
|
||||
currentMember = member
|
||||
|
||||
break
|
||||
}
|
||||
switch nodeAddress.Metadata().ID() {
|
||||
case network.NodeAddressRoutedID:
|
||||
if data.Deleted {
|
||||
nodeData.nodeAddressRouted = nil
|
||||
} else {
|
||||
nodeData.nodeAddressRouted = nodeAddress
|
||||
}
|
||||
case routedNoK8sID:
|
||||
if data.Deleted {
|
||||
nodeData.nodeAddressRoutedNoK8s = nil
|
||||
} else {
|
||||
nodeData.nodeAddressRoutedNoK8s = nodeAddress
|
||||
}
|
||||
}
|
||||
|
||||
if currentMember == nil {
|
||||
return notAvailable
|
||||
formatIPs := func(res *network.NodeAddress) string {
|
||||
if res == nil {
|
||||
return notAvailable
|
||||
}
|
||||
|
||||
strs := slices.Map(res.TypedSpec().Addresses, func(prefix netip.Prefix) string {
|
||||
return prefix.String()
|
||||
})
|
||||
|
||||
sort.Strings(strs)
|
||||
|
||||
return strings.Join(strs, ", ")
|
||||
}
|
||||
|
||||
ipStrs := slices.Map(currentMember.TypedSpec().Addresses, func(t netip.Addr) string {
|
||||
return t.String()
|
||||
})
|
||||
// if "routed-no-k8s" is available, use it
|
||||
if nodeData.nodeAddressRoutedNoK8s != nil {
|
||||
nodeData.addresses = formatIPs(nodeData.nodeAddressRoutedNoK8s)
|
||||
|
||||
return strings.Join(ipStrs, ", ")
|
||||
return
|
||||
}
|
||||
|
||||
// fallback to "routed"
|
||||
nodeData.addresses = formatIPs(nodeData.nodeAddressRouted)
|
||||
}
|
||||
|
||||
func (widget *NetworkInfo) gateway(statuses []*network.RouteStatus) string {
|
||||
resultV4 := notAvailable
|
||||
resultV6 := notAvailable
|
||||
|
||||
priorityV4 := uint32(0)
|
||||
priorityV6 := uint32(0)
|
||||
var gatewaysV4, gatewaysV6 []string
|
||||
|
||||
for _, status := range statuses {
|
||||
gateway := status.TypedSpec().Gateway
|
||||
if !gateway.IsValid() {
|
||||
if !gateway.IsValid() ||
|
||||
status.TypedSpec().Destination != zeroPrefix {
|
||||
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 gateway.Is4() {
|
||||
gatewaysV4 = append(gatewaysV4, gateway.String())
|
||||
} else {
|
||||
gatewaysV6 = append(gatewaysV6, gateway.String())
|
||||
}
|
||||
}
|
||||
|
||||
if resultV4 == notAvailable {
|
||||
return resultV6
|
||||
if len(gatewaysV4) == 0 && len(gatewaysV6) == 0 {
|
||||
return notAvailable
|
||||
}
|
||||
|
||||
return resultV4
|
||||
sort.Strings(gatewaysV4)
|
||||
sort.Strings(gatewaysV6)
|
||||
|
||||
return strings.Join(append(gatewaysV4, gatewaysV6...), ", ")
|
||||
}
|
||||
|
||||
func (widget *NetworkInfo) resolvers(status *network.ResolverStatus) string {
|
||||
@ -225,3 +263,11 @@ func (widget *NetworkInfo) timeservers(status *network.TimeServerStatus) string
|
||||
|
||||
return strings.Join(status.TypedSpec().NTPServers, ", ")
|
||||
}
|
||||
|
||||
func (widget *NetworkInfo) outbound(status *network.Status) string {
|
||||
if status.TypedSpec().ConnectivityReady {
|
||||
return "[green]OK[-]"
|
||||
}
|
||||
|
||||
return "[red]FAILED[-]"
|
||||
}
|
||||
|
@ -155,7 +155,7 @@ func buildDashboard(ctx context.Context, cli *client.Client, opts ...Option) (*D
|
||||
|
||||
dashboard.summaryGrid = NewSummaryGrid(dashboard.app)
|
||||
dashboard.monitorGrid = NewMonitorGrid(dashboard.app)
|
||||
dashboard.networkConfigGrid = NewNetworkConfigGrid(ctx, dashboard.app, dashboard.pages, cli)
|
||||
dashboard.networkConfigGrid = NewNetworkConfigGrid(ctx, dashboard)
|
||||
|
||||
err := dashboard.initScreenConfigs(defOptions.screens)
|
||||
if err != nil {
|
||||
@ -210,6 +210,7 @@ func buildDashboard(ctx context.Context, cli *client.Client, opts ...Option) (*D
|
||||
}
|
||||
|
||||
dashboard.resourceDataListeners = []ResourceDataListener{
|
||||
header,
|
||||
dashboard.summaryGrid,
|
||||
dashboard.networkConfigGrid,
|
||||
}
|
||||
@ -219,6 +220,7 @@ func buildDashboard(ctx context.Context, cli *client.Client, opts ...Option) (*D
|
||||
}
|
||||
|
||||
dashboard.nodeSelectListeners = []NodeSelectListener{
|
||||
header,
|
||||
dashboard.summaryGrid,
|
||||
dashboard.networkConfigGrid,
|
||||
dashboard.footer,
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
|
||||
@ -41,8 +42,8 @@ func (formData *networkConfigFormData) toPlatformNetworkConfig() (*runtime.Platf
|
||||
config := &formData.base
|
||||
|
||||
// zero-out the fields managed by the form
|
||||
// config.Hostnames = nil
|
||||
// config.Resolvers = nil
|
||||
config.Hostnames = nil
|
||||
config.Resolvers = nil
|
||||
config.TimeServers = nil
|
||||
config.Links = nil
|
||||
config.Operators = nil
|
||||
@ -52,7 +53,8 @@ func (formData *networkConfigFormData) toPlatformNetworkConfig() (*runtime.Platf
|
||||
if formData.hostname != "" {
|
||||
config.Hostnames = []network.HostnameSpecSpec{
|
||||
{
|
||||
Hostname: formData.hostname,
|
||||
Hostname: formData.hostname,
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -65,17 +67,19 @@ func (formData *networkConfigFormData) toPlatformNetworkConfig() (*runtime.Platf
|
||||
if len(dnsServers) > 0 {
|
||||
config.Resolvers = []network.ResolverSpecSpec{
|
||||
{
|
||||
DNSServers: dnsServers,
|
||||
DNSServers: dnsServers,
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
timeServers := formData.parseHosts(formData.timeServers)
|
||||
timeServers := formData.splitInputList(formData.timeServers)
|
||||
|
||||
if len(timeServers) > 0 {
|
||||
config.TimeServers = []network.TimeServerSpecSpec{
|
||||
{
|
||||
NTPServers: timeServers,
|
||||
NTPServers: timeServers,
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -84,10 +88,11 @@ func (formData *networkConfigFormData) toPlatformNetworkConfig() (*runtime.Platf
|
||||
if ifaceSelected {
|
||||
config.Links = []network.LinkSpecSpec{
|
||||
{
|
||||
Name: formData.iface,
|
||||
Logical: false,
|
||||
Up: true,
|
||||
Type: nethelpers.LinkEther,
|
||||
Name: formData.iface,
|
||||
Logical: false,
|
||||
Up: true,
|
||||
Type: nethelpers.LinkEther,
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
},
|
||||
}
|
||||
|
||||
@ -101,6 +106,7 @@ func (formData *networkConfigFormData) toPlatformNetworkConfig() (*runtime.Platf
|
||||
DHCP4: network.DHCP4OperatorSpec{
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
},
|
||||
}
|
||||
case modeStatic:
|
||||
@ -130,16 +136,11 @@ func (formData *networkConfigFormData) toPlatformNetworkConfig() (*runtime.Platf
|
||||
func (formData *networkConfigFormData) parseAddresses(text string) ([]netip.Addr, error) {
|
||||
var errs error
|
||||
|
||||
split := strings.Split(text, ",")
|
||||
split := formData.splitInputList(text)
|
||||
addresses := make([]netip.Addr, 0, len(split))
|
||||
|
||||
for _, address := range split {
|
||||
trimmed := strings.TrimSpace(address)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
addr, err := netip.ParseAddr(trimmed)
|
||||
addr, err := netip.ParseAddr(address)
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, fmt.Errorf("address: %w", err))
|
||||
|
||||
@ -156,35 +157,14 @@ func (formData *networkConfigFormData) parseAddresses(text string) ([]netip.Addr
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
func (formData *networkConfigFormData) parseHosts(text string) []string {
|
||||
split := strings.Split(text, ",")
|
||||
hosts := make([]string, 0, len(split))
|
||||
|
||||
for _, host := range split {
|
||||
trimmed := strings.TrimSpace(host)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
hosts = append(hosts, trimmed)
|
||||
}
|
||||
|
||||
return hosts
|
||||
}
|
||||
|
||||
func (formData *networkConfigFormData) buildAddresses(linkName string) ([]network.AddressSpecSpec, error) {
|
||||
var errs error
|
||||
|
||||
addressesSplit := strings.Split(formData.addresses, ",")
|
||||
addressesSplit := formData.splitInputList(formData.addresses)
|
||||
addresses := make([]network.AddressSpecSpec, 0, len(addressesSplit))
|
||||
|
||||
for _, address := range addressesSplit {
|
||||
trimmed := strings.TrimSpace(address)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
prefix, err := netip.ParsePrefix(trimmed)
|
||||
prefix, err := netip.ParsePrefix(address)
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
|
||||
@ -197,11 +177,12 @@ func (formData *networkConfigFormData) buildAddresses(linkName string) ([]networ
|
||||
}
|
||||
|
||||
addresses = append(addresses, network.AddressSpecSpec{
|
||||
Address: prefix,
|
||||
LinkName: linkName,
|
||||
Family: ipFamily,
|
||||
Scope: nethelpers.ScopeGlobal,
|
||||
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
|
||||
Address: prefix,
|
||||
LinkName: linkName,
|
||||
Family: ipFamily,
|
||||
Scope: nethelpers.ScopeGlobal,
|
||||
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
}
|
||||
|
||||
@ -234,6 +215,25 @@ func (formData *networkConfigFormData) buildRoutes(linkName string) ([]network.R
|
||||
Scope: nethelpers.ScopeGlobal,
|
||||
Type: nethelpers.TypeUnicast,
|
||||
Protocol: nethelpers.ProtocolStatic,
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (formData *networkConfigFormData) splitInputList(text string) []string {
|
||||
parts := strings.FieldsFunc(text, func(r rune) bool {
|
||||
return r == ',' || unicode.IsSpace(r)
|
||||
})
|
||||
|
||||
result := make([]string, 0, len(parts))
|
||||
|
||||
for _, part := range parts {
|
||||
trimmed := strings.TrimSpace(part)
|
||||
|
||||
if trimmed != "" {
|
||||
result = append(result, part)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import (
|
||||
"github.com/siderolabs/talos/internal/pkg/dashboard/resourcedata"
|
||||
"github.com/siderolabs/talos/internal/pkg/meta"
|
||||
"github.com/siderolabs/talos/pkg/machinery/client"
|
||||
"github.com/siderolabs/talos/pkg/machinery/nethelpers"
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/network"
|
||||
runtimeres "github.com/siderolabs/talos/pkg/machinery/resources/runtime"
|
||||
)
|
||||
@ -51,8 +50,7 @@ type networkConfigData struct {
|
||||
type NetworkConfigGrid struct {
|
||||
tview.Grid
|
||||
|
||||
app *tview.Application
|
||||
pages *tview.Pages
|
||||
dashboard *Dashboard
|
||||
|
||||
configForm *tview.Form
|
||||
hostnameField *tview.InputField
|
||||
@ -69,23 +67,20 @@ type NetworkConfigGrid struct {
|
||||
|
||||
selectedNode string
|
||||
nodeMap map[string]*networkConfigData
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
// NewNetworkConfigGrid initializes NetworkConfigGrid.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func NewNetworkConfigGrid(ctx context.Context, app *tview.Application, pages *tview.Pages, cli *client.Client) *NetworkConfigGrid {
|
||||
func NewNetworkConfigGrid(ctx context.Context, dashboard *Dashboard) *NetworkConfigGrid {
|
||||
widget := &NetworkConfigGrid{
|
||||
Grid: *tview.NewGrid(),
|
||||
app: app,
|
||||
pages: pages,
|
||||
configForm: tview.NewForm(),
|
||||
infoView: tview.NewTextView(),
|
||||
existingConfigView: tview.NewTextView(),
|
||||
newConfigView: tview.NewTextView(),
|
||||
nodeMap: make(map[string]*networkConfigData),
|
||||
client: cli,
|
||||
dashboard: dashboard,
|
||||
}
|
||||
|
||||
widget.configForm.SetBorder(true).SetTitle("Configure (Ctrl+Q)")
|
||||
@ -156,7 +151,7 @@ func NewNetworkConfigGrid(ctx context.Context, app *tview.Application, pages *tv
|
||||
saveButton.SetBlurFunc(widget.formEdited)
|
||||
|
||||
widget.configForm.AddButton("Delete", func() {
|
||||
pages.SwitchToPage(pageDeleteModal)
|
||||
widget.dashboard.pages.SwitchToPage(pageDeleteModal)
|
||||
})
|
||||
|
||||
deleteButton := widget.configForm.GetButton(1)
|
||||
@ -170,11 +165,11 @@ func NewNetworkConfigGrid(ctx context.Context, app *tview.Application, pages *tv
|
||||
widget.delete(ctx)
|
||||
}
|
||||
|
||||
pages.SwitchToPage(pageMain)
|
||||
app.SetFocus(deleteButton)
|
||||
widget.dashboard.pages.SwitchToPage(pageMain)
|
||||
widget.dashboard.app.SetFocus(deleteButton)
|
||||
})
|
||||
|
||||
pages.AddPage(pageDeleteModal, deleteModal, true, false)
|
||||
widget.dashboard.pages.AddPage(pageDeleteModal, deleteModal, true, false)
|
||||
|
||||
inputCapture := func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if widget.handleFocusSwitch(event) {
|
||||
@ -352,6 +347,8 @@ func (widget *NetworkConfigGrid) clearForm() {
|
||||
widget.gatewayField.SetText("")
|
||||
widget.infoView.SetText("")
|
||||
|
||||
widget.configForm.SetFocus(0)
|
||||
|
||||
widget.formEdited()
|
||||
}
|
||||
|
||||
@ -364,9 +361,7 @@ func (widget *NetworkConfigGrid) updateNodeData(data resourcedata.Data) {
|
||||
if data.Deleted {
|
||||
delete(nodeData.linkSet, res.Metadata().ID())
|
||||
} else {
|
||||
if !res.TypedSpec().LinkState ||
|
||||
res.TypedSpec().Type == nethelpers.LinkLoopbck ||
|
||||
res.TypedSpec().Kind != "" {
|
||||
if !res.TypedSpec().Physical() {
|
||||
return
|
||||
}
|
||||
|
||||
@ -417,7 +412,7 @@ func (widget *NetworkConfigGrid) getOrCreateNodeData(node string) *networkConfig
|
||||
// OnScreenSelect implements the screenSelectListener interface.
|
||||
func (widget *NetworkConfigGrid) onScreenSelect(active bool) {
|
||||
if active {
|
||||
widget.app.SetFocus(widget.configForm)
|
||||
widget.dashboard.app.SetFocus(widget.configForm)
|
||||
}
|
||||
}
|
||||
|
||||
@ -445,19 +440,21 @@ func (widget *NetworkConfigGrid) save(ctx context.Context) {
|
||||
|
||||
ctx = widget.nodeContext(ctx)
|
||||
|
||||
if err = widget.client.MetaWrite(ctx, meta.MetalNetworkPlatformConfig, configBytes); err != nil {
|
||||
if err = widget.dashboard.cli.MetaWrite(ctx, meta.MetalNetworkPlatformConfig, configBytes); err != nil {
|
||||
widget.infoView.SetText(fmt.Sprintf("[red]Error: %v[-]", err))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
widget.infoView.SetText("[green]Network config saved successfully[-]")
|
||||
widget.clearForm()
|
||||
widget.dashboard.selectScreen(ScreenSummary)
|
||||
}
|
||||
|
||||
func (widget *NetworkConfigGrid) delete(ctx context.Context) {
|
||||
ctx = widget.nodeContext(ctx)
|
||||
|
||||
if err := widget.client.MetaDelete(ctx, meta.MetalNetworkPlatformConfig); err != nil {
|
||||
if err := widget.dashboard.cli.MetaDelete(ctx, meta.MetalNetworkPlatformConfig); err != nil {
|
||||
widget.infoView.SetText(fmt.Sprintf("[red]Error: %v[-]", err))
|
||||
|
||||
return
|
||||
@ -484,15 +481,15 @@ func (widget *NetworkConfigGrid) nodeContext(ctx context.Context) context.Contex
|
||||
func (widget *NetworkConfigGrid) handleFocusSwitch(event *tcell.EventKey) bool {
|
||||
switch event.Key() { //nolint:exhaustive
|
||||
case tcell.KeyCtrlQ:
|
||||
widget.app.SetFocus(widget.configForm)
|
||||
widget.dashboard.app.SetFocus(widget.configForm)
|
||||
|
||||
return true
|
||||
case tcell.KeyCtrlW:
|
||||
widget.app.SetFocus(widget.existingConfigView)
|
||||
widget.dashboard.app.SetFocus(widget.existingConfigView)
|
||||
|
||||
return true
|
||||
case tcell.KeyCtrlE:
|
||||
widget.app.SetFocus(widget.newConfigView)
|
||||
widget.dashboard.app.SetFocus(widget.newConfigView)
|
||||
|
||||
return true
|
||||
default:
|
||||
|
@ -143,6 +143,14 @@ func (source *Source) runResourceWatch(ctx context.Context, node string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := source.COSI.Watch(ctx, network.NewStatus(network.NamespaceName, network.StatusID).Metadata(), eventCh); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := source.COSI.Watch(ctx, network.NewHostnameStatus(network.NamespaceName, network.HostnameID).Metadata(), eventCh); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := source.COSI.WatchKind(ctx, k8s.NewStaticPodStatus(k8s.NamespaceName, "").Metadata(), eventCh, state.WithBootstrapContents(true)); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -159,6 +167,10 @@ func (source *Source) runResourceWatch(ctx context.Context, node string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := source.COSI.WatchKind(ctx, network.NewNodeAddress(network.NamespaceName, "").Metadata(), eventCh, state.WithBootstrapContents(true)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
@ -38,8 +38,7 @@ func NewSummaryGrid(app *tview.Application) *SummaryGrid {
|
||||
logViewers: make(map[string]*components.LogViewer),
|
||||
}
|
||||
|
||||
widget.SetRows(8, 0).
|
||||
SetColumns(0, 0, 0)
|
||||
widget.SetRows(8, 0).SetColumns(-3, -2, -3)
|
||||
|
||||
talosInfo := components.NewTalosInfo()
|
||||
widget.AddItem(talosInfo, 0, 0, 1, 1, 0, 0, false)
|
||||
|
Loading…
Reference in New Issue
Block a user