added palette
This commit is contained in:
parent
e977d045f6
commit
843375bc14
12
config.yml
12
config.yml
|
@ -1,12 +1,14 @@
|
|||
theme: dark / bright
|
||||
theme: dark
|
||||
run-charts:
|
||||
- title: curl-latency
|
||||
data:
|
||||
- label: example.com
|
||||
script: curl -o /dev/null -s -w '%{time_total}' http://example.com
|
||||
- label: google.com
|
||||
script: curl -o /dev/null -s -w '%{time_total}' http://google.com
|
||||
refresh-rate-ms: 200
|
||||
- label: yahoo.com
|
||||
script: curl -o /dev/null -s -w '%{time_total}' http://yahoo.com
|
||||
- label: example.com
|
||||
script: curl -o /dev/null -s -w '%{time_total}' http://example.com
|
||||
refresh-rate-ms: 300
|
||||
time-scale-sec: 1
|
||||
style: dots/lines
|
||||
position:
|
||||
|
@ -19,7 +21,7 @@ run-charts:
|
|||
data:
|
||||
- label: posts
|
||||
script: mongo --quiet --host=localhost blog --eval "db.getCollection('post').find({}).size()" | grep 2
|
||||
color: red
|
||||
color: 3
|
||||
refresh-rate-ms: 200
|
||||
time-scale-sec: 1
|
||||
position:
|
||||
|
|
|
@ -1,32 +1,39 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"github.com/sqshq/vcmd/data"
|
||||
. "github.com/sqshq/vcmd/layout"
|
||||
"github.com/sqshq/vcmd/settings"
|
||||
"gopkg.in/yaml.v2"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
RunCharts []RunChart `yaml:"run-charts"`
|
||||
}
|
||||
|
||||
type DataConfig struct {
|
||||
Script string `yaml:"script"`
|
||||
Label string `yaml:"label"`
|
||||
Theme settings.Theme `yaml:"theme"`
|
||||
RunCharts []RunChart `yaml:"run-charts"`
|
||||
}
|
||||
|
||||
type RunChart struct {
|
||||
Title string `yaml:"title"`
|
||||
DataConfig []DataConfig `yaml:"data"`
|
||||
Position Position `yaml:"position"`
|
||||
Size Size `yaml:"size"`
|
||||
RefreshRateMs int `yaml:"refresh-rate-ms"`
|
||||
TimeScaleSec int `yaml:"time-scale-sec"`
|
||||
Title string `yaml:"title"`
|
||||
Items []data.Item `yaml:"data"`
|
||||
Position Position `yaml:"position"`
|
||||
Size Size `yaml:"size"`
|
||||
RefreshRateMs int `yaml:"refresh-rate-ms"`
|
||||
TimeScaleSec int `yaml:"time-scale-sec"`
|
||||
}
|
||||
|
||||
func Load(location string) *Config {
|
||||
|
||||
cfg := readFile(location)
|
||||
validate(cfg)
|
||||
setColors(cfg)
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func readFile(location string) *Config {
|
||||
|
||||
yamlFile, err := ioutil.ReadFile(location)
|
||||
if err != nil {
|
||||
log.Fatalf("Can't read config file: %s", location)
|
||||
|
@ -41,3 +48,26 @@ func Load(location string) *Config {
|
|||
|
||||
return cfg
|
||||
}
|
||||
|
||||
/*
|
||||
TODO validation
|
||||
- title uniquness and mandatory within a single type of widget
|
||||
- label uniqueness and mandatory (if > 1 data bullets)
|
||||
*/
|
||||
func validate(config *Config) {
|
||||
|
||||
}
|
||||
|
||||
func setColors(config *Config) {
|
||||
|
||||
palette := settings.GetPalette(config.Theme)
|
||||
|
||||
for i, chart := range config.RunCharts {
|
||||
for j, item := range chart.Items {
|
||||
if item.Color == 0 {
|
||||
item.Color = palette.Colors[i+j]
|
||||
chart.Items[j] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package data
|
||||
|
||||
type Consumer interface {
|
||||
ConsumeValue(value string, label string)
|
||||
ConsumeError(err error)
|
||||
ConsumeValue(item Item, value string)
|
||||
ConsumeError(item Item, err error)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
ui "github.com/sqshq/termui"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Item struct {
|
||||
Script string `yaml:"script"`
|
||||
Label string `yaml:"label"`
|
||||
Color ui.Color `yaml:"color"`
|
||||
}
|
||||
|
||||
func (self *Item) nextValue() (value string, err error) {
|
||||
|
||||
output, err := exec.Command("sh", "-c", self.Script).Output()
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(output)), nil
|
||||
}
|
|
@ -1,22 +1,19 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Poller struct {
|
||||
consumer Consumer
|
||||
script string
|
||||
label string
|
||||
item Item
|
||||
pause bool
|
||||
}
|
||||
|
||||
func NewPoller(consumer Consumer, script string, label string, rateMs int) Poller {
|
||||
func NewPoller(consumer Consumer, item Item, rateMs int) Poller {
|
||||
|
||||
ticker := time.NewTicker(time.Duration(rateMs * int(time.Millisecond)))
|
||||
poller := Poller{consumer, script, label, false}
|
||||
poller := Poller{consumer, item, false}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
|
@ -40,12 +37,11 @@ func (self *Poller) poll() {
|
|||
return
|
||||
}
|
||||
|
||||
output, err := exec.Command("sh", "-c", self.script).Output()
|
||||
value, err := self.item.nextValue()
|
||||
|
||||
if err != nil {
|
||||
self.consumer.ConsumeError(err)
|
||||
self.consumer.ConsumeError(self.item, err)
|
||||
}
|
||||
|
||||
value := strings.TrimSpace(string(output))
|
||||
self.consumer.ConsumeValue(value, self.label)
|
||||
self.consumer.ConsumeValue(self.item, value)
|
||||
}
|
||||
|
|
10
main.go
10
main.go
|
@ -10,14 +10,8 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
TODO validation
|
||||
- title uniquness and mandatory within a single type of widget
|
||||
- label uniqueness and mandatory (if > 1 data bullets)
|
||||
*/
|
||||
func main() {
|
||||
|
||||
// todo error handling + validation
|
||||
cfg := config.Load("/Users/sqshq/Go/src/github.com/sqshq/vcmd/config.yml")
|
||||
|
||||
if err := ui.Init(); err != nil {
|
||||
|
@ -35,9 +29,9 @@ func main() {
|
|||
chart := widgets.NewRunChart(chartConfig.Title)
|
||||
lout.AddItem(chart, chartConfig.Position, chartConfig.Size)
|
||||
|
||||
for _, chartData := range chartConfig.DataConfig {
|
||||
for _, item := range chartConfig.Items {
|
||||
pollers = append(pollers,
|
||||
data.NewPoller(chart, chartData.Script, chartData.Label, chartConfig.RefreshRateMs))
|
||||
data.NewPoller(chart, item, chartConfig.RefreshRateMs))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package settings
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
ui "github.com/sqshq/termui"
|
||||
)
|
||||
|
||||
type Theme string
|
||||
|
||||
const (
|
||||
ThemeDark Theme = "dark"
|
||||
ThemeLight Theme = "light"
|
||||
)
|
||||
|
||||
const (
|
||||
ColorOlive ui.Color = 178
|
||||
ColorDeepSkyBlue ui.Color = 39
|
||||
ColorDeepPink ui.Color = 162
|
||||
ColorDarkGrey ui.Color = 240
|
||||
)
|
||||
|
||||
type Palette struct {
|
||||
Colors []ui.Color
|
||||
}
|
||||
|
||||
func GetPalette(theme Theme) Palette {
|
||||
switch theme {
|
||||
case ThemeDark:
|
||||
return Palette{Colors: []ui.Color{ColorOlive, ColorDeepSkyBlue, ColorDeepPink}}
|
||||
case ThemeLight:
|
||||
return Palette{Colors: []ui.Color{ColorOlive, ColorDeepSkyBlue, ColorDeepPink}}
|
||||
default:
|
||||
panic(fmt.Sprintf("Following theme is not supported: %v", theme))
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@ package widgets
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/sqshq/vcmd/data"
|
||||
"github.com/sqshq/vcmd/settings"
|
||||
"image"
|
||||
"log"
|
||||
"math"
|
||||
|
@ -22,7 +24,7 @@ const (
|
|||
|
||||
type RunChart struct {
|
||||
Block
|
||||
items []ChartItem
|
||||
lines []TimeLine
|
||||
grid ChartGrid
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
@ -32,10 +34,9 @@ type TimePoint struct {
|
|||
Time time.Time
|
||||
}
|
||||
|
||||
type ChartItem struct {
|
||||
timePoints []TimePoint
|
||||
label string
|
||||
color Color
|
||||
type TimeLine struct {
|
||||
points []TimePoint
|
||||
item data.Item
|
||||
}
|
||||
|
||||
type ChartGrid struct {
|
||||
|
@ -62,7 +63,7 @@ func NewRunChart(title string) *RunChart {
|
|||
block.Title = title
|
||||
return &RunChart{
|
||||
Block: block,
|
||||
items: []ChartItem{},
|
||||
lines: []TimeLine{},
|
||||
mutex: &sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +79,7 @@ func (self *RunChart) newChartGrid() ChartGrid {
|
|||
paddingWidth: xAxisLabelsGap + xAxisLabelsWidth,
|
||||
maxTimeWidth: self.Inner.Max.X - xAxisLabelsWidth/2 - xAxisLabelsGap,
|
||||
timeExtremum: GetTimeExtremum(linesCount, paddingDuration),
|
||||
valueExtremum: GetValueExtremum(self.items),
|
||||
valueExtremum: GetValueExtremum(self.lines),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,7 +99,7 @@ func (self *RunChart) Draw(buf *Buffer) {
|
|||
self.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (self *RunChart) ConsumeValue(value string, label string) {
|
||||
func (self *RunChart) ConsumeValue(item data.Item, value string) {
|
||||
|
||||
float, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
|
@ -109,44 +110,43 @@ func (self *RunChart) ConsumeValue(value string, label string) {
|
|||
self.mutex.Lock()
|
||||
itemExists := false
|
||||
|
||||
for i, item := range self.items {
|
||||
if item.label == label {
|
||||
item.timePoints = append(item.timePoints, timePoint)
|
||||
self.items[i] = item
|
||||
for i, line := range self.lines {
|
||||
if line.item.Label == item.Label {
|
||||
line.points = append(line.points, timePoint)
|
||||
self.lines[i] = line
|
||||
itemExists = true
|
||||
}
|
||||
}
|
||||
|
||||
if !itemExists {
|
||||
item := &ChartItem{
|
||||
timePoints: []TimePoint{timePoint},
|
||||
label: label,
|
||||
color: ColorYellow,
|
||||
item := &TimeLine{
|
||||
points: []TimePoint{timePoint},
|
||||
item: item,
|
||||
}
|
||||
self.items = append(self.items, *item)
|
||||
self.lines = append(self.lines, *item)
|
||||
}
|
||||
|
||||
self.trimOutOfRangeValues()
|
||||
self.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (self *RunChart) ConsumeError(err error) {
|
||||
func (self *RunChart) ConsumeError(item data.Item, err error) {
|
||||
// TODO visual notification
|
||||
}
|
||||
|
||||
func (self *RunChart) trimOutOfRangeValues() {
|
||||
for i, item := range self.items {
|
||||
for i, item := range self.lines {
|
||||
lastOutOfRangeValueIndex := -1
|
||||
|
||||
for j, timePoint := range item.timePoints {
|
||||
for j, timePoint := range item.points {
|
||||
if !self.isTimePointInRange(timePoint) {
|
||||
lastOutOfRangeValueIndex = j
|
||||
}
|
||||
}
|
||||
|
||||
if lastOutOfRangeValueIndex > 0 {
|
||||
item.timePoints = append(item.timePoints[:0], item.timePoints[lastOutOfRangeValueIndex+1:]...)
|
||||
self.items[i] = item
|
||||
item.points = append(item.points[:0], item.points[lastOutOfRangeValueIndex+1:]...)
|
||||
self.lines[i] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -156,23 +156,23 @@ func (self *RunChart) renderItems(buf *Buffer, drawArea image.Rectangle) {
|
|||
canvas := NewCanvas()
|
||||
canvas.Rectangle = drawArea
|
||||
|
||||
for _, item := range self.items {
|
||||
for _, line := range self.lines {
|
||||
|
||||
xToPoint := make(map[int]image.Point)
|
||||
pointsOrder := make([]int, 0)
|
||||
|
||||
for _, timePoint := range item.timePoints {
|
||||
for _, point := range line.points {
|
||||
|
||||
if !self.isTimePointInRange(timePoint) {
|
||||
if !self.isTimePointInRange(point) {
|
||||
continue
|
||||
}
|
||||
|
||||
timeDeltaWithGridMaxTime := self.grid.timeExtremum.max.Sub(timePoint.Time)
|
||||
timeDeltaWithGridMaxTime := self.grid.timeExtremum.max.Sub(point.Time)
|
||||
deltaToPaddingRelation := float64(timeDeltaWithGridMaxTime.Nanoseconds()) / float64(self.grid.paddingDuration.Nanoseconds())
|
||||
x := self.grid.maxTimeWidth - (int(float64(self.grid.paddingWidth) * deltaToPaddingRelation))
|
||||
|
||||
valuePerYDot := (self.grid.valueExtremum.max - self.grid.valueExtremum.min) / float64(drawArea.Dy()-1)
|
||||
y := int(float64(timePoint.Value-self.grid.valueExtremum.min) / valuePerYDot)
|
||||
y := int(float64(point.Value-self.grid.valueExtremum.min) / valuePerYDot)
|
||||
|
||||
if _, exists := xToPoint[x]; exists {
|
||||
continue
|
||||
|
@ -201,7 +201,7 @@ func (self *RunChart) renderItems(buf *Buffer, drawArea image.Rectangle) {
|
|||
canvas.Line(
|
||||
braillePoint(previousPoint),
|
||||
braillePoint(currentPoint),
|
||||
item.color,
|
||||
line.item.Color,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -232,7 +232,7 @@ func (self *RunChart) plotAxes(buf *Buffer) {
|
|||
for y := 0; y < self.Inner.Dy()-xAxisLabelsHeight-1; y = y + 2 {
|
||||
for x := 0; x < self.grid.linesCount; x++ {
|
||||
buf.SetCell(
|
||||
NewCell(VERTICAL_DASH, NewStyle(ColorDarkGrey)),
|
||||
NewCell(VERTICAL_DASH, NewStyle(settings.ColorDarkGrey)),
|
||||
image.Pt(self.grid.maxTimeWidth-x*self.grid.paddingWidth, y+self.Inner.Min.Y+1),
|
||||
)
|
||||
}
|
||||
|
@ -267,7 +267,7 @@ func (self *RunChart) plotAxes(buf *Buffer) {
|
|||
}
|
||||
}
|
||||
|
||||
func GetValueExtremum(items []ChartItem) ValueExtremum {
|
||||
func GetValueExtremum(items []TimeLine) ValueExtremum {
|
||||
|
||||
if len(items) == 0 {
|
||||
return ValueExtremum{0, 0}
|
||||
|
@ -276,7 +276,7 @@ func GetValueExtremum(items []ChartItem) ValueExtremum {
|
|||
var max, min = -math.MaxFloat64, math.MaxFloat64
|
||||
|
||||
for _, item := range items {
|
||||
for _, point := range item.timePoints {
|
||||
for _, point := range item.points {
|
||||
if point.Value > max {
|
||||
max = point.Value
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
package widgets
|
||||
|
||||
import "github.com/sqshq/termui"
|
||||
|
||||
const (
|
||||
ColorDarkGrey termui.Color = 240
|
||||
)
|
Loading…
Reference in New Issue