talos/internal/pkg/dashboard/configurl.go
Utku Ozdemir 196dfb99b0
fix: do not probe kernel args in dashboard if not needed
If the dashboard is run without the "Config URL" screen, do not initialize it, and do not probe the kernel args for the code parameter.

Refactor the dashboard to do not construct the unused screens at all.

Closes siderolabs/talos#7300.

Signed-off-by: Utku Ozdemir <utku.ozdemir@siderolabs.com>
2023-06-01 10:32:43 +02:00

212 lines
5.3 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 (
"context"
"fmt"
"strings"
"github.com/rivo/tview"
"github.com/siderolabs/go-procfs/procfs"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/siderolabs/talos/internal/pkg/dashboard/resourcedata"
"github.com/siderolabs/talos/internal/pkg/meta"
"github.com/siderolabs/talos/pkg/machinery/constants"
runtimeres "github.com/siderolabs/talos/pkg/machinery/resources/runtime"
)
const unset = "[gray](unset)[-]"
type configURLData struct {
existingCode string
}
// ConfigURLGrid represents the config URL grid.
type ConfigURLGrid struct {
tview.Grid
dashboard *Dashboard
form *tview.Form
template *tview.TextView
existingCode *tview.TextView
newCodeField *tview.InputField
infoView *tview.TextView
selectedNode string
nodeMap map[string]*configURLData
}
// NewConfigURLGrid returns a new config URL grid.
func NewConfigURLGrid(ctx context.Context, dashboard *Dashboard) *ConfigURLGrid {
grid := &ConfigURLGrid{
Grid: *tview.NewGrid(),
dashboard: dashboard,
form: tview.NewForm(),
template: tview.NewTextView().SetDynamicColors(true).SetLabel("Template").SetScrollable(false),
existingCode: tview.NewTextView().SetDynamicColors(true).SetLabel("Existing Code").SetText(unset).SetSize(1, 0).SetScrollable(false),
newCodeField: tview.NewInputField().SetLabel("New Code"),
infoView: tview.NewTextView().SetDynamicColors(true).SetSize(2, 0).SetScrollable(false),
nodeMap: make(map[string]*configURLData),
}
grid.template.SetText(grid.readTemplateFromKernelArgs())
grid.SetRows(-1, 18, -1).SetColumns(-1, 72, -1)
grid.form.SetBorder(true)
grid.form.AddFormItem(grid.template)
grid.form.AddFormItem(grid.existingCode)
grid.form.AddFormItem(grid.newCodeField)
grid.form.AddButton("Save", func() {
ctx = nodeContext(ctx, grid.selectedNode)
value := grid.newCodeField.GetText()
if value == "" {
grid.infoView.SetText("[red]Error: No code entered[-]")
return
}
err := dashboard.cli.MetaWrite(ctx, meta.DownloadURLCode, []byte(value))
if err != nil {
grid.infoView.SetText(fmt.Sprintf("[red]Error: %v[-]", err))
return
}
grid.clearForm()
grid.dashboard.selectScreen(ScreenSummary)
})
grid.form.AddButton("Delete", func() {
ctx = nodeContext(ctx, grid.selectedNode)
err := dashboard.cli.MetaDelete(ctx, meta.DownloadURLCode)
if err != nil {
if status.Code(err) == codes.NotFound {
grid.clearForm()
grid.infoView.SetText("[green]Already deleted[-]")
return
}
grid.infoView.SetText(fmt.Sprintf("[red]Error: %v[-]", err))
return
}
grid.clearForm()
grid.infoView.SetText("[green]Deleted successfully[-]")
})
grid.form.AddFormItem(grid.infoView)
grid.AddItem(tview.NewBox(), 0, 0, 1, 3, 0, 0, false)
grid.AddItem(tview.NewBox(), 1, 0, 1, 1, 0, 0, false)
grid.AddItem(grid.form, 1, 1, 1, 1, 0, 0, false)
grid.AddItem(tview.NewBox(), 1, 2, 1, 1, 0, 0, false)
grid.AddItem(tview.NewBox(), 2, 0, 1, 3, 0, 0, false)
return grid
}
func (widget *ConfigURLGrid) readTemplateFromKernelArgs() (val string) {
defer func() { // catch potential panic from procfs.ProcCmdline()
if r := recover(); r != nil {
val = "error reading kernel args"
}
}()
option := procfs.ProcCmdline().Get(constants.KernelParamConfig).First()
if option == nil {
return unset
}
codeVar := fmt.Sprintf("${%s}", constants.CodeKey)
return strings.ReplaceAll(tview.Escape(*option), codeVar, fmt.Sprintf("[green]%s[-]", codeVar))
}
// OnScreenSelect implements the screenSelectListener interface.
func (widget *ConfigURLGrid) onScreenSelect(active bool) {
if active {
widget.dashboard.app.SetFocus(widget.form)
} else {
widget.clearForm()
}
}
// OnNodeSelect implements the NodeSelectListener interface.
func (widget *ConfigURLGrid) OnNodeSelect(node string) {
if node != widget.selectedNode {
widget.selectedNode = node
widget.clearForm()
widget.redraw()
}
}
// OnResourceDataChange implements the ResourceDataListener interface.
func (widget *ConfigURLGrid) OnResourceDataChange(data resourcedata.Data) {
widget.updateNodeData(data)
if data.Node == widget.selectedNode {
widget.redraw()
}
}
func (widget *ConfigURLGrid) updateNodeData(data resourcedata.Data) {
nodeData := widget.getOrCreateNodeData(data.Node)
//nolint:gocritic
switch res := data.Resource.(type) {
case *runtimeres.MetaKey:
if res.Metadata().ID() == runtimeres.MetaKeyTagToID(meta.DownloadURLCode) {
if data.Deleted {
nodeData.existingCode = unset
} else {
val := res.TypedSpec().Value
if val == "" {
val = "(empty)"
}
nodeData.existingCode = fmt.Sprintf("[blue]%s[-]", val)
}
}
}
}
func (widget *ConfigURLGrid) redraw() {
data := widget.getOrCreateNodeData(widget.selectedNode)
widget.existingCode.SetText(data.existingCode)
}
func (widget *ConfigURLGrid) getOrCreateNodeData(node string) *configURLData {
nodeData, ok := widget.nodeMap[node]
if !ok {
nodeData = &configURLData{
existingCode: unset,
}
widget.nodeMap[node] = nodeData
}
return nodeData
}
func (widget *ConfigURLGrid) clearForm() {
widget.form.SetFocus(0)
widget.infoView.SetText("")
widget.newCodeField.SetText("")
}