Add compose profile support

This commit is contained in:
Saravanabalagi Ramachandran 2025-03-24 01:22:05 +00:00
parent bedde4a037
commit c49f340d2d
No known key found for this signature in database
GPG key ID: 059CD1FAB64CB3AC
9 changed files with 619 additions and 125 deletions

View file

@ -40,6 +40,7 @@ type DockerCommand struct {
Config *config.AppConfig Config *config.AppConfig
Client *client.Client Client *client.Client
InDockerComposeProject bool InDockerComposeProject bool
HasProfiles bool
ErrorChan chan error ErrorChan chan error
ContainerMutex deadlock.Mutex ContainerMutex deadlock.Mutex
ServiceMutex deadlock.Mutex ServiceMutex deadlock.Mutex
@ -57,6 +58,7 @@ type LimitedDockerCommand interface {
// CommandObject is what we pass to our template resolvers when we are running a custom command. We do not guarantee that all fields will be populated: just the ones that make sense for the current context // CommandObject is what we pass to our template resolvers when we are running a custom command. We do not guarantee that all fields will be populated: just the ones that make sense for the current context
type CommandObject struct { type CommandObject struct {
DockerCompose string DockerCompose string
Profile string
Service *Service Service *Service
Container *Container Container *Container
Image *Image Image *Image
@ -109,7 +111,8 @@ func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Translat
Config: config, Config: config,
Client: cli, Client: cli,
ErrorChan: errorChan, ErrorChan: errorChan,
InDockerComposeProject: true, InDockerComposeProject: false,
HasProfiles: false,
Closers: []io.Closer{tunnelCloser}, Closers: []io.Closer{tunnelCloser},
} }
@ -121,8 +124,20 @@ func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Translat
dockerCommand.NewCommandObject(CommandObject{}), dockerCommand.NewCommandObject(CommandObject{}),
), ),
) )
if err != nil { if err == nil {
dockerCommand.InDockerComposeProject = false dockerCommand.InDockerComposeProject = true
err = osCommand.RunCommand(
utils.ApplyTemplate(
config.UserConfig.CommandTemplates.CheckDockerComposeProfiles,
dockerCommand.NewCommandObject(CommandObject{}),
),
)
if err == nil {
dockerCommand.HasProfiles = true
} else {
log.Warn("No profiles found in docker-compose project")
}
} else {
log.Warn(err.Error()) log.Warn(err.Error())
} }
@ -217,6 +232,22 @@ L:
} }
} }
// GetProfiles gets the profiles defined in the compose file
func (c *DockerCommand) GetProfiles() ([]string, error) {
if !c.InDockerComposeProject {
return nil, nil
}
composeCommand := c.Config.UserConfig.CommandTemplates.DockerCompose
output, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("%s config --profiles", composeCommand))
if err != nil {
return nil, err
}
profiles := utils.SplitLines(output)
return profiles, nil
}
// GetContainers gets the docker containers // GetContainers gets the docker containers
func (c *DockerCommand) GetContainers(existingContainers []*Container) ([]*Container, error) { func (c *DockerCommand) GetContainers(existingContainers []*Container) ([]*Container, error) {
c.ContainerMutex.Lock() c.ContainerMutex.Lock()
@ -279,7 +310,7 @@ func (c *DockerCommand) GetServices() ([]*Service, error) {
} }
composeCommand := c.Config.UserConfig.CommandTemplates.DockerCompose composeCommand := c.Config.UserConfig.CommandTemplates.DockerCompose
output, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("%s config --services", composeCommand)) output, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("%s --profile=* config --services", composeCommand))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -360,6 +391,19 @@ func (c *DockerCommand) DockerComposeConfig() string {
return output return output
} }
func (c *DockerCommand) DockerComposeProfileConfig(profile string) string {
output, err := c.OSCommand.RunCommandWithOutput(
utils.ApplyTemplate(
c.OSCommand.Config.UserConfig.CommandTemplates.DockerComposeProfileConfig,
c.NewCommandObject(CommandObject{Profile: profile}),
),
)
if err != nil {
output = err.Error()
}
return output
}
// determineDockerHost tries to the determine the docker host that we should connect to // determineDockerHost tries to the determine the docker host that we should connect to
// in the following order of decreasing precedence: // in the following order of decreasing precedence:
// - value of "DOCKER_HOST" environment variable // - value of "DOCKER_HOST" environment variable

View file

@ -1,5 +1,64 @@
package commands package commands
import (
"github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/utils"
)
type Project struct { type Project struct {
Name string Name string
IsProfile bool
OSCommand *OSCommand
Config *config.AppConfig
DockerCommand LimitedDockerCommand
}
// Up ups the project
func (p *Project) Up() error {
commandTemplates := p.Config.UserConfig.CommandTemplates
templateCmdStr := commandTemplates.Up
if p.IsProfile {
templateCmdStr = commandTemplates.UpProfile
}
return p.runCommand(templateCmdStr)
}
// Down downs the project
func (p *Project) Down() error {
defer func() {
if r := recover(); r != nil {
p.OSCommand.Log.Error(r)
}
}()
commandTemplates := p.Config.UserConfig.CommandTemplates
templateCmdStr := commandTemplates.Down
if p.IsProfile {
templateCmdStr = commandTemplates.DownProfile
}
return p.runCommand(templateCmdStr)
}
// Restart restarts the project
func (p *Project) Restart() error {
commandTemplates := p.Config.UserConfig.CommandTemplates
templateCmdStr := commandTemplates.Restart
if p.IsProfile {
templateCmdStr = commandTemplates.RestartProfile
}
return p.runCommand(templateCmdStr)
}
// Run custom command on the project
func (p *Project) runCommand(templateCmdStr string) error {
cmdObj := CommandObject{}
if p.IsProfile {
cmdObj.Profile = p.Name
}
command := utils.ApplyTemplate(
templateCmdStr,
p.DockerCommand.NewCommandObject(cmdObj),
)
// log command
return p.OSCommand.RunCommand(command)
} }

View file

@ -166,6 +166,9 @@ type CommandTemplatesConfig struct {
// downs and removes volumes // downs and removes volumes
DownWithVolumes string `yaml:"downWithVolumes,omitempty"` DownWithVolumes string `yaml:"downWithVolumes,omitempty"`
// Restarts everything
Restart string `yaml:"restart,omitempty"`
// DockerCompose is for your docker-compose command. You may want to combine a // DockerCompose is for your docker-compose command. You may want to combine a
// few different docker-compose.yml files together, in which case you can set // few different docker-compose.yml files together, in which case you can set
// this to "docker compose -f foo/docker-compose.yml -f // this to "docker compose -f foo/docker-compose.yml -f
@ -202,7 +205,8 @@ type CommandTemplatesConfig struct {
// AllLogs is for showing what you get from doing `docker compose logs`. It // AllLogs is for showing what you get from doing `docker compose logs`. It
// combines all the logs together // combines all the logs together
AllLogs string `yaml:"allLogs,omitempty"` AllLogs string `yaml:"allLogs,omitempty"`
AllLogsProfile string `yaml:"allLogsProfile,omitempty"`
// ViewAllLogs is the command we use when you want to see all logs in a subprocess with no filtering // ViewAllLogs is the command we use when you want to see all logs in a subprocess with no filtering
ViewAllLogs string `yaml:"viewAlLogs,omitempty"` ViewAllLogs string `yaml:"viewAlLogs,omitempty"`
@ -210,7 +214,8 @@ type CommandTemplatesConfig struct {
// DockerComposeConfig is the command for viewing the config of your docker // DockerComposeConfig is the command for viewing the config of your docker
// compose. It basically prints out the yaml from your docker-compose.yml // compose. It basically prints out the yaml from your docker-compose.yml
// file(s) // file(s)
DockerComposeConfig string `yaml:"dockerComposeConfig,omitempty"` DockerComposeConfig string `yaml:"dockerComposeConfig,omitempty"`
DockerComposeProfileConfig string `yaml:"dockerComposeProfileConfig,omitempty"`
// CheckDockerComposeConfig is what we use to check whether we are in a // CheckDockerComposeConfig is what we use to check whether we are in a
// docker-compose context. If the command returns an error then we clearly // docker-compose context. If the command returns an error then we clearly
@ -218,8 +223,22 @@ type CommandTemplatesConfig struct {
// and only show containers // and only show containers
CheckDockerComposeConfig string `yaml:"checkDockerComposeConfig,omitempty"` CheckDockerComposeConfig string `yaml:"checkDockerComposeConfig,omitempty"`
// CheckDockerComposeProfiles is what we use to check whether we are in a
// docker-compose context with profiles.
CheckDockerComposeProfiles string `yaml:"checkDockerComposeProfiles,omitempty"`
// ServiceTop is the command for viewing the processes under a given service // ServiceTop is the command for viewing the processes under a given service
ServiceTop string `yaml:"serviceTop,omitempty"` ServiceTop string `yaml:"serviceTop,omitempty"`
// UpProfile is the command for starting a project
UpProfile string `yaml:"upProfile,omitempty"`
// DownProfile is the command for stopping a project with a given profile
DownProfile string `yaml:"downProfile,omitempty"`
DownProfileWithVolumes string `yaml:"downProfileWithVolumes,omitempty"`
// RestartProfile is the command for restarting a project with a given profile
RestartProfile string `yaml:"restartProfile,omitempty"`
} }
// OSConfig contains config on the level of the os // OSConfig contains config on the level of the os
@ -385,23 +404,31 @@ func GetDefaultConfig() UserConfig {
Tail: "", Tail: "",
}, },
CommandTemplates: CommandTemplatesConfig{ CommandTemplates: CommandTemplatesConfig{
DockerCompose: "docker compose", DockerCompose: "docker compose",
RestartService: "{{ .DockerCompose }} restart {{ .Service.Name }}", RestartService: "{{ .DockerCompose }} restart {{ .Service.Name }}",
StartService: "{{ .DockerCompose }} start {{ .Service.Name }}", StartService: "{{ .DockerCompose }} start {{ .Service.Name }}",
Up: "{{ .DockerCompose }} up -d", Up: "{{ .DockerCompose }} up -d",
Down: "{{ .DockerCompose }} down", Down: "{{ .DockerCompose }} down",
DownWithVolumes: "{{ .DockerCompose }} down --volumes", DownWithVolumes: "{{ .DockerCompose }} down --volumes",
UpService: "{{ .DockerCompose }} up -d {{ .Service.Name }}", UpService: "{{ .DockerCompose }} up -d {{ .Service.Name }}",
RebuildService: "{{ .DockerCompose }} up -d --build {{ .Service.Name }}", UpProfile: "{{ .DockerCompose }} --profile {{ .Profile }} up -d",
RecreateService: "{{ .DockerCompose }} up -d --force-recreate {{ .Service.Name }}", DownProfile: "{{ .DockerCompose }} --profile {{ .Profile }} down",
StopService: "{{ .DockerCompose }} stop {{ .Service.Name }}", DownProfileWithVolumes: "{{ .DockerCompose }} --profile {{ .Profile }} down --volumes",
ServiceLogs: "{{ .DockerCompose }} logs --since=60m --follow {{ .Service.Name }}", Restart: "{{ .DockerCompose }} restart",
ViewServiceLogs: "{{ .DockerCompose }} logs --follow {{ .Service.Name }}", RestartProfile: "{{ .DockerCompose }} --profile {{ .Profile }} restart",
AllLogs: "{{ .DockerCompose }} logs --tail=300 --follow", RebuildService: "{{ .DockerCompose }} up -d --build {{ .Service.Name }}",
ViewAllLogs: "{{ .DockerCompose }} logs", RecreateService: "{{ .DockerCompose }} up -d --force-recreate {{ .Service.Name }}",
DockerComposeConfig: "{{ .DockerCompose }} config", StopService: "{{ .DockerCompose }} stop {{ .Service.Name }}",
CheckDockerComposeConfig: "{{ .DockerCompose }} config --quiet", ServiceLogs: "{{ .DockerCompose }} logs --since=60m --follow {{ .Service.Name }}",
ServiceTop: "{{ .DockerCompose }} top {{ .Service.Name }}", ViewServiceLogs: "{{ .DockerCompose }} logs --follow {{ .Service.Name }}",
AllLogs: "{{ .DockerCompose }} logs --tail=300 --follow",
AllLogsProfile: "{{ .DockerCompose }} --profile {{ .Profile }} logs --tail=300 --follow",
ViewAllLogs: "{{ .DockerCompose }} logs",
DockerComposeConfig: "{{ .DockerCompose }} config",
DockerComposeProfileConfig: "{{ .DockerCompose }} --profile {{ .Profile }} config",
CheckDockerComposeConfig: "{{ .DockerCompose }} config --quiet",
CheckDockerComposeProfiles: "{{ .DockerCompose }} config --profiles --quiet | grep -q '[^[:space:]]'; echo $?",
ServiceTop: "{{ .DockerCompose }} top {{ .Service.Name }}",
}, },
CustomCommands: CustomCommands{ CustomCommands: CustomCommands{
Containers: []CustomCommand{}, Containers: []CustomCommand{},

View file

@ -149,6 +149,55 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleViewAllLogs, Handler: gui.handleViewAllLogs,
Description: gui.Tr.ViewLogs, Description: gui.Tr.ViewLogs,
}, },
{
ViewName: "project",
Key: 'U',
Modifier: gocui.ModNone,
Handler: gui.handleProjectUpMenu,
Description: gui.Tr.UpProject,
},
{
ViewName: "project",
Key: 'D',
Modifier: gocui.ModNone,
Handler: gui.handleProjectDownMenu,
Description: gui.Tr.DownProject,
},
{
ViewName: "project",
Key: 'R',
Modifier: gocui.ModNone,
Handler: gui.handleProjectRestartMenu,
Description: gui.Tr.RestartProject,
},
{
ViewName: "project",
Key: 'u',
Modifier: gocui.ModNone,
Handler: gui.handleProjectUp,
Description: gui.Tr.UpProfile,
},
{
ViewName: "project",
Key: 'd',
Modifier: gocui.ModNone,
Handler: gui.handleProjectDown,
Description: gui.Tr.DownProfile,
},
{
ViewName: "project",
Key: 'r',
Modifier: gocui.ModNone,
Handler: gui.handleProjectRestart,
Description: gui.Tr.RestartProfile,
},
// {
// ViewName: "project",
// Key: 'b',
// Modifier: gocui.ModNone,
// Handler: gui.handleServicesBulkCommand,
// Description: gui.Tr.ViewBulkCommands,
// },
{ {
ViewName: "menu", ViewName: "menu",
Key: gocui.KeyEsc, Key: gocui.KeyEsc,
@ -322,14 +371,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
ViewName: "services", ViewName: "services",
Key: 'U', Key: 'U',
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleProjectUp, Handler: gui.handleProjectUpMenu,
Description: gui.Tr.UpProject, Description: gui.Tr.UpProject,
}, },
{ {
ViewName: "services", ViewName: "services",
Key: 'D', Key: 'D',
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleProjectDown, Handler: gui.handleProjectDownMenu,
Description: gui.Tr.DownProject, Description: gui.Tr.DownProject,
}, },
{ {

View file

@ -1,7 +1,23 @@
package presentation package presentation
import "github.com/jesseduffield/lazydocker/pkg/commands" import (
"github.com/fatih/color"
"github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/utils"
)
func GetProjectDisplayStrings(project *commands.Project) []string { func GetProjectDisplayStrings(project *commands.Project) []string {
return []string{project.Name} if project.IsProfile {
// show "profile" word in aqua
return []string{
utils.ColoredString("profile", color.FgCyan),
"",
project.Name,
}
}
return []string{
"project",
"",
project.Name,
}
} }

View file

@ -3,6 +3,7 @@ package gui
import ( import (
"bytes" "bytes"
"context" "context"
"os/exec"
"path" "path"
"strings" "strings"
@ -11,9 +12,11 @@ import (
"github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/gui/panels" "github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/gui/presentation" "github.com/jesseduffield/lazydocker/pkg/gui/presentation"
"github.com/jesseduffield/lazydocker/pkg/gui/types"
"github.com/jesseduffield/lazydocker/pkg/tasks" "github.com/jesseduffield/lazydocker/pkg/tasks"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/jesseduffield/yaml" "github.com/jesseduffield/yaml"
"github.com/samber/lo"
) )
// Although at the moment we'll only have one project, in future we could have // Although at the moment we'll only have one project, in future we could have
@ -23,33 +26,31 @@ func (gui *Gui) getProjectPanel() *panels.SideListPanel[*commands.Project] {
return &panels.SideListPanel[*commands.Project]{ return &panels.SideListPanel[*commands.Project]{
ContextState: &panels.ContextState[*commands.Project]{ ContextState: &panels.ContextState[*commands.Project]{
GetMainTabs: func() []panels.MainTab[*commands.Project] { GetMainTabs: func() []panels.MainTab[*commands.Project] {
if gui.DockerCommand.InDockerComposeProject { tabsList := []panels.MainTab[*commands.Project]{
return []panels.MainTab[*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 []panels.MainTab[*commands.Project]{
{ {
Key: "credits", Key: "credits",
Title: gui.Tr.CreditsTitle, Title: gui.Tr.CreditsTitle,
Render: gui.renderCredits, Render: gui.renderCredits,
}, },
} }
if gui.DockerCommand.InDockerComposeProject {
tabsListCompose := []panels.MainTab[*commands.Project]{
{
Key: "logs",
Title: gui.Tr.LogsTitle,
Render: gui.renderLogs,
},
{
Key: "config",
Title: gui.Tr.DockerComposeConfigTitle,
Render: gui.renderDockerComposeConfig,
},
}
tabsList = append(tabsListCompose, tabsList...)
}
return tabsList
}, },
GetItemContextCacheKey: func(project *commands.Project) string { GetItemContextCacheKey: func(project *commands.Project) string {
return "projects-" + project.Name return "projects-" + project.Name
@ -67,13 +68,36 @@ func (gui *Gui) getProjectPanel() *panels.SideListPanel[*commands.Project] {
return false return false
}, },
GetTableCells: presentation.GetProjectDisplayStrings, GetTableCells: presentation.GetProjectDisplayStrings,
// It doesn't make sense to filter a list of only one item.
DisableFilter: true, DisableFilter: true,
} }
} }
func (gui *Gui) refreshProject() error { func (gui *Gui) refreshProject() error {
gui.Panels.Projects.SetItems([]*commands.Project{{Name: gui.getProjectName()}}) projectName := gui.getProjectName()
profiles, err := gui.DockerCommand.GetProfiles()
if err != nil {
return err
}
gui.DockerCommand.HasProfiles = len(profiles) > 0
items := []*commands.Project{{
Name: projectName,
IsProfile: false,
Config: gui.Config,
OSCommand: gui.OSCommand,
DockerCommand: gui.DockerCommand,
}}
for _, profile := range profiles {
items = append(items, &commands.Project{
Name: profile,
IsProfile: true,
Config: gui.Config,
OSCommand: gui.OSCommand,
DockerCommand: gui.DockerCommand,
})
}
gui.Panels.Projects.SetItems(items)
return gui.Panels.Projects.RerenderList() return gui.Panels.Projects.RerenderList()
} }
@ -112,19 +136,29 @@ func (gui *Gui) creditsStr() string {
}, "\n\n") }, "\n\n")
} }
func (gui *Gui) renderAllLogs(_project *commands.Project) tasks.TaskFunc { func (gui *Gui) renderLogs(_project *commands.Project) tasks.TaskFunc {
return gui.NewTask(TaskOpts{ return gui.NewTask(TaskOpts{
Autoscroll: true, Autoscroll: true,
Wrap: gui.Config.UserConfig.Gui.WrapMainPanel, Wrap: gui.Config.UserConfig.Gui.WrapMainPanel,
Func: func(ctx context.Context) { Func: func(ctx context.Context) {
gui.clearMainView() gui.clearMainView()
cmd := gui.OSCommand.RunCustomCommand( var cmd *exec.Cmd
utils.ApplyTemplate( if _project.IsProfile {
gui.Config.UserConfig.CommandTemplates.AllLogs, cmd = gui.OSCommand.RunCustomCommand(
gui.DockerCommand.NewCommandObject(commands.CommandObject{}), utils.ApplyTemplate(
), gui.Config.UserConfig.CommandTemplates.AllLogsProfile,
) gui.DockerCommand.NewCommandObject(commands.CommandObject{Profile: _project.Name}),
),
)
} else {
cmd = gui.OSCommand.RunCustomCommand(
utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.AllLogs,
gui.DockerCommand.NewCommandObject(commands.CommandObject{}),
),
)
}
cmd.Stdout = gui.Views.Main cmd.Stdout = gui.Views.Main
cmd.Stderr = gui.Views.Main cmd.Stderr = gui.Views.Main
@ -145,6 +179,11 @@ func (gui *Gui) renderAllLogs(_project *commands.Project) tasks.TaskFunc {
} }
func (gui *Gui) renderDockerComposeConfig(_project *commands.Project) tasks.TaskFunc { func (gui *Gui) renderDockerComposeConfig(_project *commands.Project) tasks.TaskFunc {
if _project.IsProfile {
return gui.NewSimpleRenderStringTask(func() string {
return utils.ColoredYamlString(gui.DockerCommand.DockerComposeProfileConfig(_project.Name))
})
}
return gui.NewSimpleRenderStringTask(func() string { return gui.NewSimpleRenderStringTask(func() string {
return utils.ColoredYamlString(gui.DockerCommand.DockerComposeConfig()) return utils.ColoredYamlString(gui.DockerCommand.DockerComposeConfig())
}) })
@ -180,3 +219,312 @@ func (gui *Gui) handleViewAllLogs(g *gocui.Gui, v *gocui.View) error {
return gui.runSubprocess(c) return gui.runSubprocess(c)
} }
func (gui *Gui) handleProjectUpMenu(g *gocui.Gui, v *gocui.View) error {
upCommand := utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.Up,
gui.DockerCommand.NewCommandObject(commands.CommandObject{}),
)
options := []*commandOption{
{
description: gui.Tr.UpProject,
command: upCommand,
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.UppingProjectStatus, func() error {
if err := gui.OSCommand.RunCommand(upCommand); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
},
},
}
if gui.DockerCommand.HasProfiles {
upAllProfilesCommand := utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.UpProfile,
gui.DockerCommand.NewCommandObject(commands.CommandObject{Profile: "*"}),
)
options = append(options, &commandOption{
description: gui.Tr.UpAllProfiles,
command: upAllProfilesCommand,
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.UppingProjectStatus, func() error {
if err := gui.OSCommand.RunCommand(upAllProfilesCommand); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
},
})
profile, err := gui.Panels.Projects.GetSelectedItem()
if err == nil && profile.IsProfile {
upProfileCommand := utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.UpProfile,
gui.DockerCommand.NewCommandObject(commands.CommandObject{Profile: profile.Name}),
)
options = append(options, &commandOption{
description: gui.Tr.UpProfile,
command: upProfileCommand,
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.UppingProjectStatus, func() error {
if err := gui.OSCommand.RunCommand(upProfileCommand); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
},
})
}
}
menuItems := lo.Map(options, func(option *commandOption, _ int) *types.MenuItem {
return &types.MenuItem{
LabelColumns: option.getDisplayStrings(),
OnPress: option.onPress,
}
})
return gui.Menu(CreateMenuOptions{
Title: "",
Items: menuItems,
})
}
func (gui *Gui) handleProjectDownMenu(g *gocui.Gui, v *gocui.View) error {
downCommand := utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.Down,
gui.DockerCommand.NewCommandObject(commands.CommandObject{}),
)
downWithVolumesCommand := utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.DownWithVolumes,
gui.DockerCommand.NewCommandObject(commands.CommandObject{}),
)
options := []*commandOption{
{
description: gui.Tr.Down,
command: downCommand,
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.DowningStatus, func() error {
if err := gui.OSCommand.RunCommand(downCommand); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
},
},
{
description: gui.Tr.DownWithVolumes,
command: downWithVolumesCommand,
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.DowningStatus, func() error {
if err := gui.OSCommand.RunCommand(downWithVolumesCommand); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
},
},
}
if gui.DockerCommand.HasProfiles {
downProfileCommand := utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.DownProfile,
gui.DockerCommand.NewCommandObject(commands.CommandObject{Profile: "*"}),
)
downProfileWithVolumesCommand := utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.DownProfileWithVolumes,
gui.DockerCommand.NewCommandObject(commands.CommandObject{Profile: "*"}),
)
options = append(options, &commandOption{
description: gui.Tr.DownAllProfiles,
command: downProfileCommand,
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.DowningStatus, func() error {
if err := gui.OSCommand.RunCommand(downProfileCommand); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
},
}, &commandOption{
description: gui.Tr.DownAllProfilesWithVolumes,
command: downProfileWithVolumesCommand,
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.DowningStatus, func() error {
if err := gui.OSCommand.RunCommand(downProfileWithVolumesCommand); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
},
})
profile, err := gui.Panels.Projects.GetSelectedItem()
if err == nil && profile.IsProfile {
downProfileCommand := utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.DownProfile,
gui.DockerCommand.NewCommandObject(commands.CommandObject{Profile: profile.Name}),
)
downProfileWithVolumesCommand := utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.DownProfileWithVolumes,
gui.DockerCommand.NewCommandObject(commands.CommandObject{Profile: profile.Name}),
)
options = append(options, &commandOption{
description: gui.Tr.DownProfile,
command: downProfileCommand,
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.DowningStatus, func() error {
if err := gui.OSCommand.RunCommand(downProfileCommand); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
},
}, &commandOption{
description: gui.Tr.DownProfileWithVolumes,
command: downProfileWithVolumesCommand,
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.DowningStatus, func() error {
if err := gui.OSCommand.RunCommand(downProfileWithVolumesCommand); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
},
})
}
}
menuItems := lo.Map(options, func(option *commandOption, _ int) *types.MenuItem {
return &types.MenuItem{
LabelColumns: option.getDisplayStrings(),
OnPress: option.onPress,
}
})
return gui.Menu(CreateMenuOptions{
Title: "",
Items: menuItems,
})
}
func (gui *Gui) handleProjectRestartMenu(g *gocui.Gui, v *gocui.View) error {
restartCommand := utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.Restart,
gui.DockerCommand.NewCommandObject(commands.CommandObject{}),
)
options := []*commandOption{
{
description: gui.Tr.Restart,
command: restartCommand,
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.RestartingStatus, func() error {
if err := gui.OSCommand.RunCommand(restartCommand); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
},
},
}
if gui.DockerCommand.HasProfiles {
restartProfileCommand := utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.RestartProfile,
gui.DockerCommand.NewCommandObject(commands.CommandObject{Profile: "*"}),
)
options = append(options, &commandOption{
description: gui.Tr.Restart,
command: restartProfileCommand,
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.RestartingStatus, func() error {
if err := gui.OSCommand.RunCommand(restartProfileCommand); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
},
})
profile, err := gui.Panels.Projects.GetSelectedItem()
if err == nil && profile.IsProfile {
restartProfileCommand := utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.RestartProfile,
gui.DockerCommand.NewCommandObject(commands.CommandObject{Profile: profile.Name}),
)
options = append(options, &commandOption{
description: gui.Tr.Restart,
command: restartProfileCommand,
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.RestartingStatus, func() error {
if err := gui.OSCommand.RunCommand(restartProfileCommand); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
},
})
}
}
menuItems := lo.Map(options, func(option *commandOption, _ int) *types.MenuItem {
return &types.MenuItem{
LabelColumns: option.getDisplayStrings(),
OnPress: option.onPress,
}
})
return gui.Menu(CreateMenuOptions{
Title: "",
Items: menuItems,
})
}
func (gui *Gui) handleProjectUp(g *gocui.Gui, v *gocui.View) error {
project, err := gui.Panels.Projects.GetSelectedItem()
if err != nil {
return nil
}
return gui.WithWaitingStatus(gui.Tr.UppingProjectStatus, func() error {
if err := project.Up(); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
}
func (gui *Gui) handleProjectDown(g *gocui.Gui, v *gocui.View) error {
project, err := gui.Panels.Projects.GetSelectedItem()
if err != nil {
return nil
}
return gui.WithWaitingStatus(gui.Tr.DowningStatus, func() error {
if err := project.Down(); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
}
func (gui *Gui) handleProjectRestart(g *gocui.Gui, v *gocui.View) error {
project, err := gui.Panels.Projects.GetSelectedItem()
if err != nil {
return nil
}
return gui.WithWaitingStatus(gui.Tr.RestartingStatus, func() error {
if err := project.Restart(); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
}

View file

@ -288,73 +288,6 @@ func (gui *Gui) handleServiceRenderLogsToMain(g *gocui.Gui, v *gocui.View) error
return gui.runSubprocess(c) return gui.runSubprocess(c)
} }
func (gui *Gui) handleProjectUp(g *gocui.Gui, v *gocui.View) error {
return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.ConfirmUpProject, func(g *gocui.Gui, v *gocui.View) error {
cmdStr := utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.Up,
gui.DockerCommand.NewCommandObject(commands.CommandObject{}),
)
return gui.WithWaitingStatus(gui.Tr.UppingProjectStatus, func() error {
if err := gui.OSCommand.RunCommand(cmdStr); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
}, nil)
}
func (gui *Gui) handleProjectDown(g *gocui.Gui, v *gocui.View) error {
downCommand := utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.Down,
gui.DockerCommand.NewCommandObject(commands.CommandObject{}),
)
downWithVolumesCommand := utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.DownWithVolumes,
gui.DockerCommand.NewCommandObject(commands.CommandObject{}),
)
options := []*commandOption{
{
description: gui.Tr.Down,
command: downCommand,
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.DowningStatus, func() error {
if err := gui.OSCommand.RunCommand(downCommand); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
},
},
{
description: gui.Tr.DownWithVolumes,
command: downWithVolumesCommand,
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.DowningStatus, func() error {
if err := gui.OSCommand.RunCommand(downWithVolumesCommand); err != nil {
return gui.createErrorPanel(err.Error())
}
return nil
})
},
},
}
menuItems := lo.Map(options, func(option *commandOption, _ int) *types.MenuItem {
return &types.MenuItem{
LabelColumns: option.getDisplayStrings(),
OnPress: option.onPress,
}
})
return gui.Menu(CreateMenuOptions{
Title: "",
Items: menuItems,
})
}
func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error {
service, err := gui.Panels.Services.GetSelectedItem() service, err := gui.Panels.Services.GetSelectedItem()
if err != nil { if err != nil {

View file

@ -118,6 +118,8 @@ func (gui *Gui) createAllViews() error {
// when you run a docker container with the -it flags (interactive mode) it adds carriage returns for some reason. This is not docker's fault, it's an os-level default. // when you run a docker container with the -it flags (interactive mode) it adds carriage returns for some reason. This is not docker's fault, it's an os-level default.
gui.Views.Main.IgnoreCarriageReturns = true gui.Views.Main.IgnoreCarriageReturns = true
gui.Views.Project.Highlight = true
gui.Views.Project.SelBgColor = selectedLineBgColor
gui.Views.Project.Title = gui.Tr.ProjectTitle gui.Views.Project.Title = gui.Tr.ProjectTitle
gui.Views.Project.TitlePrefix = "[1]" gui.Views.Project.TitlePrefix = "[1]"

View file

@ -67,6 +67,14 @@ type TranslationSet struct {
ViewLogs string ViewLogs string
UpProject string UpProject string
DownProject string DownProject string
RestartProject string
UpProfile string
DownProfile string
DownProfileWithVolumes string
RestartProfile string
UpAllProfiles string
DownAllProfiles string
DownAllProfilesWithVolumes string
ServicesTitle string ServicesTitle string
ContainersTitle string ContainersTitle string
StandaloneContainersTitle string StandaloneContainersTitle string
@ -197,6 +205,14 @@ func englishSet() TranslationSet {
ViewLogs: "view logs", ViewLogs: "view logs",
UpProject: "up project", UpProject: "up project",
DownProject: "down project", DownProject: "down project",
RestartProject: "restart project",
UpProfile: "up profile",
DownProfile: "down profile",
DownProfileWithVolumes: "down profile with volumes",
RestartProfile: "restart profile",
UpAllProfiles: "up all profiles",
DownAllProfiles: "down all profiles",
DownAllProfilesWithVolumes: "down all profiles with volumes",
RemoveImage: "remove image", RemoveImage: "remove image",
RemoveVolume: "remove volume", RemoveVolume: "remove volume",
RemoveNetwork: "remove network", RemoveNetwork: "remove network",