more progress
parent
b2e60e1914
commit
aa55ff1a8e
@ -0,0 +1,62 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazydocker/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Service : A docker Service
|
||||
type Service struct {
|
||||
Name string
|
||||
ID string
|
||||
DisplayString string
|
||||
OSCommand *OSCommand
|
||||
Log *logrus.Entry
|
||||
Container *Container
|
||||
}
|
||||
|
||||
// GetDisplayStrings returns the dispaly string of Container
|
||||
func (s *Service) GetDisplayStrings(isFocused bool) []string {
|
||||
|
||||
if s.Container == nil {
|
||||
return []string{"", utils.ColoredString(s.Name, color.FgWhite)}
|
||||
}
|
||||
|
||||
cont := s.Container
|
||||
return []string{utils.ColoredString(cont.Container.State, cont.GetColor()), utils.ColoredString(cont.Name, color.FgWhite), cont.Stats.CPUPerc}
|
||||
}
|
||||
|
||||
// Remove removes the service's containers
|
||||
func (s *Service) Remove(options types.ContainerRemoveOptions) error {
|
||||
return s.Container.Remove(options)
|
||||
}
|
||||
|
||||
// Stop stops the service's containers
|
||||
func (s *Service) Stop() error {
|
||||
templateString := s.OSCommand.Config.GetUserConfig().GetString("commandTemplates.stopService")
|
||||
command := utils.ApplyTemplate(templateString, s)
|
||||
return s.OSCommand.RunCommand(command)
|
||||
}
|
||||
|
||||
// Restart restarts the service
|
||||
func (s *Service) Restart() error {
|
||||
templateString := s.OSCommand.Config.GetUserConfig().GetString("commandTemplates.restartService")
|
||||
command := utils.ApplyTemplate(templateString, s)
|
||||
return s.OSCommand.RunCommand(command)
|
||||
}
|
||||
|
||||
// Attach attaches to the service
|
||||
func (s *Service) Attach() *exec.Cmd {
|
||||
// TODO: if you have a custom command for attaching to a service here is the place to use it
|
||||
|
||||
return s.Container.Attach()
|
||||
}
|
||||
|
||||
// Top returns process information
|
||||
func (s *Service) Top() (types.ContainerProcessList, error) {
|
||||
return s.Container.Top()
|
||||
}
|
@ -0,0 +1,262 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazydocker/pkg/commands"
|
||||
"github.com/jesseduffield/lazydocker/pkg/utils"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) getServiceContexts() []string {
|
||||
return []string{"logs", "config", "stats"}
|
||||
}
|
||||
|
||||
func (gui *Gui) getSelectedService(g *gocui.Gui) (*commands.Service, error) {
|
||||
selectedLine := gui.State.Panels.Services.SelectedLine
|
||||
if selectedLine == -1 {
|
||||
return &commands.Service{}, errors.New("no service selected")
|
||||
}
|
||||
|
||||
return gui.State.Services[selectedLine], nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleServicesFocus(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
cx, cy := v.Cursor()
|
||||
_, oy := v.Origin()
|
||||
|
||||
prevSelectedLine := gui.State.Panels.Services.SelectedLine
|
||||
newSelectedLine := cy - oy
|
||||
|
||||
if newSelectedLine > len(gui.State.Services)-1 || len(utils.Decolorise(gui.State.Services[newSelectedLine].Name)) < cx {
|
||||
return gui.handleServiceSelect(gui.g, v)
|
||||
}
|
||||
|
||||
gui.State.Panels.Services.SelectedLine = newSelectedLine
|
||||
|
||||
if prevSelectedLine == newSelectedLine && gui.currentViewName() == v.Name() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.handleServiceSelect(gui.g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleServiceSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service, err := gui.getSelectedService(g)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := service.ID + "-" + gui.getServiceContexts()[gui.State.Panels.Services.ContextIndex]
|
||||
if gui.State.Panels.Main.ObjectKey == key {
|
||||
return nil
|
||||
} else {
|
||||
gui.State.Panels.Main.ObjectKey = key
|
||||
}
|
||||
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Services.SelectedLine, len(gui.State.Services), v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mainView := gui.getMainView()
|
||||
|
||||
mainView.Clear()
|
||||
mainView.SetOrigin(0, 0)
|
||||
mainView.SetCursor(0, 0)
|
||||
|
||||
switch gui.getServiceContexts()[gui.State.Panels.Services.ContextIndex] {
|
||||
case "logs":
|
||||
if err := gui.renderServiceLogs(mainView, service); err != nil {
|
||||
return err
|
||||
}
|
||||
case "config":
|
||||
if err := gui.renderServiceConfig(mainView, service); err != nil {
|
||||
return err
|
||||
}
|
||||
case "stats":
|
||||
if err := gui.renderServiceStats(mainView, service); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New("Unknown context for services panel")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) renderServiceConfig(mainView *gocui.View, service *commands.Service) error {
|
||||
mainView.Autoscroll = false
|
||||
mainView.Title = "Config"
|
||||
|
||||
gui.T.NewTask(func(stop chan struct{}) {
|
||||
// TODO: actually show service config
|
||||
data, err := json.MarshalIndent(&service.Container.Container, "", " ")
|
||||
if err != nil {
|
||||
gui.Log.Error(err)
|
||||
return
|
||||
}
|
||||
gui.renderString(gui.g, "main", string(data))
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) renderServiceStats(mainView *gocui.View, service *commands.Service) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) renderServiceLogs(mainView *gocui.View, service *commands.Service) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleServicesNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
panelState := gui.State.Panels.Services
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Services), false)
|
||||
|
||||
return gui.handleServiceSelect(gui.g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleServicesPrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
panelState := gui.State.Panels.Services
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Services), true)
|
||||
|
||||
return gui.handleServiceSelect(gui.g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleServicesPrevContext(g *gocui.Gui, v *gocui.View) error {
|
||||
contexts := gui.getServiceContexts()
|
||||
if gui.State.Panels.Services.ContextIndex >= len(contexts)-1 {
|
||||
gui.State.Panels.Services.ContextIndex = 0
|
||||
} else {
|
||||
gui.State.Panels.Services.ContextIndex++
|
||||
}
|
||||
|
||||
gui.handleServiceSelect(gui.g, v)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleServicesNextContext(g *gocui.Gui, v *gocui.View) error {
|
||||
contexts := gui.getServiceContexts()
|
||||
if gui.State.Panels.Services.ContextIndex <= 0 {
|
||||
gui.State.Panels.Services.ContextIndex = len(contexts) - 1
|
||||
} else {
|
||||
gui.State.Panels.Services.ContextIndex--
|
||||
}
|
||||
|
||||
gui.handleServiceSelect(gui.g, v)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type removeServiceOption struct {
|
||||
description string
|
||||
command string
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (r *removeServiceOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleServiceRemoveMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
service, err := gui.getSelectedService(g)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
composeCommand := gui.Config.GetUserConfig().GetString("commandTemplates.dockerCompose")
|
||||
|
||||
options := []*removeServiceOption{
|
||||
{
|
||||
description: gui.Tr.SLocalize("remove"),
|
||||
command: fmt.Sprintf("%s rm --stop --force %s", composeCommand, service.Name),
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("removeWithVolumes"),
|
||||
command: fmt.Sprintf("%s rm --stop --force -v %s", composeCommand, service.Name),
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("cancel"),
|
||||
},
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
if options[index].command == "" {
|
||||
return nil
|
||||
}
|
||||
if err := gui.OSCommand.RunCommand(options[index].command); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshContainersAndServices()
|
||||
}
|
||||
|
||||
return gui.createMenu("", options, len(options), handleMenuPress)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleServiceStop(g *gocui.Gui, v *gocui.View) error {
|
||||
service, err := gui.getSelectedService(g)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.createConfirmationPanel(gui.g, v, gui.Tr.SLocalize("Confirm"), gui.Tr.SLocalize("StopService"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("StoppingStatus"), func() error {
|
||||
if err := service.Stop(); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshContainersAndServices()
|
||||
})
|
||||
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleServiceRestart(g *gocui.Gui, v *gocui.View) error {
|
||||
service, err := gui.getSelectedService(g)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("RestartingStatus"), func() error {
|
||||
if err := service.Restart(); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshContainersAndServices()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleServiceAttach(g *gocui.Gui, v *gocui.View) error {
|
||||
service, err := gui.getSelectedService(g)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := service.Attach()
|
||||
|
||||
gui.SubProcess = c
|
||||
return gui.Errors.ErrSubProcess
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package tasks
|
||||
|
||||
import "sync"
|
||||
|
||||
type TaskManager struct {
|
||||
waitingTasks []*Task
|
||||
currentTask *Task
|
||||
waitingMutex sync.Mutex
|
||||
}
|
||||
|
||||
type Task struct {
|
||||
stop chan struct{}
|
||||
notifyStopped chan struct{}
|
||||
}
|
||||
|
||||
func NewTaskManager() *TaskManager {
|
||||
return &TaskManager{}
|
||||
}
|
||||
|
||||
func (t *TaskManager) NewTask(f func(stop chan struct{})) error {
|
||||
t.waitingMutex.Lock()
|
||||
defer t.waitingMutex.Unlock()
|
||||
|
||||
if t.currentTask != nil {
|
||||
t.currentTask.Stop()
|
||||
}
|
||||
|
||||
stop := make(chan struct{}, 1) // we don't want to block on this in case the task already returned
|
||||
notifyStopped := make(chan struct{})
|
||||
|
||||
t.currentTask = &Task{
|
||||
stop: stop,
|
||||
notifyStopped: notifyStopped,
|
||||
}
|
||||
|
||||
go func() {
|
||||
f(stop)
|
||||
notifyStopped <- struct{}{}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Task) Stop() {
|
||||
t.stop <- struct{}{}
|
||||
<-t.notifyStopped
|
||||
return
|
||||
}
|
Loading…
Reference in New Issue