wip on projects

This commit is contained in:
Clément PÉAU 2025-05-12 10:36:19 +02:00
parent 099ae97e68
commit 01f9b3973f
7 changed files with 149 additions and 48 deletions

View file

@ -41,17 +41,30 @@ func NewApp(config *config.AppConfig) (*App, error) {
}
app.OSCommand = commands.NewOSCommand(app.Log, config)
// here is the place to make use of the docker-compose.yml file in the current directory
app.DockerCommand, err = commands.NewDockerCommand(app.Log, app.OSCommand, app.Tr, app.Config, app.ErrorChan)
if err != nil {
return app, err
}
app.closers = append(app.closers, app.DockerCommand)
app.Gui, err = gui.NewGui(app.Log, app.DockerCommand, app.OSCommand, app.Tr, config, app.ErrorChan)
if err != nil {
return app, err
}
// here is the place to make use of the docker-compose.yml file in the current directory
err = app.OSCommand.RunCommand(
utils.ApplyTemplate(
config.UserConfig.CommandTemplates.CheckDockerComposeConfig,
app.DockerCommand.NewCommandObject(commands.CommandObject{}),
),
)
if err != nil {
app.DockerCommand.InDockerComposeProject = false
app.Log.Warn(err.Error())
} else {
app.Gui.State.Project = &commands.Project{Name: app.Gui.GetProjectName()}
}
return app, nil
}

View file

@ -34,15 +34,16 @@ const (
// DockerCommand is our main docker interface
type DockerCommand struct {
Log *logrus.Entry
OSCommand *OSCommand
Tr *i18n.TranslationSet
Config *config.AppConfig
Client *client.Client
InDockerComposeProject bool
ErrorChan chan error
ContainerMutex deadlock.Mutex
ServiceMutex deadlock.Mutex
Log *logrus.Entry
OSCommand *OSCommand
Tr *i18n.TranslationSet
Config *config.AppConfig
Client *client.Client
InDockerComposeProject bool
CurrentDockerComposeProject string
ErrorChan chan error
ContainerMutex deadlock.Mutex
ServiceMutex deadlock.Mutex
Closers []io.Closer
}
@ -112,17 +113,6 @@ func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Translat
dockerCommand.setDockerComposeCommand(config)
err = osCommand.RunCommand(
utils.ApplyTemplate(
config.UserConfig.CommandTemplates.CheckDockerComposeConfig,
dockerCommand.NewCommandObject(CommandObject{}),
),
)
if err != nil {
dockerCommand.InDockerComposeProject = false
log.Warn(err.Error())
}
return dockerCommand, nil
}
@ -176,7 +166,7 @@ func (c *DockerCommand) CreateClientStatMonitor(container *Container) {
container.MonitoringStats = false
}
func (c *DockerCommand) RefreshContainersAndServices(currentServices []*Service, currentContainers []*Container) ([]*Container, []*Service, error) {
func (c *DockerCommand) RefreshContainersAndServices(currentServices []*Service, currentContainers []*Container, currentProject *Project) ([]*Container, []*Service, error) {
c.ServiceMutex.Lock()
defer c.ServiceMutex.Unlock()
@ -186,17 +176,15 @@ func (c *DockerCommand) RefreshContainersAndServices(currentServices []*Service,
}
var services []*Service
// we only need to get these services once because they won't change in the runtime of the program
if currentServices != nil {
services = currentServices
} else {
services, err = c.GetServices()
if currentProject != nil {
services, err = c.GetServicesFromContainers(containers, currentProject)
if err != nil {
return nil, nil, err
}
}
c.assignContainersToServices(containers, services)
c.assignContainersToServices(containers, services)
//c.assignContainersToProjects(containers)
}
return containers, services, nil
}
@ -214,6 +202,11 @@ L:
}
}
// GetDockerProjects
func (c *DockerCommand) GetDockerProjects() ([]*Project, error) {
return []*Project{}, nil
}
// GetContainers gets the docker containers
func (c *DockerCommand) GetContainers(existingContainers []*Container) ([]*Container, error) {
c.ContainerMutex.Lock()
@ -269,6 +262,42 @@ func (c *DockerCommand) GetContainers(existingContainers []*Container) ([]*Conta
return ownContainers, nil
}
// GetServicesFromContainers gets services
func (c *DockerCommand) GetServicesFromContainers(containers []*Container, currentProject *Project) ([]*Service, error) {
var services []*Service
if currentProject.Name == "" {
return services, nil
}
for _, cont := range containers {
if cont.ProjectName != currentProject.Name {
continue
}
service := &Service{
Name: cont.Name,
ID: cont.ID,
OSCommand: c.OSCommand,
Log: c.Log,
DockerCommand: c,
}
services = append(services, service)
}
//TODO: Should be run once every time the project changes
if len(services) == 0 {
//If no services are found in running containers fetch services from dockerCompose
var err error
services, err = c.GetServices()
if err != nil {
return nil, err
}
}
return services, nil
}
// GetServices gets services
func (c *DockerCommand) GetServices() ([]*Service, error) {
if !c.InDockerComposeProject {
@ -276,6 +305,7 @@ func (c *DockerCommand) GetServices() ([]*Service, error) {
}
composeCommand := c.Config.UserConfig.CommandTemplates.DockerCompose
// TODO: Handle remote docker compose files
output, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("%s config --services", composeCommand))
if err != nil {
return nil, err

View file

@ -258,6 +258,7 @@ func (gui *Gui) refreshContainersAndServices() error {
containers, services, err := gui.DockerCommand.RefreshContainersAndServices(
gui.Panels.Services.List.GetAllItems(),
gui.Panels.Containers.List.GetAllItems(),
gui.State.Project,
)
if err != nil {
return err

View file

@ -87,8 +87,15 @@ type guiState struct {
// Maintains the state of manual filtering i.e. typing in a substring
// to filter on in the current panel.
Filter filterState
// Project used for navigation in projects
Project *commands.Project
}
//type projectState struct {
// name string
//}
type filterState struct {
// If true then we're either currently inside the filter view
// or we've committed the filter and we're back in the list view
@ -297,7 +304,7 @@ func (gui *Gui) updateContainerDetails() error {
func (gui *Gui) refresh() {
go func() {
if err := gui.refreshProject(); err != nil {
if err := gui.refreshProjects(); err != nil {
gui.Log.Error(err)
}
}()

View file

@ -135,6 +135,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleEditConfig,
Description: gui.Tr.EditConfig,
},
{
ViewName: "project",
Key: gocui.KeyCtrlP,
Modifier: gocui.ModNone,
Handler: gui.handleCreateProjectMenu,
Description: gui.Tr.SwitchProject,
},
{
ViewName: "project",
Key: 'o',

View file

@ -3,6 +3,9 @@ package gui
import (
"bytes"
"context"
"github.com/docker/docker/api/types/container"
"github.com/jesseduffield/lazydocker/pkg/gui/types"
"log"
"path"
"strings"
@ -60,35 +63,51 @@ func (gui *Gui) getProjectPanel() *panels.SideListPanel[*commands.Project] {
List: panels.NewFilteredList[*commands.Project](),
View: gui.Views.Project,
},
NoItemsMessage: "",
NoItemsMessage: "No docker compose projects found.",
Gui: gui.intoInterface(),
Sort: func(a *commands.Project, b *commands.Project) bool {
return false
return (gui.State.Project != nil && gui.State.Project.Name == a.Name) || a.Name < b.Name
},
GetTableCells: presentation.GetProjectDisplayStrings,
// It doesn't make sense to filter a list of only one item.
DisableFilter: true,
}
}
func (gui *Gui) refreshProject() error {
gui.Panels.Projects.SetItems([]*commands.Project{{Name: gui.getProjectName()}})
return gui.Panels.Projects.RerenderList()
}
func (gui *Gui) refreshProjects() error {
containers, err := gui.DockerCommand.Client.ContainerList(context.Background(), container.ListOptions{All: true})
if err != nil {
return err
}
func (gui *Gui) getProjectName() string {
projectName := path.Base(gui.Config.ProjectDir)
if gui.DockerCommand.InDockerComposeProject {
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"]
}
projectsMap := make(map[string]*commands.Project)
for _, container := range containers {
if projectName, exists := container.Labels["com.docker.compose.project"]; exists && projectName != "" {
projectsMap[projectName] = &commands.Project{Name: projectName}
}
}
return projectName
projectsList := make([]*commands.Project, 0, len(projectsMap))
for _, project := range projectsMap {
projectsList = append(projectsList, project)
}
// Add current's folder project if exists
if gui.DockerCommand.InDockerComposeProject {
projectsList = append(projectsList, &commands.Project{Name: gui.GetProjectName()})
}
gui.Panels.Projects.SetItems(projectsList)
return gui.Panels.Projects.RerenderList()
}
func (gui *Gui) GetProjectName() string {
if gui.State.Project != nil {
return gui.State.Project.Name
}
// Default to the command line argument
return path.Base(gui.Config.ProjectDir)
}
func (gui *Gui) renderCredits(_project *commands.Project) tasks.TaskFunc {
@ -180,3 +199,25 @@ func (gui *Gui) handleViewAllLogs(g *gocui.Gui, v *gocui.View) error {
return gui.runSubprocess(c)
}
func (gui *Gui) handleCreateProjectMenu(g *gocui.Gui, v *gocui.View) error {
if gui.isPopupPanel(v.Name()) {
return nil
}
testMenuItem := &types.MenuItem{
LabelColumns: []string{"t", "test"},
OnPress: func() error {
log.Println("tested")
return nil
},
}
menuItems := []*types.MenuItem{testMenuItem}
return gui.Menu(CreateMenuOptions{
Title: gui.Tr.MenuTitle,
Items: menuItems,
HideCancel: true,
})
}

View file

@ -109,6 +109,7 @@ type TranslationSet struct {
FilterList string
OpenInBrowser string
SortContainersByState string
SwitchProject string
LogsTitle string
ConfigTitle string
@ -216,6 +217,7 @@ func englishSet() TranslationSet {
FilterList: "filter list",
OpenInBrowser: "open in browser (first port is http)",
SortContainersByState: "sort containers by state",
SwitchProject: "switch project",
GlobalTitle: "Global",
MainTitle: "Main",