lots of changes

pull/1/head
Jesse Duffield 5 years ago
parent 2f1c246fd7
commit 2e9a80fe6c

@ -162,8 +162,8 @@ func (s *ContainerStats) RenderStats(viewWidth int, cpuUsageHistory []float64, m
fmt.Sprintf(
"%.2f%% Memory (%s/%s)",
memoryUsageHistory[len(memoryUsageHistory)-1],
formatBinaryBytes(s.MemoryStats.Usage),
formatBinaryBytes(int(s.MemoryStats.Limit)),
utils.FormatBinaryBytes(s.MemoryStats.Usage),
utils.FormatBinaryBytes(int(s.MemoryStats.Limit)),
),
),
)
@ -178,8 +178,8 @@ func (s *ContainerStats) RenderStats(viewWidth int, cpuUsageHistory []float64, m
)
pidsCount := fmt.Sprintf("PIDs: %d", s.PidsStats.Current)
dataReceived := fmt.Sprintf("Traffic received: %s", formatDecimalBytes(s.Networks.Eth0.RxBytes))
dataSent := fmt.Sprintf("Traffic sent: %s", formatDecimalBytes(s.Networks.Eth0.TxBytes))
dataReceived := fmt.Sprintf("Traffic received: %s", utils.FormatDecimalBytes(s.Networks.Eth0.RxBytes))
dataSent := fmt.Sprintf("Traffic sent: %s", utils.FormatDecimalBytes(s.Networks.Eth0.TxBytes))
originalJSON, err := json.MarshalIndent(s, "", " ")
if err != nil {
@ -197,29 +197,3 @@ func (s *ContainerStats) RenderStats(viewWidth int, cpuUsageHistory []float64, m
return contents, nil
}
func formatBinaryBytes(b int) string {
n := float64(b)
units := []string{"B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
for _, unit := range units {
if n > math.Pow(2, 10) {
n = n / math.Pow(2, 10)
} else {
return fmt.Sprintf("%.2f%s", n, unit)
}
}
return "a lot"
}
func formatDecimalBytes(b int) string {
n := float64(b)
units := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
for _, unit := range units {
if n > math.Pow(10, 3) {
n = n / math.Pow(10, 3)
} else {
return fmt.Sprintf("%.2f%s", n, unit)
}
}
return "a lot"
}

@ -78,11 +78,6 @@ func (c *DockerCommand) GetImages() ([]*Image, error) {
for i, image := range images {
// func (cli *Client) ImageHistory(ctx context.Context, imageID string) ([]image.HistoryResponseItem, error)
history, err := c.Client.ImageHistory(context.Background(), image.ID)
if err != nil {
return nil, err
}
name := "none"
tags := image.RepoTags
if len(tags) > 0 {
@ -99,7 +94,6 @@ func (c *DockerCommand) GetImages() ([]*Image, error) {
Client: c.Client,
OSCommand: c.OSCommand,
Log: c.Log,
History: history,
}
}

@ -2,7 +2,9 @@ package commands
import (
"context"
"strings"
"github.com/davecgh/go-spew/spew"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/fatih/color"
@ -16,7 +18,6 @@ type Image struct {
Tag string
ID string
Image types.ImageSummary
History []types.ImageHistory
Client *client.Client
OSCommand *OSCommand
Log *logrus.Entry
@ -24,7 +25,8 @@ type Image struct {
// GetDisplayStrings returns the display string of Image
func (i *Image) GetDisplayStrings(isFocused bool) []string {
return []string{utils.ColoredString(i.Name, color.FgWhite), utils.ColoredString(i.Tag, color.FgWhite)}
return []string{utils.ColoredString(i.Name, color.FgWhite), utils.ColoredString(i.Tag, color.FgWhite), utils.FormatDecimalBytes(int(i.Image.Size))}
}
// Remove removes the image
@ -35,3 +37,66 @@ func (i *Image) Remove(options types.ImageRemoveOptions) error {
return nil
}
// Layer is a layer in an image's history
type Layer struct {
types.ImageHistory
}
// GetDisplayStrings returns the array of strings describing the layer
func (l *Layer) GetDisplayStrings(isFocused bool) []string {
tag := ""
if len(l.Tags) > 0 {
tag = l.Tags[0]
}
id := strings.TrimPrefix(l.ID, "sha256:")
if len(id) > 10 {
id = id[0:10]
}
idColor := color.FgWhite
if id == "<missing>" {
idColor = color.FgBlack
}
dockerFileCommandPrefix := "/bin/sh -c #(nop) "
createdBy := l.CreatedBy
if strings.Contains(l.CreatedBy, dockerFileCommandPrefix) {
createdBy = strings.Trim(strings.TrimPrefix(l.CreatedBy, dockerFileCommandPrefix), " ")
split := strings.Split(createdBy, " ")
createdBy = utils.ColoredString(split[0], color.FgYellow) + " " + strings.Join(split[1:], " ")
}
createdBy = strings.ReplaceAll(createdBy, "\t", " ")
size := utils.FormatBinaryBytes(int(l.Size))
sizeColor := color.FgWhite
if size == "0B" {
sizeColor = color.FgBlack
}
return []string{
utils.ColoredString(id, idColor),
utils.ColoredString(tag, color.FgGreen),
utils.ColoredString(size, sizeColor),
createdBy,
}
}
// RenderHistory renders the history of the image
func (i *Image) RenderHistory() (string, error) {
history, err := i.Client.ImageHistory(context.Background(), i.ID)
if err != nil {
return "", err
}
i.Log.Warn(spew.Sdump(history))
layers := make([]*Layer, len(history))
for i, layer := range history {
layers[i] = &Layer{layer}
}
return utils.RenderList(layers, utils.WithHeader([]string{"ID", "TAG", "SIZE", "COMMAND"}))
}

@ -215,7 +215,7 @@ func (gui *Gui) refreshContainers() error {
containersView.Clear()
isFocused := gui.g.CurrentView().Name() == "containers"
list, err := utils.RenderList(gui.State.Containers, isFocused)
list, err := utils.RenderList(gui.State.Containers, utils.IsFocused(isFocused))
if err != nil {
return err
}

@ -1,8 +1,9 @@
package gui
import (
"encoding/json"
"fmt"
"strings"
"time"
"github.com/docker/docker/api/types"
"github.com/fatih/color"
@ -96,21 +97,25 @@ func (gui *Gui) handleImageSelect(g *gocui.Gui, v *gocui.View) error {
return nil
}
func (gui *Gui) renderImageConfig(mainView *gocui.View, Image *commands.Image, writerID int) error {
func (gui *Gui) renderImageConfig(mainView *gocui.View, image *commands.Image, writerID int) error {
mainView.Autoscroll = false
mainView.Wrap = false
mainView.Title = "Config"
data, err := json.MarshalIndent(&Image.Image, "", " ")
if err != nil {
return err
}
output := ""
output += utils.WithPadding("ID: ", 10) + image.Image.ID + "\n"
output += utils.WithPadding("Tags: ", 10) + utils.ColoredString(strings.Join(image.Image.RepoTags, ", "), color.FgGreen) + "\n"
output += utils.WithPadding("Size: ", 10) + utils.FormatDecimalBytes(int(image.Image.Size)) + "\n"
output += utils.WithPadding("Created: ", 10) + fmt.Sprintf("%v", time.Unix(image.Image.Created, 0).Format(time.RFC1123)) + "\n"
historyData, err := json.MarshalIndent(&Image.History, "", " ")
history, err := image.RenderHistory()
if err != nil {
return err
}
gui.renderString(gui.g, "main", string(data)+"\n"+string(historyData))
output += "\n\n" + history
gui.renderString(gui.g, "main", output)
return nil
}
@ -136,7 +141,7 @@ func (gui *Gui) refreshImages() error {
ImagesView.Clear()
isFocused := gui.g.CurrentView().Name() == "Images"
list, err := utils.RenderList(gui.State.Images, isFocused)
list, err := utils.RenderList(gui.State.Images, utils.IsFocused(isFocused))
if err != nil {
return err
}

@ -54,7 +54,7 @@ func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error {
func (gui *Gui) createMenu(title string, items interface{}, itemCount int, handlePress func(int) error) error {
isFocused := gui.g.CurrentView().Name() == "menu"
gui.State.MenuItemCount = itemCount
list, err := utils.RenderList(items, isFocused)
list, err := utils.RenderList(items, utils.IsFocused(isFocused))
if err != nil {
return err
}

@ -43,7 +43,7 @@ func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error {
if err != nil {
panic(err)
}
gui.State.Panels.Main.ObjectKey = ""
gui.resetMainView()
return gui.switchFocus(g, v, focusedView)
}
@ -68,10 +68,15 @@ func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error {
if err != nil {
panic(err)
}
gui.State.Panels.Main.ObjectKey = ""
gui.resetMainView()
return gui.switchFocus(g, v, focusedView)
}
func (gui *Gui) resetMainView() {
gui.State.Panels.Main.ObjectKey = ""
gui.getMainView().Wrap = true
}
func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
switch v.Name() {
case "menu":
@ -307,7 +312,7 @@ func (gui *Gui) refreshSelectedLine(line *int, total int) {
func (gui *Gui) renderListPanel(v *gocui.View, items interface{}) error {
gui.g.Update(func(g *gocui.Gui) error {
isFocused := gui.g.CurrentView().Name() == v.Name()
list, err := utils.RenderList(items, isFocused)
list, err := utils.RenderList(items, utils.IsFocused(isFocused))
if err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}

@ -3,6 +3,7 @@ package utils
import (
"encoding/json"
"fmt"
"math"
"reflect"
"regexp"
"strings"
@ -95,10 +96,32 @@ type Displayable interface {
GetDisplayStrings(bool) []string
}
type RenderListConfig struct {
IsFocused bool
Header []string
}
func IsFocused(isFocused bool) func(c *RenderListConfig) {
return func(c *RenderListConfig) {
c.IsFocused = isFocused
}
}
func WithHeader(header []string) func(c *RenderListConfig) {
return func(c *RenderListConfig) {
c.Header = header
}
}
// RenderList takes a slice of items, confirms they implement the Displayable
// interface, then generates a list of their displaystrings to write to a panel's
// buffer
func RenderList(slice interface{}, isFocused bool) (string, error) {
func RenderList(slice interface{}, options ...func(*RenderListConfig)) (string, error) {
config := &RenderListConfig{}
for _, option := range options {
option(config)
}
s := reflect.ValueOf(slice)
if s.Kind() != reflect.Slice {
return "", errors.New("RenderList given a non-slice type")
@ -114,19 +137,22 @@ func RenderList(slice interface{}, isFocused bool) (string, error) {
displayables[i] = value
}
return renderDisplayableList(displayables, isFocused)
return renderDisplayableList(displayables, *config)
}
// renderDisplayableList takes a list of displayable items, obtains their display
// strings via GetDisplayStrings() and then returns a single string containing
// each item's string representation on its own line, with appropriate horizontal
// padding between the item's own strings
func renderDisplayableList(items []Displayable, isFocused bool) (string, error) {
func renderDisplayableList(items []Displayable, config RenderListConfig) (string, error) {
if len(items) == 0 {
return "", nil
}
stringArrays := getDisplayStringArrays(items, isFocused)
stringArrays := getDisplayStringArrays(items, config.IsFocused)
if len(config.Header) > 0 {
stringArrays = append([][]string{config.Header}, stringArrays...)
}
if !displayArraysAligned(stringArrays) {
return "", errors.New("Each item must return the same number of strings to display")
@ -228,3 +254,37 @@ func AsJson(i interface{}) string {
bytes, _ := json.MarshalIndent(i, "", " ")
return string(bytes)
}
func FormatBinaryBytes(b int) string {
n := float64(b)
units := []string{"B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
for _, unit := range units {
if n > math.Pow(2, 10) {
n = n / math.Pow(2, 10)
} else {
val := fmt.Sprintf("%.2f%s", n, unit)
if val == "0.00B" {
return "0B"
}
return val
}
}
return "a lot"
}
func FormatDecimalBytes(b int) string {
n := float64(b)
units := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
for _, unit := range units {
if n > math.Pow(10, 3) {
n = n / math.Pow(10, 3)
} else {
val := fmt.Sprintf("%.2f%s", n, unit)
if val == "0.00B" {
return "0B"
}
return val
}
}
return "a lot"
}

@ -246,7 +246,7 @@ func TestGetDisplayStringArrays(t *testing.T) {
func TestRenderDisplayableList(t *testing.T) {
type scenario struct {
input []Displayable
isFocused bool
config RenderListConfig
expectedString string
expectedErrorMessage string
}
@ -257,7 +257,7 @@ func TestRenderDisplayableList(t *testing.T) {
Displayable(&myDisplayable{[]string{}}),
Displayable(&myDisplayable{[]string{}}),
},
false,
RenderListConfig{},
"\n",
"",
},
@ -266,7 +266,7 @@ func TestRenderDisplayableList(t *testing.T) {
Displayable(&myDisplayable{[]string{"aa", "b"}}),
Displayable(&myDisplayable{[]string{"c", "d"}}),
},
false,
RenderListConfig{},
"aa b\nc d",
"",
},
@ -275,7 +275,7 @@ func TestRenderDisplayableList(t *testing.T) {
Displayable(&myDisplayable{[]string{"a"}}),
Displayable(&myDisplayable{[]string{"b", "c"}}),
},
false,
RenderListConfig{},
"",
"Each item must return the same number of strings to display",
},
@ -284,14 +284,14 @@ func TestRenderDisplayableList(t *testing.T) {
Displayable(&myDisplayable{[]string{"a"}}),
Displayable(&myDisplayable{[]string{"b"}}),
},
true,
RenderListConfig{IsFocused: true},
"a blah\nb blah",
"",
},
}
for _, s := range scenarios {
str, err := renderDisplayableList(s.input, s.isFocused)
str, err := renderDisplayableList(s.input, s.config)
assert.EqualValues(t, s.expectedString, str)
if s.expectedErrorMessage != "" {
assert.EqualError(t, err, s.expectedErrorMessage)
@ -305,7 +305,7 @@ func TestRenderDisplayableList(t *testing.T) {
func TestRenderList(t *testing.T) {
type scenario struct {
input interface{}
isFocused bool
options []func(*RenderListConfig)
expectedString string
expectedErrorMessage string
}
@ -316,7 +316,7 @@ func TestRenderList(t *testing.T) {
{[]string{"aa", "b"}},
{[]string{"c", "d"}},
},
false,
nil,
"aa b\nc d",
"",
},
@ -325,13 +325,13 @@ func TestRenderList(t *testing.T) {
{},
{},
},
false,
nil,
"",
"item does not implement the Displayable interface",
},
{
&myStruct{},
false,
nil,
"",
"RenderList given a non-slice type",
},
@ -339,14 +339,14 @@ func TestRenderList(t *testing.T) {
[]*myDisplayable{
{[]string{"a"}},
},
true,
[]func(*RenderListConfig){IsFocused(true)},
"a blah",
"",
},
}
for _, s := range scenarios {
str, err := RenderList(s.input, s.isFocused)
str, err := RenderList(s.input, s.options...)
assert.EqualValues(t, s.expectedString, str)
if s.expectedErrorMessage != "" {
assert.EqualError(t, err, s.expectedErrorMessage)

Loading…
Cancel
Save