mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-21 23:01:03 +00:00
more
This commit is contained in:
parent
18dc37f822
commit
ee688746f0
13 changed files with 104 additions and 60 deletions
|
|
@ -195,16 +195,10 @@ type Details struct {
|
|||
App struct {
|
||||
} `json:"/app"`
|
||||
} `json:"Volumes"`
|
||||
WorkingDir string `json:"WorkingDir"`
|
||||
Entrypoint interface{} `json:"Entrypoint"`
|
||||
OnBuild interface{} `json:"OnBuild"`
|
||||
Labels struct {
|
||||
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"`
|
||||
WorkingDir string `json:"WorkingDir"`
|
||||
Entrypoint interface{} `json:"Entrypoint"`
|
||||
OnBuild interface{} `json:"OnBuild"`
|
||||
Labels map[string]string `json:"Labels"`
|
||||
} `json:"Config"`
|
||||
NetworkSettings struct {
|
||||
Bridge string `json:"Bridge"`
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ func GetDefaultConfig() UserConfig {
|
|||
Containers: []CustomCommand{
|
||||
{
|
||||
Attach: true,
|
||||
Command: "docker exec -it {{ .Container.ID }} /bin/bash",
|
||||
Command: "docker exec -it {{ .Container.ID }} /bin/sh",
|
||||
},
|
||||
},
|
||||
Services: []CustomCommand{},
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package gui
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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]
|
||||
if gui.State.Panels.Main.ObjectKey == key {
|
||||
if !gui.shouldRefresh(key) {
|
||||
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 {
|
||||
|
|
@ -115,13 +114,36 @@ func (gui *Gui) renderContainerConfig(container *commands.Container) error {
|
|||
mainView.Autoscroll = false
|
||||
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, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
output += fmt.Sprintf("\nFull details:\n\n%s", string(data))
|
||||
|
||||
return gui.T.NewTask(func(stop chan struct{}) {
|
||||
gui.renderString(gui.g, "main", string(data))
|
||||
gui.renderString(gui.g, "main", output)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ func (gui *Gui) createCustomCommandMenu(customCommands []config.CustomCommand, c
|
|||
|
||||
options[i] = &customCommandOption{
|
||||
customCommand: command,
|
||||
description: resolvedCommand,
|
||||
description: utils.WithShortSha(resolvedCommand),
|
||||
command: resolvedCommand,
|
||||
runCommand: true,
|
||||
attach: command.Attach,
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ type menuPanelState 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
|
||||
}
|
||||
|
||||
|
|
@ -278,6 +279,7 @@ func (gui *Gui) Run() error {
|
|||
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
|
||||
gui.Log.Warn(err)
|
||||
continue
|
||||
}
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) shouldRefresh(key string) bool {
|
||||
if gui.State.Panels.Main.ObjectKey == key {
|
||||
return false
|
||||
}
|
||||
|
||||
gui.State.Panels.Main.ObjectKey = key
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
if gui.State.Panels.Main.ObjectKey == key {
|
||||
if !gui.shouldRefresh(key) {
|
||||
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 {
|
||||
|
|
@ -234,16 +232,18 @@ func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
shortSha := Image.ID[7:17]
|
||||
|
||||
options := []*removeImageOption{
|
||||
{
|
||||
description: gui.Tr.Remove,
|
||||
command: "docker image rm " + Image.ID[1:20],
|
||||
command: "docker image rm " + shortSha,
|
||||
configOptions: types.ImageRemoveOptions{PruneChildren: true},
|
||||
runCommand: true,
|
||||
},
|
||||
{
|
||||
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},
|
||||
runCommand: true,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -68,11 +68,10 @@ func (gui *Gui) handleServiceSelect(g *gocui.Gui, v *gocui.View) error {
|
|||
if service.Container != nil {
|
||||
containerID = service.Container.ID
|
||||
}
|
||||
|
||||
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
|
||||
} else {
|
||||
gui.State.Panels.Main.ObjectKey = key
|
||||
}
|
||||
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Services.SelectedLine, len(gui.DockerCommand.Services), v); err != nil {
|
||||
|
|
|
|||
|
|
@ -47,10 +47,8 @@ func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error {
|
|||
}
|
||||
|
||||
key := gui.getStatusContexts()[gui.State.Panels.Status.ContextIndex]
|
||||
if gui.State.Panels.Main.ObjectKey == key {
|
||||
if !gui.shouldRefresh(key) {
|
||||
return nil
|
||||
} else {
|
||||
gui.State.Panels.Main.ObjectKey = key
|
||||
}
|
||||
|
||||
gui.clearMainView()
|
||||
|
|
|
|||
|
|
@ -70,7 +70,9 @@ func (gui *Gui) runCommand() error {
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,6 +78,10 @@ func (gui *Gui) resetMainView() {
|
|||
}
|
||||
|
||||
func (gui *Gui) newLineFocused(v *gocui.View) error {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v.Name() {
|
||||
case "menu":
|
||||
return gui.handleMenuSelect(gui.g, v)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package gui
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"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]
|
||||
if gui.State.Panels.Main.ObjectKey == key {
|
||||
if !gui.shouldRefresh(key) {
|
||||
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 {
|
||||
|
|
@ -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("Scope: ", padding) + volume.Volume.Scope + "\n"
|
||||
output += utils.WithPadding("Mountpoint: ", padding) + volume.Volume.Mountpoint + "\n"
|
||||
output += utils.WithPadding("Labels: ", padding)
|
||||
if len(volume.Volume.Labels) > 0 {
|
||||
output += "\n"
|
||||
for k, v := range volume.Volume.Labels {
|
||||
output += formatMapItem(padding, k, v)
|
||||
}
|
||||
} else {
|
||||
output += "none\n"
|
||||
}
|
||||
output += utils.WithPadding("Labels: ", padding) + utils.FormatMap(padding, volume.Volume.Labels) + "\n"
|
||||
output += utils.WithPadding("Options: ", padding) + utils.FormatMap(padding, volume.Volume.Options) + "\n"
|
||||
|
||||
output += "\n" + utils.WithPadding("Options: ", 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)
|
||||
output += utils.WithPadding("Status: ", padding)
|
||||
if volume.Volume.Status != nil {
|
||||
output += "\n"
|
||||
for k, v := range volume.Volume.Status {
|
||||
output += formatMapItem(padding, k, v)
|
||||
output += utils.FormatMapItem(padding, k, v)
|
||||
}
|
||||
} else {
|
||||
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 {
|
||||
volumesView := gui.getVolumesView()
|
||||
if volumesView == nil {
|
||||
|
|
@ -252,13 +228,13 @@ func (gui *Gui) handleVolumesRemoveMenu(g *gocui.Gui, v *gocui.View) error {
|
|||
options := []*removeVolumeOption{
|
||||
{
|
||||
description: gui.Tr.Remove,
|
||||
command: "docker volume rm " + volume.Name,
|
||||
command: utils.WithShortSha("docker volume rm " + volume.Name),
|
||||
force: false,
|
||||
runCommand: true,
|
||||
},
|
||||
{
|
||||
description: gui.Tr.ForceRemove,
|
||||
command: "docker volume rm --force " + volume.Name,
|
||||
command: utils.WithShortSha("docker volume rm --force " + volume.Name),
|
||||
force: true,
|
||||
runCommand: true,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ func (t *TaskManager) NewTickerTask(duration time.Duration, before func(stop cha
|
|||
}
|
||||
tickChan := time.NewTicker(duration)
|
||||
// calling f first so that we're not waiting for the first tick
|
||||
// f(stop, notifyStopped)
|
||||
f(stop, notifyStopped)
|
||||
for {
|
||||
select {
|
||||
case <-notifyStopped:
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"math"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -355,3 +356,40 @@ func GetColorAttribute(key string) color.Attribute {
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue