mirror of
https://github.com/siderolabs/talos.git
synced 2025-10-09 06:31:25 +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>
232 lines
6.5 KiB
Go
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
|
|
}
|