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:
Utku Ozdemir 2023-03-29 11:40:20 +02:00
parent 188560a334
commit aa662ff635
No known key found for this signature in database
GPG Key ID: 65933E76F0549B0D
9 changed files with 300 additions and 194 deletions

View File

@ -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 {

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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[-]"
}

View File

@ -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,

View File

@ -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
}

View File

@ -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:

View File

@ -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():

View File

@ -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)