2017-01-04 23:13:17 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2017-12-13 01:20:14 +00:00
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
2017-02-07 03:33:09 +00:00
|
|
|
"github.com/bcicen/ctop/config"
|
2017-06-08 14:51:02 +00:00
|
|
|
"github.com/bcicen/ctop/container"
|
2017-01-04 23:13:17 +00:00
|
|
|
"github.com/bcicen/ctop/widgets"
|
2017-02-15 07:40:16 +00:00
|
|
|
"github.com/bcicen/ctop/widgets/menu"
|
2017-01-04 23:13:17 +00:00
|
|
|
ui "github.com/gizak/termui"
|
|
|
|
)
|
|
|
|
|
2018-01-11 14:23:28 +00:00
|
|
|
// MenuFn executes a menu window, returning the next menu or nil
|
|
|
|
type MenuFn func() MenuFn
|
|
|
|
|
2017-02-15 07:40:16 +00:00
|
|
|
var helpDialog = []menu.Item{
|
2018-01-10 21:10:10 +00:00
|
|
|
{"<enter> - open container menu", ""},
|
|
|
|
{"", ""},
|
2017-12-08 11:35:04 +00:00
|
|
|
{"[a] - toggle display of all containers", ""},
|
|
|
|
{"[f] - filter displayed containers", ""},
|
|
|
|
{"[h] - open this help dialog", ""},
|
|
|
|
{"[H] - toggle ctop header", ""},
|
|
|
|
{"[s] - select container sort field", ""},
|
|
|
|
{"[r] - reverse container sort order", ""},
|
2018-01-10 21:10:10 +00:00
|
|
|
{"[o] - open single view", ""},
|
|
|
|
{"[l] - view container logs ([t] to toggle timestamp when open)", ""},
|
2018-01-11 16:15:18 +00:00
|
|
|
{"[S] - save current configuration to file", ""},
|
2017-12-08 11:35:04 +00:00
|
|
|
{"[q] - exit ctop", ""},
|
2017-01-04 23:13:17 +00:00
|
|
|
}
|
|
|
|
|
2018-01-11 14:23:28 +00:00
|
|
|
func HelpMenu() MenuFn {
|
2017-03-07 01:47:07 +00:00
|
|
|
ui.Clear()
|
2017-02-18 03:37:00 +00:00
|
|
|
ui.DefaultEvtStream.ResetHandlers()
|
|
|
|
defer ui.DefaultEvtStream.ResetHandlers()
|
2017-02-13 03:22:32 +00:00
|
|
|
|
2017-02-15 07:40:16 +00:00
|
|
|
m := menu.NewMenu()
|
2017-01-04 23:13:17 +00:00
|
|
|
m.BorderLabel = "Help"
|
2017-02-15 07:40:16 +00:00
|
|
|
m.AddItems(helpDialog...)
|
2018-02-02 15:21:33 +00:00
|
|
|
ui.Handle("/sys/wnd/resize", func(e ui.Event) {
|
|
|
|
ui.Clear()
|
|
|
|
ui.Render(m)
|
|
|
|
})
|
2017-01-04 23:13:17 +00:00
|
|
|
ui.Handle("/sys/kbd/", func(ui.Event) {
|
|
|
|
ui.StopLoop()
|
|
|
|
})
|
|
|
|
ui.Loop()
|
2018-01-11 14:23:28 +00:00
|
|
|
return nil
|
2017-01-04 23:13:17 +00:00
|
|
|
}
|
|
|
|
|
2018-01-11 14:23:28 +00:00
|
|
|
func FilterMenu() MenuFn {
|
2017-02-13 03:22:32 +00:00
|
|
|
ui.DefaultEvtStream.ResetHandlers()
|
2017-02-18 03:37:00 +00:00
|
|
|
defer ui.DefaultEvtStream.ResetHandlers()
|
2017-02-13 03:22:32 +00:00
|
|
|
|
2017-01-21 18:15:29 +00:00
|
|
|
i := widgets.NewInput()
|
|
|
|
i.BorderLabel = "Filter"
|
2017-02-13 03:22:32 +00:00
|
|
|
i.SetY(ui.TermHeight() - i.Height)
|
2017-03-12 21:32:33 +00:00
|
|
|
i.Data = config.GetVal("filterStr")
|
2017-01-21 18:15:29 +00:00
|
|
|
ui.Render(i)
|
2017-03-07 01:47:07 +00:00
|
|
|
|
|
|
|
// refresh container rows on input
|
|
|
|
stream := i.Stream()
|
|
|
|
go func() {
|
|
|
|
for s := range stream {
|
|
|
|
config.Update("filterStr", s)
|
2017-03-08 00:10:38 +00:00
|
|
|
RefreshDisplay()
|
2017-03-07 01:47:07 +00:00
|
|
|
ui.Render(i)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2017-01-21 18:15:29 +00:00
|
|
|
i.InputHandlers()
|
2017-03-12 21:32:33 +00:00
|
|
|
ui.Handle("/sys/kbd/<escape>", func(ui.Event) {
|
|
|
|
config.Update("filterStr", "")
|
|
|
|
ui.StopLoop()
|
|
|
|
})
|
2017-01-21 18:15:29 +00:00
|
|
|
ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
|
2017-02-07 03:33:09 +00:00
|
|
|
config.Update("filterStr", i.Data)
|
2017-01-21 18:15:29 +00:00
|
|
|
ui.StopLoop()
|
|
|
|
})
|
|
|
|
ui.Loop()
|
2018-01-11 14:23:28 +00:00
|
|
|
return nil
|
2017-01-21 18:15:29 +00:00
|
|
|
}
|
|
|
|
|
2018-01-11 14:23:28 +00:00
|
|
|
func SortMenu() MenuFn {
|
2017-03-07 01:47:07 +00:00
|
|
|
ui.Clear()
|
2017-02-18 03:37:00 +00:00
|
|
|
ui.DefaultEvtStream.ResetHandlers()
|
|
|
|
defer ui.DefaultEvtStream.ResetHandlers()
|
2017-02-13 03:22:32 +00:00
|
|
|
|
2017-02-15 07:40:16 +00:00
|
|
|
m := menu.NewMenu()
|
2017-01-04 23:13:17 +00:00
|
|
|
m.Selectable = true
|
2017-02-15 06:01:35 +00:00
|
|
|
m.SortItems = true
|
2017-01-04 23:13:17 +00:00
|
|
|
m.BorderLabel = "Sort Field"
|
2017-01-09 15:02:34 +00:00
|
|
|
|
2017-06-08 14:51:02 +00:00
|
|
|
for _, field := range container.SortFields() {
|
2017-02-15 07:40:16 +00:00
|
|
|
m.AddItems(menu.Item{field, ""})
|
|
|
|
}
|
2017-02-15 06:01:35 +00:00
|
|
|
|
2017-01-09 15:02:34 +00:00
|
|
|
// set cursor position to current sort field
|
2017-02-19 03:54:24 +00:00
|
|
|
m.SetCursor(config.GetVal("sortField"))
|
2017-01-09 15:02:34 +00:00
|
|
|
|
2017-03-14 22:34:09 +00:00
|
|
|
HandleKeys("up", m.Up)
|
|
|
|
HandleKeys("down", m.Down)
|
|
|
|
HandleKeys("exit", ui.StopLoop)
|
|
|
|
|
2017-01-04 23:13:17 +00:00
|
|
|
ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
|
2017-02-15 07:40:16 +00:00
|
|
|
config.Update("sortField", m.SelectedItem().Val)
|
2017-01-04 23:13:17 +00:00
|
|
|
ui.StopLoop()
|
|
|
|
})
|
2017-03-14 22:34:09 +00:00
|
|
|
|
|
|
|
ui.Render(m)
|
2017-01-04 23:13:17 +00:00
|
|
|
ui.Loop()
|
2018-01-11 14:23:28 +00:00
|
|
|
return nil
|
2017-01-04 23:13:17 +00:00
|
|
|
}
|
2017-11-20 11:09:36 +00:00
|
|
|
|
2018-01-11 14:23:28 +00:00
|
|
|
func ContainerMenu() MenuFn {
|
2017-11-20 11:09:36 +00:00
|
|
|
c := cursor.Selected()
|
|
|
|
if c == nil {
|
2018-01-11 14:23:28 +00:00
|
|
|
return nil
|
2017-11-20 11:09:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ui.DefaultEvtStream.ResetHandlers()
|
|
|
|
defer ui.DefaultEvtStream.ResetHandlers()
|
|
|
|
|
|
|
|
m := menu.NewMenu()
|
|
|
|
m.Selectable = true
|
|
|
|
m.BorderLabel = "Menu"
|
2018-01-11 13:26:58 +00:00
|
|
|
|
2018-01-10 21:10:10 +00:00
|
|
|
items := []menu.Item{
|
|
|
|
menu.Item{Val: "single", Label: "single view"},
|
|
|
|
menu.Item{Val: "logs", Label: "log view"},
|
|
|
|
}
|
2018-01-11 13:26:58 +00:00
|
|
|
|
2017-11-20 11:09:36 +00:00
|
|
|
if c.Meta["state"] == "running" {
|
|
|
|
items = append(items, menu.Item{Val: "stop", Label: "stop"})
|
2018-06-22 07:41:16 +00:00
|
|
|
items = append(items, menu.Item{Val: "pause", Label: "pause"})
|
|
|
|
items = append(items, menu.Item{Val: "restart", Label: "restart"})
|
2017-11-20 11:09:36 +00:00
|
|
|
}
|
2018-01-11 14:40:21 +00:00
|
|
|
if c.Meta["state"] == "exited" || c.Meta["state"] == "created" {
|
2017-11-20 11:09:36 +00:00
|
|
|
items = append(items, menu.Item{Val: "start", Label: "start"})
|
|
|
|
items = append(items, menu.Item{Val: "remove", Label: "remove"})
|
|
|
|
}
|
2018-06-22 07:41:16 +00:00
|
|
|
if c.Meta["state"] == "paused" {
|
|
|
|
items = append(items, menu.Item{Val: "unpause", Label: "unpause"})
|
|
|
|
}
|
2017-11-20 11:09:36 +00:00
|
|
|
items = append(items, menu.Item{Val: "cancel", Label: "cancel"})
|
|
|
|
|
|
|
|
m.AddItems(items...)
|
|
|
|
ui.Render(m)
|
|
|
|
|
2018-01-11 14:23:28 +00:00
|
|
|
var nextMenu MenuFn
|
2017-11-20 11:09:36 +00:00
|
|
|
HandleKeys("up", m.Up)
|
|
|
|
HandleKeys("down", m.Down)
|
|
|
|
ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
|
|
|
|
switch m.SelectedItem().Val {
|
2018-01-10 18:45:14 +00:00
|
|
|
case "single":
|
2018-01-11 14:23:28 +00:00
|
|
|
nextMenu = SingleView
|
2018-01-10 21:10:10 +00:00
|
|
|
case "logs":
|
2018-01-11 14:23:28 +00:00
|
|
|
nextMenu = LogMenu
|
2017-11-20 11:09:36 +00:00
|
|
|
case "start":
|
2018-01-11 14:23:28 +00:00
|
|
|
nextMenu = Confirm(confirmTxt("start", c.GetMeta("name")), c.Start)
|
2017-11-20 11:09:36 +00:00
|
|
|
case "stop":
|
2018-01-11 14:23:28 +00:00
|
|
|
nextMenu = Confirm(confirmTxt("stop", c.GetMeta("name")), c.Stop)
|
2017-11-20 11:09:36 +00:00
|
|
|
case "remove":
|
2018-01-11 14:23:28 +00:00
|
|
|
nextMenu = Confirm(confirmTxt("remove", c.GetMeta("name")), c.Remove)
|
2018-06-22 07:41:16 +00:00
|
|
|
case "pause":
|
|
|
|
nextMenu = Confirm(confirmTxt("pause", c.GetMeta("name")), c.Pause)
|
|
|
|
case "unpause":
|
|
|
|
nextMenu = Confirm(confirmTxt("unpause", c.GetMeta("name")), c.Unpause)
|
|
|
|
case "restart":
|
|
|
|
nextMenu = Confirm(confirmTxt("restart", c.GetMeta("name")), c.Restart)
|
2017-11-20 11:09:36 +00:00
|
|
|
}
|
2018-01-11 14:23:28 +00:00
|
|
|
ui.StopLoop()
|
2017-11-20 11:09:36 +00:00
|
|
|
})
|
|
|
|
ui.Handle("/sys/kbd/", func(ui.Event) {
|
|
|
|
ui.StopLoop()
|
|
|
|
})
|
|
|
|
ui.Loop()
|
2018-01-11 14:23:28 +00:00
|
|
|
return nextMenu
|
2017-11-20 11:09:36 +00:00
|
|
|
}
|
2017-11-25 18:30:50 +00:00
|
|
|
|
2018-01-11 14:23:28 +00:00
|
|
|
func LogMenu() MenuFn {
|
2017-11-25 18:30:50 +00:00
|
|
|
|
|
|
|
c := cursor.Selected()
|
|
|
|
if c == nil {
|
2018-01-11 14:23:28 +00:00
|
|
|
return nil
|
2017-11-25 18:30:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ui.DefaultEvtStream.ResetHandlers()
|
|
|
|
defer ui.DefaultEvtStream.ResetHandlers()
|
|
|
|
|
|
|
|
logs, quit := logReader(c)
|
|
|
|
m := widgets.NewTextView(logs)
|
2018-05-10 09:53:59 +00:00
|
|
|
m.BorderLabel = fmt.Sprintf("Logs [%s]", c.GetMeta("name"))
|
2017-11-25 18:30:50 +00:00
|
|
|
ui.Render(m)
|
|
|
|
|
2017-11-28 13:55:29 +00:00
|
|
|
ui.Handle("/sys/wnd/resize", func(e ui.Event) {
|
|
|
|
m.Resize()
|
2017-12-08 11:35:04 +00:00
|
|
|
})
|
|
|
|
ui.Handle("/sys/kbd/t", func(ui.Event) {
|
|
|
|
m.Toggle()
|
2017-11-28 13:55:29 +00:00
|
|
|
})
|
2017-11-25 18:30:50 +00:00
|
|
|
ui.Handle("/sys/kbd/", func(ui.Event) {
|
|
|
|
quit <- true
|
|
|
|
ui.StopLoop()
|
|
|
|
})
|
|
|
|
ui.Loop()
|
2018-01-11 14:23:28 +00:00
|
|
|
return nil
|
2017-11-25 18:30:50 +00:00
|
|
|
}
|
|
|
|
|
2018-01-11 14:23:28 +00:00
|
|
|
// Create a confirmation dialog with a given description string and
|
|
|
|
// func to perform if confirmed
|
|
|
|
func Confirm(txt string, fn func()) MenuFn {
|
|
|
|
menu := func() MenuFn {
|
|
|
|
ui.DefaultEvtStream.ResetHandlers()
|
|
|
|
defer ui.DefaultEvtStream.ResetHandlers()
|
|
|
|
|
|
|
|
m := menu.NewMenu()
|
|
|
|
m.Selectable = true
|
|
|
|
m.BorderLabel = "Confirm"
|
|
|
|
m.SubText = txt
|
|
|
|
|
|
|
|
items := []menu.Item{
|
|
|
|
menu.Item{Val: "cancel", Label: "[c]ancel"},
|
|
|
|
menu.Item{Val: "yes", Label: "[y]es"},
|
|
|
|
}
|
2018-01-11 13:26:58 +00:00
|
|
|
|
2018-01-11 14:23:28 +00:00
|
|
|
var response bool
|
2018-01-11 13:26:58 +00:00
|
|
|
|
2018-01-11 14:23:28 +00:00
|
|
|
m.AddItems(items...)
|
|
|
|
ui.Render(m)
|
2018-01-11 13:26:58 +00:00
|
|
|
|
2018-01-11 14:23:28 +00:00
|
|
|
yes := func() {
|
|
|
|
response = true
|
|
|
|
ui.StopLoop()
|
|
|
|
}
|
2018-01-11 13:26:58 +00:00
|
|
|
|
2018-01-11 14:23:28 +00:00
|
|
|
no := func() {
|
|
|
|
response = false
|
|
|
|
ui.StopLoop()
|
|
|
|
}
|
2018-01-11 13:26:58 +00:00
|
|
|
|
2018-01-11 14:23:28 +00:00
|
|
|
HandleKeys("up", m.Up)
|
|
|
|
HandleKeys("down", m.Down)
|
|
|
|
HandleKeys("exit", no)
|
|
|
|
ui.Handle("/sys/kbd/c", func(ui.Event) { no() })
|
|
|
|
ui.Handle("/sys/kbd/y", func(ui.Event) { yes() })
|
|
|
|
|
|
|
|
ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
|
|
|
|
switch m.SelectedItem().Val {
|
|
|
|
case "cancel":
|
|
|
|
no()
|
|
|
|
case "yes":
|
|
|
|
yes()
|
|
|
|
}
|
|
|
|
})
|
2018-01-11 13:26:58 +00:00
|
|
|
|
2018-01-11 14:23:28 +00:00
|
|
|
ui.Loop()
|
|
|
|
if response {
|
|
|
|
fn()
|
2018-01-11 13:26:58 +00:00
|
|
|
}
|
2018-01-11 14:23:28 +00:00
|
|
|
return nil
|
2018-01-11 13:26:58 +00:00
|
|
|
}
|
2018-01-11 14:23:28 +00:00
|
|
|
return menu
|
2018-01-11 13:26:58 +00:00
|
|
|
}
|
|
|
|
|
2017-12-08 11:35:04 +00:00
|
|
|
type toggleLog struct {
|
|
|
|
timestamp time.Time
|
|
|
|
message string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *toggleLog) Toggle(on bool) string {
|
|
|
|
if on {
|
|
|
|
return fmt.Sprintf("%s %s", t.timestamp.Format("2006-01-02T15:04:05.999Z07:00"), t.message)
|
|
|
|
}
|
|
|
|
return t.message
|
|
|
|
}
|
|
|
|
|
|
|
|
func logReader(container *container.Container) (logs chan widgets.ToggleText, quit chan bool) {
|
2017-11-25 18:30:50 +00:00
|
|
|
|
|
|
|
logCollector := container.Logs()
|
|
|
|
stream := logCollector.Stream()
|
2017-12-08 11:35:04 +00:00
|
|
|
logs = make(chan widgets.ToggleText)
|
2017-11-25 18:30:50 +00:00
|
|
|
quit = make(chan bool)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
2017-11-28 13:40:43 +00:00
|
|
|
case log := <-stream:
|
2017-12-08 11:35:04 +00:00
|
|
|
logs <- &toggleLog{timestamp: log.Timestamp, message: log.Message}
|
2017-11-28 13:40:43 +00:00
|
|
|
case <-quit:
|
2017-11-25 18:30:50 +00:00
|
|
|
logCollector.Stop()
|
|
|
|
close(logs)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
return
|
|
|
|
}
|
2018-01-11 14:23:28 +00:00
|
|
|
|
|
|
|
func confirmTxt(a, n string) string { return fmt.Sprintf("%s container %s?", a, n) }
|