// 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" ui "github.com/gizak/termui/v3" "github.com/siderolabs/talos/internal/pkg/dashboard/components" "github.com/siderolabs/talos/internal/pkg/dashboard/data" ) // DataWidget is a widget which consumes Data to draw itself. type DataWidget interface { Update(node string, data *data.Data) } // UI represents the grid, widgets and main loop. type UI struct { infoGrid *ui.Grid grid *ui.Grid sysGauges *components.SystemGauges cpuInfo *components.CPUInfo loadAvgInfo *components.LoadAvgInfo procsInfo *components.ProcsInfo memInfo *components.MemInfo cpuGraph *components.BaseGraph memGraph *components.BaseGraph loadAvgGraph *components.BaseGraph netSparkline *components.BaseSparklineGroup diskSparkline *components.BaseSparklineGroup procTable *components.ProcessTable topLine *components.TopLine tabs *components.NodeTabs drawable []ui.Drawable dataWidgets []DataWidget data *data.Data } // Main is the UI entrypoint. // //nolint:gocyclo func (u *UI) Main(ctx context.Context, dataCh <-chan *data.Data, allowExitKeys bool) error { if err := ui.Init(); err != nil { return err } defer ui.Close() ui.Theme.Block.Title.Modifier = ui.ModifierBold u.topLine = components.NewTopLine() u.tabs = components.NewNodeTabs() u.sysGauges = components.NewSystemGauges() u.cpuInfo = components.NewCPUInfo() u.loadAvgInfo = components.NewLoadAvgInfo() u.procsInfo = components.NewProcsInfo() u.memInfo = components.NewMemInfo() u.cpuGraph = components.NewCPUGraph() u.memGraph = components.NewMemGraph() u.loadAvgGraph = components.NewLoadAvgGraph() u.netSparkline = components.NewNetSparkline() u.diskSparkline = components.NewDiskSparkline() u.procTable = components.NewProcessTable() u.infoGrid = ui.NewGrid() u.infoGrid.Set( ui.NewRow(1, ui.NewCol(1.0/7, u.sysGauges), ui.NewCol(2.0/7, u.cpuInfo), ui.NewCol(1.0/7, u.loadAvgInfo), ui.NewCol(1.0/7, u.procsInfo), ui.NewCol(2.0/7, u.memInfo), ), ) u.grid = ui.NewGrid() u.grid.Set( ui.NewRow(1.0/3, ui.NewCol(1.0/3, u.cpuGraph), ui.NewCol(1.0/3, u.memGraph), ui.NewCol(1.0/3, u.loadAvgGraph), ), ui.NewRow(2.0/3, ui.NewCol(1.0/4, ui.NewRow(1.0/2, u.netSparkline), ui.NewRow(1.0/2, u.diskSparkline), ), ui.NewCol(3.0/4, u.procTable), ), ) termWidth, termHeight := ui.TerminalDimensions() u.Resize(termWidth, termHeight) u.dataWidgets = []DataWidget{ u.topLine, u.cpuInfo, u.loadAvgInfo, u.procsInfo, u.memInfo, u.sysGauges, u.cpuGraph, u.memGraph, u.loadAvgGraph, u.netSparkline, u.diskSparkline, u.procTable, } u.drawable = []ui.Drawable{u.topLine, u.infoGrid, u.grid, u.tabs} ui.Render(u.drawable...) uiEvents := ui.PollEvents() var ok bool for { select { case e := <-uiEvents: switch e.ID { case "q", "": if allowExitKeys { return nil } case "": payload := e.Payload.(ui.Resize) //nolint:errcheck,forcetypeassert u.Resize(payload.Width, payload.Height) ui.Clear() ui.Render(u.drawable...) case "h", "": u.tabs.FocusLeft() u.procTable.ScrollTop() u.UpdateData() case "l", "": u.tabs.FocusRight() u.procTable.ScrollTop() u.UpdateData() case "j", "": u.procTable.ScrollDown() ui.Render(u.procTable) case "k", "": u.procTable.ScrollUp() ui.Render(u.procTable) case "": u.procTable.ScrollHalfPageDown() ui.Render(u.procTable) case "": u.procTable.ScrollHalfPageUp() ui.Render(u.procTable) case "": u.procTable.ScrollPageDown() ui.Render(u.procTable) case "": u.procTable.ScrollPageUp() ui.Render(u.procTable) } case u.data, ok = <-dataCh: if !ok { return nil } u.UpdateData() case <-ctx.Done(): return ctx.Err() } } } // Resize handles the resize events. func (u *UI) Resize(width, height int) { u.topLine.SetRect(0, 0, width, 1) u.infoGrid.SetRect(0, 2, width, 9) u.grid.SetRect(0, 9, width, height-1) u.tabs.SetRect(0, height-1, width, height) } // UpdateData re-renders the widgets with new data. func (u *UI) UpdateData() { if u.data == nil { return } node := "" u.tabs.Update(node, u.data) if len(u.tabs.TabNames) > 0 { node = u.tabs.TabNames[u.tabs.ActiveTabIndex] } for _, widget := range u.dataWidgets { widget.Update(node, u.data) } ui.Render(u.drawable...) }