From 687e6f098e05dee368da0c8a6129456ed3dc1515 Mon Sep 17 00:00:00 2001 From: sqshq Date: Sat, 16 Feb 2019 20:54:48 -0500 Subject: [PATCH] implemented components size/position save in yaml, defaults for optional settings --- config.yml | 122 +++++++++++++---------------------- config/config.go | 89 +++++++++++++++++++++---- config/default.go | 43 +++++++----- config/validator.go | 2 +- data/item.go | 10 +-- data/sampler.go | 4 +- event/handler.go | 23 +++++-- main.go | 8 +-- widgets/component.go | 28 ++------ widgets/layout.go | 23 +++---- widgets/menu.go | 3 +- widgets/runchart/runchart.go | 8 ++- 12 files changed, 206 insertions(+), 157 deletions(-) diff --git a/config.yml b/config.yml index 4b82df3..80a24cf 100644 --- a/config.yml +++ b/config.yml @@ -1,78 +1,48 @@ theme: dark runcharts: - - title: SEARCH ENGINE RESPONSE TIME (sec) - items: - - label: GOOGLE - script: curl -o /dev/null -s -w '%{time_total}' https://www.google.com/ - - label: YAHOO - script: curl -o /dev/null -s -w '%{time_total}' https://search.yahoo.com/ - - label: BING - script: curl -o /dev/null -s -w '%{time_total}' https://www.bing.com/ - refresh-rate-ms: 200 - decimal-places: 3 - alert: - value: - more-than: 0.231 - less-than: 0.123 - equal: 0.144 - indicator: - terminal: true - beep: true - legend: - enabled: true - details: true - position: - x: 0 - y: 0 - size: - x: 30 - y: 15 - - title: SEARCH ENGINE RESPONSE TIME 2 (sec) - items: - - label: GOOGLE - script: curl -o /dev/null -s -w '%{time_total}' https://www.google.com/ - - label: YAHOO - script: curl -o /dev/null -s -w '%{time_total}' https://search.yahoo.com/ - - label: BING - script: curl -o /dev/null -s -w '%{time_total}' https://www.bing.com/ - refresh-rate-ms: 5000 - decimal-places: 3 - alert: - value: - more-than: 0.231 - less-than: 0.123 - equal: 0.144 - indicator: - terminal: true - beep: true - legend: - enabled: true - details: false - position: - x: 0 - y: 15 - size: - x: 15 - y: 15 - - title: MONGO COLLECTIONS COUNT - items: - - label: POSTS - script: mongo --quiet --host=localhost blog --eval "db.getCollection('post').find({}).size()" - position: - x: 15 - y: 15 - size: - x: 15 - y: 15 -barcharts: - none -sparklines: - none -gauges: - none -lists: - none -textboxes: - none -asciiboxes: - none \ No newline at end of file +- title: SEARCH ENGINE RESPONSE TIME (sec) + precision: 3 + position: + w: 0 + h: 0 + size: + w: 30 + h: 20 + legend: + enabled: true + details: true + items: + - script: curl -o /dev/null -s -w '%{time_total}' https://www.google.com/ + label: GOOGLE + - script: curl -o /dev/null -s -w '%{time_total}' https://search.yahoo.com/ + label: YAHOO + - script: curl -o /dev/null -s -w '%{time_total}' https://www.bing.com/ + label: BING +- title: SEARCH ENGINE RESPONSE TIME 2 (sec) + refresh-rate-ms: 5000 + position: + w: 0 + h: 20 + size: + w: 15 + h: 10 + legend: + enabled: true + details: false + items: + - script: curl -o /dev/null -s -w '%{time_total}' https://www.google.com/ + label: GOOGLE + - script: curl -o /dev/null -s -w '%{time_total}' https://search.yahoo.com/ + label: YAHOO + - script: curl -o /dev/null -s -w '%{time_total}' https://www.bing.com/ + label: BING +- title: MONGO COLLECTIONS COUNT + position: + w: 15 + h: 20 + size: + w: 15 + h: 10 + items: + - script: mongo --quiet --host=localhost blog --eval "db.getCollection('post').find({}).size()" + label: POSTS diff --git a/config/config.go b/config/config.go index 10e4594..cf0758a 100644 --- a/config/config.go +++ b/config/config.go @@ -1,9 +1,9 @@ package config import ( + "fmt" "github.com/sqshq/sampler/console" "github.com/sqshq/sampler/data" - . "github.com/sqshq/sampler/widgets" "gopkg.in/yaml.v2" "io/ioutil" "log" @@ -11,18 +11,22 @@ import ( ) type Config struct { - Theme console.Theme `yaml:"theme"` - RunCharts []RunChartConfig `yaml:"runcharts"` + Theme *console.Theme `yaml:"theme,omitempty"` + RunCharts []RunChartConfig `yaml:"runcharts,omitempty"` +} + +type ComponentConfig struct { + Title string `yaml:"title"` + RefreshRateMs *int `yaml:"refresh-rate-ms,omitempty"` + Precision *int `yaml:"precision,omitempty"` + Position Position `yaml:"position"` + Size Size `yaml:"size"` } type RunChartConfig struct { - Title string `yaml:"title"` - Items []data.Item `yaml:"items"` - Position Position `yaml:"position"` - Size Size `yaml:"size"` - RefreshRateMs int `yaml:"refresh-rate-ms"` - Precision int `yaml:"decimal-places"` - Legend LegendConfig `yaml:"legend"` + ComponentConfig `yaml:",inline"` + Legend *LegendConfig `yaml:"legend,omitempty"` + Items []data.Item `yaml:"items"` } type LegendConfig struct { @@ -30,6 +34,30 @@ type LegendConfig struct { Details bool `yaml:"details"` } +type Position struct { + X int `yaml:"w"` + Y int `yaml:"h"` +} + +type Size struct { + X int `yaml:"w"` + Y int `yaml:"h"` +} + +type ComponentType rune + +const ( + TypeRunChart ComponentType = 0 + TypeBarChart ComponentType = 1 +) + +type ComponentSettings struct { + Type ComponentType + Title string + Size Size + Position Position +} + func Load() *Config { if len(os.Args) < 2 { @@ -39,13 +67,36 @@ func Load() *Config { cfg := readFile(os.Args[1]) cfg.validate() - cfg.setDefaultValues() - cfg.setDefaultColors() - cfg.setDefaultLayout() + cfg.setDefaults() return cfg } +func Update(settings []ComponentSettings) { + cfg := readFile(os.Args[1]) + for _, s := range settings { + componentConfig := cfg.findComponent(s.Type, s.Title) + componentConfig.Size = s.Size + componentConfig.Position = s.Position + } + saveFile(cfg) +} + +func (c *Config) findComponent(componentType ComponentType, componentTitle string) *ComponentConfig { + + switch componentType { + case TypeRunChart: + for i, component := range c.RunCharts { + if component.Title == componentTitle { + return &c.RunCharts[i].ComponentConfig + } + } + } + + panic(fmt.Sprintf( + "Can't find component type %v with title %v", componentType, componentTitle)) +} + func readFile(location string) *Config { yamlFile, err := ioutil.ReadFile(location) @@ -62,3 +113,15 @@ func readFile(location string) *Config { return cfg } + +func saveFile(config *Config) { + file, err := yaml.Marshal(config) + if err != nil { + log.Fatalf("Can't marshal config file: %v", err) + } + + err = ioutil.WriteFile("config.yml", file, 0644) + if err != nil { + log.Fatalf("Can't save config file: %v", err) + } +} diff --git a/config/default.go b/config/default.go index 5233335..8b1e321 100644 --- a/config/default.go +++ b/config/default.go @@ -10,35 +10,48 @@ const ( defaultTheme = console.ThemeDark ) -func (self *Config) setDefaultValues() { +func (c *Config) setDefaults() { + c.setDefaultValues() + c.setDefaultColors() + c.setDefaultLayout() +} - if len(self.Theme) == 0 { - self.Theme = defaultTheme +func (c *Config) setDefaultValues() { + + if c.Theme == nil { + t := defaultTheme + c.Theme = &t } - for i, chart := range self.RunCharts { - if chart.RefreshRateMs == 0 { - chart.RefreshRateMs = defaultRefreshRateMs + for i, chart := range c.RunCharts { + if chart.RefreshRateMs == nil { + r := defaultRefreshRateMs + chart.RefreshRateMs = &r } - if chart.Precision == 0 { - chart.Precision = defaultPrecision + if chart.Precision == nil { + p := defaultPrecision + chart.Precision = &p } - self.RunCharts[i] = chart + if chart.Legend == nil { + chart.Legend = &LegendConfig{true, true} + c.RunCharts[i] = chart + } + c.RunCharts[i] = chart } } -func (config *Config) setDefaultLayout() { +func (c *Config) setDefaultLayout() { } -func (config *Config) setDefaultColors() { +func (c *Config) setDefaultColors() { - palette := console.GetPalette(config.Theme) + palette := console.GetPalette(*c.Theme) - for i, chart := range config.RunCharts { + for i, chart := range c.RunCharts { for j, item := range chart.Items { - if item.Color == 0 { - item.Color = palette.Colors[i+j] // TODO handle out of range case + if item.Color == nil { + item.Color = &palette.Colors[i+j] // TODO handle out of range case chart.Items[j] = item } } diff --git a/config/validator.go b/config/validator.go index 7f2f706..f1b5933 100644 --- a/config/validator.go +++ b/config/validator.go @@ -5,6 +5,6 @@ package config - title uniquness and mandatory within a single type of widget - label uniqueness and mandatory (if > 1 data bullets) */ -func (self *Config) validate() { +func (c *Config) validate() { } diff --git a/data/item.go b/data/item.go index 7145274..1bee9d1 100644 --- a/data/item.go +++ b/data/item.go @@ -7,14 +7,14 @@ import ( ) type Item struct { - Script string `yaml:"script"` - Label string `yaml:"label"` - Color ui.Color `yaml:"color"` + Script string `yaml:"script"` + Label string `yaml:"label"` + Color *ui.Color `yaml:"color,omitempty"` } -func (self *Item) nextValue() (value string, err error) { +func (i *Item) nextValue() (value string, err error) { - output, err := exec.Command("sh", "-c", self.Script).Output() + output, err := exec.Command("sh", "-c", i.Script).Output() if err != nil { return "", err diff --git a/data/sampler.go b/data/sampler.go index 8e8980a..c229c87 100644 --- a/data/sampler.go +++ b/data/sampler.go @@ -11,10 +11,12 @@ type Sampler struct { func NewSampler(consumer Consumer, item Item, rateMs int) Sampler { + ticker := time.NewTicker(time.Duration(rateMs * int(time.Millisecond))) sampler := Sampler{consumer, item} go func() { - for t := time.Tick(time.Duration(rateMs * int(time.Millisecond))); ; <-t { + sampler.sample() + for ; true; <-ticker.C { sampler.sample() } }() diff --git a/event/handler.go b/event/handler.go index 2c88601..ed759d1 100644 --- a/event/handler.go +++ b/event/handler.go @@ -1,6 +1,7 @@ package event import ( + "github.com/sqshq/sampler/config" "github.com/sqshq/sampler/console" "github.com/sqshq/sampler/widgets" ui "github.com/sqshq/termui" @@ -13,28 +14,38 @@ type Handler struct { ConsoleEvents <-chan ui.Event } -func (self *Handler) HandleEvents() { +func (h *Handler) HandleEvents() { pause := false for { select { - case <-self.RenderEvents: + case <-h.RenderEvents: if !pause { - ui.Render(self.Layout) + ui.Render(h.Layout) } - case e := <-self.ConsoleEvents: + case e := <-h.ConsoleEvents: switch e.ID { case console.KeyQuit, console.KeyExit: + h.handleExit() return case console.KeyPause: pause = !pause case console.SignalResize: payload := e.Payload.(ui.Resize) - self.Layout.ChangeDimensions(payload.Width, payload.Height) + h.Layout.ChangeDimensions(payload.Width, payload.Height) default: - self.Layout.HandleConsoleEvent(e.ID) + h.Layout.HandleConsoleEvent(e.ID) } } } } + +func (h *Handler) handleExit() { + var settings []config.ComponentSettings + for _, c := range h.Layout.Components { + settings = append(settings, + config.ComponentSettings{Type: c.Type, Title: c.Title, Size: c.Size, Position: c.Position}) + } + config.Update(settings) +} diff --git a/main.go b/main.go index 900df15..6ddac0d 100644 --- a/main.go +++ b/main.go @@ -24,12 +24,12 @@ func main() { for _, c := range cfg.RunCharts { legend := runchart.Legend{Enabled: c.Legend.Enabled, Details: c.Legend.Details} - chart := runchart.NewRunChart(c.Title, c.Precision, c.RefreshRateMs, legend) - layout.AddComponent(chart, c.Title, c.Position, c.Size, widgets.TypeRunChart) + chart := runchart.NewRunChart(c.Title, *c.Precision, *c.RefreshRateMs, legend) + layout.AddComponent(chart, c.Title, c.Position, c.Size, config.TypeRunChart) for _, item := range c.Items { - chart.AddLine(item.Label, item.Color) - data.NewSampler(chart, item, c.RefreshRateMs) + chart.AddLine(item.Label, *item.Color) + data.NewSampler(chart, item, *c.RefreshRateMs) } } diff --git a/widgets/component.go b/widgets/component.go index 41c5135..9980600 100644 --- a/widgets/component.go +++ b/widgets/component.go @@ -1,32 +1,16 @@ package widgets import ( - . "github.com/sqshq/termui" + "github.com/sqshq/sampler/config" + ui "github.com/sqshq/termui" ) type Component struct { - Drawable Drawable + Drawable ui.Drawable Title string - Position Position - Size Size - Type ComponentType -} - -type ComponentType rune - -const ( - TypeRunChart ComponentType = 0 - TypeBarChart ComponentType = 1 -) - -type Position struct { - X int `yaml:"x"` - Y int `yaml:"y"` -} - -type Size struct { - X int `yaml:"x"` - Y int `yaml:"y"` + Position config.Position + Size config.Size + Type config.ComponentType } func (c *Component) Move(x, y int) { diff --git a/widgets/layout.go b/widgets/layout.go index 25c1f10..6ed77b9 100644 --- a/widgets/layout.go +++ b/widgets/layout.go @@ -1,6 +1,7 @@ package widgets import ( + "github.com/sqshq/sampler/config" "github.com/sqshq/sampler/console" "github.com/sqshq/sampler/widgets/runchart" ui "github.com/sqshq/termui" @@ -8,7 +9,7 @@ import ( type Layout struct { ui.Block - components []Component + Components []Component menu *Menu mode Mode selection int @@ -37,22 +38,22 @@ func NewLayout(width, height int, menu *Menu) *Layout { return &Layout{ Block: block, - components: make([]Component, 0), + Components: make([]Component, 0), menu: menu, mode: ModeDefault, selection: 0, } } -func (l *Layout) AddComponent(drawable ui.Drawable, title string, position Position, size Size, Type ComponentType) { - l.components = append(l.components, Component{drawable, title, position, size, Type}) +func (l *Layout) AddComponent(drawable ui.Drawable, title string, position config.Position, size config.Size, Type config.ComponentType) { + l.Components = append(l.Components, Component{drawable, title, position, size, Type}) } -func (l *Layout) GetComponents(Type ComponentType) []ui.Drawable { +func (l *Layout) GetComponents(Type config.ComponentType) []ui.Drawable { var components []ui.Drawable - for _, component := range l.components { + for _, component := range l.Components { if component.Type == Type { components = append(components, component.Drawable) } @@ -133,7 +134,7 @@ func (l *Layout) HandleConsoleEvent(e string) { chart := l.getSelectedComponent().Drawable.(*runchart.RunChart) chart.MoveSelection(1) case ModeComponentSelect: - if l.selection < len(l.components)-1 { + if l.selection < len(l.Components)-1 { l.selection++ } l.menu.highlight(l.getComponent(l.selection)) @@ -167,7 +168,7 @@ func (l *Layout) HandleConsoleEvent(e string) { l.selection = 0 l.menu.highlight(l.getComponent(l.selection)) case ModeComponentSelect: - if l.selection < len(l.components)-1 { + if l.selection < len(l.Components)-1 { l.selection++ } l.menu.highlight(l.getComponent(l.selection)) @@ -187,11 +188,11 @@ func (l *Layout) ChangeDimensions(width, height int) { // TODO func to get prev/next component navigating left/right/top/bottom func (l *Layout) getComponent(i int) Component { - return l.components[i] + return l.Components[i] } func (l *Layout) getSelectedComponent() *Component { - return &l.components[l.selection] + return &l.Components[l.selection] } func (l *Layout) Draw(buffer *ui.Buffer) { @@ -199,7 +200,7 @@ func (l *Layout) Draw(buffer *ui.Buffer) { columnWidth := float64(l.GetRect().Dx()) / columnsCount rowHeight := float64(l.GetRect().Dy()) / rowsCount - for _, component := range l.components { + for _, component := range l.Components { x1 := float64(component.Position.X) * columnWidth y1 := float64(component.Position.Y) * rowHeight diff --git a/widgets/menu.go b/widgets/menu.go index 71fb41a..0c24a38 100644 --- a/widgets/menu.go +++ b/widgets/menu.go @@ -1,6 +1,7 @@ package widgets import ( + "github.com/sqshq/sampler/config" "github.com/sqshq/sampler/console" ui "github.com/sqshq/termui" "image" @@ -191,7 +192,7 @@ func (m *Menu) renderOptions(buffer *ui.Buffer) { style = highlightedStyle } - if option != MenuOptionPinpoint || m.component.Type == TypeRunChart { + if option != MenuOptionPinpoint || m.component.Type == config.TypeRunChart { offset += 2 point := getMiddlePoint(m.Block, string(option), offset-5) if point.In(m.GetRect()) { diff --git a/widgets/runchart/runchart.go b/widgets/runchart/runchart.go index c7477f1..da16e5a 100644 --- a/widgets/runchart/runchart.go +++ b/widgets/runchart/runchart.go @@ -328,8 +328,12 @@ func getMidRangeTime(r TimeRange) time.Time { } func formatValue(value float64, precision int) string { - format := "%." + strconv.Itoa(precision) + "f" - return fmt.Sprintf(format, value) + if math.Abs(value) == math.MaxFloat64 { + return "Inf" + } else { + format := "%." + strconv.Itoa(precision) + "f" + return fmt.Sprintf(format, value) + } } // time duration between grid lines