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
Client *client.Client
InDockerComposeProject bool
HasProfiles bool
ErrorChan chan error
ContainerMutex 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
type CommandObject struct {
DockerCompose string
Profile string
Service *Service
Container *Container
Image *Image
@ -109,7 +111,8 @@ func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Translat
Config: config,
Client: cli,
ErrorChan: errorChan,
InDockerComposeProject: true,
InDockerComposeProject: false,
HasProfiles: false,
Closers: []io.Closer{tunnelCloser},
}
@ -121,8 +124,20 @@ func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Translat
dockerCommand.NewCommandObject(CommandObject{}),
),
)
if err != nil {
dockerCommand.InDockerComposeProject = false
if err == nil {
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())
}
@ -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
func (c *DockerCommand) GetContainers(existingContainers []*Container) ([]*Container, error) {
c.ContainerMutex.Lock()
@ -279,7 +310,7 @@ func (c *DockerCommand) GetServices() ([]*Service, error) {
}
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 {
return nil, err
}
@ -360,6 +391,19 @@ func (c *DockerCommand) DockerComposeConfig() string {
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
// in the following order of decreasing precedence:
// - value of "DOCKER_HOST" environment variable

View file

@ -1,5 +1,64 @@
package commands
import (
"github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/utils"
)
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
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
// few different docker-compose.yml files together, in which case you can set
// 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
// 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 string `yaml:"viewAlLogs,omitempty"`
@ -210,7 +214,8 @@ type CommandTemplatesConfig struct {
// DockerComposeConfig is the command for viewing the config of your docker
// compose. It basically prints out the yaml from your docker-compose.yml
// 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
// docker-compose context. If the command returns an error then we clearly
@ -218,8 +223,22 @@ type CommandTemplatesConfig struct {
// and only show containers
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 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
@ -385,23 +404,31 @@ func GetDefaultConfig() UserConfig {
Tail: "",
},
CommandTemplates: CommandTemplatesConfig{
DockerCompose: "docker compose",
RestartService: "{{ .DockerCompose }} restart {{ .Service.Name }}",
StartService: "{{ .DockerCompose }} start {{ .Service.Name }}",
Up: "{{ .DockerCompose }} up -d",
Down: "{{ .DockerCompose }} down",
DownWithVolumes: "{{ .DockerCompose }} down --volumes",
UpService: "{{ .DockerCompose }} up -d {{ .Service.Name }}",
RebuildService: "{{ .DockerCompose }} up -d --build {{ .Service.Name }}",
RecreateService: "{{ .DockerCompose }} up -d --force-recreate {{ .Service.Name }}",
StopService: "{{ .DockerCompose }} stop {{ .Service.Name }}",
ServiceLogs: "{{ .DockerCompose }} logs --since=60m --follow {{ .Service.Name }}",
ViewServiceLogs: "{{ .DockerCompose }} logs --follow {{ .Service.Name }}",
AllLogs: "{{ .DockerCompose }} logs --tail=300 --follow",
ViewAllLogs: "{{ .DockerCompose }} logs",
DockerComposeConfig: "{{ .DockerCompose }} config",
CheckDockerComposeConfig: "{{ .DockerCompose }} config --quiet",
ServiceTop: "{{ .DockerCompose }} top {{ .Service.Name }}",
DockerCompose: "docker compose",
RestartService: "{{ .DockerCompose }} restart {{ .Service.Name }}",
StartService: "{{ .DockerCompose }} start {{ .Service.Name }}",
Up: "{{ .DockerCompose }} up -d",
Down: "{{ .DockerCompose }} down",
DownWithVolumes: "{{ .DockerCompose }} down --volumes",
UpService: "{{ .DockerCompose }} up -d {{ .Service.Name }}",
UpProfile: "{{ .DockerCompose }} --profile {{ .Profile }} up -d",
DownProfile: "{{ .DockerCompose }} --profile {{ .Profile }} down",
DownProfileWithVolumes: "{{ .DockerCompose }} --profile {{ .Profile }} down --volumes",
Restart: "{{ .DockerCompose }} restart",
RestartProfile: "{{ .DockerCompose }} --profile {{ .Profile }} restart",
RebuildService: "{{ .DockerCompose }} up -d --build {{ .Service.Name }}",
RecreateService: "{{ .DockerCompose }} up -d --force-recreate {{ .Service.Name }}",
StopService: "{{ .DockerCompose }} stop {{ .Service.Name }}",
ServiceLogs: "{{ .DockerCompose }} logs --since=60m --follow {{ .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{
Containers: []CustomCommand{},

View file

@ -149,6 +149,55 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleViewAllLogs,
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",
Key: gocui.KeyEsc,
@ -322,14 +371,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
ViewName: "services",
Key: 'U',
Modifier: gocui.ModNone,
Handler: gui.handleProjectUp,
Handler: gui.handleProjectUpMenu,
Description: gui.Tr.UpProject,
},
{
ViewName: "services",
Key: 'D',
Modifier: gocui.ModNone,
Handler: gui.handleProjectDown,
Handler: gui.handleProjectDownMenu,
Description: gui.Tr.DownProject,
},
{

View file

@ -1,7 +1,23 @@
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 {
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 (
"bytes"
"context"
"os/exec"
"path"
"strings"
@ -11,9 +12,11 @@ import (
"github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
"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/utils"
"github.com/jesseduffield/yaml"
"github.com/samber/lo"
)
// 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]{
ContextState: &panels.ContextState[*commands.Project]{
GetMainTabs: func() []panels.MainTab[*commands.Project] {
if gui.DockerCommand.InDockerComposeProject {
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]{
tabsList := []panels.MainTab[*commands.Project]{
{
Key: "credits",
Title: gui.Tr.CreditsTitle,
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 {
return "projects-" + project.Name
@ -67,13 +68,36 @@ func (gui *Gui) getProjectPanel() *panels.SideListPanel[*commands.Project] {
return false
},
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()}})
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()
}
@ -112,19 +136,29 @@ func (gui *Gui) creditsStr() string {
}, "\n\n")
}
func (gui *Gui) renderAllLogs(_project *commands.Project) tasks.TaskFunc {
func (gui *Gui) renderLogs(_project *commands.Project) tasks.TaskFunc {
return gui.NewTask(TaskOpts{
Autoscroll: true,
Wrap: gui.Config.UserConfig.Gui.WrapMainPanel,
Func: func(ctx context.Context) {
gui.clearMainView()
cmd := gui.OSCommand.RunCustomCommand(
utils.ApplyTemplate(
gui.Config.UserConfig.CommandTemplates.AllLogs,
gui.DockerCommand.NewCommandObject(commands.CommandObject{}),
),
)
var cmd *exec.Cmd
if _project.IsProfile {
cmd = gui.OSCommand.RunCustomCommand(
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.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 {
if _project.IsProfile {
return gui.NewSimpleRenderStringTask(func() string {
return utils.ColoredYamlString(gui.DockerCommand.DockerComposeProfileConfig(_project.Name))
})
}
return gui.NewSimpleRenderStringTask(func() string {
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)
}
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)
}
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 {
service, err := gui.Panels.Services.GetSelectedItem()
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.
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.TitlePrefix = "[1]"

View file

@ -67,6 +67,14 @@ type TranslationSet struct {
ViewLogs string
UpProject string
DownProject string
RestartProject string
UpProfile string
DownProfile string
DownProfileWithVolumes string
RestartProfile string
UpAllProfiles string
DownAllProfiles string
DownAllProfilesWithVolumes string
ServicesTitle string
ContainersTitle string
StandaloneContainersTitle string
@ -197,6 +205,14 @@ func englishSet() TranslationSet {
ViewLogs: "view logs",
UpProject: "up 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",
RemoveVolume: "remove volume",
RemoveNetwork: "remove network",