diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go index 0744d547..1ba8302a 100644 --- a/pkg/gui/containers_panel.go +++ b/pkg/gui/containers_panel.go @@ -22,58 +22,58 @@ func (gui *Gui) getContainersPanel() *SideListPanel[*commands.Container] { return true } - return !lo.SomeBy(gui.Panels.Services.list.GetAllItems(), func(service *commands.Service) bool { + return !lo.SomeBy(gui.Panels.Services.List.GetAllItems(), func(service *commands.Service) bool { return service.Name == container.ServiceName }) } return &SideListPanel[*commands.Container]{ - contextKeyPrefix: "containers", + ContextState: &ContextState[*commands.Container]{ + GetContexts: func() []ContextConfig[*commands.Container] { + return []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, + }, + } + }, + GetContextCacheKey: func(container *commands.Container) string { + return "containers-" + container.ID + "-" + container.Container.State + }, + }, ListPanel: ListPanel[*commands.Container]{ - list: NewFilteredList[*commands.Container](), + List: NewFilteredList[*commands.Container](), view: gui.Views.Containers, }, - contextIdx: 0, - noItemsMessage: gui.Tr.NoContainers, + NoItemsMessage: gui.Tr.NoContainers, gui: gui.intoInterface(), - getContexts: func() []ContextConfig[*commands.Container] { - return []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, - }, - } - }, - 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 { + Sort: func(a *commands.Container, b *commands.Container) bool { return sortContainers(a, b, gui.Config.UserConfig.Gui.LegacySortContainers) }, - filter: func(container *commands.Container) bool { + 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. @@ -87,7 +87,7 @@ func (gui *Gui) getContainersPanel() *SideListPanel[*commands.Container] { return true }, - getDisplayStrings: func(container *commands.Container) []string { + GetDisplayStrings: func(container *commands.Container) []string { image := strings.TrimPrefix(container.Container.Image, "sha256:") return []string{ @@ -261,12 +261,12 @@ func (gui *Gui) refreshContainersAndServices() error { } // keep track of current service selected so that we can reposition our cursor if it moves position in the list - originalSelectedLineIdx := gui.Panels.Services.selectedIdx - selectedService, isServiceSelected := gui.Panels.Services.list.TryGet(originalSelectedLineIdx) + originalSelectedLineIdx := gui.Panels.Services.SelectedIdx + selectedService, isServiceSelected := gui.Panels.Services.List.TryGet(originalSelectedLineIdx) containers, services, err := gui.DockerCommand.RefreshContainersAndServices( - gui.Panels.Services.list.GetAllItems(), - gui.Panels.Containers.list.GetAllItems(), + gui.Panels.Services.List.GetAllItems(), + gui.Panels.Containers.List.GetAllItems(), ) if err != nil { return err @@ -277,7 +277,7 @@ func (gui *Gui) refreshContainersAndServices() error { // see if our selected service has moved if isServiceSelected { - for i, service := range gui.Panels.Services.list.GetItems() { + for i, service := range gui.Panels.Services.List.GetItems() { if service.ID == selectedService.ID { if i == originalSelectedLineIdx { break @@ -484,7 +484,7 @@ func (gui *Gui) handleContainersCustomCommand(g *gocui.Gui, v *gocui.View) 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.WithWaitingStatus(gui.Tr.StoppingStatus, func() error { - for _, container := range gui.Panels.Containers.list.GetAllItems() { + for _, container := range gui.Panels.Containers.List.GetAllItems() { if err := container.Stop(); err != nil { gui.Log.Error(err) } @@ -498,7 +498,7 @@ func (gui *Gui) handleStopContainers() 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.WithWaitingStatus(gui.Tr.RemovingStatus, func() error { - for _, container := range gui.Panels.Containers.list.GetAllItems() { + for _, container := range gui.Panels.Containers.List.GetAllItems() { if err := container.Remove(types.ContainerRemoveOptions{Force: true}); err != nil { gui.Log.Error(err) } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 127ba591..b3f70414 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -269,7 +269,7 @@ func (gui *Gui) Run() error { } func (gui *Gui) updateContainerDetails() error { - return gui.DockerCommand.UpdateContainerDetails(gui.Panels.Containers.list.GetAllItems()) + return gui.DockerCommand.UpdateContainerDetails(gui.Panels.Containers.List.GetAllItems()) } func (gui *Gui) refresh() { @@ -455,7 +455,7 @@ func (gui *Gui) monitorContainerStats(ctx context.Context) { case <-ctx.Done(): return case <-ticker.C: - for _, container := range gui.Panels.Containers.list.GetAllItems() { + for _, container := range gui.Panels.Containers.List.GetAllItems() { if !container.MonitoringStats { go gui.DockerCommand.CreateClientStatMonitor(container) } diff --git a/pkg/gui/images_panel.go b/pkg/gui/images_panel.go index 11759028..eb0cb72d 100644 --- a/pkg/gui/images_panel.go +++ b/pkg/gui/images_panel.go @@ -18,29 +18,29 @@ func (gui *Gui) getImagesPanel() *SideListPanel[*commands.Image] { noneLabel := "" return &SideListPanel[*commands.Image]{ - contextKeyPrefix: "images", + ContextState: &ContextState[*commands.Image]{ + GetContexts: func() []ContextConfig[*commands.Image] { + return []ContextConfig[*commands.Image]{ + { + key: "config", + title: gui.Tr.ConfigTitle, + render: func(image *commands.Image) error { + return gui.renderImageConfig(image) + }, + }, + } + }, + GetContextCacheKey: func(image *commands.Image) string { + return "images-" + image.ID + }, + }, ListPanel: ListPanel[*commands.Image]{ - list: NewFilteredList[*commands.Image](), + List: NewFilteredList[*commands.Image](), view: gui.Views.Images, }, - contextIdx: 0, - noItemsMessage: gui.Tr.NoImages, + NoItemsMessage: gui.Tr.NoImages, gui: gui.intoInterface(), - getContexts: func() []ContextConfig[*commands.Image] { - return []ContextConfig[*commands.Image]{ - { - key: "config", - title: gui.Tr.ConfigTitle, - render: func(image *commands.Image) error { - return gui.renderImageConfig(image) - }, - }, - } - }, - getContextCacheKey: func(image *commands.Image) string { - return image.ID - }, - sort: func(a *commands.Image, b *commands.Image) bool { + Sort: func(a *commands.Image, b *commands.Image) bool { if a.Name == noneLabel && b.Name != noneLabel { return false } @@ -59,7 +59,7 @@ func (gui *Gui) getImagesPanel() *SideListPanel[*commands.Image] { return a.ID < b.ID }, - getDisplayStrings: func(image *commands.Image) []string { + GetDisplayStrings: func(image *commands.Image) []string { return []string{ image.Name, image.Tag, diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 8c58f873..bd86167a 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -514,7 +514,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { } for _, panel := range gui.allListPanels() { - setUpDownClickBindings(panel.View().Name(), panel.OnPrevLine, panel.OnNextLine, panel.OnClick) + setUpDownClickBindings(panel.View().Name(), panel.HandlePrevLine, panel.HandleNextLine, panel.HandleClick) } setUpDownClickBindings("main", gui.scrollUpMain, gui.scrollDownMain, gui.handleMainClick) @@ -532,14 +532,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { ViewName: panel.View().Name(), Key: '[', Modifier: gocui.ModNone, - Handler: wrappedHandler(panel.OnPrevContext), + Handler: wrappedHandler(panel.HandlePrevContext), Description: gui.Tr.PreviousContext, }, &Binding{ ViewName: panel.View().Name(), Key: ']', Modifier: gocui.ModNone, - Handler: wrappedHandler(panel.OnNextContext), + Handler: wrappedHandler(panel.HandleNextContext), Description: gui.Tr.NextContext, }, ) diff --git a/pkg/gui/list_panel.go b/pkg/gui/list_panel.go index ad970425..84ef6f13 100644 --- a/pkg/gui/list_panel.go +++ b/pkg/gui/list_panel.go @@ -12,31 +12,31 @@ import ( ) type ListPanel[T comparable] struct { - selectedIdx int - list *FilteredList[T] + SelectedIdx int + List *FilteredList[T] view *gocui.View } func (self *ListPanel[T]) setSelectedLineIdx(value int) { clampedValue := 0 - if self.list.Len() > 0 { - clampedValue = lcUtils.Clamp(value, 0, self.list.Len()-1) + if self.List.Len() > 0 { + clampedValue = lcUtils.Clamp(value, 0, self.List.Len()-1) } - self.selectedIdx = clampedValue + self.SelectedIdx = clampedValue } func (self *ListPanel[T]) clampSelectedLineIdx() { - clamped := lcUtils.Clamp(self.selectedIdx, 0, self.list.Len()-1) + clamped := lcUtils.Clamp(self.SelectedIdx, 0, self.List.Len()-1) - if clamped != self.selectedIdx { - self.selectedIdx = clamped + if clamped != self.SelectedIdx { + self.SelectedIdx = clamped } } // moves the cursor up or down by the given amount (up for negative values) func (self *ListPanel[T]) moveSelectedLine(delta int) { - self.setSelectedLineIdx(self.selectedIdx + delta) + self.setSelectedLineIdx(self.SelectedIdx + delta) } func (self *ListPanel[T]) SelectNextLine() { @@ -47,39 +47,42 @@ func (self *ListPanel[T]) SelectPrevLine() { self.moveSelectedLine(-1) } +type ContextState[T any] struct { + contextIdx int + // contexts []ContextConfig[T] + GetContexts func() []ContextConfig[T] + // this tells us whether we need to re-render to the main panel + GetContextCacheKey func(item T) string +} + // list panel at the side of the screen that renders content to the main panel type SideListPanel[T comparable] struct { - contextKeyPrefix string - contextIdx int - // contexts []ContextConfig[T] - getContexts func() []ContextConfig[T] - // this tells us whether we need to re-render to the main panel - getContextCacheKey func(item T) string + ContextState *ContextState[T] ListPanel[T] // message to render in the main view if there are no items in the panel // and it has focus. Leave empty if you don't want to render anything - noItemsMessage string + NoItemsMessage string gui IGui - // this filter is applied on top of additional default filters - filter func(T) bool - sort func(a, b T) bool + // this Filter is applied on top of additional default filters + Filter func(T) bool + Sort func(a, b T) bool - onClick func(T) error + OnClick func(T) error - getDisplayStrings func(T) []string + GetDisplayStrings func(T) []string // function to be called after re-rendering list. Can be nil - onRerender func() error + OnRerender func() error // set this to true if you don't want to allow manual filtering via '/' - disableFilter bool + DisableFilter bool // This can be nil if you want to always show the panel - hide func() bool + Hide func() bool } type ISideListPanel interface { @@ -90,11 +93,11 @@ type ISideListPanel interface { RerenderList() error IsFilterDisabled() bool IsHidden() bool - OnNextLine() error - OnPrevLine() error - OnClick() error - OnPrevContext() error - OnNextContext() error + HandleNextLine() error + HandlePrevLine() error + HandleClick() error + HandlePrevContext() error + HandleNextContext() error } var _ ISideListPanel = &SideListPanel[int]{} @@ -122,19 +125,19 @@ func (gui *Gui) intoInterface() IGui { return gui } -func (self *SideListPanel[T]) OnClick() error { - itemCount := self.list.Len() +func (self *SideListPanel[T]) HandleClick() error { + itemCount := self.List.Len() handleSelect := self.HandleSelect - selectedLine := &self.selectedIdx + selectedLine := &self.SelectedIdx if err := self.gui.HandleClick(self.view, itemCount, selectedLine, handleSelect); err != nil { return err } - if self.onClick != nil { + if self.OnClick != nil { selectedItem, err := self.GetSelectedItem() if err == nil { - return self.onClick(selectedItem) + return self.OnClick(selectedItem) } } @@ -148,12 +151,12 @@ func (self *SideListPanel[T]) View() *gocui.View { func (self *SideListPanel[T]) HandleSelect() error { item, err := self.GetSelectedItem() if err != nil { - if err.Error() != self.noItemsMessage { + if err.Error() != self.NoItemsMessage { return err } - if self.noItemsMessage != "" { - return self.gui.RenderStringMain(self.noItemsMessage) + if self.NoItemsMessage != "" { + return self.gui.RenderStringMain(self.NoItemsMessage) } return nil @@ -165,97 +168,119 @@ func (self *SideListPanel[T]) HandleSelect() error { } func (self *SideListPanel[T]) renderContext(item T) error { - contexts := self.getContexts() - - if len(contexts) == 0 { + if self.ContextState == nil { return nil } - key := self.contextKeyPrefix + "-" + self.getContextCacheKey(item) + "-" + contexts[self.contextIdx].key + key := self.ContextState.GetCurrentContextKey(item) if !self.gui.ShouldRefresh(key) { return nil } mainView := self.gui.GetMainView() - mainView.Tabs = self.GetContextTitles() - mainView.TabIndex = self.contextIdx + mainView.Tabs = self.ContextState.GetContextTitles() + mainView.TabIndex = self.ContextState.contextIdx - return contexts[self.contextIdx].render(item) + return self.ContextState.GetCurrentContext().render(item) } -func (self *SideListPanel[T]) GetContextTitles() []string { - return lo.Map(self.getContexts(), func(context ContextConfig[T], _ int) string { +func (self *ContextState[T]) GetContextTitles() []string { + return lo.Map(self.GetContexts(), func(context ContextConfig[T], _ int) string { return context.title }) } +func (self *ContextState[T]) GetCurrentContextKey(item T) string { + return self.GetContextCacheKey(item) + "-" + self.GetCurrentContext().key +} + +func (self *ContextState[T]) GetCurrentContext() ContextConfig[T] { + return self.GetContexts()[self.contextIdx] +} + func (self *SideListPanel[T]) GetSelectedItem() (T, error) { var zero T - item, ok := self.list.TryGet(self.selectedIdx) + item, ok := self.List.TryGet(self.SelectedIdx) if !ok { // could probably have a better error here - return zero, errors.New(self.noItemsMessage) + return zero, errors.New(self.NoItemsMessage) } return item, nil } -func (self *SideListPanel[T]) OnNextLine() error { +func (self *SideListPanel[T]) HandleNextLine() error { self.SelectNextLine() return self.HandleSelect() } -func (self *SideListPanel[T]) OnPrevLine() error { +func (self *SideListPanel[T]) HandlePrevLine() error { self.SelectPrevLine() return self.HandleSelect() } -func (self *SideListPanel[T]) OnNextContext() error { - contexts := self.getContexts() +func (self *ContextState[T]) HandleNextContext() { + contexts := self.GetContexts() if len(contexts) == 0 { - return nil + return } self.contextIdx = (self.contextIdx + 1) % len(contexts) +} + +func (self *ContextState[T]) HandlePrevContext() { + contexts := self.GetContexts() + + if len(contexts) == 0 { + return + } + + self.contextIdx = (self.contextIdx - 1 + len(contexts)) % len(contexts) +} + +func (self *SideListPanel[T]) HandleNextContext() error { + if self.ContextState == nil { + return nil + } + + self.ContextState.HandleNextContext() return self.HandleSelect() } -func (self *SideListPanel[T]) OnPrevContext() error { - contexts := self.getContexts() - - if len(contexts) == 0 { +func (self *SideListPanel[T]) HandlePrevContext() error { + if self.ContextState == nil { return nil } - self.contextIdx = (self.contextIdx - 1 + len(contexts)) % len(contexts) + self.ContextState.HandlePrevContext() return self.HandleSelect() } func (self *SideListPanel[T]) Refocus() { - self.gui.FocusY(self.selectedIdx, self.list.Len(), self.view) + self.gui.FocusY(self.SelectedIdx, self.List.Len(), self.view) } func (self *SideListPanel[T]) SetItems(items []T) { - self.list.SetItems(items) + self.List.SetItems(items) self.FilterAndSort() } func (self *SideListPanel[T]) FilterAndSort() { filterString := self.gui.FilterString(self.view) - self.list.Filter(func(item T, index int) bool { - if self.filter != nil && !self.filter(item) { + 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 { - return lo.SomeBy(self.getDisplayStrings(item), func(searchString string) bool { + return lo.SomeBy(self.GetDisplayStrings(item), func(searchString string) bool { return strings.Contains(searchString, ignore) }) }) { @@ -263,7 +288,7 @@ func (self *SideListPanel[T]) FilterAndSort() { } if filterString != "" { - return lo.SomeBy(self.getDisplayStrings(item), func(searchString string) bool { + return lo.SomeBy(self.GetDisplayStrings(item), func(searchString string) bool { return strings.Contains(searchString, filterString) }) } @@ -271,7 +296,7 @@ func (self *SideListPanel[T]) FilterAndSort() { return true }) - self.list.Sort(self.sort) + self.List.Sort(self.Sort) self.clampSelectedLineIdx() } @@ -281,8 +306,8 @@ func (self *SideListPanel[T]) RerenderList() error { self.gui.Update(func() error { self.view.Clear() - table := lo.Map(self.list.GetItems(), func(item T, index int) []string { - return self.getDisplayStrings(item) + table := lo.Map(self.List.GetItems(), func(item T, index int) []string { + return self.GetDisplayStrings(item) }) renderedTable, err := utils.RenderTable(table) if err != nil { @@ -290,8 +315,8 @@ func (self *SideListPanel[T]) RerenderList() error { } fmt.Fprint(self.view, renderedTable) - if self.onRerender != nil { - if err := self.onRerender(); err != nil { + if self.OnRerender != nil { + if err := self.OnRerender(); err != nil { return err } } @@ -306,17 +331,25 @@ func (self *SideListPanel[T]) RerenderList() error { } func (self *SideListPanel[T]) SetContextIndex(index int) { + if self.ContextState == nil { + return + } + + self.ContextState.SetContextIndex(index) +} + +func (self *ContextState[T]) SetContextIndex(index int) { self.contextIdx = index } func (self *SideListPanel[T]) IsFilterDisabled() bool { - return self.disableFilter + return self.DisableFilter } func (self *SideListPanel[T]) IsHidden() bool { - if self.hide == nil { + if self.Hide == nil { return false } - return self.hide() + return self.Hide() } diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index 13b2cb6e..63ade0ee 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -25,35 +25,24 @@ type CreateMenuOptions struct { func (gui *Gui) getMenuPanel() *SideListPanel[*MenuItem] { return &SideListPanel[*MenuItem]{ ListPanel: ListPanel[*MenuItem]{ - list: NewFilteredList[*MenuItem](), + List: NewFilteredList[*MenuItem](), view: gui.Views.Menu, }, - noItemsMessage: "", + NoItemsMessage: "", gui: gui.intoInterface(), - onClick: gui.onMenuPress, - sort: nil, - getDisplayStrings: func(menuItem *MenuItem) []string { + OnClick: gui.onMenuPress, + Sort: nil, + GetDisplayStrings: func(menuItem *MenuItem) []string { return menuItem.LabelColumns }, - onRerender: func() error { + OnRerender: func() error { return gui.resizePopupPanel(gui.Views.Menu) }, // so that we can avoid some UI trickiness, the menu will not have filtering // abillity yet. To support it, we would need to have filter state against // each panel (e.g. for when you filter the images panel, then bring up // the options menu, then try to filter that too. - disableFilter: true, - - // the menu panel doesn't actually have any contexts to display on the main view - // so what follows are all dummy values - contextKeyPrefix: "menu", - contextIdx: 0, - getContextCacheKey: func(menuItem *MenuItem) string { - return "" - }, - getContexts: func() []ContextConfig[*MenuItem] { - return []ContextConfig[*MenuItem]{} - }, + DisableFilter: true, } } diff --git a/pkg/gui/project_panel.go b/pkg/gui/project_panel.go index 17b5c9a9..6ba9ba28 100644 --- a/pkg/gui/project_panel.go +++ b/pkg/gui/project_panel.go @@ -17,54 +17,56 @@ import ( func (gui *Gui) getProjectPanel() *SideListPanel[*commands.Project] { return &SideListPanel[*commands.Project]{ - contextKeyPrefix: "project", - ListPanel: ListPanel[*commands.Project]{ - list: NewFilteredList[*commands.Project](), - view: gui.Views.Project, - }, - contextIdx: 0, - noItemsMessage: "", - gui: gui.intoInterface(), - getContexts: func() []ContextConfig[*commands.Project] { - if gui.DockerCommand.InDockerComposeProject { + ContextState: &ContextState[*commands.Project]{ + GetContexts: func() []ContextConfig[*commands.Project] { + if gui.DockerCommand.InDockerComposeProject { + return []ContextConfig[*commands.Project]{ + { + key: "logs", + title: gui.Tr.LogsTitle, + render: gui.renderAllLogs, + }, + { + key: "config", + title: gui.Tr.DockerComposeConfigTitle, + render: gui.renderDockerComposeConfig, + }, + { + key: "credits", + title: gui.Tr.CreditsTitle, + render: gui.renderCredits, + }, + } + } + return []ContextConfig[*commands.Project]{ - { - key: "logs", - title: gui.Tr.LogsTitle, - render: gui.renderAllLogs, - }, - { - key: "config", - title: gui.Tr.DockerComposeConfigTitle, - render: gui.renderDockerComposeConfig, - }, { key: "credits", title: gui.Tr.CreditsTitle, render: gui.renderCredits, }, } - } + }, + GetContextCacheKey: func(project *commands.Project) string { + return "projects-" + project.Name + }, + }, - return []ContextConfig[*commands.Project]{ - { - key: "credits", - title: gui.Tr.CreditsTitle, - render: gui.renderCredits, - }, - } + ListPanel: ListPanel[*commands.Project]{ + List: NewFilteredList[*commands.Project](), + view: gui.Views.Project, }, - getContextCacheKey: func(project *commands.Project) string { - return project.Name - }, - sort: func(a *commands.Project, b *commands.Project) bool { + NoItemsMessage: "", + gui: gui.intoInterface(), + + Sort: func(a *commands.Project, b *commands.Project) bool { return false }, - getDisplayStrings: func(project *commands.Project) []string { + GetDisplayStrings: func(project *commands.Project) []string { return []string{project.Name} }, // It doesn't make sense to filter a list of only one item. - disableFilter: true, + DisableFilter: true, } } @@ -76,7 +78,7 @@ func (gui *Gui) refreshProject() error { func (gui *Gui) getProjectName() string { projectName := path.Base(gui.Config.ProjectDir) if gui.DockerCommand.InDockerComposeProject { - for _, service := range gui.Panels.Services.list.GetAllItems() { + for _, service := range gui.Panels.Services.List.GetAllItems() { container := service.Container if container != nil && container.DetailsLoaded() { return container.Details.Config.Labels["com.docker.compose.project"] diff --git a/pkg/gui/services_panel.go b/pkg/gui/services_panel.go index 47d099e3..2bb06f6e 100644 --- a/pkg/gui/services_panel.go +++ b/pkg/gui/services_panel.go @@ -14,52 +14,52 @@ import ( func (gui *Gui) getServicesPanel() *SideListPanel[*commands.Service] { return &SideListPanel[*commands.Service]{ - contextKeyPrefix: "services", + ContextState: &ContextState[*commands.Service]{ + GetContexts: func() []ContextConfig[*commands.Service] { + return []ContextConfig[*commands.Service]{ + { + key: "logs", + title: gui.Tr.LogsTitle, + render: gui.renderServiceLogs, + }, + { + key: "stats", + title: gui.Tr.StatsTitle, + render: gui.renderServiceStats, + }, + { + key: "container-env", + title: gui.Tr.ContainerEnvTitle, + render: gui.renderServiceContainerEnv, + }, + { + key: "container-config", + title: gui.Tr.ContainerConfigTitle, + render: gui.renderServiceContainerConfig, + }, + { + key: "top", + title: gui.Tr.TopTitle, + render: gui.renderServiceTop, + }, + } + }, + GetContextCacheKey: func(service *commands.Service) string { + if service.Container == nil { + return "services-" + service.ID + } + return "services-" + service.ID + "-" + service.Container.ID + "-" + service.Container.Container.State + }, + }, ListPanel: ListPanel[*commands.Service]{ - list: NewFilteredList[*commands.Service](), + List: NewFilteredList[*commands.Service](), view: gui.Views.Services, }, - contextIdx: 0, // TODO: i18n - noItemsMessage: "no service selected", + NoItemsMessage: "no service selected", gui: gui.intoInterface(), - getContexts: func() []ContextConfig[*commands.Service] { - return []ContextConfig[*commands.Service]{ - { - key: "logs", - title: gui.Tr.LogsTitle, - render: gui.renderServiceLogs, - }, - { - key: "stats", - title: gui.Tr.StatsTitle, - render: gui.renderServiceStats, - }, - { - key: "container-env", - title: gui.Tr.ContainerEnvTitle, - render: gui.renderServiceContainerEnv, - }, - { - key: "container-config", - title: gui.Tr.ContainerConfigTitle, - render: gui.renderServiceContainerConfig, - }, - { - key: "top", - title: gui.Tr.TopTitle, - render: gui.renderServiceTop, - }, - } - }, - getContextCacheKey: func(service *commands.Service) string { - if service.Container == nil { - return service.ID - } - return service.ID + "-" + service.Container.ID + "-" + service.Container.Container.State - }, // sort services first by whether they have a linked container, and second by alphabetical order - sort: func(a *commands.Service, b *commands.Service) bool { + Sort: func(a *commands.Service, b *commands.Service) bool { if a.Container != nil && b.Container == nil { return true } @@ -70,7 +70,7 @@ func (gui *Gui) getServicesPanel() *SideListPanel[*commands.Service] { return a.Name < b.Name }, - getDisplayStrings: func(service *commands.Service) []string { + GetDisplayStrings: func(service *commands.Service) []string { if service.Container == nil { return []string{ utils.ColoredString("none", color.FgBlue), @@ -90,7 +90,7 @@ func (gui *Gui) getServicesPanel() *SideListPanel[*commands.Service] { utils.ColoredString(cont.DisplayPorts(), color.FgYellow), } }, - hide: func() bool { + Hide: func() bool { return !gui.DockerCommand.InDockerComposeProject }, } diff --git a/pkg/gui/volumes_panel.go b/pkg/gui/volumes_panel.go index 4ce711e2..bc70d18f 100644 --- a/pkg/gui/volumes_panel.go +++ b/pkg/gui/volumes_panel.go @@ -13,30 +13,30 @@ import ( func (gui *Gui) getVolumesPanel() *SideListPanel[*commands.Volume] { return &SideListPanel[*commands.Volume]{ - contextKeyPrefix: "volumes", + ContextState: &ContextState[*commands.Volume]{ + GetContexts: func() []ContextConfig[*commands.Volume] { + return []ContextConfig[*commands.Volume]{ + { + key: "config", + title: gui.Tr.ConfigTitle, + render: gui.renderVolumeConfig, + }, + } + }, + GetContextCacheKey: func(volume *commands.Volume) string { + return "volumes-" + volume.Name + }, + }, ListPanel: ListPanel[*commands.Volume]{ - list: NewFilteredList[*commands.Volume](), + List: NewFilteredList[*commands.Volume](), view: gui.Views.Volumes, }, - contextIdx: 0, - noItemsMessage: gui.Tr.NoVolumes, + NoItemsMessage: gui.Tr.NoVolumes, gui: gui.intoInterface(), - getContexts: func() []ContextConfig[*commands.Volume] { - return []ContextConfig[*commands.Volume]{ - { - key: "config", - title: gui.Tr.ConfigTitle, - render: gui.renderVolumeConfig, - }, - } - }, - getContextCacheKey: func(volume *commands.Volume) string { - return volume.Name - }, // we're sorting these volumes based on whether they have labels defined, // because those are the ones you typically care about. // Within that, we also sort them alphabetically - sort: func(a *commands.Volume, b *commands.Volume) bool { + Sort: func(a *commands.Volume, b *commands.Volume) bool { if len(a.Volume.Labels) == 0 && len(b.Volume.Labels) > 0 { return false } @@ -45,7 +45,7 @@ func (gui *Gui) getVolumesPanel() *SideListPanel[*commands.Volume] { } return a.Name < b.Name }, - getDisplayStrings: func(volume *commands.Volume) []string { + GetDisplayStrings: func(volume *commands.Volume) []string { return []string{volume.Volume.Driver, volume.Name} }, }