mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-25 08:31:03 +00:00
Merge pull request #334 from jesseduffield/retry-if-docker-not-available
This commit is contained in:
commit
13c0bfc0b4
12 changed files with 131 additions and 188 deletions
1
go.mod
1
go.mod
|
|
@ -4,7 +4,6 @@ go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c
|
github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c
|
||||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
|
||||||
github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1
|
github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1
|
||||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
||||||
github.com/docker/docker v0.7.3-0.20190307005417-54dddadc7d5d
|
github.com/docker/docker v0.7.3-0.20190307005417-54dddadc7d5d
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -6,8 +6,6 @@ github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+q
|
||||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||||
github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c h1:YDsGA6tou+tAxVe0Dre29iSbQ8TrWdWfwOisKArJT5E=
|
github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c h1:YDsGA6tou+tAxVe0Dre29iSbQ8TrWdWfwOisKArJT5E=
|
||||||
github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c/go.mod h1:tMoSueLQlMf0TCldjrJLNIjAc5qAOIcHt5REi88/Ygo=
|
github.com/OpenPeeDeeP/xdg v0.2.1-0.20190312153938-4ba9e1eb294c/go.mod h1:tMoSueLQlMf0TCldjrJLNIjAc5qAOIcHt5REi88/Ygo=
|
||||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
|
||||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
|
||||||
github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1 h1:1fx+RA5lk1ZkzPAUP7DEgZnVHYxEcHO77vQO/V8z/2Q=
|
github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1 h1:1fx+RA5lk1ZkzPAUP7DEgZnVHYxEcHO77vQO/V8z/2Q=
|
||||||
github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1/go.mod h1:z0nyIb42Zs97wyX1V+8MbEFhHeTw1OgFQfR6q57ZuHc=
|
github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1/go.mod h1:z0nyIb42Zs97wyX1V+8MbEFhHeTw1OgFQfR6q57ZuHc=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
|
|
@ -38,12 +39,13 @@ type Container struct {
|
||||||
OSCommand *OSCommand
|
OSCommand *OSCommand
|
||||||
Config *config.AppConfig
|
Config *config.AppConfig
|
||||||
Log *logrus.Entry
|
Log *logrus.Entry
|
||||||
CLIStats ContainerCliStat // for realtime we use the CLI, for long-term we use the client
|
StatHistory []*RecordedStats
|
||||||
StatHistory []RecordedStats
|
|
||||||
Details Details
|
Details Details
|
||||||
MonitoringStats bool
|
MonitoringStats bool
|
||||||
DockerCommand LimitedDockerCommand
|
DockerCommand LimitedDockerCommand
|
||||||
Tr *i18n.TranslationSet
|
Tr *i18n.TranslationSet
|
||||||
|
|
||||||
|
StatsMutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Details is a struct containing what we get back from `docker inspect` on a container
|
// Details is a struct containing what we get back from `docker inspect` on a container
|
||||||
|
|
@ -230,19 +232,6 @@ type Details struct {
|
||||||
} `json:"NetworkSettings"`
|
} `json:"NetworkSettings"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
image := strings.TrimPrefix(c.Container.Image, "sha256:")
|
image := strings.TrimPrefix(c.Container.Image, "sha256:")
|
||||||
|
|
@ -285,17 +274,13 @@ func (c *Container) getHealthStatus() string {
|
||||||
|
|
||||||
// GetDisplayCPUPerc colors the cpu percentage based on how extreme it is
|
// GetDisplayCPUPerc colors the cpu percentage based on how extreme it is
|
||||||
func (c *Container) GetDisplayCPUPerc() string {
|
func (c *Container) GetDisplayCPUPerc() string {
|
||||||
stats := c.CLIStats
|
stats, ok := c.getLastStats()
|
||||||
|
if !ok {
|
||||||
if stats.CPUPerc == "" {
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
percentage, err := strconv.ParseFloat(strings.TrimSuffix(stats.CPUPerc, "%"), 32)
|
percentage := stats.DerivedStats.CPUPercentage
|
||||||
if err != nil {
|
formattedPercentage := fmt.Sprintf("%.2f%%", stats.DerivedStats.CPUPercentage)
|
||||||
// probably complaining about not being able to convert '--'
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var clr color.Attribute
|
var clr color.Attribute
|
||||||
if percentage > 90 {
|
if percentage > 90 {
|
||||||
|
|
@ -306,7 +291,7 @@ func (c *Container) GetDisplayCPUPerc() string {
|
||||||
clr = color.FgWhite
|
clr = color.FgWhite
|
||||||
}
|
}
|
||||||
|
|
||||||
return utils.ColoredString(stats.CPUPerc, clr)
|
return utils.ColoredString(formattedPercentage, clr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProducingLogs tells us whether we should bother checking a container's logs
|
// ProducingLogs tells us whether we should bother checking a container's logs
|
||||||
|
|
@ -400,20 +385,6 @@ func (c *Container) Top() (container.ContainerTopOKBody, error) {
|
||||||
return c.Client.ContainerTop(context.Background(), c.ID, []string{})
|
return c.Client.ContainerTop(context.Background(), c.ID, []string{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// EraseOldHistory removes any history before the user-specified max duration
|
|
||||||
func (c *Container) EraseOldHistory() {
|
|
||||||
if c.Config.UserConfig.Stats.MaxDuration == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, stat := range c.StatHistory {
|
|
||||||
if time.Since(stat.RecordedAt) < c.Config.UserConfig.Stats.MaxDuration {
|
|
||||||
c.StatHistory = c.StatHistory[i:]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ViewLogs attaches to a subprocess viewing the container's logs
|
// ViewLogs attaches to a subprocess viewing the container's logs
|
||||||
func (c *Container) ViewLogs() (*exec.Cmd, error) {
|
func (c *Container) ViewLogs() (*exec.Cmd, error) {
|
||||||
templateString := c.OSCommand.Config.UserConfig.CommandTemplates.ViewContainerLogs
|
templateString := c.OSCommand.Config.UserConfig.CommandTemplates.ViewContainerLogs
|
||||||
|
|
|
||||||
|
|
@ -149,9 +149,8 @@ type ContainerStats struct {
|
||||||
func (s *ContainerStats) CalculateContainerCPUPercentage() float64 {
|
func (s *ContainerStats) CalculateContainerCPUPercentage() float64 {
|
||||||
cpuUsageDelta := s.CPUStats.CPUUsage.TotalUsage - s.PrecpuStats.CPUUsage.TotalUsage
|
cpuUsageDelta := s.CPUStats.CPUUsage.TotalUsage - s.PrecpuStats.CPUUsage.TotalUsage
|
||||||
cpuTotalUsageDelta := s.CPUStats.SystemCPUUsage - s.PrecpuStats.SystemCPUUsage
|
cpuTotalUsageDelta := s.CPUStats.SystemCPUUsage - s.PrecpuStats.SystemCPUUsage
|
||||||
numberOfCores := len(s.CPUStats.CPUUsage.PercpuUsage)
|
|
||||||
|
|
||||||
value := float64(cpuUsageDelta*100) * float64(numberOfCores) / float64(cpuTotalUsageDelta)
|
value := float64(cpuUsageDelta*100) / float64(cpuTotalUsageDelta)
|
||||||
if math.IsNaN(value) {
|
if math.IsNaN(value) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
@ -169,11 +168,10 @@ func (s *ContainerStats) CalculateContainerMemoryUsage() float64 {
|
||||||
|
|
||||||
// RenderStats returns a string containing the rendered stats of the container
|
// RenderStats returns a string containing the rendered stats of the container
|
||||||
func (c *Container) RenderStats(viewWidth int) (string, error) {
|
func (c *Container) RenderStats(viewWidth int) (string, error) {
|
||||||
history := c.StatHistory
|
stats, ok := c.getLastStats()
|
||||||
if len(history) == 0 {
|
if !ok {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
currentStats := history[len(history)-1]
|
|
||||||
|
|
||||||
graphSpecs := c.Config.UserConfig.Stats.Graphs
|
graphSpecs := c.Config.UserConfig.Stats.Graphs
|
||||||
graphs := make([]string, len(graphSpecs))
|
graphs := make([]string, len(graphSpecs))
|
||||||
|
|
@ -185,11 +183,11 @@ func (c *Container) RenderStats(viewWidth int) (string, error) {
|
||||||
graphs[i] = utils.ColoredString(graph, utils.GetColorAttribute(spec.Color))
|
graphs[i] = utils.ColoredString(graph, utils.GetColorAttribute(spec.Color))
|
||||||
}
|
}
|
||||||
|
|
||||||
pidsCount := fmt.Sprintf("PIDs: %d", currentStats.ClientStats.PidsStats.Current)
|
pidsCount := fmt.Sprintf("PIDs: %d", stats.ClientStats.PidsStats.Current)
|
||||||
dataReceived := fmt.Sprintf("Traffic received: %s", utils.FormatDecimalBytes(currentStats.ClientStats.Networks.Eth0.RxBytes))
|
dataReceived := fmt.Sprintf("Traffic received: %s", utils.FormatDecimalBytes(stats.ClientStats.Networks.Eth0.RxBytes))
|
||||||
dataSent := fmt.Sprintf("Traffic sent: %s", utils.FormatDecimalBytes(currentStats.ClientStats.Networks.Eth0.TxBytes))
|
dataSent := fmt.Sprintf("Traffic sent: %s", utils.FormatDecimalBytes(stats.ClientStats.Networks.Eth0.TxBytes))
|
||||||
|
|
||||||
originalJSON, err := json.MarshalIndent(currentStats, "", " ")
|
originalJSON, err := json.MarshalIndent(stats, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
@ -205,8 +203,43 @@ func (c *Container) RenderStats(viewWidth int) (string, error) {
|
||||||
return contents, nil
|
return contents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Container) appendStats(stats *RecordedStats) {
|
||||||
|
c.StatsMutex.Lock()
|
||||||
|
defer c.StatsMutex.Unlock()
|
||||||
|
|
||||||
|
c.StatHistory = append(c.StatHistory, stats)
|
||||||
|
c.eraseOldHistory()
|
||||||
|
}
|
||||||
|
|
||||||
|
// eraseOldHistory removes any history before the user-specified max duration
|
||||||
|
func (c *Container) eraseOldHistory() {
|
||||||
|
if c.Config.UserConfig.Stats.MaxDuration == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, stat := range c.StatHistory {
|
||||||
|
if time.Since(stat.RecordedAt) < c.Config.UserConfig.Stats.MaxDuration {
|
||||||
|
c.StatHistory = c.StatHistory[i:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) getLastStats() (*RecordedStats, bool) {
|
||||||
|
c.StatsMutex.Lock()
|
||||||
|
defer c.StatsMutex.Unlock()
|
||||||
|
history := c.StatHistory
|
||||||
|
if len(history) == 0 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return history[len(history)-1], true
|
||||||
|
}
|
||||||
|
|
||||||
// PlotGraph returns the plotted graph based on the graph spec and the stat history
|
// PlotGraph returns the plotted graph based on the graph spec and the stat history
|
||||||
func (c *Container) PlotGraph(spec config.GraphConfig, width int) (string, error) {
|
func (c *Container) PlotGraph(spec config.GraphConfig, width int) (string, error) {
|
||||||
|
c.StatsMutex.Lock()
|
||||||
|
defer c.StatsMutex.Unlock()
|
||||||
|
|
||||||
data := make([]float64, len(c.StatHistory))
|
data := make([]float64, len(c.StatHistory))
|
||||||
|
|
||||||
max := spec.Max
|
max := spec.Max
|
||||||
|
|
|
||||||
17
pkg/commands/container_stats_test.go
Normal file
17
pkg/commands/container_stats_test.go
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCalculateContainerCPUPercentage(t *testing.T) {
|
||||||
|
container := &ContainerStats{}
|
||||||
|
container.CPUStats.CPUUsage.TotalUsage = 10
|
||||||
|
container.CPUStats.SystemCPUUsage = 10
|
||||||
|
container.PrecpuStats.CPUUsage.TotalUsage = 5
|
||||||
|
container.PrecpuStats.SystemCPUUsage = 2
|
||||||
|
|
||||||
|
assert.EqualValues(t, 62.5, container.CalculateContainerCPUPercentage())
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/acarl005/stripansi"
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
|
|
@ -121,58 +120,20 @@ func (c *DockerCommand) Close() error {
|
||||||
return utils.CloseMany(c.Closers)
|
return utils.CloseMany(c.Closers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MonitorContainerStats is a function
|
func (c *DockerCommand) MonitorContainerStats(ctx context.Context) {
|
||||||
func (c *DockerCommand) MonitorContainerStats() {
|
|
||||||
// TODO: pass in a stop channel to these so we don't restart every time we come back from a subprocess
|
|
||||||
go c.MonitorCLIContainerStats()
|
|
||||||
go c.MonitorClientContainerStats()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MonitorCLIContainerStats monitors a stream of container stats and updates the containers as each new stats object is received
|
|
||||||
func (c *DockerCommand) MonitorCLIContainerStats() {
|
|
||||||
command := `docker stats --all --no-trunc --format '{{json .}}'`
|
|
||||||
cmd := c.OSCommand.RunCustomCommand(command)
|
|
||||||
|
|
||||||
r, err := cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
c.ErrorChan <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = cmd.Start()
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(r)
|
|
||||||
scanner.Split(bufio.ScanLines)
|
|
||||||
for scanner.Scan() {
|
|
||||||
var stats ContainerCliStat
|
|
||||||
// need to strip ANSI codes because uses escape sequences to clear the screen with each refresh
|
|
||||||
cleanString := stripansi.Strip(scanner.Text())
|
|
||||||
if err := json.Unmarshal([]byte(cleanString), &stats); err != nil {
|
|
||||||
c.ErrorChan <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.ContainerMutex.Lock()
|
|
||||||
for _, container := range c.Containers {
|
|
||||||
if container.ID == stats.ID {
|
|
||||||
container.CLIStats = stats
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.ContainerMutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = cmd.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MonitorClientContainerStats is a function
|
|
||||||
func (c *DockerCommand) MonitorClientContainerStats() {
|
|
||||||
// periodically loop through running containers and see if we need to create a monitor goroutine for any
|
// periodically loop through running containers and see if we need to create a monitor goroutine for any
|
||||||
// every second we check if we need to spawn a new goroutine
|
// every second we check if we need to spawn a new goroutine
|
||||||
ticker := time.NewTicker(time.Second)
|
ticker := time.NewTicker(time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
for range ticker.C {
|
for {
|
||||||
for _, container := range c.Containers {
|
select {
|
||||||
if !container.MonitoringStats {
|
case <-ctx.Done():
|
||||||
go c.createClientStatMonitor(container)
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
for _, container := range c.Containers {
|
||||||
|
if !container.MonitoringStats {
|
||||||
|
go c.createClientStatMonitor(container)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -182,7 +143,10 @@ func (c *DockerCommand) createClientStatMonitor(container *Container) {
|
||||||
container.MonitoringStats = true
|
container.MonitoringStats = true
|
||||||
stream, err := c.Client.ContainerStats(context.Background(), container.ID, true)
|
stream, err := c.Client.ContainerStats(context.Background(), container.ID, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ErrorChan <- err
|
// not creating error panel because if we've disconnected from docker we'll
|
||||||
|
// have already created an error panel
|
||||||
|
c.Log.Error(err)
|
||||||
|
container.MonitoringStats = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -194,7 +158,7 @@ func (c *DockerCommand) createClientStatMonitor(container *Container) {
|
||||||
var stats ContainerStats
|
var stats ContainerStats
|
||||||
_ = json.Unmarshal(data, &stats)
|
_ = json.Unmarshal(data, &stats)
|
||||||
|
|
||||||
recordedStats := RecordedStats{
|
recordedStats := &RecordedStats{
|
||||||
ClientStats: stats,
|
ClientStats: stats,
|
||||||
DerivedStats: DerivedStats{
|
DerivedStats: DerivedStats{
|
||||||
CPUPercentage: stats.CalculateContainerCPUPercentage(),
|
CPUPercentage: stats.CalculateContainerCPUPercentage(),
|
||||||
|
|
@ -203,10 +167,7 @@ func (c *DockerCommand) createClientStatMonitor(container *Container) {
|
||||||
RecordedAt: time.Now(),
|
RecordedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ContainerMutex.Lock()
|
container.appendStats(recordedStats)
|
||||||
container.StatHistory = append(container.StatHistory, recordedStats)
|
|
||||||
container.EraseOldHistory()
|
|
||||||
c.ContainerMutex.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
container.MonitoringStats = false
|
container.MonitoringStats = false
|
||||||
|
|
|
||||||
|
|
@ -20,20 +20,20 @@ func (gui *Gui) wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.Vie
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return gui.closeConfirmationPrompt(g)
|
return gui.closeConfirmationPrompt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) closeConfirmationPrompt(g *gocui.Gui) error {
|
func (gui *Gui) closeConfirmationPrompt() error {
|
||||||
view, err := g.View("confirmation")
|
view, err := gui.g.View("confirmation")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil // if it's already been closed we can just return
|
return nil // if it's already been closed we can just return
|
||||||
}
|
}
|
||||||
if err := gui.returnFocus(g, view); err != nil {
|
if err := gui.returnFocus(gui.g, view); err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
g.DeleteViewKeybindings("confirmation")
|
gui.g.DeleteViewKeybindings("confirmation")
|
||||||
return g.DeleteView("confirmation")
|
return gui.g.DeleteView("confirmation")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) getMessageHeight(wrap bool, message string, width int) int {
|
func (gui *Gui) getMessageHeight(wrap bool, message string, width int) int {
|
||||||
|
|
@ -110,7 +110,7 @@ func (gui *Gui) createPopupPanel(g *gocui.Gui, currentView *gocui.View, title, p
|
||||||
g.Update(func(g *gocui.Gui) error {
|
g.Update(func(g *gocui.Gui) error {
|
||||||
// delete the existing confirmation panel if it exists
|
// delete the existing confirmation panel if it exists
|
||||||
if view, _ := g.View("confirmation"); view != nil {
|
if view, _ := g.View("confirmation"); view != nil {
|
||||||
if err := gui.closeConfirmationPrompt(g); err != nil {
|
if err := gui.closeConfirmationPrompt(); err != nil {
|
||||||
gui.Log.Error(err.Error())
|
gui.Log.Error(err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -235,10 +235,11 @@ func (gui *Gui) Run() error {
|
||||||
throttledRefresh := throttle.ThrottleFunc(time.Millisecond*50, true, gui.refresh)
|
throttledRefresh := throttle.ThrottleFunc(time.Millisecond*50, true, gui.refresh)
|
||||||
defer throttledRefresh.Stop()
|
defer throttledRefresh.Stop()
|
||||||
|
|
||||||
finish := make(chan struct{})
|
ctx, finish := context.WithCancel(context.Background())
|
||||||
defer func() { close(finish) }()
|
defer finish()
|
||||||
|
|
||||||
go gui.listenForEvents(finish, throttledRefresh.Trigger)
|
go gui.listenForEvents(ctx, throttledRefresh.Trigger)
|
||||||
|
go gui.DockerCommand.MonitorContainerStats(ctx)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
gui.waitForIntro.Wait()
|
gui.waitForIntro.Wait()
|
||||||
|
|
@ -247,11 +248,9 @@ func (gui *Gui) Run() error {
|
||||||
gui.goEvery(time.Millisecond*30, gui.reRenderMain)
|
gui.goEvery(time.Millisecond*30, gui.reRenderMain)
|
||||||
gui.goEvery(time.Millisecond*1000, gui.DockerCommand.UpdateContainerDetails)
|
gui.goEvery(time.Millisecond*1000, gui.DockerCommand.UpdateContainerDetails)
|
||||||
gui.goEvery(time.Millisecond*1000, gui.checkForContextChange)
|
gui.goEvery(time.Millisecond*1000, gui.checkForContextChange)
|
||||||
gui.goEvery(time.Millisecond*2000, gui.rerenderContainersAndServices)
|
gui.goEvery(time.Millisecond*1000, gui.rerenderContainersAndServices)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
gui.DockerCommand.MonitorContainerStats()
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for err := range gui.ErrorChan {
|
for err := range gui.ErrorChan {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -301,12 +300,42 @@ func (gui *Gui) refresh() {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) listenForEvents(finish chan struct{}, refresh func()) {
|
func (gui *Gui) listenForEvents(ctx context.Context, refresh func()) {
|
||||||
|
errorCount := 0
|
||||||
|
|
||||||
|
onError := func(err error) {
|
||||||
|
if err != nil {
|
||||||
|
gui.ErrorChan <- errors.Errorf("Docker event stream returned error: %s\nRetry count: %d", err.Error(), errorCount)
|
||||||
|
}
|
||||||
|
errorCount++
|
||||||
|
time.Sleep(time.Second * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
outer:
|
||||||
for {
|
for {
|
||||||
messageChan, errChan := gui.DockerCommand.Client.Events(context.Background(), types.EventsOptions{})
|
messageChan, errChan := gui.DockerCommand.Client.Events(context.Background(), types.EventsOptions{})
|
||||||
|
|
||||||
|
if errorCount > 0 {
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
onError(err)
|
||||||
|
continue outer
|
||||||
|
default:
|
||||||
|
// If we're here then we lost connection to docker and we just got it back.
|
||||||
|
// The reason we do this refresh explicitly is because successfully
|
||||||
|
// reconnecting with docker does not mean it's going to send us a new
|
||||||
|
// event any time soon.
|
||||||
|
|
||||||
|
// Assuming the confirmation prompt currently holds the given error
|
||||||
|
_ = gui.closeConfirmationPrompt()
|
||||||
|
refresh()
|
||||||
|
errorCount = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-finish:
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case message := <-messageChan:
|
case message := <-messageChan:
|
||||||
// We could be more granular about what events should trigger which refreshes.
|
// We could be more granular about what events should trigger which refreshes.
|
||||||
|
|
@ -316,8 +345,8 @@ func (gui *Gui) listenForEvents(finish chan struct{}, refresh func()) {
|
||||||
|
|
||||||
gui.Log.Infof("received event of type: %s", message.Type)
|
gui.Log.Infof("received event of type: %s", message.Type)
|
||||||
case err := <-errChan:
|
case err := <-errChan:
|
||||||
gui.ErrorChan <- errors.Errorf("Docker event stream returned error: %s", err.Error())
|
onError(err)
|
||||||
break
|
continue outer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
vendor/github.com/acarl005/stripansi/LICENSE
generated
vendored
21
vendor/github.com/acarl005/stripansi/LICENSE
generated
vendored
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2018 Andrew Carlson
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
30
vendor/github.com/acarl005/stripansi/README.md
generated
vendored
30
vendor/github.com/acarl005/stripansi/README.md
generated
vendored
|
|
@ -1,30 +0,0 @@
|
||||||
Strip ANSI
|
|
||||||
==========
|
|
||||||
|
|
||||||
This Go package removes ANSI escape codes from strings.
|
|
||||||
|
|
||||||
Ideally, we would prevent these from appearing in any text we want to process.
|
|
||||||
However, sometimes this can't be helped, and we need to be able to deal with that noise.
|
|
||||||
This will use a regexp to remove those unwanted escape codes.
|
|
||||||
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ go get -u github.com/acarl005/stripansi
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/acarl005/stripansi"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
msg := "\x1b[38;5;140m foo\x1b[0m bar"
|
|
||||||
cleanMsg := stripansi.Strip(msg)
|
|
||||||
fmt.Println(cleanMsg) // " foo bar"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
13
vendor/github.com/acarl005/stripansi/stripansi.go
generated
vendored
13
vendor/github.com/acarl005/stripansi/stripansi.go
generated
vendored
|
|
@ -1,13 +0,0 @@
|
||||||
package stripansi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ansi = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"
|
|
||||||
|
|
||||||
var re = regexp.MustCompile(ansi)
|
|
||||||
|
|
||||||
func Strip(str string) string {
|
|
||||||
return re.ReplaceAllString(str, "")
|
|
||||||
}
|
|
||||||
1
vendor/modules.txt
vendored
1
vendor/modules.txt
vendored
|
|
@ -9,7 +9,6 @@ github.com/Microsoft/go-winio/pkg/guid
|
||||||
github.com/OpenPeeDeeP/xdg
|
github.com/OpenPeeDeeP/xdg
|
||||||
# github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
# github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||||
## explicit
|
## explicit
|
||||||
github.com/acarl005/stripansi
|
|
||||||
# github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1
|
# github.com/boz/go-throttle v0.0.0-20160922054636-fdc4eab740c1
|
||||||
## explicit
|
## explicit
|
||||||
github.com/boz/go-throttle
|
github.com/boz/go-throttle
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue