move context stuff into its own struct

This commit is contained in:
Jesse Duffield 2022-10-29 09:50:47 -07:00
parent c6d424e4a4
commit 5d498c796a
9 changed files with 281 additions and 257 deletions

View file

@ -22,58 +22,58 @@ func (gui *Gui) getContainersPanel() *SideListPanel[*commands.Container] {
return true 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 service.Name == container.ServiceName
}) })
} }
return &SideListPanel[*commands.Container]{ 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]{ ListPanel: ListPanel[*commands.Container]{
list: NewFilteredList[*commands.Container](), List: NewFilteredList[*commands.Container](),
view: gui.Views.Containers, view: gui.Views.Containers,
}, },
contextIdx: 0, NoItemsMessage: gui.Tr.NoContainers,
noItemsMessage: gui.Tr.NoContainers,
gui: gui.intoInterface(), 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) // 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 // 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) 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 // 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, // 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. // so we will need to keep an eye on this.
@ -87,7 +87,7 @@ func (gui *Gui) getContainersPanel() *SideListPanel[*commands.Container] {
return true return true
}, },
getDisplayStrings: func(container *commands.Container) []string { GetDisplayStrings: func(container *commands.Container) []string {
image := strings.TrimPrefix(container.Container.Image, "sha256:") image := strings.TrimPrefix(container.Container.Image, "sha256:")
return []string{ 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 // 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 originalSelectedLineIdx := gui.Panels.Services.SelectedIdx
selectedService, isServiceSelected := gui.Panels.Services.list.TryGet(originalSelectedLineIdx) selectedService, isServiceSelected := gui.Panels.Services.List.TryGet(originalSelectedLineIdx)
containers, 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(), gui.Panels.Containers.List.GetAllItems(),
) )
if err != nil { if err != nil {
return err return err
@ -277,7 +277,7 @@ func (gui *Gui) refreshContainersAndServices() error {
// see if our selected service has moved // see if our selected service has moved
if isServiceSelected { 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 service.ID == selectedService.ID {
if i == originalSelectedLineIdx { if i == originalSelectedLineIdx {
break break
@ -484,7 +484,7 @@ 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.Panels.Containers.list.GetAllItems() { for _, container := range gui.Panels.Containers.List.GetAllItems() {
if err := container.Stop(); err != nil { if err := container.Stop(); err != nil {
gui.Log.Error(err) gui.Log.Error(err)
} }
@ -498,7 +498,7 @@ 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.Panels.Containers.list.GetAllItems() { for _, container := range gui.Panels.Containers.List.GetAllItems() {
if err := container.Remove(types.ContainerRemoveOptions{Force: true}); err != nil { if err := container.Remove(types.ContainerRemoveOptions{Force: true}); err != nil {
gui.Log.Error(err) gui.Log.Error(err)
} }

View file

@ -269,7 +269,7 @@ func (gui *Gui) Run() error {
} }
func (gui *Gui) updateContainerDetails() 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() { func (gui *Gui) refresh() {
@ -455,7 +455,7 @@ func (gui *Gui) monitorContainerStats(ctx context.Context) {
case <-ctx.Done(): case <-ctx.Done():
return return
case <-ticker.C: case <-ticker.C:
for _, container := range gui.Panels.Containers.list.GetAllItems() { for _, container := range gui.Panels.Containers.List.GetAllItems() {
if !container.MonitoringStats { if !container.MonitoringStats {
go gui.DockerCommand.CreateClientStatMonitor(container) go gui.DockerCommand.CreateClientStatMonitor(container)
} }

View file

@ -18,29 +18,29 @@ func (gui *Gui) getImagesPanel() *SideListPanel[*commands.Image] {
noneLabel := "<none>" noneLabel := "<none>"
return &SideListPanel[*commands.Image]{ 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]{ ListPanel: ListPanel[*commands.Image]{
list: NewFilteredList[*commands.Image](), List: NewFilteredList[*commands.Image](),
view: gui.Views.Images, view: gui.Views.Images,
}, },
contextIdx: 0, NoItemsMessage: gui.Tr.NoImages,
noItemsMessage: gui.Tr.NoImages,
gui: gui.intoInterface(), gui: gui.intoInterface(),
getContexts: func() []ContextConfig[*commands.Image] { Sort: func(a *commands.Image, b *commands.Image) bool {
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 {
if a.Name == noneLabel && b.Name != noneLabel { if a.Name == noneLabel && b.Name != noneLabel {
return false return false
} }
@ -59,7 +59,7 @@ func (gui *Gui) getImagesPanel() *SideListPanel[*commands.Image] {
return a.ID < b.ID return a.ID < b.ID
}, },
getDisplayStrings: func(image *commands.Image) []string { GetDisplayStrings: func(image *commands.Image) []string {
return []string{ return []string{
image.Name, image.Name,
image.Tag, image.Tag,

View file

@ -514,7 +514,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
} }
for _, panel := range gui.allListPanels() { 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) setUpDownClickBindings("main", gui.scrollUpMain, gui.scrollDownMain, gui.handleMainClick)
@ -532,14 +532,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
ViewName: panel.View().Name(), ViewName: panel.View().Name(),
Key: '[', Key: '[',
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: wrappedHandler(panel.OnPrevContext), Handler: wrappedHandler(panel.HandlePrevContext),
Description: gui.Tr.PreviousContext, Description: gui.Tr.PreviousContext,
}, },
&Binding{ &Binding{
ViewName: panel.View().Name(), ViewName: panel.View().Name(),
Key: ']', Key: ']',
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: wrappedHandler(panel.OnNextContext), Handler: wrappedHandler(panel.HandleNextContext),
Description: gui.Tr.NextContext, Description: gui.Tr.NextContext,
}, },
) )

View file

@ -12,31 +12,31 @@ import (
) )
type ListPanel[T comparable] struct { type ListPanel[T comparable] struct {
selectedIdx int SelectedIdx int
list *FilteredList[T] List *FilteredList[T]
view *gocui.View view *gocui.View
} }
func (self *ListPanel[T]) setSelectedLineIdx(value int) { func (self *ListPanel[T]) setSelectedLineIdx(value int) {
clampedValue := 0 clampedValue := 0
if self.list.Len() > 0 { if self.List.Len() > 0 {
clampedValue = lcUtils.Clamp(value, 0, self.list.Len()-1) clampedValue = lcUtils.Clamp(value, 0, self.List.Len()-1)
} }
self.selectedIdx = clampedValue self.SelectedIdx = clampedValue
} }
func (self *ListPanel[T]) clampSelectedLineIdx() { 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 { if clamped != self.SelectedIdx {
self.selectedIdx = clamped self.SelectedIdx = clamped
} }
} }
// moves the cursor up or down by the given amount (up for negative values) // moves the cursor up or down by the given amount (up for negative values)
func (self *ListPanel[T]) moveSelectedLine(delta int) { func (self *ListPanel[T]) moveSelectedLine(delta int) {
self.setSelectedLineIdx(self.selectedIdx + delta) self.setSelectedLineIdx(self.SelectedIdx + delta)
} }
func (self *ListPanel[T]) SelectNextLine() { func (self *ListPanel[T]) SelectNextLine() {
@ -47,39 +47,42 @@ func (self *ListPanel[T]) SelectPrevLine() {
self.moveSelectedLine(-1) 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 // list panel at the side of the screen that renders content to the main panel
type SideListPanel[T comparable] struct { type SideListPanel[T comparable] struct {
contextKeyPrefix string ContextState *ContextState[T]
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
ListPanel[T] ListPanel[T]
// message to render in the main view if there are no items in the panel // 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 // and it has focus. Leave empty if you don't want to render anything
noItemsMessage string NoItemsMessage string
gui IGui gui IGui
// this filter is applied on top of additional default filters // this Filter is applied on top of additional default filters
filter func(T) bool Filter func(T) bool
sort func(a, b 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 // 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 '/' // 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 // This can be nil if you want to always show the panel
hide func() bool Hide func() bool
} }
type ISideListPanel interface { type ISideListPanel interface {
@ -90,11 +93,11 @@ type ISideListPanel interface {
RerenderList() error RerenderList() error
IsFilterDisabled() bool IsFilterDisabled() bool
IsHidden() bool IsHidden() bool
OnNextLine() error HandleNextLine() error
OnPrevLine() error HandlePrevLine() error
OnClick() error HandleClick() error
OnPrevContext() error HandlePrevContext() error
OnNextContext() error HandleNextContext() error
} }
var _ ISideListPanel = &SideListPanel[int]{} var _ ISideListPanel = &SideListPanel[int]{}
@ -122,19 +125,19 @@ func (gui *Gui) intoInterface() IGui {
return gui return gui
} }
func (self *SideListPanel[T]) OnClick() error { func (self *SideListPanel[T]) HandleClick() error {
itemCount := self.list.Len() itemCount := self.List.Len()
handleSelect := self.HandleSelect handleSelect := self.HandleSelect
selectedLine := &self.selectedIdx selectedLine := &self.SelectedIdx
if err := self.gui.HandleClick(self.view, itemCount, selectedLine, handleSelect); err != nil { if err := self.gui.HandleClick(self.view, itemCount, selectedLine, handleSelect); err != nil {
return err return err
} }
if self.onClick != nil { if self.OnClick != nil {
selectedItem, err := self.GetSelectedItem() selectedItem, err := self.GetSelectedItem()
if err == nil { 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 { func (self *SideListPanel[T]) HandleSelect() error {
item, err := self.GetSelectedItem() item, err := self.GetSelectedItem()
if err != nil { if err != nil {
if err.Error() != self.noItemsMessage { if err.Error() != self.NoItemsMessage {
return err return err
} }
if self.noItemsMessage != "" { if self.NoItemsMessage != "" {
return self.gui.RenderStringMain(self.noItemsMessage) return self.gui.RenderStringMain(self.NoItemsMessage)
} }
return nil return nil
@ -165,97 +168,119 @@ func (self *SideListPanel[T]) HandleSelect() error {
} }
func (self *SideListPanel[T]) renderContext(item T) error { func (self *SideListPanel[T]) renderContext(item T) error {
contexts := self.getContexts() if self.ContextState == nil {
if len(contexts) == 0 {
return nil return nil
} }
key := self.contextKeyPrefix + "-" + self.getContextCacheKey(item) + "-" + contexts[self.contextIdx].key key := self.ContextState.GetCurrentContextKey(item)
if !self.gui.ShouldRefresh(key) { if !self.gui.ShouldRefresh(key) {
return nil return nil
} }
mainView := self.gui.GetMainView() mainView := self.gui.GetMainView()
mainView.Tabs = self.GetContextTitles() mainView.Tabs = self.ContextState.GetContextTitles()
mainView.TabIndex = self.contextIdx mainView.TabIndex = self.ContextState.contextIdx
return contexts[self.contextIdx].render(item) return self.ContextState.GetCurrentContext().render(item)
} }
func (self *SideListPanel[T]) GetContextTitles() []string { func (self *ContextState[T]) GetContextTitles() []string {
return lo.Map(self.getContexts(), func(context ContextConfig[T], _ int) string { return lo.Map(self.GetContexts(), func(context ContextConfig[T], _ int) string {
return context.title 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) { func (self *SideListPanel[T]) GetSelectedItem() (T, error) {
var zero T var zero T
item, ok := self.list.TryGet(self.selectedIdx) item, ok := self.List.TryGet(self.SelectedIdx)
if !ok { if !ok {
// could probably have a better error here // could probably have a better error here
return zero, errors.New(self.noItemsMessage) return zero, errors.New(self.NoItemsMessage)
} }
return item, nil return item, nil
} }
func (self *SideListPanel[T]) OnNextLine() error { func (self *SideListPanel[T]) HandleNextLine() error {
self.SelectNextLine() self.SelectNextLine()
return self.HandleSelect() return self.HandleSelect()
} }
func (self *SideListPanel[T]) OnPrevLine() error { func (self *SideListPanel[T]) HandlePrevLine() error {
self.SelectPrevLine() self.SelectPrevLine()
return self.HandleSelect() return self.HandleSelect()
} }
func (self *SideListPanel[T]) OnNextContext() error { func (self *ContextState[T]) HandleNextContext() {
contexts := self.getContexts() contexts := self.GetContexts()
if len(contexts) == 0 { if len(contexts) == 0 {
return nil return
} }
self.contextIdx = (self.contextIdx + 1) % len(contexts) 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() return self.HandleSelect()
} }
func (self *SideListPanel[T]) OnPrevContext() error { func (self *SideListPanel[T]) HandlePrevContext() error {
contexts := self.getContexts() if self.ContextState == nil {
if len(contexts) == 0 {
return nil return nil
} }
self.contextIdx = (self.contextIdx - 1 + len(contexts)) % len(contexts) self.ContextState.HandlePrevContext()
return self.HandleSelect() return self.HandleSelect()
} }
func (self *SideListPanel[T]) Refocus() { 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) { func (self *SideListPanel[T]) SetItems(items []T) {
self.list.SetItems(items) self.List.SetItems(items)
self.FilterAndSort() self.FilterAndSort()
} }
func (self *SideListPanel[T]) FilterAndSort() { 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) { if self.Filter != nil && !self.Filter(item) {
return false 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.getDisplayStrings(item), func(searchString string) bool { return lo.SomeBy(self.GetDisplayStrings(item), func(searchString string) bool {
return strings.Contains(searchString, ignore) return strings.Contains(searchString, ignore)
}) })
}) { }) {
@ -263,7 +288,7 @@ func (self *SideListPanel[T]) FilterAndSort() {
} }
if filterString != "" { 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) return strings.Contains(searchString, filterString)
}) })
} }
@ -271,7 +296,7 @@ func (self *SideListPanel[T]) FilterAndSort() {
return true return true
}) })
self.list.Sort(self.sort) self.List.Sort(self.Sort)
self.clampSelectedLineIdx() self.clampSelectedLineIdx()
} }
@ -281,8 +306,8 @@ func (self *SideListPanel[T]) RerenderList() error {
self.gui.Update(func() error { self.gui.Update(func() error {
self.view.Clear() self.view.Clear()
table := lo.Map(self.list.GetItems(), func(item T, index int) []string { table := lo.Map(self.List.GetItems(), func(item T, index int) []string {
return self.getDisplayStrings(item) return self.GetDisplayStrings(item)
}) })
renderedTable, err := utils.RenderTable(table) renderedTable, err := utils.RenderTable(table)
if err != nil { if err != nil {
@ -290,8 +315,8 @@ func (self *SideListPanel[T]) RerenderList() error {
} }
fmt.Fprint(self.view, renderedTable) fmt.Fprint(self.view, renderedTable)
if self.onRerender != nil { if self.OnRerender != nil {
if err := self.onRerender(); err != nil { if err := self.OnRerender(); err != nil {
return err return err
} }
} }
@ -306,17 +331,25 @@ func (self *SideListPanel[T]) RerenderList() error {
} }
func (self *SideListPanel[T]) SetContextIndex(index int) { 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 self.contextIdx = index
} }
func (self *SideListPanel[T]) IsFilterDisabled() bool { func (self *SideListPanel[T]) IsFilterDisabled() bool {
return self.disableFilter return self.DisableFilter
} }
func (self *SideListPanel[T]) IsHidden() bool { func (self *SideListPanel[T]) IsHidden() bool {
if self.hide == nil { if self.Hide == nil {
return false return false
} }
return self.hide() return self.Hide()
} }

View file

@ -25,35 +25,24 @@ type CreateMenuOptions struct {
func (gui *Gui) getMenuPanel() *SideListPanel[*MenuItem] { func (gui *Gui) getMenuPanel() *SideListPanel[*MenuItem] {
return &SideListPanel[*MenuItem]{ return &SideListPanel[*MenuItem]{
ListPanel: ListPanel[*MenuItem]{ ListPanel: ListPanel[*MenuItem]{
list: NewFilteredList[*MenuItem](), List: NewFilteredList[*MenuItem](),
view: gui.Views.Menu, view: gui.Views.Menu,
}, },
noItemsMessage: "", NoItemsMessage: "",
gui: gui.intoInterface(), gui: gui.intoInterface(),
onClick: gui.onMenuPress, OnClick: gui.onMenuPress,
sort: nil, Sort: nil,
getDisplayStrings: func(menuItem *MenuItem) []string { GetDisplayStrings: func(menuItem *MenuItem) []string {
return menuItem.LabelColumns return menuItem.LabelColumns
}, },
onRerender: func() error { OnRerender: func() error {
return gui.resizePopupPanel(gui.Views.Menu) return gui.resizePopupPanel(gui.Views.Menu)
}, },
// so that we can avoid some UI trickiness, the menu will not have filtering // 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 // 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 // each panel (e.g. for when you filter the images panel, then bring up
// the options menu, then try to filter that too. // the options menu, then try to filter that too.
disableFilter: true, 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]{}
},
} }
} }

View file

@ -17,54 +17,56 @@ import (
func (gui *Gui) getProjectPanel() *SideListPanel[*commands.Project] { func (gui *Gui) getProjectPanel() *SideListPanel[*commands.Project] {
return &SideListPanel[*commands.Project]{ return &SideListPanel[*commands.Project]{
contextKeyPrefix: "project", ContextState: &ContextState[*commands.Project]{
ListPanel: ListPanel[*commands.Project]{ GetContexts: func() []ContextConfig[*commands.Project] {
list: NewFilteredList[*commands.Project](), if gui.DockerCommand.InDockerComposeProject {
view: gui.Views.Project, return []ContextConfig[*commands.Project]{
}, {
contextIdx: 0, key: "logs",
noItemsMessage: "", title: gui.Tr.LogsTitle,
gui: gui.intoInterface(), render: gui.renderAllLogs,
getContexts: func() []ContextConfig[*commands.Project] { },
if gui.DockerCommand.InDockerComposeProject { {
key: "config",
title: gui.Tr.DockerComposeConfigTitle,
render: gui.renderDockerComposeConfig,
},
{
key: "credits",
title: gui.Tr.CreditsTitle,
render: gui.renderCredits,
},
}
}
return []ContextConfig[*commands.Project]{ return []ContextConfig[*commands.Project]{
{
key: "logs",
title: gui.Tr.LogsTitle,
render: gui.renderAllLogs,
},
{
key: "config",
title: gui.Tr.DockerComposeConfigTitle,
render: gui.renderDockerComposeConfig,
},
{ {
key: "credits", key: "credits",
title: gui.Tr.CreditsTitle, title: gui.Tr.CreditsTitle,
render: gui.renderCredits, render: gui.renderCredits,
}, },
} }
} },
GetContextCacheKey: func(project *commands.Project) string {
return "projects-" + project.Name
},
},
return []ContextConfig[*commands.Project]{ ListPanel: ListPanel[*commands.Project]{
{ List: NewFilteredList[*commands.Project](),
key: "credits", view: gui.Views.Project,
title: gui.Tr.CreditsTitle,
render: gui.renderCredits,
},
}
}, },
getContextCacheKey: func(project *commands.Project) string { NoItemsMessage: "",
return project.Name gui: gui.intoInterface(),
},
sort: func(a *commands.Project, b *commands.Project) bool { Sort: func(a *commands.Project, b *commands.Project) bool {
return false return false
}, },
getDisplayStrings: func(project *commands.Project) []string { GetDisplayStrings: func(project *commands.Project) []string {
return []string{project.Name} return []string{project.Name}
}, },
// It doesn't make sense to filter a list of only one item. // 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 { func (gui *Gui) getProjectName() string {
projectName := path.Base(gui.Config.ProjectDir) projectName := path.Base(gui.Config.ProjectDir)
if gui.DockerCommand.InDockerComposeProject { if gui.DockerCommand.InDockerComposeProject {
for _, service := range gui.Panels.Services.list.GetAllItems() { for _, service := range gui.Panels.Services.List.GetAllItems() {
container := service.Container container := service.Container
if container != nil && container.DetailsLoaded() { if container != nil && container.DetailsLoaded() {
return container.Details.Config.Labels["com.docker.compose.project"] return container.Details.Config.Labels["com.docker.compose.project"]

View file

@ -14,52 +14,52 @@ import (
func (gui *Gui) getServicesPanel() *SideListPanel[*commands.Service] { func (gui *Gui) getServicesPanel() *SideListPanel[*commands.Service] {
return &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]{ ListPanel: ListPanel[*commands.Service]{
list: NewFilteredList[*commands.Service](), List: NewFilteredList[*commands.Service](),
view: gui.Views.Services, view: gui.Views.Services,
}, },
contextIdx: 0,
// TODO: i18n // TODO: i18n
noItemsMessage: "no service selected", NoItemsMessage: "no service selected",
gui: gui.intoInterface(), 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 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 { if a.Container != nil && b.Container == nil {
return true return true
} }
@ -70,7 +70,7 @@ func (gui *Gui) getServicesPanel() *SideListPanel[*commands.Service] {
return a.Name < b.Name return a.Name < b.Name
}, },
getDisplayStrings: func(service *commands.Service) []string { GetDisplayStrings: func(service *commands.Service) []string {
if service.Container == nil { if service.Container == nil {
return []string{ return []string{
utils.ColoredString("none", color.FgBlue), utils.ColoredString("none", color.FgBlue),
@ -90,7 +90,7 @@ func (gui *Gui) getServicesPanel() *SideListPanel[*commands.Service] {
utils.ColoredString(cont.DisplayPorts(), color.FgYellow), utils.ColoredString(cont.DisplayPorts(), color.FgYellow),
} }
}, },
hide: func() bool { Hide: func() bool {
return !gui.DockerCommand.InDockerComposeProject return !gui.DockerCommand.InDockerComposeProject
}, },
} }

View file

@ -13,30 +13,30 @@ import (
func (gui *Gui) getVolumesPanel() *SideListPanel[*commands.Volume] { func (gui *Gui) getVolumesPanel() *SideListPanel[*commands.Volume] {
return &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]{ ListPanel: ListPanel[*commands.Volume]{
list: NewFilteredList[*commands.Volume](), List: NewFilteredList[*commands.Volume](),
view: gui.Views.Volumes, view: gui.Views.Volumes,
}, },
contextIdx: 0, NoItemsMessage: gui.Tr.NoVolumes,
noItemsMessage: gui.Tr.NoVolumes,
gui: gui.intoInterface(), 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, // we're sorting these volumes based on whether they have labels defined,
// because those are the ones you typically care about. // because those are the ones you typically care about.
// Within that, we also sort them alphabetically // 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 { if len(a.Volume.Labels) == 0 && len(b.Volume.Labels) > 0 {
return false return false
} }
@ -45,7 +45,7 @@ func (gui *Gui) getVolumesPanel() *SideListPanel[*commands.Volume] {
} }
return a.Name < b.Name return a.Name < b.Name
}, },
getDisplayStrings: func(volume *commands.Volume) []string { GetDisplayStrings: func(volume *commands.Volume) []string {
return []string{volume.Volume.Driver, volume.Name} return []string{volume.Volume.Driver, volume.Name}
}, },
} }