mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-25 08:31:03 +00:00
convert containers panel to use new struct
This commit is contained in:
parent
a4b4fee868
commit
5aea90c08b
8 changed files with 180 additions and 328 deletions
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"io"
|
"io"
|
||||||
ogLog "log"
|
ogLog "log"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -20,7 +19,6 @@ import (
|
||||||
"github.com/jesseduffield/lazydocker/pkg/config"
|
"github.com/jesseduffield/lazydocker/pkg/config"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/i18n"
|
"github.com/jesseduffield/lazydocker/pkg/i18n"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/utils"
|
"github.com/jesseduffield/lazydocker/pkg/utils"
|
||||||
"github.com/samber/lo"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -36,15 +34,11 @@ type DockerCommand struct {
|
||||||
Config *config.AppConfig
|
Config *config.AppConfig
|
||||||
Client *client.Client
|
Client *client.Client
|
||||||
InDockerComposeProject bool
|
InDockerComposeProject bool
|
||||||
ShowExited bool
|
|
||||||
ErrorChan chan error
|
ErrorChan chan error
|
||||||
ContainerMutex sync.Mutex
|
ContainerMutex sync.Mutex
|
||||||
ServiceMutex sync.Mutex
|
ServiceMutex sync.Mutex
|
||||||
|
|
||||||
Containers []*Container
|
Closers []io.Closer
|
||||||
// DisplayContainers is the array of containers we will display in the containers panel. If Gui.ShowAllContainers is false, this will only be those containers which aren't based on a service. This reduces clutter and duplication in the UI
|
|
||||||
DisplayContainers []*Container
|
|
||||||
Closers []io.Closer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ io.Closer = &DockerCommand{}
|
var _ io.Closer = &DockerCommand{}
|
||||||
|
|
@ -89,7 +83,6 @@ func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Translat
|
||||||
Config: config,
|
Config: config,
|
||||||
Client: cli,
|
Client: cli,
|
||||||
ErrorChan: errorChan,
|
ErrorChan: errorChan,
|
||||||
ShowExited: true,
|
|
||||||
InDockerComposeProject: true,
|
InDockerComposeProject: true,
|
||||||
Closers: []io.Closer{tunnelCloser},
|
Closers: []io.Closer{tunnelCloser},
|
||||||
}
|
}
|
||||||
|
|
@ -119,26 +112,7 @@ func (c *DockerCommand) Close() error {
|
||||||
return utils.CloseMany(c.Closers)
|
return utils.CloseMany(c.Closers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DockerCommand) MonitorContainerStats(ctx context.Context) {
|
func (c *DockerCommand) CreateClientStatMonitor(container *Container) {
|
||||||
// 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
|
|
||||||
ticker := time.NewTicker(time.Second)
|
|
||||||
defer ticker.Stop()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
for _, container := range c.Containers {
|
|
||||||
if !container.MonitoringStats {
|
|
||||||
go c.createClientStatMonitor(container)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
|
|
@ -172,11 +146,11 @@ func (c *DockerCommand) createClientStatMonitor(container *Container) {
|
||||||
container.MonitoringStats = false
|
container.MonitoringStats = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DockerCommand) RefreshContainersAndServices(currentServices []*Service) ([]*Container, []*Service, error) {
|
func (c *DockerCommand) RefreshContainersAndServices(currentServices []*Service, currentContainers []*Container) ([]*Container, []*Service, error) {
|
||||||
c.ServiceMutex.Lock()
|
c.ServiceMutex.Lock()
|
||||||
defer c.ServiceMutex.Unlock()
|
defer c.ServiceMutex.Unlock()
|
||||||
|
|
||||||
containers, err := c.GetContainers()
|
containers, err := c.GetContainers(currentContainers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -194,17 +168,7 @@ func (c *DockerCommand) RefreshContainersAndServices(currentServices []*Service)
|
||||||
|
|
||||||
c.assignContainersToServices(containers, services)
|
c.assignContainersToServices(containers, services)
|
||||||
|
|
||||||
displayContainers := containers
|
return containers, services, nil
|
||||||
if !c.Config.UserConfig.Gui.ShowAllContainers {
|
|
||||||
displayContainers = c.obtainStandaloneContainers(containers, services)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Containers = containers
|
|
||||||
c.DisplayContainers = c.filterOutExited(displayContainers)
|
|
||||||
c.DisplayContainers = c.filterOutIgnoredContainers(c.DisplayContainers)
|
|
||||||
c.DisplayContainers = c.sortedContainers(c.DisplayContainers)
|
|
||||||
|
|
||||||
return c.DisplayContainers, services, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DockerCommand) assignContainersToServices(containers []*Container, services []*Service) {
|
func (c *DockerCommand) assignContainersToServices(containers []*Container, services []*Service) {
|
||||||
|
|
@ -220,72 +184,11 @@ L:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterOutExited filters out the exited containers if c.ShowExited is false
|
|
||||||
func (c *DockerCommand) filterOutExited(containers []*Container) []*Container {
|
|
||||||
if c.ShowExited {
|
|
||||||
return containers
|
|
||||||
}
|
|
||||||
toReturn := []*Container{}
|
|
||||||
for _, container := range containers {
|
|
||||||
if container.Container.State != "exited" {
|
|
||||||
toReturn = append(toReturn, container)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return toReturn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *DockerCommand) filterOutIgnoredContainers(containers []*Container) []*Container {
|
|
||||||
return lo.Filter(containers, func(container *Container, _ int) bool {
|
|
||||||
return !lo.SomeBy(c.Config.UserConfig.Ignore, func(ignore string) bool {
|
|
||||||
return strings.Contains(container.Name, ignore)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// sortedContainers returns containers sorted by state if c.SortContainersByState is true (follows 1- running, 2- exited, 3- created)
|
|
||||||
// and sorted by name if c.SortContainersByState is false
|
|
||||||
func (c *DockerCommand) sortedContainers(containers []*Container) []*Container {
|
|
||||||
if !c.Config.UserConfig.Gui.LegacySortContainers {
|
|
||||||
states := map[string]int{
|
|
||||||
"running": 1,
|
|
||||||
"exited": 2,
|
|
||||||
"created": 3,
|
|
||||||
}
|
|
||||||
sort.Slice(containers, func(i, j int) bool {
|
|
||||||
stateLeft := states[containers[i].Container.State]
|
|
||||||
stateRight := states[containers[j].Container.State]
|
|
||||||
if stateLeft == stateRight {
|
|
||||||
return containers[i].Name < containers[j].Name
|
|
||||||
}
|
|
||||||
return states[containers[i].Container.State] < states[containers[j].Container.State]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return containers
|
|
||||||
}
|
|
||||||
|
|
||||||
// obtainStandaloneContainers returns standalone containers. Standalone containers are containers which are either one-off containers, or whose service is not part of this docker-compose context
|
|
||||||
func (c *DockerCommand) obtainStandaloneContainers(containers []*Container, services []*Service) []*Container {
|
|
||||||
standaloneContainers := []*Container{}
|
|
||||||
L:
|
|
||||||
for _, container := range containers {
|
|
||||||
for _, service := range services {
|
|
||||||
if !container.OneOff && container.ServiceName != "" && container.ServiceName == service.Name {
|
|
||||||
continue L
|
|
||||||
}
|
|
||||||
}
|
|
||||||
standaloneContainers = append(standaloneContainers, container)
|
|
||||||
}
|
|
||||||
|
|
||||||
return standaloneContainers
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetContainers gets the docker containers
|
// GetContainers gets the docker containers
|
||||||
func (c *DockerCommand) GetContainers() ([]*Container, error) {
|
func (c *DockerCommand) GetContainers(existingContainers []*Container) ([]*Container, error) {
|
||||||
c.ContainerMutex.Lock()
|
c.ContainerMutex.Lock()
|
||||||
defer c.ContainerMutex.Unlock()
|
defer c.ContainerMutex.Unlock()
|
||||||
|
|
||||||
existingContainers := c.Containers
|
|
||||||
|
|
||||||
containers, err := c.Client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
|
containers, err := c.Client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -369,11 +272,11 @@ func (c *DockerCommand) GetServices() ([]*Service, error) {
|
||||||
|
|
||||||
// UpdateContainerDetails attaches the details returned from docker inspect to each of the containers
|
// UpdateContainerDetails attaches the details returned from docker inspect to each of the containers
|
||||||
// this contains a bit more info than what you get from the go-docker client
|
// this contains a bit more info than what you get from the go-docker client
|
||||||
func (c *DockerCommand) UpdateContainerDetails() error {
|
func (c *DockerCommand) UpdateContainerDetails(containers []*Container) error {
|
||||||
c.ContainerMutex.Lock()
|
c.ContainerMutex.Lock()
|
||||||
defer c.ContainerMutex.Unlock()
|
defer c.ContainerMutex.Unlock()
|
||||||
|
|
||||||
for _, container := range c.Containers {
|
for _, container := range containers {
|
||||||
details, err := c.Client.ContainerInspect(context.Background(), container.ID)
|
details, err := c.Client.ContainerInspect(context.Background(), container.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Log.Error(err)
|
c.Log.Error(err)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import (
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/go-errors/errors"
|
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/commands"
|
"github.com/jesseduffield/lazydocker/pkg/commands"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/config"
|
"github.com/jesseduffield/lazydocker/pkg/config"
|
||||||
|
|
@ -16,81 +15,97 @@ import (
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// list panel functions
|
func (gui *Gui) getContainersPanel() *SideListPanel[*commands.Container] {
|
||||||
|
states := map[string]int{
|
||||||
func (gui *Gui) getContainerContexts() []string {
|
"running": 1,
|
||||||
return []string{"logs", "stats", "env", "config", "top"}
|
"exited": 2,
|
||||||
}
|
"created": 3,
|
||||||
|
|
||||||
func (gui *Gui) getContainerContextTitles() []string {
|
|
||||||
return []string{gui.Tr.LogsTitle, gui.Tr.StatsTitle, gui.Tr.EnvTitle, gui.Tr.ConfigTitle, gui.Tr.TopTitle}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) getSelectedContainer() (*commands.Container, error) {
|
|
||||||
selectedLine := gui.State.Panels.Containers.SelectedLine
|
|
||||||
if selectedLine == -1 {
|
|
||||||
return &commands.Container{}, gui.Errors.ErrNoContainers
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.DockerCommand.DisplayContainers[selectedLine], nil
|
// Standalone containers are containers which are either one-off containers, or whose service is not part of this docker-compose context.
|
||||||
}
|
isStandaloneContainer := func(container *commands.Container) bool {
|
||||||
|
if container.OneOff || container.ServiceName == "" {
|
||||||
func (gui *Gui) handleContainersClick(g *gocui.Gui, v *gocui.View) error {
|
return true
|
||||||
itemCount := len(gui.DockerCommand.DisplayContainers)
|
|
||||||
handleSelect := gui.handleContainerSelect
|
|
||||||
selectedLine := &gui.State.Panels.Containers.SelectedLine
|
|
||||||
|
|
||||||
return gui.handleClick(v, itemCount, selectedLine, handleSelect)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleContainerSelect(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
container, err := gui.getSelectedContainer()
|
|
||||||
if err != nil {
|
|
||||||
if err != gui.Errors.ErrNoContainers {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
return !lo.SomeBy(gui.Panels.Services.list.GetAllItems(), func(service *commands.Service) bool {
|
||||||
|
return service.Name == container.ServiceName
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.focusY(gui.State.Panels.Containers.SelectedLine, len(gui.DockerCommand.DisplayContainers), v)
|
return &SideListPanel[*commands.Container]{
|
||||||
|
contextKeyPrefix: "containers",
|
||||||
|
ListPanel: ListPanel[*commands.Container]{
|
||||||
|
list: NewFilteredList[*commands.Container](),
|
||||||
|
view: gui.Views.Containers,
|
||||||
|
},
|
||||||
|
contextIdx: 0,
|
||||||
|
noItemsMessge: gui.Tr.NoContainers,
|
||||||
|
gui: gui.intoInterface(),
|
||||||
|
contexts: []ContextConfig[*commands.Container]{
|
||||||
|
{
|
||||||
|
key: "logs",
|
||||||
|
title: gui.Tr.LogsTitle,
|
||||||
|
render: gui.renderContainerLogsToMain,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "stats",
|
||||||
|
title: gui.Tr.StatsTitle,
|
||||||
|
render: gui.renderContainerStats,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "env",
|
||||||
|
title: gui.Tr.EnvTitle,
|
||||||
|
render: gui.renderContainerEnv,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "config",
|
||||||
|
title: gui.Tr.ConfigTitle,
|
||||||
|
render: gui.renderContainerConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "top",
|
||||||
|
title: gui.Tr.TopTitle,
|
||||||
|
render: gui.renderContainerTop,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getSearchStrings: func(container *commands.Container) []string {
|
||||||
|
// TODO: think about more things to search on
|
||||||
|
return []string{container.Name}
|
||||||
|
},
|
||||||
|
getContextCacheKey: func(container *commands.Container) string {
|
||||||
|
return container.ID + "-" + container.Container.State
|
||||||
|
},
|
||||||
|
// sortedContainers returns containers sorted by state if c.SortContainersByState is true (follows 1- running, 2- exited, 3- created)
|
||||||
|
// and sorted by name if c.SortContainersByState is false
|
||||||
|
sort: func(a *commands.Container, b *commands.Container) bool {
|
||||||
|
if gui.Config.UserConfig.Gui.LegacySortContainers {
|
||||||
|
return a.Name < b.Name
|
||||||
|
}
|
||||||
|
|
||||||
key := "containers-" + container.ID + "-" + gui.getContainerContexts()[gui.State.Panels.Containers.ContextIndex]
|
stateLeft := states[a.Container.State]
|
||||||
if !gui.shouldRefresh(key) {
|
stateRight := states[b.Container.State]
|
||||||
return nil
|
if stateLeft == stateRight {
|
||||||
|
return a.Name < b.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
return states[a.Container.State] < states[b.Container.State]
|
||||||
|
},
|
||||||
|
filter: func(container *commands.Container) bool {
|
||||||
|
// Note that this is O(N*M) time complexity where N is the number of services
|
||||||
|
// and M is the number of containers. We expect N to be small but M may be large,
|
||||||
|
// so we will need to keep an eye on this.
|
||||||
|
if !gui.Config.UserConfig.Gui.ShowAllContainers && !isStandaloneContainer(container) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !gui.State.ShowExitedContainers && container.Container.State == "exited" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
mainView := gui.getMainView()
|
|
||||||
mainView.Tabs = gui.getContainerContextTitles()
|
|
||||||
mainView.TabIndex = gui.State.Panels.Containers.ContextIndex
|
|
||||||
|
|
||||||
gui.clearMainView()
|
|
||||||
|
|
||||||
switch gui.getContainerContexts()[gui.State.Panels.Containers.ContextIndex] {
|
|
||||||
case "logs":
|
|
||||||
if err := gui.renderContainerLogsToMain(container); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "config":
|
|
||||||
if err := gui.renderContainerConfig(container); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "env":
|
|
||||||
if err := gui.renderContainerEnv(container); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "stats":
|
|
||||||
if err := gui.renderContainerStats(container); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "top":
|
|
||||||
if err := gui.renderContainerTop(container); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("Unknown context for containers panel")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) renderContainerEnv(container *commands.Container) error {
|
func (gui *Gui) renderContainerEnv(container *commands.Container) error {
|
||||||
|
|
@ -236,14 +251,16 @@ func (gui *Gui) refreshContainersAndServices() error {
|
||||||
originalSelectedLineIdx := gui.Panels.Services.selectedIdx
|
originalSelectedLineIdx := gui.Panels.Services.selectedIdx
|
||||||
selectedService, isServiceSelected := gui.Panels.Services.list.TryGet(originalSelectedLineIdx)
|
selectedService, isServiceSelected := gui.Panels.Services.list.TryGet(originalSelectedLineIdx)
|
||||||
|
|
||||||
_, services, err := gui.DockerCommand.RefreshContainersAndServices(
|
containers, services, err := gui.DockerCommand.RefreshContainersAndServices(
|
||||||
gui.Panels.Services.list.GetAllItems(),
|
gui.Panels.Services.list.GetAllItems(),
|
||||||
|
gui.Panels.Containers.list.GetAllItems(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.Panels.Services.SetItems(services)
|
gui.Panels.Services.SetItems(services)
|
||||||
|
gui.Panels.Containers.SetItems(containers)
|
||||||
|
|
||||||
// see if our selected service has moved
|
// see if our selected service has moved
|
||||||
if isServiceSelected {
|
if isServiceSelected {
|
||||||
|
|
@ -258,91 +275,20 @@ func (gui *Gui) refreshContainersAndServices() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(gui.DockerCommand.DisplayContainers) > 0 && gui.State.Panels.Containers.SelectedLine == -1 {
|
return gui.renderContainersAndServices()
|
||||||
gui.State.Panels.Containers.SelectedLine = 0
|
|
||||||
}
|
|
||||||
if len(gui.DockerCommand.DisplayContainers)-1 < gui.State.Panels.Containers.SelectedLine {
|
|
||||||
gui.State.Panels.Containers.SelectedLine = len(gui.DockerCommand.DisplayContainers) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
gui.renderContainersAndServices()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) renderContainersAndServices() {
|
func (gui *Gui) renderContainersAndServices() error {
|
||||||
if gui.DockerCommand.InDockerComposeProject {
|
if gui.DockerCommand.InDockerComposeProject {
|
||||||
if err := gui.Panels.Services.RerenderList(); err != nil {
|
if err := gui.Panels.Services.RerenderList(); err != nil {
|
||||||
gui.ErrorChan <- err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gui.g.Update(func(g *gocui.Gui) error {
|
|
||||||
containersView := gui.getContainersView()
|
|
||||||
containersView.Clear()
|
|
||||||
isFocused := gui.g.CurrentView().Name() == "containers"
|
|
||||||
|
|
||||||
list, err := utils.RenderList(gui.DockerCommand.DisplayContainers, utils.IsFocused(isFocused))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprint(containersView, list)
|
|
||||||
|
|
||||||
if containersView == g.CurrentView() {
|
|
||||||
if err := gui.handleContainerSelect(g, containersView); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleContainersNextLine(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
if gui.popupPanelFocused() || gui.g.CurrentView() != v {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
panelState := gui.State.Panels.Containers
|
if err := gui.Panels.Containers.RerenderList(); err != nil {
|
||||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.DockerCommand.DisplayContainers), false)
|
return err
|
||||||
|
|
||||||
return gui.handleContainerSelect(gui.g, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleContainersPrevLine(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
if gui.popupPanelFocused() || gui.g.CurrentView() != v {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
panelState := gui.State.Panels.Containers
|
|
||||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.DockerCommand.DisplayContainers), true)
|
|
||||||
|
|
||||||
return gui.handleContainerSelect(gui.g, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleContainersNextContext(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
contexts := gui.getContainerContexts()
|
|
||||||
if gui.State.Panels.Containers.ContextIndex >= len(contexts)-1 {
|
|
||||||
gui.State.Panels.Containers.ContextIndex = 0
|
|
||||||
} else {
|
|
||||||
gui.State.Panels.Containers.ContextIndex++
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = gui.handleContainerSelect(gui.g, v)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleContainersPrevContext(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
contexts := gui.getContainerContexts()
|
|
||||||
if gui.State.Panels.Containers.ContextIndex <= 0 {
|
|
||||||
gui.State.Panels.Containers.ContextIndex = len(contexts) - 1
|
|
||||||
} else {
|
|
||||||
gui.State.Panels.Containers.ContextIndex--
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = gui.handleContainerSelect(gui.g, v)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -358,17 +304,13 @@ func (r *removeContainerOption) GetDisplayStrings(isFocused bool) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleHideStoppedContainers(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleHideStoppedContainers(g *gocui.Gui, v *gocui.View) error {
|
||||||
gui.DockerCommand.ShowExited = !gui.DockerCommand.ShowExited
|
gui.State.ShowExitedContainers = !gui.State.ShowExitedContainers
|
||||||
|
|
||||||
if err := gui.refreshContainersAndServices(); err != nil {
|
return gui.Panels.Containers.RerenderList()
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleContainersRemoveMenu(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleContainersRemoveMenu(g *gocui.Gui, v *gocui.View) error {
|
||||||
container, err := gui.getSelectedContainer()
|
container, err := gui.Panels.Containers.GetSelectedItem()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -431,7 +373,7 @@ func (gui *Gui) PauseContainer(container *commands.Container) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleContainerPause(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleContainerPause(g *gocui.Gui, v *gocui.View) error {
|
||||||
container, err := gui.getSelectedContainer()
|
container, err := gui.Panels.Containers.GetSelectedItem()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -440,7 +382,7 @@ func (gui *Gui) handleContainerPause(g *gocui.Gui, v *gocui.View) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleContainerStop(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleContainerStop(g *gocui.Gui, v *gocui.View) error {
|
||||||
container, err := gui.getSelectedContainer()
|
container, err := gui.Panels.Containers.GetSelectedItem()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -457,7 +399,7 @@ func (gui *Gui) handleContainerStop(g *gocui.Gui, v *gocui.View) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleContainerRestart(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleContainerRestart(g *gocui.Gui, v *gocui.View) error {
|
||||||
container, err := gui.getSelectedContainer()
|
container, err := gui.Panels.Containers.GetSelectedItem()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -472,7 +414,7 @@ func (gui *Gui) handleContainerRestart(g *gocui.Gui, v *gocui.View) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error {
|
||||||
container, err := gui.getSelectedContainer()
|
container, err := gui.Panels.Containers.GetSelectedItem()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -498,7 +440,7 @@ func (gui *Gui) handlePruneContainers() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleContainerViewLogs(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleContainerViewLogs(g *gocui.Gui, v *gocui.View) error {
|
||||||
container, err := gui.getSelectedContainer()
|
container, err := gui.Panels.Containers.GetSelectedItem()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -509,7 +451,7 @@ func (gui *Gui) handleContainerViewLogs(g *gocui.Gui, v *gocui.View) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleContainersExecShell(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleContainersExecShell(g *gocui.Gui, v *gocui.View) error {
|
||||||
container, err := gui.getSelectedContainer()
|
container, err := gui.Panels.Containers.GetSelectedItem()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -530,7 +472,7 @@ func (gui *Gui) containerExecShell(container *commands.Container) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleContainersCustomCommand(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleContainersCustomCommand(g *gocui.Gui, v *gocui.View) error {
|
||||||
container, err := gui.getSelectedContainer()
|
container, err := gui.Panels.Containers.GetSelectedItem()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -547,8 +489,10 @@ func (gui *Gui) handleContainersCustomCommand(g *gocui.Gui, v *gocui.View) error
|
||||||
func (gui *Gui) handleStopContainers() error {
|
func (gui *Gui) handleStopContainers() error {
|
||||||
return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.ConfirmStopContainers, func(g *gocui.Gui, v *gocui.View) error {
|
return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.ConfirmStopContainers, func(g *gocui.Gui, v *gocui.View) error {
|
||||||
return gui.WithWaitingStatus(gui.Tr.StoppingStatus, func() error {
|
return gui.WithWaitingStatus(gui.Tr.StoppingStatus, func() error {
|
||||||
for _, container := range gui.DockerCommand.Containers {
|
for _, container := range gui.Panels.Containers.list.GetAllItems() {
|
||||||
_ = container.Stop()
|
if err := container.Stop(); err != nil {
|
||||||
|
gui.Log.Error(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -559,8 +503,10 @@ func (gui *Gui) handleStopContainers() error {
|
||||||
func (gui *Gui) handleRemoveContainers() error {
|
func (gui *Gui) handleRemoveContainers() error {
|
||||||
return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.ConfirmRemoveContainers, func(g *gocui.Gui, v *gocui.View) error {
|
return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.ConfirmRemoveContainers, func(g *gocui.Gui, v *gocui.View) error {
|
||||||
return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error {
|
return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error {
|
||||||
for _, container := range gui.DockerCommand.Containers {
|
for _, container := range gui.Panels.Containers.list.GetAllItems() {
|
||||||
_ = container.Remove(types.ContainerRemoveOptions{Force: true})
|
if err := container.Remove(types.ContainerRemoveOptions{Force: true}); err != nil {
|
||||||
|
gui.Log.Error(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -592,7 +538,7 @@ func (gui *Gui) handleContainersBulkCommand(g *gocui.Gui, v *gocui.View) error {
|
||||||
|
|
||||||
// Open first port in browser
|
// Open first port in browser
|
||||||
func (gui *Gui) handleContainersOpenInBrowserCommand(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handleContainersOpenInBrowserCommand(g *gocui.Gui, v *gocui.View) error {
|
||||||
container, err := gui.getSelectedContainer()
|
container, err := gui.Panels.Containers.GetSelectedItem()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,30 +22,6 @@ import (
|
||||||
// OverlappingEdges determines if panel edges overlap
|
// OverlappingEdges determines if panel edges overlap
|
||||||
var OverlappingEdges = false
|
var OverlappingEdges = false
|
||||||
|
|
||||||
// SentinelErrors are the errors that have special meaning and need to be checked
|
|
||||||
// by calling functions. The less of these, the better
|
|
||||||
type SentinelErrors struct {
|
|
||||||
ErrNoContainers error
|
|
||||||
ErrNoVolumes error
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateSentinelErrors makes the sentinel errors for the gui. We're defining it here
|
|
||||||
// because we can't do package-scoped errors with localization, and also because
|
|
||||||
// it seems like package-scoped variables are bad in general
|
|
||||||
// https://dave.cheney.net/2017/06/11/go-without-package-scoped-variables
|
|
||||||
// In the future it would be good to implement some of the recommendations of
|
|
||||||
// that article. For now, if we don't need an error to be a sentinel, we will just
|
|
||||||
// define it inline. This has implications for error messages that pop up everywhere
|
|
||||||
// in that we'll be duplicating the default values. We may need to look at
|
|
||||||
// having a default localisation bundle defined, and just using keys-only when
|
|
||||||
// localising things in the code.
|
|
||||||
func (gui *Gui) GenerateSentinelErrors() {
|
|
||||||
gui.Errors = SentinelErrors{
|
|
||||||
ErrNoContainers: errors.New(gui.Tr.NoContainers),
|
|
||||||
ErrNoVolumes: errors.New(gui.Tr.NoVolumes),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gui wraps the gocui Gui object which handles rendering and events
|
// Gui wraps the gocui Gui object which handles rendering and events
|
||||||
type Gui struct {
|
type Gui struct {
|
||||||
g *gocui.Gui
|
g *gocui.Gui
|
||||||
|
|
@ -55,7 +31,6 @@ type Gui struct {
|
||||||
State guiState
|
State guiState
|
||||||
Config *config.AppConfig
|
Config *config.AppConfig
|
||||||
Tr *i18n.TranslationSet
|
Tr *i18n.TranslationSet
|
||||||
Errors SentinelErrors
|
|
||||||
statusManager *statusManager
|
statusManager *statusManager
|
||||||
T *tasks.TaskManager
|
T *tasks.TaskManager
|
||||||
ErrorChan chan error
|
ErrorChan chan error
|
||||||
|
|
@ -73,9 +48,10 @@ type Gui struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Panels struct {
|
type Panels struct {
|
||||||
Images *SideListPanel[*commands.Image]
|
Services *SideListPanel[*commands.Service]
|
||||||
Services *SideListPanel[*commands.Service]
|
Containers *SideListPanel[*commands.Container]
|
||||||
Volumes *SideListPanel[*commands.Volume]
|
Images *SideListPanel[*commands.Image]
|
||||||
|
Volumes *SideListPanel[*commands.Volume]
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutexes struct {
|
type Mutexes struct {
|
||||||
|
|
@ -83,11 +59,6 @@ type Mutexes struct {
|
||||||
ViewStackMutex sync.Mutex
|
ViewStackMutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type containerPanelState struct {
|
|
||||||
SelectedLine int
|
|
||||||
ContextIndex int // for specifying if you are looking at logs/stats/config/etc
|
|
||||||
}
|
|
||||||
|
|
||||||
type projectState struct {
|
type projectState struct {
|
||||||
ContextIndex int // for specifying if you are looking at credits/logs
|
ContextIndex int // for specifying if you are looking at credits/logs
|
||||||
}
|
}
|
||||||
|
|
@ -103,10 +74,9 @@ type mainPanelState struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type panelStates struct {
|
type panelStates struct {
|
||||||
Containers *containerPanelState
|
Menu *menuPanelState
|
||||||
Menu *menuPanelState
|
Main *mainPanelState
|
||||||
Main *mainPanelState
|
Project *projectState
|
||||||
Project *projectState
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type guiState struct {
|
type guiState struct {
|
||||||
|
|
@ -118,6 +88,9 @@ type guiState struct {
|
||||||
SubProcessOutput string
|
SubProcessOutput string
|
||||||
Stats map[string]commands.ContainerStats
|
Stats map[string]commands.ContainerStats
|
||||||
|
|
||||||
|
// if true, we show containers with an 'exited' status in the contaniners panel
|
||||||
|
ShowExitedContainers bool
|
||||||
|
|
||||||
ScreenMode WindowMaximisation
|
ScreenMode WindowMaximisation
|
||||||
|
|
||||||
Searching searchingState
|
Searching searchingState
|
||||||
|
|
@ -156,8 +129,7 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand
|
||||||
initialState := guiState{
|
initialState := guiState{
|
||||||
Platform: *oSCommand.Platform,
|
Platform: *oSCommand.Platform,
|
||||||
Panels: &panelStates{
|
Panels: &panelStates{
|
||||||
Containers: &containerPanelState{SelectedLine: -1, ContextIndex: 0},
|
Menu: &menuPanelState{SelectedLine: 0},
|
||||||
Menu: &menuPanelState{SelectedLine: 0},
|
|
||||||
Main: &mainPanelState{
|
Main: &mainPanelState{
|
||||||
ObjectKey: "",
|
ObjectKey: "",
|
||||||
},
|
},
|
||||||
|
|
@ -170,6 +142,7 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand
|
||||||
Images: NewFilteredList[*commands.Image](),
|
Images: NewFilteredList[*commands.Image](),
|
||||||
Volumes: NewFilteredList[*commands.Volume](),
|
Volumes: NewFilteredList[*commands.Volume](),
|
||||||
},
|
},
|
||||||
|
ShowExitedContainers: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
cyclableViews := []string{"project", "containers", "images", "volumes"}
|
cyclableViews := []string{"project", "containers", "images", "volumes"}
|
||||||
|
|
@ -191,8 +164,6 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand
|
||||||
CyclableViews: cyclableViews,
|
CyclableViews: cyclableViews,
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.GenerateSentinelErrors()
|
|
||||||
|
|
||||||
return gui, nil
|
return gui, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,12 +217,6 @@ 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()
|
||||||
|
|
||||||
ctx, finish := context.WithCancel(context.Background())
|
|
||||||
defer finish()
|
|
||||||
|
|
||||||
go gui.listenForEvents(ctx, throttledRefresh.Trigger)
|
|
||||||
go gui.DockerCommand.MonitorContainerStats(ctx)
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for err := range gui.ErrorChan {
|
for err := range gui.ErrorChan {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -274,9 +239,10 @@ func (gui *Gui) Run() error {
|
||||||
|
|
||||||
// TODO: see if we can avoid the circular dependency
|
// TODO: see if we can avoid the circular dependency
|
||||||
gui.Panels = Panels{
|
gui.Panels = Panels{
|
||||||
Images: gui.getImagesPanel(),
|
Services: gui.getServicesPanel(),
|
||||||
Services: gui.getServicesPanel(),
|
Containers: gui.getContainersPanel(),
|
||||||
Volumes: gui.getVolumesPanel(),
|
Images: gui.getImagesPanel(),
|
||||||
|
Volumes: gui.getVolumesPanel(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = gui.keybindings(g); err != nil {
|
if err = gui.keybindings(g); err != nil {
|
||||||
|
|
@ -295,11 +261,17 @@ func (gui *Gui) Run() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx, finish := context.WithCancel(context.Background())
|
||||||
|
defer finish()
|
||||||
|
|
||||||
|
go gui.listenForEvents(ctx, throttledRefresh.Trigger)
|
||||||
|
go gui.monitorContainerStats(ctx)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
throttledRefresh.Trigger()
|
throttledRefresh.Trigger()
|
||||||
|
|
||||||
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.updateContainerDetails)
|
||||||
gui.goEvery(time.Millisecond*1000, gui.checkForContextChange)
|
gui.goEvery(time.Millisecond*1000, gui.checkForContextChange)
|
||||||
gui.goEvery(time.Millisecond*1000, gui.rerenderContainersAndServices)
|
gui.goEvery(time.Millisecond*1000, gui.rerenderContainersAndServices)
|
||||||
}()
|
}()
|
||||||
|
|
@ -311,6 +283,10 @@ func (gui *Gui) Run() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) updateContainerDetails() error {
|
||||||
|
return gui.DockerCommand.UpdateContainerDetails(gui.Panels.Containers.list.GetAllItems())
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) rerenderContainersAndServices() error {
|
func (gui *Gui) rerenderContainersAndServices() error {
|
||||||
// we need to regularly re-render these because their stats will be changed in the background
|
// we need to regularly re-render these because their stats will be changed in the background
|
||||||
gui.renderContainersAndServices()
|
gui.renderContainersAndServices()
|
||||||
|
|
@ -479,3 +455,22 @@ func (gui *Gui) IgnoreStrings() []string {
|
||||||
func (gui *Gui) Update(f func() error) {
|
func (gui *Gui) Update(f func() error) {
|
||||||
gui.g.Update(func(*gocui.Gui) error { return f() })
|
gui.g.Update(func(*gocui.Gui) error { return f() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) monitorContainerStats(ctx context.Context) {
|
||||||
|
// 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
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
for _, container := range gui.Panels.Containers.list.GetAllItems() {
|
||||||
|
if !container.MonitoringStats {
|
||||||
|
go gui.DockerCommand.CreateClientStatMonitor(container)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -202,14 +202,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||||
ViewName: "containers",
|
ViewName: "containers",
|
||||||
Key: '[',
|
Key: '[',
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleContainersPrevContext,
|
Handler: wrappedHandler(gui.Panels.Containers.OnPrevContext),
|
||||||
Description: gui.Tr.PreviousContext,
|
Description: gui.Tr.PreviousContext,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "containers",
|
ViewName: "containers",
|
||||||
Key: ']',
|
Key: ']',
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleContainersNextContext,
|
Handler: wrappedHandler(gui.Panels.Containers.OnNextContext),
|
||||||
Description: gui.Tr.NextContext,
|
Description: gui.Tr.NextContext,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -585,7 +585,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||||
}{
|
}{
|
||||||
"menu": {onKeyUpPress: gui.handleMenuPrevLine, onKeyDownPress: gui.handleMenuNextLine, onClick: gui.handleMenuClick},
|
"menu": {onKeyUpPress: gui.handleMenuPrevLine, onKeyDownPress: gui.handleMenuNextLine, onClick: gui.handleMenuClick},
|
||||||
"services": {onKeyUpPress: wrappedHandler(gui.Panels.Services.OnPrevLine), onKeyDownPress: wrappedHandler(gui.Panels.Services.OnNextLine), onClick: wrappedHandler(gui.Panels.Services.OnClick)},
|
"services": {onKeyUpPress: wrappedHandler(gui.Panels.Services.OnPrevLine), onKeyDownPress: wrappedHandler(gui.Panels.Services.OnNextLine), onClick: wrappedHandler(gui.Panels.Services.OnClick)},
|
||||||
"containers": {onKeyUpPress: gui.handleContainersPrevLine, onKeyDownPress: gui.handleContainersNextLine, onClick: gui.handleContainersClick},
|
"containers": {onKeyUpPress: wrappedHandler(gui.Panels.Containers.OnPrevLine), onKeyDownPress: wrappedHandler(gui.Panels.Containers.OnNextLine), onClick: wrappedHandler(gui.Panels.Containers.OnClick)},
|
||||||
"images": {onKeyUpPress: wrappedHandler(gui.Panels.Images.OnPrevLine), onKeyDownPress: wrappedHandler(gui.Panels.Images.OnNextLine), onClick: wrappedHandler(gui.Panels.Images.OnClick)},
|
"images": {onKeyUpPress: wrappedHandler(gui.Panels.Images.OnPrevLine), onKeyDownPress: wrappedHandler(gui.Panels.Images.OnNextLine), onClick: wrappedHandler(gui.Panels.Images.OnClick)},
|
||||||
"volumes": {onKeyUpPress: wrappedHandler(gui.Panels.Volumes.OnPrevLine), onKeyDownPress: wrappedHandler(gui.Panels.Volumes.OnNextLine), onClick: wrappedHandler(gui.Panels.Volumes.OnClick)},
|
"volumes": {onKeyUpPress: wrappedHandler(gui.Panels.Volumes.OnPrevLine), onKeyDownPress: wrappedHandler(gui.Panels.Volumes.OnNextLine), onClick: wrappedHandler(gui.Panels.Volumes.OnClick)},
|
||||||
"main": {onKeyUpPress: gui.scrollUpMain, onKeyDownPress: gui.scrollDownMain, onClick: gui.handleMainClick},
|
"main": {onKeyUpPress: gui.scrollUpMain, onKeyDownPress: gui.scrollDownMain, onClick: gui.handleMainClick},
|
||||||
|
|
|
||||||
|
|
@ -126,8 +126,7 @@ func (gui *Gui) focusPointInView(view *gocui.View) {
|
||||||
}
|
}
|
||||||
|
|
||||||
listViews := map[string]listViewState{
|
listViews := map[string]listViewState{
|
||||||
"containers": {selectedLine: gui.State.Panels.Containers.SelectedLine, lineCount: len(gui.DockerCommand.DisplayContainers)},
|
"menu": {selectedLine: gui.State.Panels.Menu.SelectedLine, lineCount: gui.State.MenuItemCount},
|
||||||
"menu": {selectedLine: gui.State.Panels.Menu.SelectedLine, lineCount: gui.State.MenuItemCount},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if state, ok := listViews[view.Name()]; ok {
|
if state, ok := listViews[view.Name()]; ok {
|
||||||
|
|
@ -141,6 +140,8 @@ func (gui *Gui) focusPointInView(view *gocui.View) {
|
||||||
gui.Panels.Services.Refocus()
|
gui.Panels.Services.Refocus()
|
||||||
case "volumes":
|
case "volumes":
|
||||||
gui.Panels.Volumes.Refocus()
|
gui.Panels.Volumes.Refocus()
|
||||||
|
case "containers":
|
||||||
|
gui.Panels.Containers.Refocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,9 @@ type SideListPanel[T comparable] struct {
|
||||||
getContextCacheKey func(item T) string
|
getContextCacheKey func(item T) string
|
||||||
|
|
||||||
sort func(a, b T) bool
|
sort func(a, b T) bool
|
||||||
|
|
||||||
|
// this filter is applied on top of additional default filters
|
||||||
|
filter func(T) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContextConfig[T any] struct {
|
type ContextConfig[T any] struct {
|
||||||
|
|
@ -192,6 +195,10 @@ func (self *SideListPanel[T]) FilterAndSort() {
|
||||||
filterString := self.gui.FilterString(self.view)
|
filterString := self.gui.FilterString(self.view)
|
||||||
|
|
||||||
self.list.Filter(func(item T, index int) bool {
|
self.list.Filter(func(item T, index int) bool {
|
||||||
|
if self.filter != nil && !self.filter(item) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if lo.SomeBy(self.gui.IgnoreStrings(), func(ignore string) bool {
|
if lo.SomeBy(self.gui.IgnoreStrings(), func(ignore string) bool {
|
||||||
return lo.SomeBy(self.getSearchStrings(item), func(searchString string) bool {
|
return lo.SomeBy(self.getSearchStrings(item), func(searchString string) bool {
|
||||||
return strings.Contains(searchString, ignore)
|
return strings.Contains(searchString, ignore)
|
||||||
|
|
|
||||||
|
|
@ -91,8 +91,8 @@ func (gui *Gui) onMainTabClick(tabIndex int) error {
|
||||||
gui.Panels.Services.SetContextIndex(tabIndex)
|
gui.Panels.Services.SetContextIndex(tabIndex)
|
||||||
return gui.Panels.Services.HandleSelect()
|
return gui.Panels.Services.HandleSelect()
|
||||||
case "containers":
|
case "containers":
|
||||||
gui.State.Panels.Containers.ContextIndex = tabIndex
|
gui.Panels.Containers.SetContextIndex(tabIndex)
|
||||||
return gui.handleContainerSelect(gui.g, gui.getContainersView())
|
return gui.Panels.Containers.HandleSelect()
|
||||||
case "images":
|
case "images":
|
||||||
gui.Panels.Images.SetContextIndex(tabIndex)
|
gui.Panels.Images.SetContextIndex(tabIndex)
|
||||||
return gui.Panels.Images.HandleSelect()
|
return gui.Panels.Images.HandleSelect()
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ func (gui *Gui) newLineFocused(v *gocui.View) error {
|
||||||
case "services":
|
case "services":
|
||||||
return gui.Panels.Services.HandleSelect()
|
return gui.Panels.Services.HandleSelect()
|
||||||
case "containers":
|
case "containers":
|
||||||
return gui.handleContainerSelect(gui.g, v)
|
return gui.Panels.Containers.HandleSelect()
|
||||||
case "images":
|
case "images":
|
||||||
return gui.Panels.Images.HandleSelect()
|
return gui.Panels.Images.HandleSelect()
|
||||||
case "volumes":
|
case "volumes":
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue