Andrey Smirnov 8dbe2128a9
feat: implement Talos diagnostics
Talos diagnostics analyzes current system state and comes up with detailed
warnings on the system misconfiguration which might be tricky to figure
out other way.

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
2024-06-05 22:28:15 +04:00

108 lines
2.7 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 components
import (
"fmt"
"slices"
"github.com/rivo/tview"
"github.com/siderolabs/talos/internal/pkg/dashboard/resourcedata"
"github.com/siderolabs/talos/pkg/machinery/resources/runtime"
)
// Diagnostics represents the diagnostics widget.
type Diagnostics struct {
tview.Grid
hline *HorizontalLine
info *tview.TextView
selectedNode string
perNodeWarnings map[string][]*runtime.Diagnostic
}
// NewDiagnostics initializes Diagnostics.
func NewDiagnostics() *Diagnostics {
widget := &Diagnostics{
Grid: *tview.NewGrid(),
info: tview.NewTextView(),
hline: NewHorizontalLine("Diagnostics"),
perNodeWarnings: make(map[string][]*runtime.Diagnostic),
}
widget.info.
SetDynamicColors(true).
SetBorderPadding(0, 0, 1, 1)
widget.SetRows(1, 0).SetColumns(0)
widget.AddItem(widget.hline, 0, 0, 1, 1, 0, 0, false)
widget.AddItem(widget.info, 1, 0, 1, 1, 0, 0, false)
return widget
}
// GetCurrentHeight returns the height of the widget.
func (widget *Diagnostics) GetCurrentHeight() int {
numWarnings := len(widget.perNodeWarnings[widget.selectedNode])
if numWarnings == 0 {
return 0
}
return 1 + numWarnings
}
// OnNodeSelect implements the NodeSelectListener interface.
func (widget *Diagnostics) OnNodeSelect(node string) {
if node != widget.selectedNode {
widget.selectedNode = node
widget.redraw()
}
}
// OnResourceDataChange implements the ResourceDataListener interface.
func (widget *Diagnostics) OnResourceDataChange(data resourcedata.Data) {
r, ok := data.Resource.(*runtime.Diagnostic)
if !ok {
return
}
idx := slices.IndexFunc(widget.perNodeWarnings[data.Node], func(warning *runtime.Diagnostic) bool {
return warning.Metadata().ID() == r.Metadata().ID()
})
if data.Deleted {
if idx != -1 {
widget.perNodeWarnings[data.Node] = slices.Delete(widget.perNodeWarnings[data.Node], idx, idx+1)
}
} else {
if idx == -1 {
widget.perNodeWarnings[data.Node] = append(widget.perNodeWarnings[data.Node], r)
} else {
widget.perNodeWarnings[data.Node][idx] = r
}
}
if data.Node == widget.selectedNode {
widget.redraw()
}
}
// WriteLog writes the log line to the widget.
func (widget *Diagnostics) redraw() {
widget.info.SetWrap(true)
widget.info.Clear()
for _, warning := range widget.perNodeWarnings[widget.selectedNode] {
widget.info.Write([]byte(fmt.Sprintf("■ (%s) [red]%s[-]\n", //nolint:errcheck
tview.Escape(warning.TypedSpec().DocumentationURL(warning.Metadata().ID())),
tview.Escape(warning.TypedSpec().Message))),
)
}
}