mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-25 08:31:03 +00:00
more progress
This commit is contained in:
parent
88758de164
commit
f0ffdcd81f
8 changed files with 139 additions and 44 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
||||||
|
|
||||||
TODO.md
|
TODO.md
|
||||||
|
Lazydocker.code-workspace
|
||||||
|
|
@ -15,19 +15,35 @@ import (
|
||||||
|
|
||||||
// Container : A docker Container
|
// Container : A docker Container
|
||||||
type Container struct {
|
type Container struct {
|
||||||
Name string
|
Name string
|
||||||
ServiceName string
|
ServiceName string
|
||||||
ID string
|
ContainerNumber string // might make this an int in the future if need be
|
||||||
Container types.Container
|
ProjectName string
|
||||||
DisplayString string
|
ID string
|
||||||
Client *client.Client
|
Container types.Container
|
||||||
OSCommand *OSCommand
|
DisplayString string
|
||||||
Log *logrus.Entry
|
Client *client.Client
|
||||||
|
OSCommand *OSCommand
|
||||||
|
Log *logrus.Entry
|
||||||
|
Stats ContainerCliStat
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerCliStat is a stat object returned by the CLI docker stat command
|
||||||
|
type ContainerCliStat struct {
|
||||||
|
BlockIO string `json:"BlockIO"`
|
||||||
|
CPUPerc string `json:"CPUPerc"`
|
||||||
|
Container string `json:"Container"`
|
||||||
|
ID string `json:"ID"`
|
||||||
|
MemPerc string `json:"MemPerc"`
|
||||||
|
MemUsage string `json:"MemUsage"`
|
||||||
|
Name string `json:"Name"`
|
||||||
|
NetIO string `json:"NetIO"`
|
||||||
|
PIDs string `json:"PIDs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDisplayStrings returns the dispaly string of Container
|
// GetDisplayStrings returns the dispaly string of Container
|
||||||
func (c *Container) GetDisplayStrings(isFocused bool) []string {
|
func (c *Container) GetDisplayStrings(isFocused bool) []string {
|
||||||
return []string{utils.ColoredString(c.Container.State, c.GetColor()), utils.ColoredString(c.Name, color.FgWhite)}
|
return []string{utils.ColoredString(c.Container.State, c.GetColor()), utils.ColoredString(c.Name, color.FgWhite), c.Stats.CPUPerc}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetColor Container color
|
// GetColor Container color
|
||||||
|
|
@ -79,8 +95,20 @@ func (c *Container) Restart() error {
|
||||||
return c.Client.ContainerRestart(context.Background(), c.ID, nil)
|
return c.Client.ContainerRestart(context.Background(), c.ID, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RestartService restarts the container
|
||||||
|
func (c *Container) RestartService() error {
|
||||||
|
templateString := c.OSCommand.Config.GetUserConfig().GetString("commandTemplates.restartService")
|
||||||
|
command := utils.ApplyTemplate(templateString, c)
|
||||||
|
return c.OSCommand.RunCommand(command)
|
||||||
|
}
|
||||||
|
|
||||||
// Attach attaches the container
|
// Attach attaches the container
|
||||||
func (c *Container) Attach() *exec.Cmd {
|
func (c *Container) Attach() *exec.Cmd {
|
||||||
cmd := c.OSCommand.PrepareSubProcess("docker", "attach", "--sig-proxy=false", c.ID)
|
cmd := c.OSCommand.PrepareSubProcess("docker", "attach", "--sig-proxy=false", c.ID)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Top returns process information
|
||||||
|
func (c *Container) Top() (types.ContainerProcessList, error) {
|
||||||
|
return c.Client.ContainerTop(context.Background(), c.ID, []string{})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
|
@ -37,6 +38,40 @@ func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localize
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateContainerStats takes a slice of containers and returns the same slice but with new stats added
|
||||||
|
// TODO: consider using this for everything stats-related
|
||||||
|
func (c *DockerCommand) UpdateContainerStats(containers []*Container) ([]*Container, error) {
|
||||||
|
// TODO: consider using a stream rather than polling
|
||||||
|
command := `docker stats --all --no-trunc --no-stream --format '{{json .}}'`
|
||||||
|
output, err := c.OSCommand.RunCommandWithOutput(command)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonStats := "[" + strings.Join(
|
||||||
|
strings.Split(
|
||||||
|
strings.Trim(output, "\n"), "\n",
|
||||||
|
), ",",
|
||||||
|
) + "]"
|
||||||
|
|
||||||
|
c.Log.Warn(jsonStats)
|
||||||
|
|
||||||
|
var stats []ContainerCliStat
|
||||||
|
if err := json.Unmarshal([]byte(jsonStats), &stats); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, stat := range stats {
|
||||||
|
for _, container := range containers {
|
||||||
|
if container.ID == stat.ID {
|
||||||
|
container.Stats = stat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return containers, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetContainers returns a slice of docker containers
|
// GetContainers returns a slice of docker containers
|
||||||
func (c *DockerCommand) GetContainers() ([]*Container, error) {
|
func (c *DockerCommand) GetContainers() ([]*Container, error) {
|
||||||
containers, err := c.Client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
|
containers, err := c.Client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
|
||||||
|
|
@ -47,22 +82,24 @@ func (c *DockerCommand) GetContainers() ([]*Container, error) {
|
||||||
ownContainers := make([]*Container, len(containers))
|
ownContainers := make([]*Container, len(containers))
|
||||||
|
|
||||||
for i, container := range containers {
|
for i, container := range containers {
|
||||||
serviceName, ok := container.Labels["com.docker.compose.service"]
|
|
||||||
if !ok {
|
|
||||||
serviceName = ""
|
|
||||||
c.Log.Warn("Could not get service name from docker container")
|
|
||||||
}
|
|
||||||
ownContainers[i] = &Container{
|
ownContainers[i] = &Container{
|
||||||
ID: container.ID,
|
ID: container.ID,
|
||||||
Name: strings.TrimLeft(container.Names[0], "/"),
|
Name: strings.TrimLeft(container.Names[0], "/"),
|
||||||
ServiceName: serviceName,
|
ServiceName: container.Labels["com.docker.compose.service"],
|
||||||
Container: container,
|
ProjectName: container.Labels["com.docker.compose.project"],
|
||||||
Client: c.Client,
|
ContainerNumber: container.Labels["com.docker.compose.container"],
|
||||||
OSCommand: c.OSCommand,
|
Container: container,
|
||||||
Log: c.Log,
|
Client: c.Client,
|
||||||
|
OSCommand: c.OSCommand,
|
||||||
|
Log: c.Log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ownContainers, err = c.UpdateContainerStats(ownContainers)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
|
||||||
return ownContainers, nil
|
return ownContainers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -248,6 +248,8 @@ update:
|
||||||
days: 14 # how often a update is checked for
|
days: 14 # how often a update is checked for
|
||||||
reporting: 'undetermined' # one of: 'on' | 'off' | 'undetermined'
|
reporting: 'undetermined' # one of: 'on' | 'off' | 'undetermined'
|
||||||
confirmOnQuit: false
|
confirmOnQuit: false
|
||||||
|
commandTemplates:
|
||||||
|
restartService: "docker-compose restart {{ .ServiceName }}"
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
|
@ -374,11 +372,14 @@ func (gui *Gui) handleContainerStop(g *gocui.Gui, v *gocui.View) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.createConfirmationPanel(gui.g, v, gui.Tr.SLocalize("Confirm"), gui.Tr.SLocalize("StopContainer"), func(g *gocui.Gui, v *gocui.View) error {
|
return gui.createConfirmationPanel(gui.g, v, gui.Tr.SLocalize("Confirm"), gui.Tr.SLocalize("StopContainer"), func(g *gocui.Gui, v *gocui.View) error {
|
||||||
if err := container.Stop(); err != nil {
|
return gui.WithWaitingStatus(gui.Tr.SLocalize("StoppingStatus"), func() error {
|
||||||
return gui.createErrorPanel(gui.g, err.Error())
|
if err := container.Stop(); err != nil {
|
||||||
}
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.refreshContainers()
|
||||||
|
})
|
||||||
|
|
||||||
return gui.refreshContainers()
|
|
||||||
}, nil)
|
}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -405,7 +406,9 @@ func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
|
||||||
gui.State.Panels.Main.WriterID++
|
gui.State.Panels.Main.WriterID++
|
||||||
|
|
||||||
// Create arbitrary command.
|
// c := container.Attach()
|
||||||
|
|
||||||
|
// // Create arbitrary command.
|
||||||
c := exec.Command("bash")
|
c := exec.Command("bash")
|
||||||
|
|
||||||
// Start the command with a pty.
|
// Start the command with a pty.
|
||||||
|
|
@ -414,20 +417,6 @@ func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle pty size.
|
|
||||||
ch := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(ch, syscall.SIGWINCH)
|
|
||||||
go func() {
|
|
||||||
|
|
||||||
for range ch {
|
|
||||||
x, y := gui.getMainView().Size()
|
|
||||||
if err := pty.Setsize(ptmx, &pty.Winsize{Cols: uint16(x), Rows: uint16(y), X: 0, Y: 0}); err != nil {
|
|
||||||
gui.Log.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
ch <- syscall.SIGWINCH // Initial resize.
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
// Set stdin in raw mode.
|
// Set stdin in raw mode.
|
||||||
oldState, err := terminal.MakeRaw(int(os.Stdin.Fd()))
|
oldState, err := terminal.MakeRaw(int(os.Stdin.Fd()))
|
||||||
|
|
@ -445,7 +434,7 @@ func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error {
|
||||||
gui.State.Panels.Main.ObjectKey = "attached-" + container.ID
|
gui.State.Panels.Main.ObjectKey = "attached-" + container.ID
|
||||||
|
|
||||||
scanner := bufio.NewScanner(ptmx)
|
scanner := bufio.NewScanner(ptmx)
|
||||||
scanner.Split(bufio.ScanRunes)
|
scanner.Split(bufio.ScanBytes)
|
||||||
|
|
||||||
content := ""
|
content := ""
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
|
|
@ -465,3 +454,18 @@ func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) handleServiceRestart(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
container, err := gui.getSelectedContainer(g)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.WithWaitingStatus(gui.Tr.SLocalize("RestartingStatus"), func() error {
|
||||||
|
if err := container.RestartService(); err != nil {
|
||||||
|
return gui.createErrorPanel(gui.g, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.refreshContainers()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||||
Handler: gui.handleContainerAttach,
|
Handler: gui.handleContainerAttach,
|
||||||
Description: gui.Tr.SLocalize("attachContainer"),
|
Description: gui.Tr.SLocalize("attachContainer"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ViewName: "containers",
|
||||||
|
Key: 'R',
|
||||||
|
Modifier: gocui.ModNone,
|
||||||
|
Handler: gui.handleServiceRestart,
|
||||||
|
Description: gui.Tr.SLocalize("restartService"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ViewName: "images",
|
ViewName: "images",
|
||||||
Key: '[',
|
Key: '[',
|
||||||
|
|
|
||||||
|
|
@ -780,6 +780,10 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||||
ID: "RestartingStatus",
|
ID: "RestartingStatus",
|
||||||
Other: "restarting",
|
Other: "restarting",
|
||||||
},
|
},
|
||||||
|
&i18n.Message{
|
||||||
|
ID: "StoppingStatus",
|
||||||
|
Other: "stopping",
|
||||||
|
},
|
||||||
&i18n.Message{
|
&i18n.Message{
|
||||||
ID: "removeContainer",
|
ID: "removeContainer",
|
||||||
Other: "remove container",
|
Other: "remove container",
|
||||||
|
|
@ -792,6 +796,10 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||||
ID: "restartContainer",
|
ID: "restartContainer",
|
||||||
Other: "restart container",
|
Other: "restart container",
|
||||||
},
|
},
|
||||||
|
&i18n.Message{
|
||||||
|
ID: "restartService",
|
||||||
|
Other: "restart container's service",
|
||||||
|
},
|
||||||
&i18n.Message{
|
&i18n.Message{
|
||||||
ID: "previousContext",
|
ID: "previousContext",
|
||||||
Other: "previous context",
|
Other: "previous context",
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
@ -288,3 +290,9 @@ func FormatDecimalBytes(b int) string {
|
||||||
}
|
}
|
||||||
return "a lot"
|
return "a lot"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ApplyTemplate(str string, object interface{}) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
template.Must(template.New("").Parse(str)).Execute(&buf, object)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue