diff --git a/container.go b/container.go index d1f7c6b..fb2ee53 100644 --- a/container.go +++ b/container.go @@ -3,8 +3,10 @@ package main import ( "strings" + "github.com/bcicen/ctop/cwidgets" + "github.com/bcicen/ctop/cwidgets/compact" + "github.com/bcicen/ctop/cwidgets/expanded" "github.com/bcicen/ctop/metrics" - "github.com/bcicen/ctop/widgets" ) type Container struct { @@ -12,7 +14,7 @@ type Container struct { name string state string metrics metrics.Metrics - widgets widgets.ContainerWidgets + widgets cwidgets.ContainerWidgets } func NewContainer(id, name string) *Container { @@ -20,7 +22,7 @@ func NewContainer(id, name string) *Container { id: id, name: name, } - c.widgets = widgets.NewCompact(c.ShortID(), c.ShortName(), c.state) + c.widgets = compact.NewCompact(c.ShortID(), c.ShortName(), c.state) return c } @@ -33,10 +35,10 @@ func (c *Container) ShortName() string { } func (c *Container) Expand() { - var curWidgets widgets.ContainerWidgets + var curWidgets cwidgets.ContainerWidgets curWidgets = c.widgets - c.widgets = widgets.NewExpanded(c.ShortID(), c.name) + c.widgets = expanded.NewExpanded(c.ShortID(), c.name) c.widgets.Render(0, 0) c.widgets = curWidgets } diff --git a/cwidgets/compact/grid.go b/cwidgets/compact/grid.go new file mode 100644 index 0000000..bae1271 --- /dev/null +++ b/cwidgets/compact/grid.go @@ -0,0 +1,50 @@ +package compact + +import ( + "github.com/bcicen/ctop/cwidgets" + ui "github.com/gizak/termui" +) + +type CompactGrid struct { + ui.GridBufferer + Rows []cwidgets.ContainerWidgets + X, Y int + Width int + Height int + header *CompactHeader +} + +func NewCompactGrid() *CompactGrid { + return &CompactGrid{ + header: NewCompactHeader(), + } +} + +func (c *CompactGrid) Align() { + // Update y recursively + c.header.SetY(c.Y) + y := c.Y + 1 + for n, r := range c.Rows { + r.SetY(y + n) + } + // Update width recursively + c.header.SetWidth(c.Width) + for _, r := range c.Rows { + r.SetWidth(c.Width) + } +} + +func (c *CompactGrid) Clear() { c.Rows = []cwidgets.ContainerWidgets{} } +func (c *CompactGrid) GetHeight() int { return len(c.Rows) } +func (c *CompactGrid) SetX(x int) { c.X = x } +func (c *CompactGrid) SetY(y int) { c.Y = y } +func (c *CompactGrid) SetWidth(w int) { c.Width = w } + +func (c *CompactGrid) Buffer() ui.Buffer { + buf := ui.NewBuffer() + buf.Merge(c.header.Buffer()) + for _, r := range c.Rows { + buf.Merge(r.Buffer()) + } + return buf +} diff --git a/cwidgets/compact/header.go b/cwidgets/compact/header.go new file mode 100644 index 0000000..b70131c --- /dev/null +++ b/cwidgets/compact/header.go @@ -0,0 +1,48 @@ +package compact + +import ( + ui "github.com/gizak/termui" +) + +type CompactHeader struct { + pars []*ui.Par +} + +func NewCompactHeader() *CompactHeader { + fields := []string{"", "NAME", "CID", "CPU", "MEM", "NET RX/TX"} + header := &CompactHeader{} + for _, f := range fields { + header.pars = append(header.pars, slimHeaderPar(f)) + } + return header +} + +func (c *CompactHeader) SetWidth(w int) { + x := 1 + autoWidth := calcWidth(w, 5) + for n, col := range c.pars { + if n == 0 { + col.SetX(x) + col.SetWidth(statusWidth) + x += statusWidth + continue + } + col.SetX(x) + col.SetWidth(autoWidth) + x += autoWidth + colSpacing + } +} + +func (c *CompactHeader) SetY(y int) { + for _, p := range c.pars { + p.SetY(y) + } +} + +func (c *CompactHeader) Buffer() ui.Buffer { + buf := ui.NewBuffer() + for _, p := range c.pars { + buf.Merge(p.Buffer()) + } + return buf +} diff --git a/cwidgets/compact/main.go b/cwidgets/compact/main.go new file mode 100644 index 0000000..4afd304 --- /dev/null +++ b/cwidgets/compact/main.go @@ -0,0 +1,143 @@ +package compact + +import ( + "fmt" + "github.com/bcicen/ctop/cwidgets" + "strconv" + + ui "github.com/gizak/termui" +) + +const ( + mark = string('\u25C9') + vBar = string('\u25AE') + colSpacing = 1 + statusWidth = 3 +) + +type Compact struct { + Status *ui.Par + Cid *ui.Par + Net *ui.Par + Name *ui.Par + Cpu *ui.Gauge + Memory *ui.Gauge +} + +func NewCompact(id, name, status string) *Compact { + w := &Compact{ + Status: slimPar(mark), + Cid: slimPar(id), + Name: slimPar(name), + } + w.Reset() + w.SetStatus(status) + return w +} + +// Set gauges, counters to default unread values +func (w *Compact) Reset() { + w.Net = slimPar("-") + w.Cpu = slimGauge() + w.Memory = slimGauge() +} + +func (w *Compact) all() []ui.GridBufferer { + return []ui.GridBufferer{ + w.Status, + w.Name, + w.Cid, + w.Cpu, + w.Memory, + w.Net, + } +} + +func (w *Compact) SetY(y int) { + for _, col := range w.all() { + col.SetY(y) + } +} + +func (w *Compact) SetWidth(width int) { + x := 1 + autoWidth := calcWidth(width, 5) + for n, col := range w.all() { + if n == 0 { + col.SetX(x) + col.SetWidth(statusWidth) + x += statusWidth + continue + } + col.SetX(x) + col.SetWidth(autoWidth) + x += autoWidth + colSpacing + } +} + +func (w *Compact) Render(y, rowWidth int) {} + +func (w *Compact) Buffer() ui.Buffer { + buf := ui.NewBuffer() + + buf.Merge(w.Status.Buffer()) + buf.Merge(w.Name.Buffer()) + buf.Merge(w.Cid.Buffer()) + buf.Merge(w.Cpu.Buffer()) + buf.Merge(w.Memory.Buffer()) + buf.Merge(w.Net.Buffer()) + + return buf +} + +func (w *Compact) Highlight() { + w.Name.TextFgColor = ui.ColorDefault + w.Name.TextBgColor = ui.ColorWhite +} + +func (w *Compact) UnHighlight() { + w.Name.TextFgColor = ui.ColorWhite + w.Name.TextBgColor = ui.ColorDefault +} + +func (w *Compact) SetStatus(val string) { + switch val { + case "running": + w.Status.Text = mark + w.Status.TextFgColor = ui.ColorGreen + case "exited": + w.Status.Text = mark + w.Status.TextFgColor = ui.ColorRed + case "paused": + w.Status.Text = fmt.Sprintf("%s%s", vBar, vBar) + w.Status.TextFgColor = ui.ColorDefault + default: + w.Status.Text = mark + w.Status.TextFgColor = ui.ColorRed + } +} + +func (w *Compact) SetCPU(val int) { + w.Cpu.BarColor = cwidgets.ColorScale(val) + w.Cpu.Label = fmt.Sprintf("%s%%", strconv.Itoa(val)) + if val < 5 { + val = 5 + w.Cpu.BarColor = ui.ColorBlack + } + w.Cpu.Percent = val +} + +func (w *Compact) SetNet(rx int64, tx int64) { + w.Net.Text = fmt.Sprintf("%s / %s", cwidgets.ByteFormat(rx), cwidgets.ByteFormat(tx)) +} + +func (w *Compact) SetMem(val int64, limit int64, percent int) { + w.Memory.Label = fmt.Sprintf("%s / %s", cwidgets.ByteFormat(val), cwidgets.ByteFormat(limit)) + if percent < 5 { + percent = 5 + w.Memory.BarColor = ui.ColorBlack + } else { + w.Memory.BarColor = ui.ColorGreen + } + w.Memory.Percent = percent +} diff --git a/cwidgets/compact/util.go b/cwidgets/compact/util.go new file mode 100644 index 0000000..14781b1 --- /dev/null +++ b/cwidgets/compact/util.go @@ -0,0 +1,60 @@ +package compact + +// Common helper functions + +import ( + "fmt" + ui "github.com/gizak/termui" +) + +// Calculate per-column width, given total width and number of items +func calcWidth(width, items int) int { + spacing := colSpacing * items + return (width - statusWidth - spacing) / items +} + +func slimHeaderPar(s string) *ui.Par { + p := slimPar(s) + p.Y = 2 + p.Height = 2 + return p +} + +func slimPar(s string) *ui.Par { + p := ui.NewPar(s) + p.Border = false + p.Height = 1 + p.Width = 20 + p.TextFgColor = ui.ColorWhite + return p +} + +func slimGauge() *ui.Gauge { + g := ui.NewGauge() + g.Height = 1 + g.Border = false + g.Percent = 0 + g.PaddingBottom = 0 + g.BarColor = ui.ColorGreen + g.Label = "-" + return g +} + +func centerParText(p *ui.Par) { + var text string + var padding string + + // strip existing left-padding + for i, ch := range p.Text { + if string(ch) != " " { + text = p.Text[i:] + break + } + } + + padlen := (p.InnerWidth() - len(text)) / 2 + for i := 0; i < padlen; i++ { + padding += " " + } + p.Text = fmt.Sprintf("%s%s", padding, text) +} diff --git a/widgets/expanded_cpu.go b/cwidgets/expanded/cpu.go similarity index 96% rename from widgets/expanded_cpu.go rename to cwidgets/expanded/cpu.go index b2d7d50..12cb775 100644 --- a/widgets/expanded_cpu.go +++ b/cwidgets/expanded/cpu.go @@ -1,4 +1,4 @@ -package widgets +package expanded import ( ui "github.com/gizak/termui" diff --git a/widgets/hist.go b/cwidgets/expanded/hist.go similarity index 98% rename from widgets/hist.go rename to cwidgets/expanded/hist.go index 42d6706..1483149 100644 --- a/widgets/hist.go +++ b/cwidgets/expanded/hist.go @@ -1,4 +1,4 @@ -package widgets +package expanded type IntHist struct { data []int diff --git a/widgets/expanded.go b/cwidgets/expanded/main.go similarity index 98% rename from widgets/expanded.go rename to cwidgets/expanded/main.go index 4f474a6..8982534 100644 --- a/widgets/expanded.go +++ b/cwidgets/expanded/main.go @@ -1,4 +1,4 @@ -package widgets +package expanded import ( ui "github.com/gizak/termui" diff --git a/widgets/expanded_mem.go b/cwidgets/expanded/mem.go similarity index 87% rename from widgets/expanded_mem.go rename to cwidgets/expanded/mem.go index 5a2043f..49dc776 100644 --- a/widgets/expanded_mem.go +++ b/cwidgets/expanded/mem.go @@ -1,6 +1,7 @@ -package widgets +package expanded import ( + "github.com/bcicen/ctop/cwidgets" ui "github.com/gizak/termui" ) @@ -25,7 +26,7 @@ func NewExpandedMem() *ExpandedMem { mem.Data = mem.hist.data mem.BarColor = ui.ColorGreen mem.DataLabels = mem.hist.labels - mem.NumFmt = byteFormatInt + mem.NumFmt = cwidgets.ByteFormatInt return mem } diff --git a/widgets/expanded_net.go b/cwidgets/expanded/net.go similarity index 83% rename from widgets/expanded_net.go rename to cwidgets/expanded/net.go index d3a73f1..dddc7be 100644 --- a/widgets/expanded_net.go +++ b/cwidgets/expanded/net.go @@ -1,9 +1,10 @@ -package widgets +package expanded import ( "fmt" "strings" + "github.com/bcicen/ctop/cwidgets" ui "github.com/gizak/termui" ) @@ -43,10 +44,10 @@ func (w *ExpandedNet) Update(rx int64, tx int64) { var rate string w.rxHist.Append(int(rx)) - rate = strings.ToLower(byteFormatInt(w.rxHist.Last())) + rate = strings.ToLower(cwidgets.ByteFormatInt(w.rxHist.Last())) w.Lines[0].Title = fmt.Sprintf("RX [%s/s]", rate) w.txHist.Append(int(tx)) - rate = strings.ToLower(byteFormatInt(w.txHist.Last())) + rate = strings.ToLower(cwidgets.ByteFormatInt(w.txHist.Last())) w.Lines[1].Title = fmt.Sprintf("TX [%s/s]", rate) } diff --git a/cwidgets/main.go b/cwidgets/main.go new file mode 100644 index 0000000..c5cc062 --- /dev/null +++ b/cwidgets/main.go @@ -0,0 +1,22 @@ +package cwidgets + +import ( + "github.com/bcicen/ctop/logging" + ui "github.com/gizak/termui" +) + +var log = logging.Init() + +type ContainerWidgets interface { + Render(int, int) + Reset() + Buffer() ui.Buffer + Highlight() + UnHighlight() + SetY(int) + SetWidth(int) + SetStatus(string) + SetCPU(int) + SetNet(int64, int64) + SetMem(int64, int64, int) +} diff --git a/widgets/util.go b/cwidgets/util.go similarity index 84% rename from widgets/util.go rename to cwidgets/util.go index 8af6bb4..3288d43 100644 --- a/widgets/util.go +++ b/cwidgets/util.go @@ -1,4 +1,4 @@ -package widgets +package cwidgets import ( "fmt" @@ -14,11 +14,11 @@ const ( ) // convenience method -func byteFormatInt(n int) string { - return byteFormat(int64(n)) +func ByteFormatInt(n int) string { + return ByteFormat(int64(n)) } -func byteFormat(n int64) string { +func ByteFormat(n int64) string { if n < kb { return fmt.Sprintf("%sB", strconv.FormatInt(n, 10)) } @@ -49,7 +49,7 @@ func getPrecision(f float64) int { return 2 // default precision } -func colorScale(n int) ui.Attribute { +func ColorScale(n int) ui.Attribute { if n > 70 { return ui.ColorRed } diff --git a/grid.go b/grid.go index cf5ce06..4bb8970 100644 --- a/grid.go +++ b/grid.go @@ -4,11 +4,12 @@ import ( "fmt" "github.com/bcicen/ctop/config" + "github.com/bcicen/ctop/cwidgets/compact" "github.com/bcicen/ctop/widgets" ui "github.com/gizak/termui" ) -var cGrid = widgets.NewCompactGrid() +var cGrid = compact.NewCompactGrid() func maxRows() int { return ui.TermHeight() - 2 - cGrid.Y @@ -85,7 +86,7 @@ func (g *Grid) cursorDown() { func (g *Grid) redrawRows() { // reinit body rows - cGrid.Rows = []widgets.ContainerWidgets{} + cGrid.Clear() // build layout y := 1 diff --git a/widgets/compact.go b/widgets/compact.go deleted file mode 100644 index 7dbc60c..0000000 --- a/widgets/compact.go +++ /dev/null @@ -1,297 +0,0 @@ -package widgets - -import ( - "fmt" - "strconv" - - "github.com/bcicen/ctop/logging" - ui "github.com/gizak/termui" -) - -var log = logging.Init() - -const ( - mark = string('\u25C9') - vBar = string('\u25AE') - colSpacing = 1 - statusWidth = 3 -) - -type CompactGrid struct { - ui.GridBufferer - Rows []ContainerWidgets - X, Y int - Width int - Height int - header *CompactHeader -} - -func NewCompactGrid() *CompactGrid { - return &CompactGrid{ - header: NewCompactHeader(), - } -} - -func (c *CompactGrid) Align() { - // Update y recursively - c.header.SetY(c.Y) - y := c.Y + 1 - for n, r := range c.Rows { - r.SetY(y + n) - } - // Update width recursively - c.header.SetWidth(c.Width) - for _, r := range c.Rows { - r.SetWidth(c.Width) - } -} - -func (c *CompactGrid) GetHeight() int { return len(c.Rows) } -func (c *CompactGrid) SetX(x int) { c.X = x } -func (c *CompactGrid) SetY(y int) { c.Y = y } -func (c *CompactGrid) SetWidth(w int) { c.Width = w } - -func (c *CompactGrid) Buffer() ui.Buffer { - buf := ui.NewBuffer() - buf.Merge(c.header.Buffer()) - for _, r := range c.Rows { - buf.Merge(r.Buffer()) - } - return buf -} - -type ContainerWidgets interface { - Render(int, int) - Reset() - Buffer() ui.Buffer - Highlight() - UnHighlight() - SetY(int) - SetWidth(int) - SetStatus(string) - SetCPU(int) - SetNet(int64, int64) - SetMem(int64, int64, int) -} - -type CompactHeader struct { - pars []*ui.Par -} - -func NewCompactHeader() *CompactHeader { - fields := []string{"", "NAME", "CID", "CPU", "MEM", "NET RX/TX"} - header := &CompactHeader{} - for _, f := range fields { - header.pars = append(header.pars, slimHeaderPar(f)) - } - return header -} - -// Calculate per-column width, given total width and number of items -func calcWidth(width, items int) int { - spacing := colSpacing * items - return (width - statusWidth - spacing) / items -} - -func (c *CompactHeader) SetWidth(w int) { - x := 1 - autoWidth := calcWidth(w, 5) - for n, col := range c.pars { - if n == 0 { - col.SetX(x) - col.SetWidth(statusWidth) - x += statusWidth - continue - } - col.SetX(x) - col.SetWidth(autoWidth) - x += autoWidth + colSpacing - } -} - -func (c *CompactHeader) SetY(y int) { - for _, p := range c.pars { - p.SetY(y) - } -} - -func (c *CompactHeader) Buffer() ui.Buffer { - buf := ui.NewBuffer() - for _, p := range c.pars { - buf.Merge(p.Buffer()) - } - return buf -} - -type Compact struct { - Status *ui.Par - Cid *ui.Par - Net *ui.Par - Name *ui.Par - Cpu *ui.Gauge - Memory *ui.Gauge -} - -func NewCompact(id, name, status string) *Compact { - w := &Compact{ - Status: slimPar(mark), - Cid: slimPar(id), - Name: slimPar(name), - } - w.Reset() - w.SetStatus(status) - return w -} - -// Set gauges, counters to default unread values -func (w *Compact) Reset() { - w.Net = slimPar("-") - w.Cpu = slimGauge() - w.Memory = slimGauge() -} - -func (w *Compact) all() []ui.GridBufferer { - return []ui.GridBufferer{ - w.Status, - w.Name, - w.Cid, - w.Cpu, - w.Memory, - w.Net, - } -} - -func (w *Compact) SetY(y int) { - for _, col := range w.all() { - col.SetY(y) - } -} - -func (w *Compact) SetWidth(width int) { - x := 1 - autoWidth := calcWidth(width, 5) - for n, col := range w.all() { - if n == 0 { - col.SetX(x) - col.SetWidth(statusWidth) - x += statusWidth - continue - } - col.SetX(x) - col.SetWidth(autoWidth) - x += autoWidth + colSpacing - } -} - -func (w *Compact) Render(y, rowWidth int) {} - -func (w *Compact) Buffer() ui.Buffer { - buf := ui.NewBuffer() - - buf.Merge(w.Status.Buffer()) - buf.Merge(w.Name.Buffer()) - buf.Merge(w.Cid.Buffer()) - buf.Merge(w.Cpu.Buffer()) - buf.Merge(w.Memory.Buffer()) - buf.Merge(w.Net.Buffer()) - - return buf -} - -func (w *Compact) Highlight() { - w.Name.TextFgColor = ui.ColorDefault - w.Name.TextBgColor = ui.ColorWhite -} - -func (w *Compact) UnHighlight() { - w.Name.TextFgColor = ui.ColorWhite - w.Name.TextBgColor = ui.ColorDefault -} - -func (w *Compact) SetStatus(val string) { - switch val { - case "running": - w.Status.Text = mark - w.Status.TextFgColor = ui.ColorGreen - case "exited": - w.Status.Text = mark - w.Status.TextFgColor = ui.ColorRed - case "paused": - w.Status.Text = fmt.Sprintf("%s%s", vBar, vBar) - w.Status.TextFgColor = ui.ColorDefault - default: - w.Status.Text = mark - w.Status.TextFgColor = ui.ColorRed - } -} - -func (w *Compact) SetCPU(val int) { - w.Cpu.BarColor = colorScale(val) - w.Cpu.Label = fmt.Sprintf("%s%%", strconv.Itoa(val)) - if val < 5 { - val = 5 - w.Cpu.BarColor = ui.ColorBlack - } - w.Cpu.Percent = val -} - -func (w *Compact) SetNet(rx int64, tx int64) { - w.Net.Text = fmt.Sprintf("%s / %s", byteFormat(rx), byteFormat(tx)) -} - -func (w *Compact) SetMem(val int64, limit int64, percent int) { - w.Memory.Label = fmt.Sprintf("%s / %s", byteFormat(val), byteFormat(limit)) - if percent < 5 { - percent = 5 - w.Memory.BarColor = ui.ColorBlack - } else { - w.Memory.BarColor = ui.ColorGreen - } - w.Memory.Percent = percent -} - -func centerParText(p *ui.Par) { - var text string - var padding string - - // strip existing left-padding - for i, ch := range p.Text { - if string(ch) != " " { - text = p.Text[i:] - break - } - } - - padlen := (p.InnerWidth() - len(text)) / 2 - for i := 0; i < padlen; i++ { - padding += " " - } - p.Text = fmt.Sprintf("%s%s", padding, text) -} - -func slimHeaderPar(s string) *ui.Par { - p := slimPar(s) - p.Y = 2 - p.Height = 2 - return p -} - -func slimPar(s string) *ui.Par { - p := ui.NewPar(s) - p.Border = false - p.Height = 1 - p.Width = 20 - p.TextFgColor = ui.ColorWhite - return p -} - -func slimGauge() *ui.Gauge { - g := ui.NewGauge() - g.Height = 1 - g.Border = false - g.Percent = 0 - g.PaddingBottom = 0 - g.BarColor = ui.ColorGreen - g.Label = "-" - return g -}