mirror of
https://github.com/bcicen/ctop.git
synced 2024-08-30 18:23:19 +00:00
Merge branch 'refactor-widgets'
This commit is contained in:
commit
b16561dccb
145
config/columns.go
Normal file
145
config/columns.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaults
|
||||||
|
var defaultColumns = []Column{
|
||||||
|
Column{
|
||||||
|
Name: "status",
|
||||||
|
Label: "Status Indicator",
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
Column{
|
||||||
|
Name: "name",
|
||||||
|
Label: "Container Name",
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
Column{
|
||||||
|
Name: "id",
|
||||||
|
Label: "Container ID",
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
Column{
|
||||||
|
Name: "cpu",
|
||||||
|
Label: "CPU Usage",
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
Column{
|
||||||
|
Name: "mem",
|
||||||
|
Label: "Memory Usage",
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
Column{
|
||||||
|
Name: "net",
|
||||||
|
Label: "Network RX/TX",
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
Column{
|
||||||
|
Name: "io",
|
||||||
|
Label: "Disk IO Read/Write",
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
Column{
|
||||||
|
Name: "pids",
|
||||||
|
Label: "Container PID Count",
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type Column struct {
|
||||||
|
Name string
|
||||||
|
Label string
|
||||||
|
Enabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnsString returns an ordered and comma-delimited string of currently enabled Columns
|
||||||
|
func ColumnsString() string { return strings.Join(EnabledColumns(), ",") }
|
||||||
|
|
||||||
|
// EnabledColumns returns an ordered array of enabled column names
|
||||||
|
func EnabledColumns() (a []string) {
|
||||||
|
lock.RLock()
|
||||||
|
defer lock.RUnlock()
|
||||||
|
for _, col := range GlobalColumns {
|
||||||
|
if col.Enabled {
|
||||||
|
a = append(a, col.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnToggle toggles the enabled status of a given column name
|
||||||
|
func ColumnToggle(name string) {
|
||||||
|
col := GlobalColumns[colIndex(name)]
|
||||||
|
col.Enabled = !col.Enabled
|
||||||
|
log.Noticef("config change [column-%s]: %t -> %t", col.Name, !col.Enabled, col.Enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnLeft moves the column with given name up one position, if possible
|
||||||
|
func ColumnLeft(name string) {
|
||||||
|
idx := colIndex(name)
|
||||||
|
if idx > 0 {
|
||||||
|
swapCols(idx, idx-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnRight moves the column with given name up one position, if possible
|
||||||
|
func ColumnRight(name string) {
|
||||||
|
idx := colIndex(name)
|
||||||
|
if idx < len(GlobalColumns)-1 {
|
||||||
|
swapCols(idx, idx+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Column order and enabled status from one or more provided Column names
|
||||||
|
func SetColumns(names []string) {
|
||||||
|
var (
|
||||||
|
n int
|
||||||
|
curColStr = ColumnsString()
|
||||||
|
newColumns = make([]*Column, len(GlobalColumns))
|
||||||
|
)
|
||||||
|
|
||||||
|
lock.Lock()
|
||||||
|
|
||||||
|
// add enabled columns by name
|
||||||
|
for _, name := range names {
|
||||||
|
newColumns[n] = popColumn(name)
|
||||||
|
newColumns[n].Enabled = true
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
|
||||||
|
// extend with omitted columns as disabled
|
||||||
|
for _, col := range GlobalColumns {
|
||||||
|
newColumns[n] = col
|
||||||
|
newColumns[n].Enabled = false
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalColumns = newColumns
|
||||||
|
lock.Unlock()
|
||||||
|
|
||||||
|
log.Noticef("config change [columns]: %s -> %s", curColStr, ColumnsString())
|
||||||
|
}
|
||||||
|
|
||||||
|
func swapCols(i, j int) { GlobalColumns[i], GlobalColumns[j] = GlobalColumns[j], GlobalColumns[i] }
|
||||||
|
|
||||||
|
func popColumn(name string) *Column {
|
||||||
|
idx := colIndex(name)
|
||||||
|
if idx < 0 {
|
||||||
|
panic("no such column name: " + name)
|
||||||
|
}
|
||||||
|
col := GlobalColumns[idx]
|
||||||
|
GlobalColumns = append(GlobalColumns[:idx], GlobalColumns[idx+1:]...)
|
||||||
|
return col
|
||||||
|
}
|
||||||
|
|
||||||
|
// return index of column with given name, if any
|
||||||
|
func colIndex(name string) int {
|
||||||
|
for n, c := range GlobalColumns {
|
||||||
|
if c.Name == name {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
@ -19,19 +19,28 @@ type File struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func exportConfig() File {
|
func exportConfig() File {
|
||||||
|
// update columns param from working config
|
||||||
|
Update("columns", ColumnsString())
|
||||||
|
|
||||||
|
lock.RLock()
|
||||||
|
defer lock.RUnlock()
|
||||||
|
|
||||||
c := File{
|
c := File{
|
||||||
Options: make(map[string]string),
|
Options: make(map[string]string),
|
||||||
Toggles: make(map[string]bool),
|
Toggles: make(map[string]bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range GlobalParams {
|
for _, p := range GlobalParams {
|
||||||
c.Options[p.Key] = p.Val
|
c.Options[p.Key] = p.Val
|
||||||
}
|
}
|
||||||
for _, sw := range GlobalSwitches {
|
for _, sw := range GlobalSwitches {
|
||||||
c.Toggles[sw.Key] = sw.Val
|
c.Toggles[sw.Key] = sw.Val
|
||||||
}
|
}
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
func Read() error {
|
func Read() error {
|
||||||
var config File
|
var config File
|
||||||
|
|
||||||
@ -43,13 +52,26 @@ func Read() error {
|
|||||||
if _, err := toml.DecodeFile(path, &config); err != nil {
|
if _, err := toml.DecodeFile(path, &config); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range config.Options {
|
for k, v := range config.Options {
|
||||||
Update(k, v)
|
Update(k, v)
|
||||||
}
|
}
|
||||||
for k, v := range config.Toggles {
|
for k, v := range config.Toggles {
|
||||||
UpdateSwitch(k, v)
|
UpdateSwitch(k, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set working column config, if provided
|
||||||
|
colStr := GetVal("columns")
|
||||||
|
if len(colStr) > 0 {
|
||||||
|
var colNames []string
|
||||||
|
for _, s := range strings.Split(colStr, ",") {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
if s != "" {
|
||||||
|
colNames = append(colNames, strings.TrimSpace(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SetColumns(colNames)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package config
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/bcicen/ctop/logging"
|
"github.com/bcicen/ctop/logging"
|
||||||
)
|
)
|
||||||
@ -10,17 +11,24 @@ import (
|
|||||||
var (
|
var (
|
||||||
GlobalParams []*Param
|
GlobalParams []*Param
|
||||||
GlobalSwitches []*Switch
|
GlobalSwitches []*Switch
|
||||||
|
GlobalColumns []*Column
|
||||||
|
lock sync.RWMutex
|
||||||
log = logging.Init()
|
log = logging.Init()
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init() {
|
func Init() {
|
||||||
for _, p := range params {
|
for _, p := range defaultParams {
|
||||||
GlobalParams = append(GlobalParams, p)
|
GlobalParams = append(GlobalParams, p)
|
||||||
log.Infof("loaded config param: %s: %s", quote(p.Key), quote(p.Val))
|
log.Infof("loaded default config param [%s]: %s", quote(p.Key), quote(p.Val))
|
||||||
}
|
}
|
||||||
for _, s := range switches {
|
for _, s := range defaultSwitches {
|
||||||
GlobalSwitches = append(GlobalSwitches, s)
|
GlobalSwitches = append(GlobalSwitches, s)
|
||||||
log.Infof("loaded config switch: %s: %t", quote(s.Key), s.Val)
|
log.Infof("loaded default config switch [%s]: %t", quote(s.Key), s.Val)
|
||||||
|
}
|
||||||
|
for _, c := range defaultColumns {
|
||||||
|
x := c
|
||||||
|
GlobalColumns = append(GlobalColumns, &x)
|
||||||
|
log.Infof("loaded default widget config [%s]: %t", quote(x.Name), x.Enabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
// defaults
|
// defaults
|
||||||
var params = []*Param{
|
var defaultParams = []*Param{
|
||||||
&Param{
|
&Param{
|
||||||
Key: "filterStr",
|
Key: "filterStr",
|
||||||
Val: "",
|
Val: "",
|
||||||
@ -17,6 +17,11 @@ var params = []*Param{
|
|||||||
Val: "sh",
|
Val: "sh",
|
||||||
Label: "Shell",
|
Label: "Shell",
|
||||||
},
|
},
|
||||||
|
&Param{
|
||||||
|
Key: "columns",
|
||||||
|
Val: "status,name,id,cpu,mem,net,io,pids",
|
||||||
|
Label: "Enabled Columns",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type Param struct {
|
type Param struct {
|
||||||
@ -27,6 +32,9 @@ type Param struct {
|
|||||||
|
|
||||||
// Get Param by key
|
// Get Param by key
|
||||||
func Get(k string) *Param {
|
func Get(k string) *Param {
|
||||||
|
lock.RLock()
|
||||||
|
defer lock.RUnlock()
|
||||||
|
|
||||||
for _, p := range GlobalParams {
|
for _, p := range GlobalParams {
|
||||||
if p.Key == k {
|
if p.Key == k {
|
||||||
return p
|
return p
|
||||||
@ -43,7 +51,10 @@ func GetVal(k string) string {
|
|||||||
// Set param value
|
// Set param value
|
||||||
func Update(k, v string) {
|
func Update(k, v string) {
|
||||||
p := Get(k)
|
p := Get(k)
|
||||||
log.Noticef("config change: %s: %s -> %s", k, quote(p.Val), quote(v))
|
log.Noticef("config change [%s]: %s -> %s", k, quote(p.Val), quote(v))
|
||||||
|
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
p.Val = v
|
p.Val = v
|
||||||
// log.Errorf("ignoring update for non-existant parameter: %s", k)
|
// log.Errorf("ignoring update for non-existant parameter: %s", k)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
// defaults
|
// defaults
|
||||||
var switches = []*Switch{
|
var defaultSwitches = []*Switch{
|
||||||
&Switch{
|
&Switch{
|
||||||
Key: "sortReversed",
|
Key: "sortReversed",
|
||||||
Val: false,
|
Val: false,
|
||||||
@ -37,6 +37,9 @@ type Switch struct {
|
|||||||
|
|
||||||
// GetSwitch returns Switch by key
|
// GetSwitch returns Switch by key
|
||||||
func GetSwitch(k string) *Switch {
|
func GetSwitch(k string) *Switch {
|
||||||
|
lock.RLock()
|
||||||
|
defer lock.RUnlock()
|
||||||
|
|
||||||
for _, sw := range GlobalSwitches {
|
for _, sw := range GlobalSwitches {
|
||||||
if sw.Key == k {
|
if sw.Key == k {
|
||||||
return sw
|
return sw
|
||||||
@ -52,8 +55,12 @@ func GetSwitchVal(k string) bool {
|
|||||||
|
|
||||||
func UpdateSwitch(k string, val bool) {
|
func UpdateSwitch(k string, val bool) {
|
||||||
sw := GetSwitch(k)
|
sw := GetSwitch(k)
|
||||||
|
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
|
||||||
if sw.Val != val {
|
if sw.Val != val {
|
||||||
log.Noticef("config change: %s: %t -> %t", k, sw.Val, val)
|
log.Noticef("config change [%s]: %t -> %t", k, sw.Val, val)
|
||||||
sw.Val = val
|
sw.Val = val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,8 +68,11 @@ func UpdateSwitch(k string, val bool) {
|
|||||||
// Toggle a boolean switch
|
// Toggle a boolean switch
|
||||||
func Toggle(k string) {
|
func Toggle(k string) {
|
||||||
sw := GetSwitch(k)
|
sw := GetSwitch(k)
|
||||||
newVal := !sw.Val
|
|
||||||
log.Noticef("config change: %s: %t -> %t", k, sw.Val, newVal)
|
lock.Lock()
|
||||||
sw.Val = newVal
|
defer lock.Unlock()
|
||||||
|
|
||||||
|
sw.Val = !sw.Val
|
||||||
|
log.Noticef("config change [%s]: %t -> %t", k, !sw.Val, sw.Val)
|
||||||
//log.Errorf("ignoring toggle for non-existant switch: %s", k)
|
//log.Errorf("ignoring toggle for non-existant switch: %s", k)
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,12 @@ func New(id string, collector collector.Collector, manager manager.Manager) *Con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Container) RecreateWidgets() {
|
||||||
|
c.SetUpdater(cwidgets.NullWidgetUpdater{})
|
||||||
|
c.Widgets = compact.NewCompactRow()
|
||||||
|
c.SetUpdater(c.Widgets)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Container) SetUpdater(u cwidgets.WidgetUpdater) {
|
func (c *Container) SetUpdater(u cwidgets.WidgetUpdater) {
|
||||||
c.updater = u
|
c.updater = u
|
||||||
c.updater.SetMeta(c.Meta)
|
c.updater.SetMeta(c.Meta)
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/bcicen/ctop/cwidgets"
|
"github.com/bcicen/ctop/cwidgets"
|
||||||
"github.com/bcicen/ctop/models"
|
"github.com/bcicen/ctop/models"
|
||||||
|
|
||||||
ui "github.com/gizak/termui"
|
ui "github.com/gizak/termui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,11 +17,7 @@ type CompactGrid struct {
|
|||||||
|
|
||||||
func NewCompactGrid() *CompactGrid {
|
func NewCompactGrid() *CompactGrid {
|
||||||
cg := &CompactGrid{header: NewCompactHeader()}
|
cg := &CompactGrid{header: NewCompactHeader()}
|
||||||
for _, wFn := range allCols {
|
cg.rebuildHeader()
|
||||||
w := wFn()
|
|
||||||
cg.cols = append(cg.cols, w)
|
|
||||||
cg.header.addFieldPar(w.Header())
|
|
||||||
}
|
|
||||||
return cg
|
return cg
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,7 +37,11 @@ func (cg *CompactGrid) Align() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cg *CompactGrid) Clear() { cg.Rows = []RowBufferer{} }
|
func (cg *CompactGrid) Clear() {
|
||||||
|
cg.Rows = []RowBufferer{}
|
||||||
|
cg.rebuildHeader()
|
||||||
|
}
|
||||||
|
|
||||||
func (cg *CompactGrid) GetHeight() int { return len(cg.Rows) + cg.header.Height }
|
func (cg *CompactGrid) GetHeight() int { return len(cg.Rows) + cg.header.Height }
|
||||||
func (cg *CompactGrid) SetX(x int) { cg.X = x }
|
func (cg *CompactGrid) SetX(x int) { cg.X = x }
|
||||||
func (cg *CompactGrid) SetY(y int) { cg.Y = y }
|
func (cg *CompactGrid) SetY(y int) { cg.Y = y }
|
||||||
@ -89,3 +89,11 @@ func (cg *CompactGrid) Buffer() ui.Buffer {
|
|||||||
func (cg *CompactGrid) AddRows(rows ...RowBufferer) {
|
func (cg *CompactGrid) AddRows(rows ...RowBufferer) {
|
||||||
cg.Rows = append(cg.Rows, rows...)
|
cg.Rows = append(cg.Rows, rows...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cg *CompactGrid) rebuildHeader() {
|
||||||
|
cg.cols = newRowWidgets()
|
||||||
|
cg.header.clearFieldPars()
|
||||||
|
for _, col := range cg.cols {
|
||||||
|
cg.header.addFieldPar(col.Header())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,7 +14,10 @@ type CompactHeader struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewCompactHeader() *CompactHeader {
|
func NewCompactHeader() *CompactHeader {
|
||||||
return &CompactHeader{Height: 2}
|
return &CompactHeader{
|
||||||
|
X: rowPadding,
|
||||||
|
Height: 2,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (row *CompactHeader) GetHeight() int {
|
func (row *CompactHeader) GetHeight() int {
|
||||||
@ -51,6 +54,10 @@ func (row *CompactHeader) Buffer() ui.Buffer {
|
|||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (row *CompactHeader) clearFieldPars() {
|
||||||
|
row.pars = []*ui.Par{}
|
||||||
|
}
|
||||||
|
|
||||||
func (row *CompactHeader) addFieldPar(s string) {
|
func (row *CompactHeader) addFieldPar(s string) {
|
||||||
p := ui.NewPar(s)
|
p := ui.NewPar(s)
|
||||||
p.Height = row.Height
|
p.Height = row.Height
|
||||||
|
@ -2,6 +2,7 @@ package compact
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bcicen/ctop/models"
|
"github.com/bcicen/ctop/models"
|
||||||
|
|
||||||
ui "github.com/gizak/termui"
|
ui "github.com/gizak/termui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/bcicen/ctop/cwidgets"
|
"github.com/bcicen/ctop/cwidgets"
|
||||||
"github.com/bcicen/ctop/models"
|
"github.com/bcicen/ctop/models"
|
||||||
|
|
||||||
ui "github.com/gizak/termui"
|
ui "github.com/gizak/termui"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,6 +35,9 @@ func NewCIDCol() CompactCol {
|
|||||||
|
|
||||||
func (w *CIDCol) SetMeta(m models.Meta) {
|
func (w *CIDCol) SetMeta(m models.Meta) {
|
||||||
w.Text = m.Get("id")
|
w.Text = m.Get("id")
|
||||||
|
if len(w.Text) > 12 {
|
||||||
|
w.Text = w.Text[:12]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type NetCol struct {
|
type NetCol struct {
|
||||||
|
@ -10,18 +10,6 @@ import (
|
|||||||
|
|
||||||
const colSpacing = 1
|
const colSpacing = 1
|
||||||
|
|
||||||
// per-column width. 0 == auto width
|
|
||||||
var colWidths = []int{
|
|
||||||
5, // status
|
|
||||||
0, // name
|
|
||||||
0, // cid
|
|
||||||
0, // cpu
|
|
||||||
0, // memory
|
|
||||||
0, // net
|
|
||||||
0, // io
|
|
||||||
4, // pids
|
|
||||||
}
|
|
||||||
|
|
||||||
func centerParText(p *ui.Par) {
|
func centerParText(p *ui.Par) {
|
||||||
var text string
|
var text string
|
||||||
var padding string
|
var padding string
|
||||||
|
@ -11,3 +11,11 @@ type WidgetUpdater interface {
|
|||||||
SetMeta(models.Meta)
|
SetMeta(models.Meta)
|
||||||
SetMetrics(models.Metrics)
|
SetMetrics(models.Metrics)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NullWidgetUpdater struct{}
|
||||||
|
|
||||||
|
// NullWidgetUpdater implements WidgetUpdater
|
||||||
|
func (wu NullWidgetUpdater) SetMeta(models.Meta) {}
|
||||||
|
|
||||||
|
// NullWidgetUpdater implements WidgetUpdater
|
||||||
|
func (wu NullWidgetUpdater) SetMetrics(models.Metrics) {}
|
||||||
|
4
grid.go
4
grid.go
@ -191,6 +191,10 @@ func Display() bool {
|
|||||||
menu = SortMenu
|
menu = SortMenu
|
||||||
ui.StopLoop()
|
ui.StopLoop()
|
||||||
})
|
})
|
||||||
|
ui.Handle("/sys/kbd/c", func(ui.Event) {
|
||||||
|
menu = ColumnsMenu
|
||||||
|
ui.StopLoop()
|
||||||
|
})
|
||||||
ui.Handle("/sys/kbd/S", func(ui.Event) {
|
ui.Handle("/sys/kbd/S", func(ui.Event) {
|
||||||
path, err := config.Write()
|
path, err := config.Write()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
90
menus.go
90
menus.go
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bcicen/ctop/config"
|
"github.com/bcicen/ctop/config"
|
||||||
@ -104,7 +105,90 @@ func SortMenu() MenuFn {
|
|||||||
HandleKeys("exit", ui.StopLoop)
|
HandleKeys("exit", ui.StopLoop)
|
||||||
|
|
||||||
ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
|
ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
|
||||||
config.Update("sortField", m.SelectedItem().Val)
|
config.Update("sortField", m.SelectedValue())
|
||||||
|
ui.StopLoop()
|
||||||
|
})
|
||||||
|
|
||||||
|
ui.Render(m)
|
||||||
|
ui.Loop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ColumnsMenu() MenuFn {
|
||||||
|
const (
|
||||||
|
enabledStr = "[X]"
|
||||||
|
disabledStr = "[ ]"
|
||||||
|
padding = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
ui.Clear()
|
||||||
|
ui.DefaultEvtStream.ResetHandlers()
|
||||||
|
defer ui.DefaultEvtStream.ResetHandlers()
|
||||||
|
|
||||||
|
m := menu.NewMenu()
|
||||||
|
m.Selectable = true
|
||||||
|
m.SortItems = false
|
||||||
|
m.BorderLabel = "Columns"
|
||||||
|
//m.SubText = "Enabled Columns"
|
||||||
|
|
||||||
|
rebuild := func() {
|
||||||
|
// get padding for right alignment of enabled status
|
||||||
|
var maxLen int
|
||||||
|
for _, col := range config.GlobalColumns {
|
||||||
|
if len(col.Label) > maxLen {
|
||||||
|
maxLen = len(col.Label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maxLen += padding
|
||||||
|
|
||||||
|
// rebuild menu items
|
||||||
|
m.ClearItems()
|
||||||
|
for _, col := range config.GlobalColumns {
|
||||||
|
txt := col.Label + strings.Repeat(" ", maxLen-len(col.Label))
|
||||||
|
if col.Enabled {
|
||||||
|
txt += enabledStr
|
||||||
|
} else {
|
||||||
|
txt += disabledStr
|
||||||
|
}
|
||||||
|
m.AddItems(menu.Item{col.Name, txt})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
upFn := func() {
|
||||||
|
config.ColumnLeft(m.SelectedValue())
|
||||||
|
m.Up()
|
||||||
|
rebuild()
|
||||||
|
}
|
||||||
|
|
||||||
|
downFn := func() {
|
||||||
|
config.ColumnRight(m.SelectedValue())
|
||||||
|
m.Down()
|
||||||
|
rebuild()
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleFn := func() {
|
||||||
|
config.ColumnToggle(m.SelectedValue())
|
||||||
|
rebuild()
|
||||||
|
}
|
||||||
|
|
||||||
|
rebuild()
|
||||||
|
|
||||||
|
HandleKeys("up", m.Up)
|
||||||
|
HandleKeys("down", m.Down)
|
||||||
|
HandleKeys("enter", toggleFn)
|
||||||
|
HandleKeys("pgup", upFn)
|
||||||
|
HandleKeys("pgdown", downFn)
|
||||||
|
|
||||||
|
ui.Handle("/sys/kbd/x", func(ui.Event) { toggleFn() })
|
||||||
|
ui.Handle("/sys/kbd/<enter>", func(ui.Event) { toggleFn() })
|
||||||
|
|
||||||
|
HandleKeys("exit", func() {
|
||||||
|
cSource, err := cursor.cSuper.Get()
|
||||||
|
if err == nil {
|
||||||
|
for _, c := range cSource.All() {
|
||||||
|
c.RecreateWidgets()
|
||||||
|
}
|
||||||
|
}
|
||||||
ui.StopLoop()
|
ui.StopLoop()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -202,7 +286,7 @@ func ContainerMenu() MenuFn {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
|
ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
|
||||||
selected = m.SelectedItem().Val
|
selected = m.SelectedValue()
|
||||||
ui.StopLoop()
|
ui.StopLoop()
|
||||||
})
|
})
|
||||||
ui.Handle("/sys/kbd/", func(ui.Event) {
|
ui.Handle("/sys/kbd/", func(ui.Event) {
|
||||||
@ -321,7 +405,7 @@ func Confirm(txt string, fn func()) MenuFn {
|
|||||||
ui.Handle("/sys/kbd/y", func(ui.Event) { yes() })
|
ui.Handle("/sys/kbd/y", func(ui.Event) { yes() })
|
||||||
|
|
||||||
ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
|
ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
|
||||||
switch m.SelectedItem().Val {
|
switch m.SelectedValue() {
|
||||||
case "cancel":
|
case "cancel":
|
||||||
no()
|
no()
|
||||||
case "yes":
|
case "yes":
|
||||||
|
@ -14,14 +14,10 @@ type Meta map[string]string
|
|||||||
func NewMeta(kvs ...string) Meta {
|
func NewMeta(kvs ...string) Meta {
|
||||||
m := make(Meta)
|
m := make(Meta)
|
||||||
|
|
||||||
var k string
|
var i int
|
||||||
for i := 0; i < len(kvs)-1; i++ {
|
for i < len(kvs)-1 {
|
||||||
if k == "" {
|
m[kvs[i]] = kvs[i+1]
|
||||||
k = kvs[i]
|
i += 2
|
||||||
} else {
|
|
||||||
m[k] = kvs[i]
|
|
||||||
k = ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return m
|
return m
|
||||||
|
@ -11,13 +11,14 @@ type Padding [2]int // x,y padding
|
|||||||
type Menu struct {
|
type Menu struct {
|
||||||
ui.Block
|
ui.Block
|
||||||
SortItems bool // enable automatic sorting of menu items
|
SortItems bool // enable automatic sorting of menu items
|
||||||
|
Selectable bool // whether menu is navigable
|
||||||
SubText string // optional text to display before items
|
SubText string // optional text to display before items
|
||||||
TextFgColor ui.Attribute
|
TextFgColor ui.Attribute
|
||||||
TextBgColor ui.Attribute
|
TextBgColor ui.Attribute
|
||||||
Selectable bool
|
|
||||||
cursorPos int
|
cursorPos int
|
||||||
items Items
|
items Items
|
||||||
padding Padding
|
padding Padding
|
||||||
|
toolTip *ToolTip
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMenu() *Menu {
|
func NewMenu() *Menu {
|
||||||
@ -55,6 +56,11 @@ func (m *Menu) DelItem(s string) (success bool) {
|
|||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearItems removes all current menu items
|
||||||
|
func (m *Menu) ClearItems() {
|
||||||
|
m.items = m.items[:0]
|
||||||
|
}
|
||||||
|
|
||||||
// Move cursor to an position by Item value or label
|
// Move cursor to an position by Item value or label
|
||||||
func (m *Menu) SetCursor(s string) (success bool) {
|
func (m *Menu) SetCursor(s string) (success bool) {
|
||||||
for n, i := range m.items {
|
for n, i := range m.items {
|
||||||
@ -66,19 +72,19 @@ func (m *Menu) SetCursor(s string) (success bool) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort menu items(if enabled) and re-calculate window size
|
// SetToolTip sets an optional tooltip string to show at bottom of screen
|
||||||
func (m *Menu) refresh() {
|
func (m *Menu) SetToolTip(lines ...string) {
|
||||||
if m.SortItems {
|
m.toolTip = NewToolTip(lines...)
|
||||||
sort.Sort(m.items)
|
|
||||||
}
|
|
||||||
m.calcSize()
|
|
||||||
ui.Render(m)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Menu) SelectedItem() Item {
|
func (m *Menu) SelectedItem() Item {
|
||||||
return m.items[m.cursorPos]
|
return m.items[m.cursorPos]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Menu) SelectedValue() string {
|
||||||
|
return m.items[m.cursorPos].Val
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Menu) Buffer() ui.Buffer {
|
func (m *Menu) Buffer() ui.Buffer {
|
||||||
var cell ui.Cell
|
var cell ui.Cell
|
||||||
buf := m.Block.Buffer()
|
buf := m.Block.Buffer()
|
||||||
@ -108,6 +114,10 @@ func (m *Menu) Buffer() ui.Buffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if m.toolTip != nil {
|
||||||
|
buf.Merge(m.toolTip.Buffer())
|
||||||
|
}
|
||||||
|
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,6 +135,15 @@ func (m *Menu) Down() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort menu items(if enabled) and re-calculate window size
|
||||||
|
func (m *Menu) refresh() {
|
||||||
|
if m.SortItems {
|
||||||
|
sort.Sort(m.items)
|
||||||
|
}
|
||||||
|
m.calcSize()
|
||||||
|
ui.Render(m)
|
||||||
|
}
|
||||||
|
|
||||||
// Set width and height based on menu items
|
// Set width and height based on menu items
|
||||||
func (m *Menu) calcSize() {
|
func (m *Menu) calcSize() {
|
||||||
m.Width = 7 // minimum width
|
m.Width = 7 // minimum width
|
||||||
|
55
widgets/menu/tooltip.go
Normal file
55
widgets/menu/tooltip.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package menu
|
||||||
|
|
||||||
|
import (
|
||||||
|
ui "github.com/gizak/termui"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ToolTip struct {
|
||||||
|
ui.Block
|
||||||
|
Lines []string
|
||||||
|
TextFgColor ui.Attribute
|
||||||
|
TextBgColor ui.Attribute
|
||||||
|
padding Padding
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewToolTip(lines ...string) *ToolTip {
|
||||||
|
t := &ToolTip{
|
||||||
|
Block: *ui.NewBlock(),
|
||||||
|
Lines: lines,
|
||||||
|
TextFgColor: ui.ThemeAttr("menu.text.fg"),
|
||||||
|
TextBgColor: ui.ThemeAttr("menu.text.bg"),
|
||||||
|
padding: Padding{2, 1},
|
||||||
|
}
|
||||||
|
t.BorderFg = ui.ThemeAttr("menu.border.fg")
|
||||||
|
t.BorderLabelFg = ui.ThemeAttr("menu.label.fg")
|
||||||
|
t.X = 1
|
||||||
|
t.Align()
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ToolTip) Buffer() ui.Buffer {
|
||||||
|
var cell ui.Cell
|
||||||
|
buf := t.Block.Buffer()
|
||||||
|
|
||||||
|
y := t.Y + t.padding[1]
|
||||||
|
|
||||||
|
for n, line := range t.Lines {
|
||||||
|
x := t.X + t.padding[0]
|
||||||
|
for _, ch := range line {
|
||||||
|
cell = ui.Cell{Ch: ch, Fg: t.TextFgColor, Bg: t.TextBgColor}
|
||||||
|
buf.Set(x, y+n, cell)
|
||||||
|
x++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set width and height based on screen size
|
||||||
|
func (t *ToolTip) Align() {
|
||||||
|
t.Width = ui.TermWidth() - (t.padding[0] * 2)
|
||||||
|
t.Height = len(t.Lines) + (t.padding[1] * 2)
|
||||||
|
t.Y = ui.TermHeight() - t.Height
|
||||||
|
|
||||||
|
t.Block.Align()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user