mirror of
https://github.com/siderolabs/talos.git
synced 2025-10-08 06:01:12 +02:00
Prevent dashboard from crashing when a dead/non-existent node is specified on `talosctl --nodes`. Signed-off-by: Utku Ozdemir <utku.ozdemir@siderolabs.com>
185 lines
4.2 KiB
Go
185 lines
4.2 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"
|
|
"math"
|
|
"time"
|
|
|
|
"github.com/dustin/go-humanize"
|
|
"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)
|
|
|
|
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) {
|
|
nodeAPIData := data.Nodes[node]
|
|
|
|
widget.updateNodeAPIData(node, nodeAPIData)
|
|
|
|
if node == widget.selectedNode {
|
|
widget.redraw()
|
|
}
|
|
}
|
|
|
|
func (widget *Header) humanizeCPUFrequency(mhz float64) string {
|
|
value := math.Round(mhz)
|
|
unit := "MHz"
|
|
|
|
if mhz >= 1000 {
|
|
ghz := value / 1000
|
|
value = math.Round(ghz*100) / 100
|
|
unit = "GHz"
|
|
}
|
|
|
|
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) {
|
|
nodeData := widget.getOrCreateNodeData(node)
|
|
|
|
if data == nil {
|
|
return
|
|
}
|
|
|
|
nodeData.cpuUsagePercent = fmt.Sprintf("%.1f%%", data.CPUUsageByName("usage")*100.0)
|
|
nodeData.memUsagePercent = fmt.Sprintf("%.1f%%", data.MemUsage()*100.0)
|
|
|
|
if data.Hostname != nil {
|
|
nodeData.hostname = data.Hostname.GetHostname()
|
|
}
|
|
|
|
if data.Version != nil {
|
|
nodeData.version = data.Version.GetVersion().GetTag()
|
|
}
|
|
|
|
if data.SystemStat != nil {
|
|
nodeData.uptime = time.Since(time.Unix(int64(data.SystemStat.GetBootTime()), 0)).Round(time.Second).String()
|
|
}
|
|
|
|
if data.CPUsInfo != nil {
|
|
numCPUs := len(data.CPUsInfo.GetCpuInfo())
|
|
|
|
nodeData.numCPUs = fmt.Sprintf("%d", numCPUs)
|
|
|
|
if numCPUs > 0 {
|
|
nodeData.cpuFreq = widget.humanizeCPUFrequency(data.CPUsInfo.GetCpuInfo()[0].GetCpuMhz())
|
|
}
|
|
}
|
|
|
|
if data.Processes != nil {
|
|
nodeData.numProcesses = fmt.Sprintf("%d", len(data.Processes.GetProcesses()))
|
|
}
|
|
|
|
if data.Memory != nil {
|
|
nodeData.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
|
|
}
|