// 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 }