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