From d46ce783c226512189092498b60473f5512892d9 Mon Sep 17 00:00:00 2001 From: Bradley Cicenas Date: Thu, 11 Jan 2018 18:19:01 +0000 Subject: [PATCH] add statusline widget, status messages to logging --- config/file.go | 14 ++++---- grid.go | 23 ++++++++++++- logging/main.go | 27 +++++++++++++++ main.go | 5 ++- widgets/header.go | 4 +-- widgets/status.go | 87 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 149 insertions(+), 11 deletions(-) create mode 100644 widgets/status.go diff --git a/config/file.go b/config/file.go index 513054a..e344e8c 100644 --- a/config/file.go +++ b/config/file.go @@ -53,10 +53,10 @@ func Read() error { return nil } -func Write() error { - path, err := getConfigPath() +func Write() (path string, err error) { + path, err = getConfigPath() if err != nil { - return err + return path, err } cfgdir := basedir(path) @@ -64,22 +64,22 @@ func Write() error { if _, err := os.Stat(cfgdir); err != nil { err = os.MkdirAll(cfgdir, 0755) if err != nil { - return fmt.Errorf("failed to create config dir [%s]: %s", cfgdir, err) + return path, fmt.Errorf("failed to create config dir [%s]: %s", cfgdir, err) } } file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644) if err != nil { - return fmt.Errorf("failed to open config for writing: %s", err) + return path, fmt.Errorf("failed to open config for writing: %s", err) } writer := toml.NewEncoder(file) err = writer.Encode(exportConfig()) if err != nil { - return fmt.Errorf("failed to write config: %s", err) + return path, fmt.Errorf("failed to write config: %s", err) } - return nil + return path, nil } // determine config path from environment diff --git a/grid.go b/grid.go index bb15263..6c11866 100644 --- a/grid.go +++ b/grid.go @@ -17,6 +17,7 @@ func RedrawRows(clr bool) { header.SetFilter(config.GetVal("filterStr")) y += header.Height() } + cGrid.SetY(y) for _, c := range cursor.filtered { @@ -32,6 +33,7 @@ func RedrawRows(clr bool) { } cGrid.Align() ui.Render(cGrid) + } func SingleView() MenuFn { @@ -82,6 +84,7 @@ func Display() bool { // initial draw header.Align() + status.Align() cursor.RefreshContainers() RedrawRows(true) @@ -132,7 +135,13 @@ func Display() bool { ui.StopLoop() }) ui.Handle("/sys/kbd/S", func(ui.Event) { - config.Write() + path, err := config.Write() + if err == nil { + log.Statusf("wrote config to %s", path) + } else { + log.StatusErr(err) + } + ui.StopLoop() }) ui.Handle("/timer/1s", func(e ui.Event) { @@ -141,6 +150,7 @@ func Display() bool { ui.Handle("/sys/wnd/resize", func(e ui.Event) { header.Align() + status.Align() cursor.ScrollPage() cGrid.SetWidth(ui.TermWidth()) log.Infof("resize: width=%v max-rows=%v", cGrid.Width, cGrid.MaxRows()) @@ -149,6 +159,17 @@ func Display() bool { ui.Loop() + if log.StatusQueued() { + for sm := range log.FlushStatus() { + if sm.IsError { + status.ShowErr(sm.Text) + } else { + status.Show(sm.Text) + } + } + return false + } + if menu != nil { for menu != nil { menu = menu() diff --git a/logging/main.go b/logging/main.go index 44927e8..9e9dd74 100644 --- a/logging/main.go +++ b/logging/main.go @@ -1,6 +1,7 @@ package logging import ( + "fmt" "os" "time" @@ -20,11 +21,36 @@ var ( ) ) +type statusMsg struct { + Text string + IsError bool +} + type CTopLogger struct { *logging.Logger backend *logging.MemoryBackend + sLog []statusMsg } +func (c *CTopLogger) FlushStatus() chan statusMsg { + ch := make(chan statusMsg) + go func() { + for _, sm := range c.sLog { + ch <- sm + } + close(ch) + c.sLog = []statusMsg{} + }() + return ch +} + +func (c *CTopLogger) StatusQueued() bool { return len(c.sLog) > 0 } +func (c *CTopLogger) Status(s string) { c.addStatus(statusMsg{s, false}) } +func (c *CTopLogger) StatusErr(err error) { c.addStatus(statusMsg{err.Error(), true}) } +func (c *CTopLogger) addStatus(sm statusMsg) { c.sLog = append(c.sLog, sm) } + +func (c *CTopLogger) Statusf(s string, a ...interface{}) { c.Status(fmt.Sprintf(s, a...)) } + func Init() *CTopLogger { if Log == nil { logging.SetFormatter(format) // setup default formatter @@ -32,6 +58,7 @@ func Init() *CTopLogger { Log = &CTopLogger{ logging.MustGetLogger("ctop"), logging.NewMemoryBackend(size), + []statusMsg{}, } if debugMode() { diff --git a/main.go b/main.go index b6ee215..2301482 100644 --- a/main.go +++ b/main.go @@ -25,6 +25,7 @@ var ( cursor *GridCursor cGrid *compact.CompactGrid header *widgets.CTopHeader + status *widgets.StatusLine versionStr = fmt.Sprintf("ctop version %v, build %v %v", version, build, goVersion) ) @@ -59,8 +60,9 @@ func main() { // init logger log = logging.Init() - // init global config + // init global config and read config file if exists config.Init() + config.Read() // override default config values with command line flags if *filterFlag != "" { @@ -102,6 +104,7 @@ func main() { cursor = &GridCursor{cSource: conn} cGrid = compact.NewCompactGrid() header = widgets.NewCTopHeader() + status = widgets.NewStatusLine() for { exit := Display() diff --git a/widgets/header.go b/widgets/header.go index 89134c2..4a96f8f 100644 --- a/widgets/header.go +++ b/widgets/header.go @@ -17,8 +17,8 @@ type CTopHeader struct { func NewCTopHeader() *CTopHeader { return &CTopHeader{ Time: headerPar(2, timeStr()), - Count: headerPar(27, "-"), - Filter: headerPar(47, ""), + Count: headerPar(24, "-"), + Filter: headerPar(40, ""), bg: headerBg(), } } diff --git a/widgets/status.go b/widgets/status.go new file mode 100644 index 0000000..ebd6348 --- /dev/null +++ b/widgets/status.go @@ -0,0 +1,87 @@ +package widgets + +import ( + ui "github.com/gizak/termui" +) + +var ( + statusHeight = 1 + statusIter = 3 +) + +type StatusLine struct { + Message *ui.Par + bg *ui.Par +} + +func NewStatusLine() *StatusLine { + p := ui.NewPar("") + p.X = 2 + p.Border = false + p.Height = statusHeight + p.Bg = ui.ThemeAttr("header.bg") + p.TextFgColor = ui.ThemeAttr("header.fg") + p.TextBgColor = ui.ThemeAttr("header.bg") + return &StatusLine{ + Message: p, + bg: statusBg(), + } +} + +func (sl *StatusLine) Display() { + ui.DefaultEvtStream.ResetHandlers() + defer ui.DefaultEvtStream.ResetHandlers() + + iter := statusIter + ui.Handle("/sys/kbd/", func(ui.Event) { + ui.StopLoop() + }) + ui.Handle("/timer/1s", func(ui.Event) { + iter-- + if iter <= 0 { + ui.StopLoop() + } + }) + + ui.Render(sl) + ui.Loop() +} + +// change given message on the status line +func (sl *StatusLine) Show(s string) { + sl.Message.TextFgColor = ui.ThemeAttr("header.fg") + sl.Message.Text = s + sl.Display() +} + +func (sl *StatusLine) ShowErr(s string) { + sl.Message.TextFgColor = ui.ThemeAttr("status.danger") + sl.Message.Text = s + sl.Display() +} + +func (sl *StatusLine) Buffer() ui.Buffer { + buf := ui.NewBuffer() + buf.Merge(sl.bg.Buffer()) + buf.Merge(sl.Message.Buffer()) + return buf +} + +func (sl *StatusLine) Align() { + sl.bg.SetWidth(ui.TermWidth() - 1) + sl.Message.SetWidth(ui.TermWidth() - 2) + + sl.bg.Y = ui.TermHeight() - 1 + sl.Message.Y = ui.TermHeight() - 1 +} + +func (sl *StatusLine) Height() int { return statusHeight } + +func statusBg() *ui.Par { + bg := ui.NewPar("") + bg.X = 1 + bg.Height = statusHeight + bg.Border = false + bg.Bg = ui.ThemeAttr("header.bg") + return bg +}