talos/internal/pkg/dashboard/networkconfig.go
Utku Ozdemir 2dd0964c5f
refactor: use resource watches on dashboard
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>
2023-03-17 23:06:35 +01:00

232 lines
6.5 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 dashboard
import (
"sort"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"github.com/siderolabs/gen/maps"
"github.com/siderolabs/talos/internal/pkg/dashboard/resourcedata"
"github.com/siderolabs/talos/pkg/machinery/nethelpers"
"github.com/siderolabs/talos/pkg/machinery/resources/network"
)
type networkConfigData struct {
linkSet map[string]struct{}
}
// NetworkConfigGrid represents the network configuration widget.
type NetworkConfigGrid struct {
tview.Grid
app *tview.Application
configForm *tview.Form
hostnameField *tview.InputField
dnsServersField *tview.InputField
timeServersField *tview.InputField
interfaceDropdown *tview.DropDown
modeDropdown *tview.DropDown
addressesField *tview.InputField
gatewayField *tview.InputField
infoView *tview.TextView
selectedNode string
nodeMap map[string]*networkConfigData
}
// NewNetworkConfigGrid initializes NetworkConfigGrid.
func NewNetworkConfigGrid(app *tview.Application) *NetworkConfigGrid {
widget := &NetworkConfigGrid{
Grid: *tview.NewGrid(),
app: app,
configForm: tview.NewForm(),
infoView: tview.NewTextView(),
nodeMap: make(map[string]*networkConfigData),
}
widget.infoView.SetBorderPadding(1, 0, 1, 0)
widget.configForm.SetBorder(true)
widget.SetRows(0).SetColumns(0, 0, 0)
widget.AddItem(tview.NewBox(), 0, 0, 1, 1, 0, 0, false)
widget.AddItem(widget.configForm, 0, 1, 1, 1, 0, 0, false)
widget.AddItem(widget.infoView, 0, 2, 1, 1, 0, 0, false)
widget.hostnameField = tview.NewInputField().SetLabel("Hostname")
widget.dnsServersField = tview.NewInputField().SetLabel("DNS Servers")
widget.timeServersField = tview.NewInputField().SetLabel("Time Servers")
widget.interfaceDropdown = tview.NewDropDown().SetLabel("Interface")
widget.modeDropdown = tview.NewDropDown().SetLabel("Mode")
widget.addressesField = tview.NewInputField().SetLabel("Addresses")
widget.gatewayField = tview.NewInputField().SetLabel("Gateway")
widget.configForm.AddFormItem(widget.hostnameField)
widget.configForm.AddFormItem(widget.dnsServersField)
widget.configForm.AddFormItem(widget.timeServersField)
widget.configForm.AddFormItem(widget.interfaceDropdown)
widget.configForm.AddFormItem(widget.modeDropdown)
widget.configForm.AddFormItem(widget.addressesField)
widget.configForm.AddFormItem(widget.gatewayField)
widget.configForm.AddButton("Save", func() {
widget.save()
})
widget.interfaceDropdown.SetSelectedFunc(func(text string, index int) {
// TODO(dashboard): Clear the form & load existing config for the selected interface.
})
widget.modeDropdown.SetOptions([]string{"No Config", "DHCP", "Static"}, func(text string, _ int) {
switch text {
case "Static":
if itemIndex := widget.configForm.GetFormItemIndex("Addresses"); itemIndex == -1 {
widget.configForm.AddFormItem(widget.addressesField)
}
if itemIndex := widget.configForm.GetFormItemIndex("Gateway"); itemIndex == -1 {
widget.configForm.AddFormItem(widget.gatewayField)
}
default:
if itemIndex := widget.configForm.GetFormItemIndex("Addresses"); itemIndex != -1 {
widget.configForm.RemoveFormItem(itemIndex)
}
if itemIndex := widget.configForm.GetFormItemIndex("Gateway"); itemIndex != -1 {
widget.configForm.RemoveFormItem(itemIndex)
}
}
})
widget.configForm.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
formItemIndex, buttonIndex := widget.configForm.GetFocusedItemIndex()
currIndex := formItemIndex
if formItemIndex == -1 {
currIndex = widget.configForm.GetFormItemCount() + buttonIndex
}
//nolint:exhaustive
switch event.Key() {
case tcell.KeyUp:
widget.configForm.SetFocus(currIndex - 1)
widget.app.SetFocus(widget.configForm)
return nil
case tcell.KeyDown:
// prevent jumping to the first field if we are at the end of the form
if currIndex < widget.configForm.GetFormItemCount()+widget.configForm.GetButtonCount()-1 {
widget.configForm.SetFocus(currIndex + 1)
}
widget.app.SetFocus(widget.configForm)
return nil
default:
return event
}
})
widget.AddItem(widget.configForm, 0, 1, 1, 1, 0, 0, false)
return widget
}
// OnNodeSelect implements the NodeSelectListener interface.
func (widget *NetworkConfigGrid) OnNodeSelect(node string) {
if node != widget.selectedNode {
widget.selectedNode = node
widget.redraw()
}
}
// OnResourceDataChange implements the ResourceDataListener interface.
func (widget *NetworkConfigGrid) OnResourceDataChange(data resourcedata.Data) {
widget.updateNodeData(data)
if data.Node == widget.selectedNode {
widget.redraw()
}
}
func (widget *NetworkConfigGrid) redraw() {
// todo
}
func (widget *NetworkConfigGrid) updateNodeData(data resourcedata.Data) {
nodeData := widget.getOrCreateNodeData(data.Node)
//nolint:gocritic
switch res := data.Resource.(type) {
case *network.LinkStatus:
if data.Deleted {
delete(nodeData.linkSet, res.Metadata().ID())
} else {
if !res.TypedSpec().LinkState ||
res.TypedSpec().Type == nethelpers.LinkLoopbck ||
res.TypedSpec().Kind != "" {
return
}
nodeData.linkSet[res.Metadata().ID()] = struct{}{}
}
links := maps.Keys(nodeData.linkSet)
sort.Strings(links)
widget.interfaceDropdown.SetOptions(links, nil)
}
}
func (widget *NetworkConfigGrid) getOrCreateNodeData(node string) *networkConfigData {
nodeData, ok := widget.nodeMap[node]
if !ok {
nodeData = &networkConfigData{
linkSet: make(map[string]struct{}),
}
widget.nodeMap[node] = nodeData
}
return nodeData
}
// OnScreenSelect implements the screenSelectListener interface.
func (widget *NetworkConfigGrid) onScreenSelect(active bool) {
if active {
widget.app.SetFocus(widget.configForm)
}
}
func (widget *NetworkConfigGrid) save() {
_, iface := widget.interfaceDropdown.GetCurrentOption()
_, mode := widget.modeDropdown.GetCurrentOption()
formData := networkConfigFormData{
hostname: widget.hostnameField.GetText(),
dnsServers: widget.dnsServersField.GetText(),
timeServers: widget.timeServersField.GetText(),
iface: iface,
mode: mode,
addresses: widget.addressesField.GetText(),
gateway: widget.gatewayField.GetText(),
}
platformNetworkConfig, err := formData.toPlatformNetworkConfig()
if err != nil { // TODO(dashboard): show error
return
}
_ = platformNetworkConfig // TODO(dashboard): save config
}