This commit is contained in:
Jesse Duffield 2019-06-23 12:27:31 +10:00
parent 18dc37f822
commit ee688746f0
13 changed files with 104 additions and 60 deletions

View file

@ -195,16 +195,10 @@ type Details struct {
App struct { App struct {
} `json:"/app"` } `json:"/app"`
} `json:"Volumes"` } `json:"Volumes"`
WorkingDir string `json:"WorkingDir"` WorkingDir string `json:"WorkingDir"`
Entrypoint interface{} `json:"Entrypoint"` Entrypoint interface{} `json:"Entrypoint"`
OnBuild interface{} `json:"OnBuild"` OnBuild interface{} `json:"OnBuild"`
Labels struct { Labels map[string]string `json:"Labels"`
ComDockerComposeOneoff string `json:"com.docker.compose.oneoff"`
ComDockerComposeProject string `json:"com.docker.compose.project"`
ComDockerComposeService string `json:"com.docker.compose.service"`
ComDockerComposeSlug string `json:"com.docker.compose.slug"`
ComDockerComposeVersion string `json:"com.docker.compose.version"`
} `json:"Labels"`
} `json:"Config"` } `json:"Config"`
NetworkSettings struct { NetworkSettings struct {
Bridge string `json:"Bridge"` Bridge string `json:"Bridge"`

View file

@ -196,7 +196,7 @@ func GetDefaultConfig() UserConfig {
Containers: []CustomCommand{ Containers: []CustomCommand{
{ {
Attach: true, Attach: true,
Command: "docker exec -it {{ .Container.ID }} /bin/bash", Command: "docker exec -it {{ .Container.ID }} /bin/sh",
}, },
}, },
Services: []CustomCommand{}, Services: []CustomCommand{},

View file

@ -3,6 +3,7 @@ package gui
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@ -70,10 +71,8 @@ func (gui *Gui) handleContainerSelect(g *gocui.Gui, v *gocui.View) error {
} }
key := "containers-" + container.ID + "-" + gui.getContainerContexts()[gui.State.Panels.Containers.ContextIndex] key := "containers-" + container.ID + "-" + gui.getContainerContexts()[gui.State.Panels.Containers.ContextIndex]
if gui.State.Panels.Main.ObjectKey == key { if !gui.shouldRefresh(key) {
return nil return nil
} else {
gui.State.Panels.Main.ObjectKey = key
} }
if err := gui.focusPoint(0, gui.State.Panels.Containers.SelectedLine, len(gui.DockerCommand.DisplayContainers), v); err != nil { if err := gui.focusPoint(0, gui.State.Panels.Containers.SelectedLine, len(gui.DockerCommand.DisplayContainers), v); err != nil {
@ -115,13 +114,36 @@ func (gui *Gui) renderContainerConfig(container *commands.Container) error {
mainView.Autoscroll = false mainView.Autoscroll = false
mainView.Wrap = true mainView.Wrap = true
padding := 15
output := ""
output += utils.WithPadding("ID: ", padding) + container.ID + "\n"
output += utils.WithPadding("Name: ", padding) + container.Name + "\n"
output += utils.WithPadding("Command: ", padding) + strings.Join(append([]string{container.Details.Path}, container.Details.Args...), " ") + "\n"
output += utils.WithPadding("Labels: ", padding) + utils.FormatMap(padding, container.Details.Config.Labels)
output += "\n"
output += utils.WithPadding("Mounts: ", padding)
if len(container.Details.Mounts) > 0 {
output += "\n"
for _, mount := range container.Details.Mounts {
if mount.Type == "volume" {
output += fmt.Sprintf("%s%s %s\n", strings.Repeat(" ", padding), utils.ColoredString(mount.Type+":", color.FgYellow), mount.Name)
} else {
output += fmt.Sprintf("%s%s %s:%s\n", strings.Repeat(" ", padding), utils.ColoredString(mount.Type+":", color.FgYellow), mount.Source, mount.Destination)
}
}
} else {
output += "none\n"
}
data, err := json.MarshalIndent(&container.Details, "", " ") data, err := json.MarshalIndent(&container.Details, "", " ")
if err != nil { if err != nil {
return err return err
} }
output += fmt.Sprintf("\nFull details:\n\n%s", string(data))
return gui.T.NewTask(func(stop chan struct{}) { return gui.T.NewTask(func(stop chan struct{}) {
gui.renderString(gui.g, "main", string(data)) gui.renderString(gui.g, "main", output)
}) })
} }

View file

@ -26,7 +26,7 @@ func (gui *Gui) createCustomCommandMenu(customCommands []config.CustomCommand, c
options[i] = &customCommandOption{ options[i] = &customCommandOption{
customCommand: command, customCommand: command,
description: resolvedCommand, description: utils.WithShortSha(resolvedCommand),
command: resolvedCommand, command: resolvedCommand,
runCommand: true, runCommand: true,
attach: command.Attach, attach: command.Attach,

View file

@ -90,6 +90,7 @@ type menuPanelState struct {
} }
type mainPanelState struct { type mainPanelState struct {
// ObjectKey tells us what context we are in. For example, if we are looking at the logs of a particular service in the services panel this key might be 'services-<service id>-logs'. The key is made so that if something changes which might require us to re-run the logs command or run a different command, the key will be different, and we'll then know to do whatever is required. Object key probably isn't the best name for this but Context is already used to refer to tabs. Maybe I should just call them tabs.
ObjectKey string ObjectKey string
} }
@ -278,6 +279,7 @@ func (gui *Gui) Run() error {
if strings.Contains(err.Error(), "No such container") { if strings.Contains(err.Error(), "No such container") {
// this happens all the time when e.g. restarting containers so we won't worry about it // this happens all the time when e.g. restarting containers so we won't worry about it
gui.Log.Warn(err) gui.Log.Warn(err)
continue
} }
gui.createErrorPanel(gui.g, err.Error()) gui.createErrorPanel(gui.g, err.Error())
} }
@ -368,3 +370,12 @@ func (gui *Gui) handleCustomCommand(g *gocui.Gui, v *gocui.View) error {
return gui.Errors.ErrSubProcess return gui.Errors.ErrSubProcess
}) })
} }
func (gui *Gui) shouldRefresh(key string) bool {
if gui.State.Panels.Main.ObjectKey == key {
return false
}
gui.State.Panels.Main.ObjectKey = key
return true
}

View file

@ -70,10 +70,8 @@ func (gui *Gui) handleImageSelect(g *gocui.Gui, v *gocui.View) error {
} }
key := "images-" + Image.ID + "-" + gui.getImageContexts()[gui.State.Panels.Images.ContextIndex] key := "images-" + Image.ID + "-" + gui.getImageContexts()[gui.State.Panels.Images.ContextIndex]
if gui.State.Panels.Main.ObjectKey == key { if !gui.shouldRefresh(key) {
return nil return nil
} else {
gui.State.Panels.Main.ObjectKey = key
} }
if err := gui.focusPoint(0, gui.State.Panels.Images.SelectedLine, len(gui.DockerCommand.Images), v); err != nil { if err := gui.focusPoint(0, gui.State.Panels.Images.SelectedLine, len(gui.DockerCommand.Images), v); err != nil {
@ -234,16 +232,18 @@ func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error {
return nil return nil
} }
shortSha := Image.ID[7:17]
options := []*removeImageOption{ options := []*removeImageOption{
{ {
description: gui.Tr.Remove, description: gui.Tr.Remove,
command: "docker image rm " + Image.ID[1:20], command: "docker image rm " + shortSha,
configOptions: types.ImageRemoveOptions{PruneChildren: true}, configOptions: types.ImageRemoveOptions{PruneChildren: true},
runCommand: true, runCommand: true,
}, },
{ {
description: gui.Tr.RemoveWithoutPrune, description: gui.Tr.RemoveWithoutPrune,
command: "docker image rm --no-prune " + Image.ID[1:20], command: "docker image rm --no-prune " + shortSha,
configOptions: types.ImageRemoveOptions{PruneChildren: false}, configOptions: types.ImageRemoveOptions{PruneChildren: false},
runCommand: true, runCommand: true,
}, },

View file

@ -68,11 +68,10 @@ func (gui *Gui) handleServiceSelect(g *gocui.Gui, v *gocui.View) error {
if service.Container != nil { if service.Container != nil {
containerID = service.Container.ID containerID = service.Container.ID
} }
key := "services-" + service.ID + "-" + containerID + "-" + gui.getServiceContexts()[gui.State.Panels.Services.ContextIndex] key := "services-" + service.ID + "-" + containerID + "-" + gui.getServiceContexts()[gui.State.Panels.Services.ContextIndex]
if gui.State.Panels.Main.ObjectKey == key { if !gui.shouldRefresh(key) {
return nil return nil
} else {
gui.State.Panels.Main.ObjectKey = key
} }
if err := gui.focusPoint(0, gui.State.Panels.Services.SelectedLine, len(gui.DockerCommand.Services), v); err != nil { if err := gui.focusPoint(0, gui.State.Panels.Services.SelectedLine, len(gui.DockerCommand.Services), v); err != nil {

View file

@ -47,10 +47,8 @@ func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error {
} }
key := gui.getStatusContexts()[gui.State.Panels.Status.ContextIndex] key := gui.getStatusContexts()[gui.State.Panels.Status.ContextIndex]
if gui.State.Panels.Main.ObjectKey == key { if !gui.shouldRefresh(key) {
return nil return nil
} else {
gui.State.Panels.Main.ObjectKey = key
} }
gui.clearMainView() gui.clearMainView()

View file

@ -70,7 +70,9 @@ func (gui *Gui) runCommand() error {
fmt.Fprintf(os.Stdout, "\n%s", utils.ColoredString(gui.Tr.PressEnterToReturn, color.FgGreen)) fmt.Fprintf(os.Stdout, "\n%s", utils.ColoredString(gui.Tr.PressEnterToReturn, color.FgGreen))
fmt.Scanln() // wait for enter press if _, err := fmt.Scanln(); err != nil { // wait for enter press
gui.Log.Error(err)
}
return nil return nil
} }

View file

@ -78,6 +78,10 @@ func (gui *Gui) resetMainView() {
} }
func (gui *Gui) newLineFocused(v *gocui.View) error { func (gui *Gui) newLineFocused(v *gocui.View) error {
if v == nil {
return nil
}
switch v.Name() { switch v.Name() {
case "menu": case "menu":
return gui.handleMenuSelect(gui.g, v) return gui.handleMenuSelect(gui.g, v)

View file

@ -2,7 +2,6 @@ package gui
import ( import (
"fmt" "fmt"
"strings"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/go-errors/errors" "github.com/go-errors/errors"
@ -68,10 +67,8 @@ func (gui *Gui) handleVolumeSelect(g *gocui.Gui, v *gocui.View) error {
} }
key := "volumes-" + volume.Name + "-" + gui.getVolumeContexts()[gui.State.Panels.Volumes.ContextIndex] key := "volumes-" + volume.Name + "-" + gui.getVolumeContexts()[gui.State.Panels.Volumes.ContextIndex]
if gui.State.Panels.Main.ObjectKey == key { if !gui.shouldRefresh(key) {
return nil return nil
} else {
gui.State.Panels.Main.ObjectKey = key
} }
if err := gui.focusPoint(0, gui.State.Panels.Volumes.SelectedLine, len(gui.DockerCommand.Volumes), v); err != nil { if err := gui.focusPoint(0, gui.State.Panels.Volumes.SelectedLine, len(gui.DockerCommand.Volumes), v); err != nil {
@ -105,31 +102,14 @@ func (gui *Gui) renderVolumeConfig(mainView *gocui.View, volume *commands.Volume
output += utils.WithPadding("Driver: ", padding) + volume.Volume.Driver + "\n" output += utils.WithPadding("Driver: ", padding) + volume.Volume.Driver + "\n"
output += utils.WithPadding("Scope: ", padding) + volume.Volume.Scope + "\n" output += utils.WithPadding("Scope: ", padding) + volume.Volume.Scope + "\n"
output += utils.WithPadding("Mountpoint: ", padding) + volume.Volume.Mountpoint + "\n" output += utils.WithPadding("Mountpoint: ", padding) + volume.Volume.Mountpoint + "\n"
output += utils.WithPadding("Labels: ", padding) output += utils.WithPadding("Labels: ", padding) + utils.FormatMap(padding, volume.Volume.Labels) + "\n"
if len(volume.Volume.Labels) > 0 { output += utils.WithPadding("Options: ", padding) + utils.FormatMap(padding, volume.Volume.Options) + "\n"
output += "\n"
for k, v := range volume.Volume.Labels {
output += formatMapItem(padding, k, v)
}
} else {
output += "none\n"
}
output += "\n" + utils.WithPadding("Options: ", padding) output += utils.WithPadding("Status: ", padding)
if len(volume.Volume.Options) > 0 {
output += "\n"
for k, v := range volume.Volume.Options {
output += formatMapItem(padding, k, v)
}
} else {
output += "none\n"
}
output += "\n" + utils.WithPadding("Status: ", padding)
if volume.Volume.Status != nil { if volume.Volume.Status != nil {
output += "\n" output += "\n"
for k, v := range volume.Volume.Status { for k, v := range volume.Volume.Status {
output += formatMapItem(padding, k, v) output += utils.FormatMapItem(padding, k, v)
} }
} else { } else {
output += "n/a" output += "n/a"
@ -144,10 +124,6 @@ func (gui *Gui) renderVolumeConfig(mainView *gocui.View, volume *commands.Volume
}) })
} }
func formatMapItem(padding int, k string, v interface{}) string {
return fmt.Sprintf("%s%s %v\n", strings.Repeat(" ", padding), utils.ColoredString(k+":", color.FgYellow), utils.ColoredString(fmt.Sprintf("%v", v), color.FgMagenta))
}
func (gui *Gui) refreshVolumes() error { func (gui *Gui) refreshVolumes() error {
volumesView := gui.getVolumesView() volumesView := gui.getVolumesView()
if volumesView == nil { if volumesView == nil {
@ -252,13 +228,13 @@ func (gui *Gui) handleVolumesRemoveMenu(g *gocui.Gui, v *gocui.View) error {
options := []*removeVolumeOption{ options := []*removeVolumeOption{
{ {
description: gui.Tr.Remove, description: gui.Tr.Remove,
command: "docker volume rm " + volume.Name, command: utils.WithShortSha("docker volume rm " + volume.Name),
force: false, force: false,
runCommand: true, runCommand: true,
}, },
{ {
description: gui.Tr.ForceRemove, description: gui.Tr.ForceRemove,
command: "docker volume rm --force " + volume.Name, command: utils.WithShortSha("docker volume rm --force " + volume.Name),
force: true, force: true,
runCommand: true, runCommand: true,
}, },

View file

@ -87,7 +87,7 @@ func (t *TaskManager) NewTickerTask(duration time.Duration, before func(stop cha
} }
tickChan := time.NewTicker(duration) tickChan := time.NewTicker(duration)
// calling f first so that we're not waiting for the first tick // calling f first so that we're not waiting for the first tick
// f(stop, notifyStopped) f(stop, notifyStopped)
for { for {
select { select {
case <-notifyStopped: case <-notifyStopped:

View file

@ -8,6 +8,7 @@ import (
"math" "math"
"reflect" "reflect"
"regexp" "regexp"
"sort"
"strings" "strings"
"time" "time"
@ -355,3 +356,40 @@ func GetColorAttribute(key string) color.Attribute {
} }
return color.FgWhite return color.FgWhite
} }
// WithShortSha returns a command but with a shorter SHA. in the terminal we're all used to 10 character SHAs but under the hood they're actually 64 characters long. No need including all the characters when we're just displaying a command
func WithShortSha(str string) string {
split := strings.Split(str, " ")
for i, word := range split {
// good enough proxy for now
if len(word) == 64 {
split[i] = word[0:10]
}
}
return strings.Join(split, " ")
}
// FormatMapItem is for displaying items in a map
func FormatMapItem(padding int, k string, v interface{}) string {
return fmt.Sprintf("%s%s %v\n", strings.Repeat(" ", padding), ColoredString(k+":", color.FgYellow), ColoredString(fmt.Sprintf("%v", v), color.FgWhite))
}
// FormatMap is for displaying a map
func FormatMap(padding int, m map[string]string) string {
if len(m) == 0 {
return "none\n"
}
output := "\n"
keys := make([]string, 0, len(m))
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
output += FormatMapItem(padding, key, m[key])
}
return output
}