Merge branch 'master' into multitenancy

pull/10616/head
Jared Scheib 2017-12-06 19:05:16 -08:00
commit 31f012ff8f
47 changed files with 2479 additions and 508 deletions

View File

@ -13,6 +13,8 @@
1. [#2327](https://github.com/influxdata/chronograf/pull/2327): After CREATE/DELETE queries, refresh list of databases in Data Explorer
1. [#2327](https://github.com/influxdata/chronograf/pull/2327): Visualize CREATE/DELETE queries with Table view in Data Explorer
1. [#2329](https://github.com/influxdata/chronograf/pull/2329): Include tag values alongside measurement name in Data Explorer result tabs
1. [#2410](https://github.com/influxdata/chronograf/pull/2410): Redesign cell display options panel
1. [#2410](https://github.com/influxdata/chronograf/pull/2410): Introduce customizable Gauge visualization type for dashboard cells
1. [#2386](https://github.com/influxdata/chronograf/pull/2386): Fix queries that include regex, numbers and wildcard
1. [#2398](https://github.com/influxdata/chronograf/pull/2398): Fix apps on hosts page from parsing tags with null values
1. [#2408](https://github.com/influxdata/chronograf/pull/2408): Fix updated Dashboard names not updating dashboard list
@ -37,6 +39,7 @@
1. [#2477](https://github.com/influxdata/chronograf/pull/2477): Improve performance of hoverline rendering
### UI Improvements
1. [#2427](https://github.com/influxdata/chronograf/pull/2427): Improve performance of Hosts, Alert History, and TICKscript logging pages when there are many items to display
## v1.3.10.0 [2017-10-24]
### Bug Fixes

View File

@ -219,6 +219,17 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
queries[j].Shifts = shifts
}
colors := make([]*Color, len(c.CellColors))
for j, color := range c.CellColors {
colors[j] = &Color{
ID: color.ID,
Type: color.Type,
Hex: color.Hex,
Name: color.Name,
Value: color.Value,
}
}
axes := make(map[string]*Axis, len(c.Axes))
for a, r := range c.Axes {
axes[a] = &Axis{
@ -241,6 +252,7 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
Queries: queries,
Type: c.Type,
Axes: axes,
Colors: colors,
}
}
templates := make([]*Template, len(d.Templates))
@ -320,6 +332,17 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error {
queries[j].Shifts = shifts
}
colors := make([]chronograf.CellColor, len(c.Colors))
for j, color := range c.Colors {
colors[j] = chronograf.CellColor{
ID: color.ID,
Type: color.Type,
Hex: color.Hex,
Name: color.Name,
Value: color.Value,
}
}
axes := make(map[string]chronograf.Axis, len(c.Axes))
for a, r := range c.Axes {
// axis base defaults to 10
@ -351,15 +374,16 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error {
}
cells[i] = chronograf.DashboardCell{
ID: c.ID,
X: c.X,
Y: c.Y,
W: c.W,
H: c.H,
Name: c.Name,
Queries: queries,
Type: c.Type,
Axes: axes,
ID: c.ID,
X: c.X,
Y: c.Y,
W: c.W,
H: c.H,
Name: c.Name,
Queries: queries,
Type: c.Type,
Axes: axes,
CellColors: colors,
}
}

View File

@ -12,6 +12,7 @@ It has these top-level messages:
Source
Dashboard
DashboardCell
Color
Axis
Template
TemplateValue
@ -102,6 +103,7 @@ type DashboardCell struct {
Type string `protobuf:"bytes,7,opt,name=type,proto3" json:"type,omitempty"`
ID string `protobuf:"bytes,8,opt,name=ID,proto3" json:"ID,omitempty"`
Axes map[string]*Axis `protobuf:"bytes,9,rep,name=axes" json:"axes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value"`
Colors []*Color `protobuf:"bytes,10,rep,name=colors" json:"colors,omitempty"`
}
func (m *DashboardCell) Reset() { *m = DashboardCell{} }
@ -123,6 +125,26 @@ func (m *DashboardCell) GetAxes() map[string]*Axis {
return nil
}
func (m *DashboardCell) GetColors() []*Color {
if m != nil {
return m.Colors
}
return nil
}
type Color struct {
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
Type string `protobuf:"bytes,2,opt,name=Type,proto3" json:"Type,omitempty"`
Hex string `protobuf:"bytes,3,opt,name=Hex,proto3" json:"Hex,omitempty"`
Name string `protobuf:"bytes,4,opt,name=Name,proto3" json:"Name,omitempty"`
Value string `protobuf:"bytes,5,opt,name=Value,proto3" json:"Value,omitempty"`
}
func (m *Color) Reset() { *m = Color{} }
func (m *Color) String() string { return proto.CompactTextString(m) }
func (*Color) ProtoMessage() {}
func (*Color) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} }
type Axis struct {
LegacyBounds []int64 `protobuf:"varint,1,rep,name=legacyBounds" json:"legacyBounds,omitempty"`
Bounds []string `protobuf:"bytes,2,rep,name=bounds" json:"bounds,omitempty"`
@ -136,7 +158,7 @@ type Axis struct {
func (m *Axis) Reset() { *m = Axis{} }
func (m *Axis) String() string { return proto.CompactTextString(m) }
func (*Axis) ProtoMessage() {}
func (*Axis) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} }
func (*Axis) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} }
type Template struct {
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
@ -150,7 +172,7 @@ type Template struct {
func (m *Template) Reset() { *m = Template{} }
func (m *Template) String() string { return proto.CompactTextString(m) }
func (*Template) ProtoMessage() {}
func (*Template) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} }
func (*Template) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} }
func (m *Template) GetValues() []*TemplateValue {
if m != nil {
@ -175,7 +197,7 @@ type TemplateValue struct {
func (m *TemplateValue) Reset() { *m = TemplateValue{} }
func (m *TemplateValue) String() string { return proto.CompactTextString(m) }
func (*TemplateValue) ProtoMessage() {}
func (*TemplateValue) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} }
func (*TemplateValue) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} }
type TemplateQuery struct {
Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"`
@ -189,7 +211,7 @@ type TemplateQuery struct {
func (m *TemplateQuery) Reset() { *m = TemplateQuery{} }
func (m *TemplateQuery) String() string { return proto.CompactTextString(m) }
func (*TemplateQuery) ProtoMessage() {}
func (*TemplateQuery) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} }
func (*TemplateQuery) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} }
type Server struct {
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
@ -205,7 +227,7 @@ type Server struct {
func (m *Server) Reset() { *m = Server{} }
func (m *Server) String() string { return proto.CompactTextString(m) }
func (*Server) ProtoMessage() {}
func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} }
func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} }
type Layout struct {
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
@ -218,7 +240,7 @@ type Layout struct {
func (m *Layout) Reset() { *m = Layout{} }
func (m *Layout) String() string { return proto.CompactTextString(m) }
func (*Layout) ProtoMessage() {}
func (*Layout) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} }
func (*Layout) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{9} }
func (m *Layout) GetCells() []*Cell {
if m != nil {
@ -244,7 +266,7 @@ type Cell struct {
func (m *Cell) Reset() { *m = Cell{} }
func (m *Cell) String() string { return proto.CompactTextString(m) }
func (*Cell) ProtoMessage() {}
func (*Cell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{9} }
func (*Cell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{10} }
func (m *Cell) GetQueries() []*Query {
if m != nil {
@ -275,7 +297,7 @@ type Query struct {
func (m *Query) Reset() { *m = Query{} }
func (m *Query) String() string { return proto.CompactTextString(m) }
func (*Query) ProtoMessage() {}
func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{10} }
func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{11} }
func (m *Query) GetRange() *Range {
if m != nil {
@ -300,7 +322,7 @@ type TimeShift struct {
func (m *TimeShift) Reset() { *m = TimeShift{} }
func (m *TimeShift) String() string { return proto.CompactTextString(m) }
func (*TimeShift) ProtoMessage() {}
func (*TimeShift) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{11} }
func (*TimeShift) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{12} }
type Range struct {
Upper int64 `protobuf:"varint,1,opt,name=Upper,proto3" json:"Upper,omitempty"`
@ -310,7 +332,7 @@ type Range struct {
func (m *Range) Reset() { *m = Range{} }
func (m *Range) String() string { return proto.CompactTextString(m) }
func (*Range) ProtoMessage() {}
func (*Range) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{12} }
func (*Range) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{13} }
type AlertRule struct {
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
@ -322,7 +344,7 @@ type AlertRule struct {
func (m *AlertRule) Reset() { *m = AlertRule{} }
func (m *AlertRule) String() string { return proto.CompactTextString(m) }
func (*AlertRule) ProtoMessage() {}
func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{13} }
func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{14} }
type User struct {
ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
@ -336,7 +358,7 @@ type User struct {
func (m *User) Reset() { *m = User{} }
func (m *User) String() string { return proto.CompactTextString(m) }
func (*User) ProtoMessage() {}
func (*User) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{14} }
func (*User) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{15} }
func (m *User) GetRoles() []*Role {
if m != nil {
@ -353,7 +375,7 @@ type Role struct {
func (m *Role) Reset() { *m = Role{} }
func (m *Role) String() string { return proto.CompactTextString(m) }
func (*Role) ProtoMessage() {}
func (*Role) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{15} }
func (*Role) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{16} }
type Organization struct {
ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
@ -365,12 +387,13 @@ type Organization struct {
func (m *Organization) Reset() { *m = Organization{} }
func (m *Organization) String() string { return proto.CompactTextString(m) }
func (*Organization) ProtoMessage() {}
func (*Organization) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{16} }
func (*Organization) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{17} }
func init() {
proto.RegisterType((*Source)(nil), "internal.Source")
proto.RegisterType((*Dashboard)(nil), "internal.Dashboard")
proto.RegisterType((*DashboardCell)(nil), "internal.DashboardCell")
proto.RegisterType((*Color)(nil), "internal.Color")
proto.RegisterType((*Axis)(nil), "internal.Axis")
proto.RegisterType((*Template)(nil), "internal.Template")
proto.RegisterType((*TemplateValue)(nil), "internal.TemplateValue")
@ -390,81 +413,84 @@ func init() {
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
var fileDescriptorInternal = []byte{
// 1207 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x56, 0x5f, 0x8f, 0xdb, 0x44,
0x10, 0xd7, 0xc6, 0x71, 0x62, 0x4f, 0xae, 0x05, 0x2d, 0x15, 0x35, 0x45, 0x42, 0xc1, 0x02, 0xe9,
0x10, 0xf4, 0x40, 0xad, 0x90, 0x10, 0x0f, 0x48, 0xb9, 0x0b, 0xaa, 0x8e, 0xfe, 0xbb, 0x6e, 0x7a,
0xe5, 0x09, 0x55, 0x1b, 0x67, 0x72, 0xb1, 0xea, 0xd8, 0x66, 0x6d, 0xdf, 0x9d, 0xf9, 0x30, 0x48,
0x48, 0x3c, 0xf1, 0x88, 0x78, 0xe7, 0x15, 0xf1, 0x41, 0xf8, 0x0a, 0xbc, 0xa2, 0xd9, 0x5d, 0x3b,
0x4e, 0x2f, 0x54, 0x7d, 0x81, 0xb7, 0xfd, 0xcd, 0xac, 0x67, 0x77, 0x66, 0x7e, 0xf3, 0xf3, 0xc2,
0xf5, 0x38, 0x2d, 0x51, 0xa5, 0x32, 0x39, 0xc8, 0x55, 0x56, 0x66, 0xdc, 0x6b, 0x70, 0xf8, 0x57,
0x0f, 0x06, 0xb3, 0xac, 0x52, 0x11, 0xf2, 0xeb, 0xd0, 0x3b, 0x9e, 0x06, 0x6c, 0xcc, 0xf6, 0x1d,
0xd1, 0x3b, 0x9e, 0x72, 0x0e, 0xfd, 0x47, 0x72, 0x8d, 0x41, 0x6f, 0xcc, 0xf6, 0x7d, 0xa1, 0xd7,
0x64, 0x7b, 0x5a, 0xe7, 0x18, 0x38, 0xc6, 0x46, 0x6b, 0x7e, 0x0b, 0xbc, 0xd3, 0x82, 0xa2, 0xad,
0x31, 0xe8, 0x6b, 0x7b, 0x8b, 0xc9, 0x77, 0x22, 0x8b, 0xe2, 0x22, 0x53, 0x8b, 0xc0, 0x35, 0xbe,
0x06, 0xf3, 0x37, 0xc1, 0x39, 0x15, 0x0f, 0x82, 0x81, 0x36, 0xd3, 0x92, 0x07, 0x30, 0x9c, 0xe2,
0x52, 0x56, 0x49, 0x19, 0x0c, 0xc7, 0x6c, 0xdf, 0x13, 0x0d, 0xa4, 0x38, 0x4f, 0x31, 0xc1, 0x33,
0x25, 0x97, 0x81, 0x67, 0xe2, 0x34, 0x98, 0x1f, 0x00, 0x3f, 0x4e, 0x0b, 0x8c, 0x2a, 0x85, 0xb3,
0x17, 0x71, 0xfe, 0x0c, 0x55, 0xbc, 0xac, 0x03, 0x5f, 0x07, 0xd8, 0xe1, 0xa1, 0x53, 0x1e, 0x62,
0x29, 0xe9, 0x6c, 0xd0, 0xa1, 0x1a, 0xc8, 0x43, 0xd8, 0x9b, 0xad, 0xa4, 0xc2, 0xc5, 0x0c, 0x23,
0x85, 0x65, 0x30, 0xd2, 0xee, 0x2d, 0x1b, 0xed, 0x79, 0xac, 0xce, 0x64, 0x1a, 0xff, 0x20, 0xcb,
0x38, 0x4b, 0x83, 0x3d, 0xb3, 0xa7, 0x6b, 0xa3, 0x2a, 0x89, 0x2c, 0xc1, 0xe0, 0x9a, 0xa9, 0x12,
0xad, 0xc3, 0xdf, 0x18, 0xf8, 0x53, 0x59, 0xac, 0xe6, 0x99, 0x54, 0x8b, 0xd7, 0xaa, 0xf5, 0x6d,
0x70, 0x23, 0x4c, 0x92, 0x22, 0x70, 0xc6, 0xce, 0xfe, 0xe8, 0xce, 0xcd, 0x83, 0xb6, 0x89, 0x6d,
0x9c, 0x23, 0x4c, 0x12, 0x61, 0x76, 0xf1, 0xcf, 0xc0, 0x2f, 0x71, 0x9d, 0x27, 0xb2, 0xc4, 0x22,
0xe8, 0xeb, 0x4f, 0xf8, 0xe6, 0x93, 0xa7, 0xd6, 0x25, 0x36, 0x9b, 0xae, 0xa4, 0xe2, 0x5e, 0x4d,
0x25, 0xfc, 0xa5, 0x07, 0xd7, 0xb6, 0x8e, 0xe3, 0x7b, 0xc0, 0x2e, 0xf5, 0xcd, 0x5d, 0xc1, 0x2e,
0x09, 0xd5, 0xfa, 0xd6, 0xae, 0x60, 0x35, 0xa1, 0x0b, 0xcd, 0x0d, 0x57, 0xb0, 0x0b, 0x42, 0x2b,
0xcd, 0x08, 0x57, 0xb0, 0x15, 0xff, 0x08, 0x86, 0xdf, 0x57, 0xa8, 0x62, 0x2c, 0x02, 0x57, 0xdf,
0xee, 0x8d, 0xcd, 0xed, 0x9e, 0x54, 0xa8, 0x6a, 0xd1, 0xf8, 0xa9, 0x1a, 0x9a, 0x4d, 0x86, 0x1a,
0x7a, 0x4d, 0xb6, 0x92, 0x98, 0x37, 0x34, 0x36, 0x5a, 0xdb, 0x2a, 0x1a, 0x3e, 0x50, 0x15, 0x3f,
0x87, 0xbe, 0xbc, 0xc4, 0x22, 0xf0, 0x75, 0xfc, 0xf7, 0xff, 0xa5, 0x60, 0x07, 0x93, 0x4b, 0x2c,
0xbe, 0x4e, 0x4b, 0x55, 0x0b, 0xbd, 0xfd, 0xd6, 0x3d, 0xf0, 0x5b, 0x13, 0xb1, 0xf2, 0x05, 0xd6,
0x3a, 0x41, 0x5f, 0xd0, 0x92, 0x7f, 0x00, 0xee, 0xb9, 0x4c, 0x2a, 0xd3, 0x9c, 0xd1, 0x9d, 0xeb,
0x9b, 0xb0, 0x93, 0xcb, 0xb8, 0x10, 0xc6, 0xf9, 0x65, 0xef, 0x0b, 0x16, 0xfe, 0xca, 0xa0, 0x4f,
0x36, 0xaa, 0x6c, 0x82, 0x67, 0x32, 0xaa, 0x0f, 0xb3, 0x2a, 0x5d, 0x14, 0x01, 0x1b, 0x3b, 0xfb,
0x8e, 0xd8, 0xb2, 0xf1, 0xb7, 0x61, 0x30, 0x37, 0xde, 0xde, 0xd8, 0xd9, 0xf7, 0x85, 0x45, 0xfc,
0x06, 0xb8, 0x89, 0x9c, 0x63, 0x62, 0x67, 0xcc, 0x00, 0xda, 0x9d, 0x2b, 0x5c, 0xc6, 0x97, 0x76,
0xc4, 0x2c, 0x22, 0x7b, 0x51, 0x2d, 0xc9, 0x6e, 0xba, 0x67, 0x11, 0x95, 0x6b, 0x2e, 0x8b, 0xb6,
0x84, 0xb4, 0xa6, 0xc8, 0x45, 0x24, 0x93, 0xa6, 0x86, 0x06, 0x84, 0xbf, 0x33, 0x9a, 0x2d, 0xc3,
0x89, 0x0e, 0x2f, 0x4d, 0x45, 0xdf, 0x01, 0x8f, 0xf8, 0xf2, 0xfc, 0x5c, 0x2a, 0xcb, 0xcd, 0x21,
0xe1, 0x67, 0x52, 0xf1, 0x4f, 0x61, 0xa0, 0x33, 0xdf, 0xc1, 0xcf, 0x26, 0xdc, 0x33, 0xf2, 0x0b,
0xbb, 0xad, 0xed, 0x60, 0xbf, 0xd3, 0xc1, 0x36, 0x59, 0xb7, 0x9b, 0xec, 0x6d, 0x70, 0x89, 0x0a,
0xb5, 0xbe, 0xfd, 0xce, 0xc8, 0x86, 0x30, 0x66, 0x57, 0x78, 0x0a, 0xd7, 0xb6, 0x4e, 0x6c, 0x4f,
0x62, 0xdb, 0x27, 0x6d, 0xba, 0xe8, 0xdb, 0xae, 0x91, 0xae, 0x14, 0x98, 0x60, 0x54, 0xe2, 0x42,
0xd7, 0xdb, 0x13, 0x2d, 0x0e, 0x7f, 0x62, 0x9b, 0xb8, 0xfa, 0x3c, 0x52, 0x8e, 0x28, 0x5b, 0xaf,
0x65, 0xba, 0xb0, 0xa1, 0x1b, 0x48, 0x75, 0x5b, 0xcc, 0x6d, 0xe8, 0xde, 0x62, 0x4e, 0x58, 0xe5,
0xb6, 0x83, 0x3d, 0x95, 0xf3, 0x31, 0x8c, 0xd6, 0x28, 0x8b, 0x4a, 0xe1, 0x1a, 0xd3, 0xd2, 0x96,
0xa0, 0x6b, 0xe2, 0x37, 0x61, 0x58, 0xca, 0xb3, 0xe7, 0xc4, 0x3d, 0xdb, 0xc9, 0x52, 0x9e, 0xdd,
0xc7, 0x9a, 0xbf, 0x0b, 0xfe, 0x32, 0xc6, 0x64, 0xa1, 0x5d, 0xa6, 0x9d, 0x9e, 0x36, 0xdc, 0xc7,
0x3a, 0xfc, 0x83, 0xc1, 0x60, 0x86, 0xea, 0x1c, 0xd5, 0x6b, 0x49, 0x4a, 0x57, 0xaa, 0x9d, 0x57,
0x48, 0x75, 0x7f, 0xb7, 0x54, 0xbb, 0x1b, 0xa9, 0xbe, 0x01, 0xee, 0x4c, 0x45, 0xc7, 0x53, 0x7d,
0x23, 0x47, 0x18, 0x40, 0x6c, 0x9c, 0x44, 0x65, 0x7c, 0x8e, 0x56, 0xbf, 0x2d, 0xba, 0xa2, 0x34,
0xde, 0x0e, 0xa5, 0xf9, 0x91, 0xc1, 0xe0, 0x81, 0xac, 0xb3, 0xaa, 0xbc, 0xc2, 0xc2, 0x31, 0x8c,
0x26, 0x79, 0x9e, 0xc4, 0x91, 0xf9, 0xda, 0x64, 0xd4, 0x35, 0xd1, 0x8e, 0x87, 0x9d, 0xfa, 0x9a,
0xdc, 0xba, 0x26, 0x9a, 0xe2, 0x23, 0xad, 0xa6, 0x46, 0x1a, 0x3b, 0x53, 0x6c, 0x44, 0x54, 0x3b,
0xa9, 0x08, 0x93, 0xaa, 0xcc, 0x96, 0x49, 0x76, 0xa1, 0xb3, 0xf5, 0x44, 0x8b, 0xc3, 0x3f, 0x7b,
0xd0, 0xff, 0xbf, 0x14, 0x70, 0x0f, 0x58, 0x6c, 0x9b, 0xcd, 0xe2, 0x56, 0x0f, 0x87, 0x1d, 0x3d,
0x0c, 0x60, 0x58, 0x2b, 0x99, 0x9e, 0x61, 0x11, 0x78, 0x5a, 0x5d, 0x1a, 0xa8, 0x3d, 0x7a, 0x8e,
0x8c, 0x10, 0xfa, 0xa2, 0x81, 0xed, 0x5c, 0x40, 0x67, 0x2e, 0x3e, 0xb1, 0x9a, 0x39, 0xd2, 0x37,
0x0a, 0xb6, 0xcb, 0xf2, 0xdf, 0x49, 0xe5, 0xdf, 0x0c, 0xdc, 0x76, 0xa8, 0x8e, 0xb6, 0x87, 0xea,
0x68, 0x33, 0x54, 0xd3, 0xc3, 0x66, 0xa8, 0xa6, 0x87, 0x84, 0xc5, 0x49, 0x33, 0x54, 0xe2, 0x84,
0x9a, 0x75, 0x4f, 0x65, 0x55, 0x7e, 0x58, 0x9b, 0xae, 0xfa, 0xa2, 0xc5, 0xc4, 0xc4, 0x6f, 0x57,
0xa8, 0x6c, 0xa9, 0x7d, 0x61, 0x11, 0xf1, 0xf6, 0x81, 0x16, 0x1c, 0x53, 0x5c, 0x03, 0xf8, 0x87,
0xe0, 0x0a, 0x2a, 0x9e, 0xae, 0xf0, 0x56, 0x5f, 0xb4, 0x59, 0x18, 0x2f, 0x05, 0x35, 0x6f, 0x25,
0x4b, 0xe0, 0xe6, 0xe5, 0xf4, 0x31, 0x0c, 0x66, 0xab, 0x78, 0x59, 0x36, 0x7f, 0x9e, 0xb7, 0x3a,
0x82, 0x15, 0xaf, 0x51, 0xfb, 0x84, 0xdd, 0x12, 0x3e, 0x01, 0xbf, 0x35, 0x6e, 0xae, 0xc3, 0xba,
0xd7, 0xe1, 0xd0, 0x3f, 0x4d, 0xe3, 0xb2, 0x19, 0x5d, 0x5a, 0x53, 0xb2, 0x4f, 0x2a, 0x99, 0x96,
0x71, 0x59, 0x37, 0xa3, 0xdb, 0xe0, 0xf0, 0xae, 0xbd, 0x3e, 0x85, 0x3b, 0xcd, 0x73, 0x54, 0x56,
0x06, 0x0c, 0xd0, 0x87, 0x64, 0x17, 0x68, 0x14, 0xdc, 0x11, 0x06, 0x84, 0xdf, 0x81, 0x3f, 0x49,
0x50, 0x95, 0xa2, 0x4a, 0xae, 0xea, 0x3e, 0x87, 0xfe, 0x37, 0xb3, 0xc7, 0x8f, 0x9a, 0x1b, 0xd0,
0x7a, 0x33, 0xf2, 0xce, 0x4b, 0x23, 0x7f, 0x5f, 0xe6, 0xf2, 0x78, 0xaa, 0x79, 0xee, 0x08, 0x8b,
0xc2, 0x9f, 0x19, 0xf4, 0x49, 0x5b, 0x3a, 0xa1, 0xfb, 0xaf, 0xd2, 0xa5, 0x13, 0x95, 0x9d, 0xc7,
0x0b, 0x54, 0x4d, 0x72, 0x0d, 0xd6, 0x45, 0x8f, 0x56, 0xd8, 0x3e, 0x2e, 0x2d, 0x22, 0xae, 0xd1,
0xc3, 0xaa, 0x99, 0xa5, 0x0e, 0xd7, 0xc8, 0x2c, 0x8c, 0x93, 0xbf, 0x07, 0x30, 0xab, 0x72, 0x54,
0x93, 0xc5, 0x3a, 0x4e, 0x75, 0xd3, 0x3d, 0xd1, 0xb1, 0x84, 0x5f, 0x99, 0xa7, 0xda, 0x15, 0x85,
0x62, 0xbb, 0x9f, 0x75, 0x2f, 0xdf, 0x3c, 0x4c, 0xb6, 0xbf, 0x7b, 0xad, 0x6c, 0xc7, 0x30, 0xb2,
0xef, 0x5a, 0xfd, 0x4a, 0xb4, 0x62, 0xd5, 0x31, 0x51, 0xce, 0x27, 0xd5, 0x3c, 0x89, 0x23, 0x9d,
0xb3, 0x27, 0x2c, 0x9a, 0x0f, 0xf4, 0xf3, 0xfd, 0xee, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xaa,
0x43, 0x90, 0xf1, 0xd0, 0x0b, 0x00, 0x00,
// 1264 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x57, 0xdf, 0x8e, 0xdb, 0xc4,
0x17, 0x96, 0xe3, 0x38, 0xb1, 0x4f, 0xb6, 0xfd, 0x55, 0xf3, 0xab, 0xa8, 0x29, 0x12, 0x0a, 0x16,
0x88, 0x45, 0xd0, 0x05, 0xb5, 0x42, 0x42, 0x5c, 0x20, 0x65, 0x37, 0xa8, 0x2c, 0xfd, 0xb7, 0x9d,
0x74, 0xcb, 0x15, 0xaa, 0x26, 0xce, 0x49, 0x62, 0xd5, 0xb1, 0xcd, 0xd8, 0xde, 0x8d, 0x79, 0x18,
0x24, 0x24, 0x9e, 0x00, 0x71, 0xcf, 0x2d, 0xe2, 0x96, 0x77, 0xe0, 0x15, 0xb8, 0x45, 0x67, 0x66,
0xec, 0x38, 0x9b, 0x50, 0xf5, 0x02, 0x71, 0x37, 0xdf, 0x39, 0x93, 0x33, 0x67, 0xce, 0xf9, 0xce,
0x37, 0x0e, 0x5c, 0x8f, 0x92, 0x02, 0x65, 0x22, 0xe2, 0xa3, 0x4c, 0xa6, 0x45, 0xca, 0xdc, 0x1a,
0x07, 0x7f, 0x76, 0xa0, 0x37, 0x49, 0x4b, 0x19, 0x22, 0xbb, 0x0e, 0x9d, 0xd3, 0xb1, 0x6f, 0x0d,
0xad, 0x43, 0x9b, 0x77, 0x4e, 0xc7, 0x8c, 0x41, 0xf7, 0xb1, 0x58, 0xa1, 0xdf, 0x19, 0x5a, 0x87,
0x1e, 0x57, 0x6b, 0xb2, 0x3d, 0xab, 0x32, 0xf4, 0x6d, 0x6d, 0xa3, 0x35, 0xbb, 0x0d, 0xee, 0x79,
0x4e, 0xd1, 0x56, 0xe8, 0x77, 0x95, 0xbd, 0xc1, 0xe4, 0x3b, 0x13, 0x79, 0x7e, 0x99, 0xca, 0x99,
0xef, 0x68, 0x5f, 0x8d, 0xd9, 0x0d, 0xb0, 0xcf, 0xf9, 0x43, 0xbf, 0xa7, 0xcc, 0xb4, 0x64, 0x3e,
0xf4, 0xc7, 0x38, 0x17, 0x65, 0x5c, 0xf8, 0xfd, 0xa1, 0x75, 0xe8, 0xf2, 0x1a, 0x52, 0x9c, 0x67,
0x18, 0xe3, 0x42, 0x8a, 0xb9, 0xef, 0xea, 0x38, 0x35, 0x66, 0x47, 0xc0, 0x4e, 0x93, 0x1c, 0xc3,
0x52, 0xe2, 0xe4, 0x65, 0x94, 0x3d, 0x47, 0x19, 0xcd, 0x2b, 0xdf, 0x53, 0x01, 0xf6, 0x78, 0xe8,
0x94, 0x47, 0x58, 0x08, 0x3a, 0x1b, 0x54, 0xa8, 0x1a, 0xb2, 0x00, 0x0e, 0x26, 0x4b, 0x21, 0x71,
0x36, 0xc1, 0x50, 0x62, 0xe1, 0x0f, 0x94, 0x7b, 0xcb, 0x46, 0x7b, 0x9e, 0xc8, 0x85, 0x48, 0xa2,
0xef, 0x45, 0x11, 0xa5, 0x89, 0x7f, 0xa0, 0xf7, 0xb4, 0x6d, 0x54, 0x25, 0x9e, 0xc6, 0xe8, 0x5f,
0xd3, 0x55, 0xa2, 0x75, 0xf0, 0x8b, 0x05, 0xde, 0x58, 0xe4, 0xcb, 0x69, 0x2a, 0xe4, 0xec, 0xb5,
0x6a, 0x7d, 0x07, 0x9c, 0x10, 0xe3, 0x38, 0xf7, 0xed, 0xa1, 0x7d, 0x38, 0xb8, 0x7b, 0xeb, 0xa8,
0x69, 0x62, 0x13, 0xe7, 0x04, 0xe3, 0x98, 0xeb, 0x5d, 0xec, 0x13, 0xf0, 0x0a, 0x5c, 0x65, 0xb1,
0x28, 0x30, 0xf7, 0xbb, 0xea, 0x27, 0x6c, 0xf3, 0x93, 0x67, 0xc6, 0xc5, 0x37, 0x9b, 0x76, 0xae,
0xe2, 0xec, 0x5e, 0x25, 0xf8, 0xa3, 0x03, 0xd7, 0xb6, 0x8e, 0x63, 0x07, 0x60, 0xad, 0x55, 0xe6,
0x0e, 0xb7, 0xd6, 0x84, 0x2a, 0x95, 0xb5, 0xc3, 0xad, 0x8a, 0xd0, 0xa5, 0xe2, 0x86, 0xc3, 0xad,
0x4b, 0x42, 0x4b, 0xc5, 0x08, 0x87, 0x5b, 0x4b, 0xf6, 0x01, 0xf4, 0xbf, 0x2b, 0x51, 0x46, 0x98,
0xfb, 0x8e, 0xca, 0xee, 0x7f, 0x9b, 0xec, 0x9e, 0x96, 0x28, 0x2b, 0x5e, 0xfb, 0xa9, 0x1a, 0x8a,
0x4d, 0x9a, 0x1a, 0x6a, 0x4d, 0xb6, 0x82, 0x98, 0xd7, 0xd7, 0x36, 0x5a, 0x9b, 0x2a, 0x6a, 0x3e,
0x50, 0x15, 0x3f, 0x85, 0xae, 0x58, 0x63, 0xee, 0x7b, 0x2a, 0xfe, 0x3b, 0xff, 0x50, 0xb0, 0xa3,
0xd1, 0x1a, 0xf3, 0x2f, 0x93, 0x42, 0x56, 0x5c, 0x6d, 0x67, 0xef, 0x43, 0x2f, 0x4c, 0xe3, 0x54,
0xe6, 0x3e, 0x5c, 0x4d, 0xec, 0x84, 0xec, 0xdc, 0xb8, 0x6f, 0xdf, 0x07, 0xaf, 0xf9, 0x2d, 0xd1,
0xf7, 0x25, 0x56, 0xaa, 0x12, 0x1e, 0xa7, 0x25, 0x7b, 0x17, 0x9c, 0x0b, 0x11, 0x97, 0xba, 0x8b,
0x83, 0xbb, 0xd7, 0x37, 0x61, 0x46, 0xeb, 0x28, 0xe7, 0xda, 0xf9, 0x79, 0xe7, 0x33, 0x2b, 0x58,
0x80, 0xa3, 0x22, 0xb7, 0x78, 0xe0, 0xd5, 0x3c, 0x50, 0xf3, 0xd5, 0x69, 0xcd, 0xd7, 0x0d, 0xb0,
0xbf, 0xc2, 0xb5, 0x19, 0x39, 0x5a, 0x36, 0x6c, 0xe9, 0xb6, 0xd8, 0x72, 0x13, 0x9c, 0xe7, 0xea,
0x70, 0xdd, 0x45, 0x0d, 0x82, 0x9f, 0x2d, 0xe8, 0xd2, 0xe1, 0xd4, 0xeb, 0x18, 0x17, 0x22, 0xac,
0x8e, 0xd3, 0x32, 0x99, 0xe5, 0xbe, 0x35, 0xb4, 0x0f, 0x6d, 0xbe, 0x65, 0x63, 0x6f, 0x40, 0x6f,
0xaa, 0xbd, 0x9d, 0xa1, 0x7d, 0xe8, 0x71, 0x83, 0x28, 0x74, 0x2c, 0xa6, 0x18, 0x9b, 0x14, 0x34,
0xa0, 0xdd, 0x99, 0xc4, 0x79, 0xb4, 0x36, 0x69, 0x18, 0x44, 0xf6, 0xbc, 0x9c, 0x93, 0x5d, 0x67,
0x62, 0x10, 0x25, 0x3d, 0x15, 0x79, 0xd3, 0x54, 0x5a, 0x53, 0xe4, 0x3c, 0x14, 0x71, 0xdd, 0x55,
0x0d, 0x82, 0x5f, 0x2d, 0x9a, 0x76, 0xcd, 0xd2, 0x9d, 0x0a, 0xbd, 0x09, 0x2e, 0x31, 0xf8, 0xc5,
0x85, 0x90, 0xa6, 0x4a, 0x7d, 0xc2, 0xcf, 0x85, 0x64, 0x1f, 0x43, 0x4f, 0x95, 0x78, 0xcf, 0xc4,
0xd4, 0xe1, 0x54, 0x55, 0xb8, 0xd9, 0xd6, 0x70, 0xaa, 0xdb, 0xe2, 0x54, 0x73, 0x59, 0xa7, 0x7d,
0xd9, 0x3b, 0xe0, 0x10, 0x39, 0x2b, 0x95, 0xfd, 0xde, 0xc8, 0x9a, 0xc2, 0x7a, 0x57, 0x70, 0x0e,
0xd7, 0xb6, 0x4e, 0x6c, 0x4e, 0xb2, 0xb6, 0x4f, 0xda, 0xd0, 0xc5, 0x33, 0xf4, 0x20, 0xa5, 0xcb,
0x31, 0xc6, 0xb0, 0xc0, 0x99, 0xaa, 0xb7, 0xcb, 0x1b, 0x1c, 0xfc, 0x68, 0x6d, 0xe2, 0xaa, 0xf3,
0x48, 0xcb, 0xc2, 0x74, 0xb5, 0x12, 0xc9, 0xcc, 0x84, 0xae, 0x21, 0xd5, 0x6d, 0x36, 0x35, 0xa1,
0x3b, 0xb3, 0x29, 0x61, 0x99, 0x99, 0x0e, 0x76, 0x64, 0xc6, 0x86, 0x30, 0x58, 0xa1, 0xc8, 0x4b,
0x89, 0x2b, 0x4c, 0x0a, 0x53, 0x82, 0xb6, 0x89, 0xdd, 0x82, 0x7e, 0x21, 0x16, 0x2f, 0x88, 0xe4,
0xa6, 0x93, 0x85, 0x58, 0x3c, 0xc0, 0x8a, 0xbd, 0x05, 0xde, 0x3c, 0xc2, 0x78, 0xa6, 0x5c, 0xba,
0x9d, 0xae, 0x32, 0x3c, 0xc0, 0x2a, 0xf8, 0xcd, 0x82, 0xde, 0x04, 0xe5, 0x05, 0xca, 0xd7, 0x12,
0xb9, 0xf6, 0xe3, 0x61, 0xbf, 0xe2, 0xf1, 0xe8, 0xee, 0x7f, 0x3c, 0x9c, 0xcd, 0xe3, 0x71, 0x13,
0x9c, 0x89, 0x0c, 0x4f, 0xc7, 0x2a, 0x23, 0x9b, 0x6b, 0x40, 0x6c, 0x1c, 0x85, 0x45, 0x74, 0x81,
0xe6, 0x45, 0x31, 0x68, 0x47, 0xfb, 0xdc, 0x3d, 0xda, 0xf7, 0x83, 0x05, 0xbd, 0x87, 0xa2, 0x4a,
0xcb, 0x62, 0x87, 0x85, 0x43, 0x18, 0x8c, 0xb2, 0x2c, 0x8e, 0x42, 0xfd, 0x6b, 0x7d, 0xa3, 0xb6,
0x89, 0x76, 0x3c, 0x6a, 0xd5, 0x57, 0xdf, 0xad, 0x6d, 0x22, 0xb9, 0x38, 0x51, 0xfa, 0xae, 0xc5,
0xba, 0x25, 0x17, 0x5a, 0xd6, 0x95, 0x93, 0x8a, 0x30, 0x2a, 0x8b, 0x74, 0x1e, 0xa7, 0x97, 0xea,
0xb6, 0x2e, 0x6f, 0x70, 0xf0, 0x7b, 0x07, 0xba, 0xff, 0x95, 0x26, 0x1f, 0x80, 0x15, 0x99, 0x66,
0x5b, 0x51, 0xa3, 0xd0, 0xfd, 0x96, 0x42, 0xfb, 0xd0, 0xaf, 0xa4, 0x48, 0x16, 0x98, 0xfb, 0xae,
0x52, 0x97, 0x1a, 0x2a, 0x8f, 0x9a, 0x23, 0x2d, 0xcd, 0x1e, 0xaf, 0x61, 0x33, 0x17, 0xd0, 0x9a,
0x8b, 0x8f, 0x8c, 0x8a, 0x0f, 0x54, 0x46, 0xfe, 0x76, 0x59, 0xae, 0x8a, 0xf7, 0xbf, 0xa7, 0xc9,
0x7f, 0x59, 0xe0, 0x34, 0x43, 0x75, 0xb2, 0x3d, 0x54, 0x27, 0x9b, 0xa1, 0x1a, 0x1f, 0xd7, 0x43,
0x35, 0x3e, 0x26, 0xcc, 0xcf, 0xea, 0xa1, 0xe2, 0x67, 0xd4, 0xac, 0xfb, 0x32, 0x2d, 0xb3, 0xe3,
0x4a, 0x77, 0xd5, 0xe3, 0x0d, 0x26, 0x26, 0x7e, 0xb3, 0x44, 0x69, 0x4a, 0xed, 0x71, 0x83, 0x88,
0xb7, 0x0f, 0x95, 0xe0, 0xe8, 0xe2, 0x6a, 0xc0, 0xde, 0x03, 0x87, 0x53, 0xf1, 0x54, 0x85, 0xb7,
0xfa, 0xa2, 0xcc, 0x5c, 0x7b, 0x29, 0xa8, 0xfe, 0x7a, 0x33, 0x04, 0xae, 0xbf, 0xe5, 0x3e, 0x84,
0xde, 0x64, 0x19, 0xcd, 0x8b, 0xfa, 0x2d, 0xfc, 0x7f, 0x4b, 0xb0, 0xa2, 0x15, 0x2a, 0x1f, 0x37,
0x5b, 0x82, 0xa7, 0xe0, 0x35, 0xc6, 0x4d, 0x3a, 0x56, 0x3b, 0x1d, 0x06, 0xdd, 0xf3, 0x24, 0x2a,
0xea, 0xd1, 0xa5, 0x35, 0x5d, 0xf6, 0x69, 0x29, 0x92, 0x22, 0x2a, 0xaa, 0x7a, 0x74, 0x6b, 0x1c,
0xdc, 0x33, 0xe9, 0x53, 0xb8, 0xf3, 0x2c, 0x43, 0x69, 0x64, 0x40, 0x03, 0x75, 0x48, 0x7a, 0x89,
0x5a, 0xc1, 0x6d, 0xae, 0x41, 0xf0, 0x2d, 0x78, 0xa3, 0x18, 0x65, 0xc1, 0xcb, 0x18, 0xf7, 0xbd,
0x8c, 0x5f, 0x4f, 0x9e, 0x3c, 0xae, 0x33, 0xa0, 0xf5, 0x66, 0xe4, 0xed, 0x2b, 0x23, 0xff, 0x40,
0x64, 0xe2, 0x74, 0xac, 0x78, 0x6e, 0x73, 0x83, 0x82, 0x9f, 0x2c, 0xe8, 0x92, 0xb6, 0xb4, 0x42,
0x77, 0x5f, 0xa5, 0x4b, 0x67, 0x32, 0xbd, 0x88, 0x66, 0x28, 0xeb, 0xcb, 0xd5, 0x58, 0x15, 0x3d,
0x5c, 0x62, 0xf3, 0x00, 0x1b, 0x44, 0x5c, 0xa3, 0x4f, 0xbd, 0x7a, 0x96, 0x5a, 0x5c, 0x23, 0x33,
0xd7, 0x4e, 0xf6, 0x36, 0xc0, 0xa4, 0xcc, 0x50, 0x8e, 0x66, 0xab, 0x28, 0x51, 0x4d, 0x77, 0x79,
0xcb, 0x12, 0x7c, 0xa1, 0x3f, 0x1e, 0x77, 0x14, 0xca, 0xda, 0xff, 0xa1, 0x79, 0x35, 0xf3, 0x20,
0xde, 0xfe, 0xdd, 0x6b, 0xdd, 0x76, 0x08, 0x03, 0xf3, 0xa5, 0xad, 0xbe, 0x5b, 0x8d, 0x58, 0xb5,
0x4c, 0x74, 0xe7, 0xb3, 0x72, 0x1a, 0x47, 0xa1, 0xba, 0xb3, 0xcb, 0x0d, 0x9a, 0xf6, 0xd4, 0x1f,
0x8a, 0x7b, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0xe0, 0xc4, 0x7a, 0x3e, 0x62, 0x0c, 0x00, 0x00,
}

View File

@ -26,15 +26,24 @@ message Dashboard {
}
message DashboardCell {
int32 x = 1; // X-coordinate of Cell in the Dashboard
int32 y = 2; // Y-coordinate of Cell in the Dashboard
int32 w = 3; // Width of Cell in the Dashboard
int32 h = 4; // Height of Cell in the Dashboard
repeated Query queries = 5; // Time-series data queries for Dashboard
string name = 6; // User-facing name for this Dashboard
string type = 7; // Dashboard visualization type
string ID = 8; // id is the unique id of the dashboard. MIGRATED FIELD added in 1.2.0-beta6
map<string, Axis> axes = 9; // Axes represent the graphical viewport for a cell's visualizations
int32 x = 1; // X-coordinate of Cell in the Dashboard
int32 y = 2; // Y-coordinate of Cell in the Dashboard
int32 w = 3; // Width of Cell in the Dashboard
int32 h = 4; // Height of Cell in the Dashboard
repeated Query queries = 5; // Time-series data queries for Dashboard
string name = 6; // User-facing name for this Dashboard
string type = 7; // Dashboard visualization type
string ID = 8; // id is the unique id of the dashboard. MIGRATED FIELD added in 1.2.0-beta6
map<string, Axis> axes = 9; // Axes represent the graphical viewport for a cell's visualizations
repeated Color colors = 10; // Colors represent encoding data values to color
}
message Color {
string ID = 1; // ID is the unique id of the cell color
string Type = 2; // Type is how the color is used. Accepted (min,max,threshold)
string Hex = 3; // Hex is the hex number of the color
string Name = 4; // Name is the user-facing name of the hex color
string Value = 5; // Value is the data value mapped to this color
}
message Axis {

View File

@ -177,6 +177,22 @@ func Test_MarshalDashboard(t *testing.T) {
},
},
Type: "line",
CellColors: []chronograf.CellColor{
{
ID: "myid",
Type: "min",
Hex: "#234567",
Name: "Laser",
Value: "0",
},
{
ID: "id2",
Type: "max",
Hex: "#876543",
Name: "Solitude",
Value: "100",
},
},
},
},
Templates: []chronograf.Template{},
@ -219,6 +235,22 @@ func Test_MarshalDashboard_WithLegacyBounds(t *testing.T) {
LegacyBounds: [2]int64{0, 5},
},
},
CellColors: []chronograf.CellColor{
{
ID: "myid",
Type: "min",
Hex: "#234567",
Name: "Laser",
Value: "0",
},
{
ID: "id2",
Type: "max",
Hex: "#876543",
Name: "Solitude",
Value: "100",
},
},
Type: "line",
},
},
@ -253,6 +285,22 @@ func Test_MarshalDashboard_WithLegacyBounds(t *testing.T) {
Scale: "linear",
},
},
CellColors: []chronograf.CellColor{
{
ID: "myid",
Type: "min",
Hex: "#234567",
Name: "Laser",
Value: "0",
},
{
ID: "id2",
Type: "max",
Hex: "#876543",
Name: "Solitude",
Value: "100",
},
},
Type: "line",
},
},
@ -296,6 +344,22 @@ func Test_MarshalDashboard_WithEmptyLegacyBounds(t *testing.T) {
LegacyBounds: [2]int64{},
},
},
CellColors: []chronograf.CellColor{
{
ID: "myid",
Type: "min",
Hex: "#234567",
Name: "Laser",
Value: "0",
},
{
ID: "id2",
Type: "max",
Hex: "#876543",
Name: "Solitude",
Value: "100",
},
},
Type: "line",
},
},
@ -330,6 +394,22 @@ func Test_MarshalDashboard_WithEmptyLegacyBounds(t *testing.T) {
Scale: "linear",
},
},
CellColors: []chronograf.CellColor{
{
ID: "myid",
Type: "min",
Hex: "#234567",
Name: "Laser",
Value: "0",
},
{
ID: "id2",
Type: "max",
Hex: "#876543",
Name: "Solitude",
Value: "100",
},
},
Type: "line",
},
},

View File

@ -15,13 +15,15 @@ const (
ErrLayoutNotFound = Error("layout not found")
ErrDashboardNotFound = Error("dashboard not found")
ErrUserNotFound = Error("user not found")
ErrUserAlreadyExists = Error("user already exists")
ErrOrganizationNotFound = Error("organization not found")
ErrLayoutInvalid = Error("layout is invalid")
ErrAlertNotFound = Error("alert not found")
ErrAuthentication = Error("user not authenticated")
ErrUninitialized = Error("client uninitialized. Call Open() method")
ErrInvalidAxis = Error("Unexpected axis in cell. Valid axes are 'x', 'y', and 'y2'")
ErrInvalidColorType = Error("Invalid color type. Valid color types are 'min', 'max', 'threshold'")
ErrInvalidColor = Error("Invalid color. Accepted color format is #RRGGBB")
ErrUserAlreadyExists = Error("user already exists")
ErrOrganizationNotFound = Error("organization not found")
ErrOrganizationAlreadyExists = Error("organization already exists")
ErrCannotDeleteDefaultOrganization = Error("cannot delete default organization")
)
@ -484,17 +486,27 @@ type Axis struct {
Scale string `json:"scale"` // Scale is the axis formatting scale. Supported: "log", "linear"
}
// CellColor represents the encoding of data into visualizations
type CellColor struct {
ID string `json:"id"` // ID is the unique id of the cell color
Type string `json:"type"` // Type is how the color is used. Accepted (min,max,threshold)
Hex string `json:"hex"` // Hex is the hex number of the color
Name string `json:"name"` // Name is the user-facing name of the hex color
Value string `json:"value"` // Value is the data value mapped to this color
}
// DashboardCell holds visual and query information for a cell
type DashboardCell struct {
ID string `json:"i"`
X int32 `json:"x"`
Y int32 `json:"y"`
W int32 `json:"w"`
H int32 `json:"h"`
Name string `json:"name"`
Queries []DashboardQuery `json:"queries"`
Axes map[string]Axis `json:"axes"`
Type string `json:"type"`
ID string `json:"i"`
X int32 `json:"x"`
Y int32 `json:"y"`
W int32 `json:"w"`
H int32 `json:"h"`
Name string `json:"name"`
Queries []DashboardQuery `json:"queries"`
Axes map[string]Axis `json:"axes"`
Type string `json:"type"`
CellColors []CellColor `json:"colors"`
}
// DashboardsStore is the storage and retrieval of dashboards

View File

@ -34,6 +34,9 @@ func newCellResponses(dID chronograf.DashboardID, dcells []chronograf.DashboardC
newCell.Queries = make([]chronograf.DashboardQuery, len(cell.Queries))
copy(newCell.Queries, cell.Queries)
newCell.CellColors = make([]chronograf.CellColor, len(cell.CellColors))
copy(newCell.CellColors, cell.CellColors)
// ensure x, y, and y2 axes always returned
labels := []string{"x", "y", "y2"}
newCell.Axes = make(map[string]chronograf.Axis, len(labels))
@ -80,7 +83,11 @@ func ValidDashboardCellRequest(c *chronograf.DashboardCell) error {
}
}
MoveTimeShift(c)
return HasCorrectAxes(c)
err := HasCorrectAxes(c)
if err != nil {
return err
}
return HasCorrectColors(c)
}
// HasCorrectAxes verifies that only permitted axes exist within a DashboardCell
@ -102,6 +109,19 @@ func HasCorrectAxes(c *chronograf.DashboardCell) error {
return nil
}
// HasCorrectColors verifies that the format of each color is correct
func HasCorrectColors(c *chronograf.DashboardCell) error {
for _, color := range c.CellColors {
if !oneOf(color.Type, "max", "min", "threshold") {
return chronograf.ErrInvalidColorType
}
if len(color.Hex) != 7 {
return chronograf.ErrInvalidColor
}
}
return nil
}
// oneOf reports whether a provided string is a member of a variadic list of
// valid options
func oneOf(prop string, validOpts ...string) bool {

View File

@ -25,8 +25,8 @@ func Test_Cells_CorrectAxis(t *testing.T) {
shouldFail bool
}{
{
"correct axes",
&chronograf.DashboardCell{
name: "correct axes",
cell: &chronograf.DashboardCell{
Axes: map[string]chronograf.Axis{
"x": chronograf.Axis{
Bounds: []string{"0", "100"},
@ -39,11 +39,10 @@ func Test_Cells_CorrectAxis(t *testing.T) {
},
},
},
false,
},
{
"invalid axes present",
&chronograf.DashboardCell{
name: "invalid axes present",
cell: &chronograf.DashboardCell{
Axes: map[string]chronograf.Axis{
"axis of evil": chronograf.Axis{
Bounds: []string{"666", "666"},
@ -53,11 +52,11 @@ func Test_Cells_CorrectAxis(t *testing.T) {
},
},
},
true,
shouldFail: true,
},
{
"linear scale value",
&chronograf.DashboardCell{
name: "linear scale value",
cell: &chronograf.DashboardCell{
Axes: map[string]chronograf.Axis{
"x": chronograf.Axis{
Scale: "linear",
@ -65,11 +64,10 @@ func Test_Cells_CorrectAxis(t *testing.T) {
},
},
},
false,
},
{
"log scale value",
&chronograf.DashboardCell{
name: "log scale value",
cell: &chronograf.DashboardCell{
Axes: map[string]chronograf.Axis{
"x": chronograf.Axis{
Scale: "log",
@ -77,11 +75,10 @@ func Test_Cells_CorrectAxis(t *testing.T) {
},
},
},
false,
},
{
"invalid scale value",
&chronograf.DashboardCell{
name: "invalid scale value",
cell: &chronograf.DashboardCell{
Axes: map[string]chronograf.Axis{
"x": chronograf.Axis{
Scale: "potatoes",
@ -89,11 +86,11 @@ func Test_Cells_CorrectAxis(t *testing.T) {
},
},
},
true,
shouldFail: true,
},
{
"base 10 axis",
&chronograf.DashboardCell{
name: "base 10 axis",
cell: &chronograf.DashboardCell{
Axes: map[string]chronograf.Axis{
"x": chronograf.Axis{
Base: "10",
@ -101,11 +98,10 @@ func Test_Cells_CorrectAxis(t *testing.T) {
},
},
},
false,
},
{
"base 2 axis",
&chronograf.DashboardCell{
name: "base 2 axis",
cell: &chronograf.DashboardCell{
Axes: map[string]chronograf.Axis{
"x": chronograf.Axis{
Base: "2",
@ -113,11 +109,10 @@ func Test_Cells_CorrectAxis(t *testing.T) {
},
},
},
false,
},
{
"invalid base",
&chronograf.DashboardCell{
name: "invalid base",
cell: &chronograf.DashboardCell{
Axes: map[string]chronograf.Axis{
"x": chronograf.Axis{
Base: "all your base are belong to us",
@ -125,7 +120,7 @@ func Test_Cells_CorrectAxis(t *testing.T) {
},
},
},
true,
shouldFail: true,
},
}
@ -150,16 +145,16 @@ func Test_Service_DashboardCells(t *testing.T) {
expectedCode int
}{
{
"happy path",
&url.URL{
name: "happy path",
reqURL: &url.URL{
Path: "/chronograf/v1/dashboards/1/cells",
},
map[string]string{
ctxParams: map[string]string{
"id": "1",
},
[]chronograf.DashboardCell{},
[]chronograf.DashboardCell{},
http.StatusOK,
mockResponse: []chronograf.DashboardCell{},
expected: []chronograf.DashboardCell{},
expectedCode: http.StatusOK,
},
{
name: "cell axes should always be \"x\", \"y\", and \"y2\"",
@ -184,14 +179,15 @@ func Test_Service_DashboardCells(t *testing.T) {
},
expected: []chronograf.DashboardCell{
{
ID: "3899be5a-f6eb-4347-b949-de2f4fbea859",
X: 0,
Y: 0,
W: 4,
H: 4,
Name: "CPU",
Type: "bar",
Queries: []chronograf.DashboardQuery{},
ID: "3899be5a-f6eb-4347-b949-de2f4fbea859",
X: 0,
Y: 0,
W: 4,
H: 4,
Name: "CPU",
Type: "bar",
Queries: []chronograf.DashboardQuery{},
CellColors: []chronograf.CellColor{},
Axes: map[string]chronograf.Axis{
"x": chronograf.Axis{
Bounds: []string{},
@ -280,3 +276,76 @@ func Test_Service_DashboardCells(t *testing.T) {
})
}
}
func TestHasCorrectColors(t *testing.T) {
tests := []struct {
name string
c *chronograf.DashboardCell
wantErr bool
}{
{
name: "min type is valid",
c: &chronograf.DashboardCell{
CellColors: []chronograf.CellColor{
{
Type: "min",
Hex: "#FFFFFF",
},
},
},
},
{
name: "max type is valid",
c: &chronograf.DashboardCell{
CellColors: []chronograf.CellColor{
{
Type: "max",
Hex: "#FFFFFF",
},
},
},
},
{
name: "threshold type is valid",
c: &chronograf.DashboardCell{
CellColors: []chronograf.CellColor{
{
Type: "threshold",
Hex: "#FFFFFF",
},
},
},
},
{
name: "invalid color type",
c: &chronograf.DashboardCell{
CellColors: []chronograf.CellColor{
{
Type: "unknown",
Hex: "#FFFFFF",
},
},
},
wantErr: true,
},
{
name: "invalid color hex",
c: &chronograf.DashboardCell{
CellColors: []chronograf.CellColor{
{
Type: "min",
Hex: "bad",
},
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := server.HasCorrectColors(tt.c); (err != nil) != tt.wantErr {
t.Errorf("HasCorrectColors() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@ -289,6 +289,7 @@ func Test_newDashboardResponse(t *testing.T) {
},
},
},
CellColors: []chronograf.CellColor{},
Axes: map[string]chronograf.Axis{
"x": chronograf.Axis{
Bounds: []string{"0", "100"},
@ -322,6 +323,7 @@ func Test_newDashboardResponse(t *testing.T) {
Bounds: []string{},
},
},
CellColors: []chronograf.CellColor{},
Queries: []chronograf.DashboardQuery{
{
Command: "SELECT winning_horses from grays_sports_alamanc where time > now() - 15m",

View File

@ -3956,6 +3956,14 @@
],
"default": "line"
},
"colors": {
"description":
"Colors define encoding data into a visualization",
"type": "array",
"items": {
"$ref": "#/definitions/DashboardColor"
}
},
"links": {
"type": "object",
"properties": {
@ -4026,6 +4034,36 @@
}
}
},
"DashboardColor": {
"type": "object",
"description":
"Color defines an encoding of a data value into color space",
"properties": {
"id": {
"description": "ID is the unique id of the cell color",
"type": "string"
},
"type": {
"description": "Type is how the color is used.",
"type": "string",
"enum": ["min", "max", "threshold"]
},
"hex": {
"description": "Hex is the hex number of the color",
"type": "string",
"maxLength": 7,
"minLength": 7
},
"name": {
"description": "Name is the user-facing name of the hex color",
"type": "string"
},
"value": {
"description": "Value is the data value mapped to this color",
"type": "string"
}
}
},
"Axis": {
"type": "object",
"description": "A description of a particular axis for a visualization",

View File

@ -5,7 +5,7 @@ import classnames from 'classnames'
import {Link} from 'react-router'
import uuid from 'node-uuid'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import InfiniteScroll from 'shared/components/InfiniteScroll'
import {ALERTS_TABLE} from 'src/alerts/constants/tableSizing'
@ -49,7 +49,7 @@ class AlertsTable extends Component {
}
}
sortableClasses = key => () => {
sortableClasses = key => {
if (this.state.sortKey === key) {
if (this.state.sortDirection === 'asc') {
return 'alert-history-table--th sortable-header sorting-ascending'
@ -117,11 +117,10 @@ class AlertsTable extends Component {
Value
</div>
</div>
<FancyScrollbar
<InfiniteScroll
className="alert-history-table--tbody"
autoHide={false}
>
{alerts.map(({name, level, time, host, value}) => {
itemHeight={25}
items={alerts.map(({name, level, time, host, value}) => {
return (
<div className="alert-history-table--tr" key={uuid.v4()}>
<div
@ -165,7 +164,7 @@ class AlertsTable extends Component {
</div>
)
})}
</FancyScrollbar>
/>
</div>
: this.renderTableEmpty()
}

View File

@ -3,7 +3,10 @@ import React, {PropTypes} from 'react'
import OptIn from 'shared/components/OptIn'
import Input from 'src/dashboards/components/DisplayOptionsInput'
import {Tabber, Tab} from 'src/dashboards/components/Tabber'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import {DISPLAY_OPTIONS, TOOLTIP_CONTENT} from 'src/dashboards/constants'
import {GRAPH_TYPES} from 'src/dashboards/graphics/graph'
const {LINEAR, LOG, BASE_2, BASE_10} = DISPLAY_OPTIONS
const getInputMin = scale => (scale === LOG ? '0' : null)
@ -16,86 +19,98 @@ const AxesOptions = ({
onSetPrefixSuffix,
onSetYAxisBoundMin,
onSetYAxisBoundMax,
selectedGraphType,
}) => {
const [min, max] = bounds
const {menuOption} = GRAPH_TYPES.find(
graph => graph.type === selectedGraphType
)
return (
<div className="display-options--cell y-axis-controls">
<h5 className="display-options--header">Y Axis Controls</h5>
<form autoComplete="off" style={{margin: '0 -6px'}}>
<div className="form-group col-sm-12">
<label htmlFor="prefix">Title</label>
<OptIn
customPlaceholder={defaultYLabel}
customValue={label}
onSetValue={onSetLabel}
type="text"
<FancyScrollbar
className="display-options--cell y-axis-controls"
autoHide={false}
>
<div className="display-options--cell-wrapper">
<h5 className="display-options--header">
{menuOption} Controls
</h5>
<form autoComplete="off" style={{margin: '0 -6px'}}>
<div className="form-group col-sm-12">
<label htmlFor="prefix">Title</label>
<OptIn
customPlaceholder={defaultYLabel}
customValue={label}
onSetValue={onSetLabel}
type="text"
/>
</div>
<div className="form-group col-sm-6">
<label htmlFor="min">Min</label>
<OptIn
customPlaceholder={'min'}
customValue={min}
onSetValue={onSetYAxisBoundMin}
type="number"
min={getInputMin(scale)}
/>
</div>
<div className="form-group col-sm-6">
<label htmlFor="max">Max</label>
<OptIn
customPlaceholder={'max'}
customValue={max}
onSetValue={onSetYAxisBoundMax}
type="number"
min={getInputMin(scale)}
/>
</div>
<Input
name="prefix"
id="prefix"
value={prefix}
labelText="Y-Value's Prefix"
onChange={onSetPrefixSuffix}
/>
</div>
<div className="form-group col-sm-6">
<label htmlFor="min">Min</label>
<OptIn
customPlaceholder={'min'}
customValue={min}
onSetValue={onSetYAxisBoundMin}
type="number"
min={getInputMin(scale)}
<Input
name="suffix"
id="suffix"
value={suffix}
labelText="Y-Value's Suffix"
onChange={onSetPrefixSuffix}
/>
</div>
<div className="form-group col-sm-6">
<label htmlFor="max">Max</label>
<OptIn
customPlaceholder={'max'}
customValue={max}
onSetValue={onSetYAxisBoundMax}
type="number"
min={getInputMin(scale)}
/>
</div>
<Input
name="prefix"
id="prefix"
value={prefix}
labelText="Y-Value's Prefix"
onChange={onSetPrefixSuffix}
/>
<Input
name="suffix"
id="suffix"
value={suffix}
labelText="Y-Value's Suffix"
onChange={onSetPrefixSuffix}
/>
<Tabber
labelText="Y-Value's Format"
tipID="Y-Values's Format"
tipContent={TOOLTIP_CONTENT.FORMAT}
>
<Tab
text="K/M/B"
isActive={base === BASE_10}
onClickTab={onSetBase(BASE_10)}
/>
<Tab
text="K/M/G"
isActive={base === BASE_2}
onClickTab={onSetBase(BASE_2)}
/>
</Tabber>
<Tabber labelText="Scale">
<Tab
text="Linear"
isActive={scale === LINEAR}
onClickTab={onSetScale(LINEAR)}
/>
<Tab
text="Logarithmic"
isActive={scale === LOG}
onClickTab={onSetScale(LOG)}
/>
</Tabber>
</form>
</div>
<Tabber
labelText="Y-Value's Format"
tipID="Y-Values's Format"
tipContent={TOOLTIP_CONTENT.FORMAT}
>
<Tab
text="K/M/B"
isActive={base === BASE_10}
onClickTab={onSetBase(BASE_10)}
/>
<Tab
text="K/M/G"
isActive={base === BASE_2}
onClickTab={onSetBase(BASE_2)}
/>
</Tabber>
<Tabber labelText="Scale">
<Tab
text="Linear"
isActive={scale === LINEAR}
onClickTab={onSetScale(LINEAR)}
/>
<Tab
text="Logarithmic"
isActive={scale === LOG}
onClickTab={onSetScale(LOG)}
/>
</Tabber>
</form>
</div>
</FancyScrollbar>
)
}
@ -115,6 +130,7 @@ AxesOptions.defaultProps = {
}
AxesOptions.propTypes = {
selectedGraphType: string.isRequired,
onSetPrefixSuffix: func.isRequired,
onSetYAxisBoundMin: func.isRequired,
onSetYAxisBoundMax: func.isRequired,

View File

@ -22,12 +22,21 @@ import {
import {OVERLAY_TECHNOLOGY} from 'shared/constants/classNames'
import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants'
import {AUTO_GROUP_BY} from 'shared/constants'
import {
COLOR_TYPE_THRESHOLD,
MAX_THRESHOLDS,
DEFAULT_COLORS,
GAUGE_COLORS,
COLOR_TYPE_MIN,
COLOR_TYPE_MAX,
validateColors,
} from 'src/dashboards/constants/gaugeColors'
class CellEditorOverlay extends Component {
constructor(props) {
super(props)
const {cell: {name, type, queries, axes}, sources} = props
const {cell: {name, type, queries, axes, colors}, sources} = props
let source = _.get(queries, ['0', 'source'], null)
source = sources.find(s => s.links.self === source) || props.source
@ -47,6 +56,7 @@ class CellEditorOverlay extends Component {
activeQueryIndex: 0,
isDisplayOptionsTabActive: false,
axes,
colors: validateColors(colors) ? colors : DEFAULT_COLORS,
}
}
@ -63,6 +73,111 @@ class CellEditorOverlay extends Component {
}
}
handleAddThreshold = () => {
const {colors} = this.state
if (colors.length <= MAX_THRESHOLDS) {
const randomColor = _.random(0, GAUGE_COLORS.length)
const maxValue = Number(
colors.find(color => color.type === COLOR_TYPE_MAX).value
)
const minValue = Number(
colors.find(color => color.type === COLOR_TYPE_MIN).value
)
const colorsValues = _.mapValues(colors, 'value')
let randomValue
do {
randomValue = `${_.round(_.random(minValue, maxValue, true), 2)}`
} while (_.includes(colorsValues, randomValue))
const newThreshold = {
type: COLOR_TYPE_THRESHOLD,
id: uuid.v4(),
value: randomValue,
hex: GAUGE_COLORS[randomColor].hex,
name: GAUGE_COLORS[randomColor].name,
}
this.setState({colors: [...colors, newThreshold]})
}
}
handleDeleteThreshold = threshold => () => {
const {colors} = this.state
const newColors = colors.filter(color => color.id !== threshold.id)
this.setState({colors: newColors})
}
handleChooseColor = threshold => chosenColor => {
const {colors} = this.state
const newColors = colors.map(
color =>
color.id === threshold.id
? {...color, hex: chosenColor.hex, name: chosenColor.name}
: color
)
this.setState({colors: newColors})
}
handleUpdateColorValue = (threshold, newValue) => {
const {colors} = this.state
const newColors = colors.map(
color => (color.id === threshold.id ? {...color, value: newValue} : color)
)
this.setState({colors: newColors})
}
handleValidateColorValue = (threshold, e) => {
const {colors} = this.state
const sortedColors = _.sortBy(colors, color => Number(color.value))
const targetValueNumber = Number(e.target.value)
const maxValue = Number(
colors.find(color => color.type === COLOR_TYPE_MAX).value
)
const minValue = Number(
colors.find(color => color.type === COLOR_TYPE_MIN).value
)
let allowedToUpdate = false
// If type === min, make sure it is less than the next threshold
if (threshold.type === COLOR_TYPE_MIN) {
const nextValue = Number(sortedColors[1].value)
allowedToUpdate = targetValueNumber < nextValue && targetValueNumber >= 0
}
// If type === max, make sure it is greater than the previous threshold
if (threshold.type === COLOR_TYPE_MAX) {
const previousValue = Number(sortedColors[sortedColors.length - 2].value)
allowedToUpdate = previousValue < targetValueNumber
}
// If type === threshold, make sure new value is greater than min, less than max, and unique
if (threshold.type === COLOR_TYPE_THRESHOLD) {
const greaterThanMin = targetValueNumber > minValue
const lessThanMax = targetValueNumber < maxValue
const colorsWithoutMinOrMax = sortedColors.slice(
1,
sortedColors.length - 1
)
const isUnique = !colorsWithoutMinOrMax.some(
color => color.value === e.target.value
)
allowedToUpdate = greaterThanMin && lessThanMax && isUnique
}
return allowedToUpdate
}
queryStateReducer = queryModifier => (queryID, ...payload) => {
const {queriesWorkingDraft} = this.state
const query = queriesWorkingDraft.find(q => q.id === queryID)
@ -145,6 +260,7 @@ class CellEditorOverlay extends Component {
cellWorkingType: type,
cellWorkingName: name,
axes,
colors,
} = this.state
const {cell} = this.props
@ -166,6 +282,7 @@ class CellEditorOverlay extends Component {
type,
queries,
axes,
colors,
})
}
@ -296,6 +413,7 @@ class CellEditorOverlay extends Component {
const {
axes,
colors,
activeQueryIndex,
cellWorkingName,
cellWorkingType,
@ -323,6 +441,7 @@ class CellEditorOverlay extends Component {
>
<Visualization
axes={axes}
colors={colors}
type={cellWorkingType}
name={cellWorkingName}
timeRange={timeRange}
@ -347,6 +466,12 @@ class CellEditorOverlay extends Component {
{isDisplayOptionsTabActive
? <DisplayOptions
axes={axes}
colors={colors}
onChooseColor={this.handleChooseColor}
onValidateColorValue={this.handleValidateColorValue}
onUpdateColorValue={this.handleUpdateColorValue}
onAddThreshold={this.handleAddThreshold}
onDeleteThreshold={this.handleDeleteThreshold}
onSetBase={this.handleSetBase}
onSetLabel={this.handleSetLabel}
onSetScale={this.handleSetScale}

View File

@ -1,6 +1,7 @@
import React, {Component, PropTypes} from 'react'
import GraphTypeSelector from 'src/dashboards/components/GraphTypeSelector'
import GaugeOptions from 'src/dashboards/components/GaugeOptions'
import AxesOptions from 'src/dashboards/components/AxesOptions'
import {buildDefaultYLabel} from 'shared/presenters'
@ -33,6 +34,7 @@ class DisplayOptions extends Component {
render() {
const {
colors,
onSetBase,
onSetScale,
onSetLabel,
@ -41,24 +43,41 @@ class DisplayOptions extends Component {
onSetPrefixSuffix,
onSetYAxisBoundMin,
onSetYAxisBoundMax,
onAddThreshold,
onDeleteThreshold,
onChooseColor,
onValidateColorValue,
onUpdateColorValue,
} = this.props
const {axes} = this.state
const isGauge = selectedGraphType === 'gauge'
return (
<div className="display-options">
<AxesOptions
axes={axes}
onSetBase={onSetBase}
onSetLabel={onSetLabel}
onSetScale={onSetScale}
onSetPrefixSuffix={onSetPrefixSuffix}
onSetYAxisBoundMin={onSetYAxisBoundMin}
onSetYAxisBoundMax={onSetYAxisBoundMax}
/>
<GraphTypeSelector
selectedGraphType={selectedGraphType}
onSelectGraphType={onSelectGraphType}
/>
{isGauge
? <GaugeOptions
colors={colors}
onChooseColor={onChooseColor}
onValidateColorValue={onValidateColorValue}
onUpdateColorValue={onUpdateColorValue}
onAddThreshold={onAddThreshold}
onDeleteThreshold={onDeleteThreshold}
/>
: <AxesOptions
selectedGraphType={selectedGraphType}
axes={axes}
onSetBase={onSetBase}
onSetLabel={onSetLabel}
onSetScale={onSetScale}
onSetPrefixSuffix={onSetPrefixSuffix}
onSetYAxisBoundMin={onSetYAxisBoundMin}
onSetYAxisBoundMax={onSetYAxisBoundMax}
/>}
</div>
)
}
@ -66,6 +85,11 @@ class DisplayOptions extends Component {
const {arrayOf, func, shape, string} = PropTypes
DisplayOptions.propTypes = {
onAddThreshold: func.isRequired,
onDeleteThreshold: func.isRequired,
onChooseColor: func.isRequired,
onValidateColorValue: func.isRequired,
onUpdateColorValue: func.isRequired,
selectedGraphType: string.isRequired,
onSelectGraphType: func.isRequired,
onSetPrefixSuffix: func.isRequired,
@ -75,6 +99,15 @@ DisplayOptions.propTypes = {
onSetLabel: func.isRequired,
onSetBase: func.isRequired,
axes: shape({}).isRequired,
colors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: string.isRequired,
}).isRequired
),
queryConfigs: arrayOf(shape()).isRequired,
}

View File

@ -1,7 +1,15 @@
import React, {PropTypes} from 'react'
const DisplayOptionsInput = ({id, name, value, onChange, labelText}) =>
<div className="form-group col-sm-6">
const DisplayOptionsInput = ({
id,
name,
value,
onChange,
labelText,
colWidth,
placeholder,
}) =>
<div className={`form-group ${colWidth}`}>
<label htmlFor={name}>
{labelText}
</label>
@ -12,6 +20,7 @@ const DisplayOptionsInput = ({id, name, value, onChange, labelText}) =>
id={id}
value={value}
onChange={onChange}
placeholder={placeholder}
/>
</div>
@ -19,6 +28,8 @@ const {func, string} = PropTypes
DisplayOptionsInput.defaultProps = {
value: '',
colWidth: 'col-sm-6',
placeholder: '',
}
DisplayOptionsInput.propTypes = {
@ -27,6 +38,8 @@ DisplayOptionsInput.propTypes = {
value: string.isRequired,
onChange: func.isRequired,
labelText: string,
colWidth: string,
placeholder: string,
}
export default DisplayOptionsInput

View File

@ -0,0 +1,82 @@
import React, {PropTypes} from 'react'
import _ from 'lodash'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import GaugeThreshold from 'src/dashboards/components/GaugeThreshold'
import {
MAX_THRESHOLDS,
MIN_THRESHOLDS,
DEFAULT_COLORS,
} from 'src/dashboards/constants/gaugeColors'
const GaugeOptions = ({
colors,
onAddThreshold,
onDeleteThreshold,
onChooseColor,
onValidateColorValue,
onUpdateColorValue,
}) => {
const disableMaxColor = colors.length > MIN_THRESHOLDS
const disableAddThreshold = colors.length > MAX_THRESHOLDS
const sortedColors = _.sortBy(colors, color => Number(color.value))
return (
<FancyScrollbar
className="display-options--cell y-axis-controls"
autoHide={false}
>
<div className="display-options--cell-wrapper">
<h5 className="display-options--header">Gauge Controls</h5>
<div className="gauge-controls">
{sortedColors.map(color =>
<GaugeThreshold
threshold={color}
key={color.id}
disableMaxColor={disableMaxColor}
onChooseColor={onChooseColor}
onValidateColorValue={onValidateColorValue}
onUpdateColorValue={onUpdateColorValue}
onDeleteThreshold={onDeleteThreshold}
/>
)}
<button
className="btn btn-sm btn-primary gauge-controls--add-threshold"
onClick={onAddThreshold}
disabled={disableAddThreshold}
>
<span className="icon plus" /> Add Threshold
</button>
</div>
</div>
</FancyScrollbar>
)
}
const {arrayOf, func, shape, string} = PropTypes
GaugeOptions.defaultProps = {
colors: DEFAULT_COLORS,
}
GaugeOptions.propTypes = {
colors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: string.isRequired,
}).isRequired
),
onAddThreshold: func.isRequired,
onDeleteThreshold: func.isRequired,
onChooseColor: func.isRequired,
onValidateColorValue: func.isRequired,
onUpdateColorValue: func.isRequired,
}
export default GaugeOptions

View File

@ -0,0 +1,116 @@
import React, {Component, PropTypes} from 'react'
import ColorDropdown from 'shared/components/ColorDropdown'
import {
COLOR_TYPE_MIN,
COLOR_TYPE_MAX,
GAUGE_COLORS,
} from 'src/dashboards/constants/gaugeColors'
class GaugeThreshold extends Component {
constructor(props) {
super(props)
this.state = {
workingValue: this.props.threshold.value,
valid: true,
}
}
handleChangeWorkingValue = e => {
const {threshold, onValidateColorValue, onUpdateColorValue} = this.props
const valid = onValidateColorValue(threshold, e)
if (valid) {
onUpdateColorValue(threshold, e.target.value)
}
this.setState({valid, workingValue: e.target.value})
}
handleBlur = () => {
this.setState({workingValue: this.props.threshold.value, valid: true})
}
render() {
const {
threshold,
threshold: {type, hex, name},
disableMaxColor,
onChooseColor,
onDeleteThreshold,
} = this.props
const {workingValue, valid} = this.state
const selectedColor = {hex, name}
const labelClass =
type === COLOR_TYPE_MIN || type === COLOR_TYPE_MAX
? 'gauge-controls--label'
: 'gauge-controls--label-editable'
const canBeDeleted = !(type === COLOR_TYPE_MIN || type === COLOR_TYPE_MAX)
let label = 'Threshold'
if (type === COLOR_TYPE_MIN) {
label = 'Minimum'
}
if (type === COLOR_TYPE_MAX) {
label = 'Maximum'
}
const inputClass = valid
? 'form-control input-sm gauge-controls--input'
: 'form-control input-sm gauge-controls--input form-volcano'
return (
<div className="gauge-controls--section">
<div className={labelClass}>
{label}
</div>
{canBeDeleted
? <button
className="btn btn-default btn-sm btn-square gauge-controls--delete"
onClick={onDeleteThreshold(threshold)}
>
<span className="icon remove" />
</button>
: null}
<input
value={workingValue}
className={inputClass}
type="number"
onChange={this.handleChangeWorkingValue}
onBlur={this.handleBlur}
min={0}
/>
<ColorDropdown
colors={GAUGE_COLORS}
selected={selectedColor}
onChoose={onChooseColor(threshold)}
disabled={type === COLOR_TYPE_MAX && disableMaxColor}
/>
</div>
)
}
}
const {bool, func, shape, string} = PropTypes
GaugeThreshold.propTypes = {
threshold: shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: string.isRequired,
}).isRequired,
disableMaxColor: bool,
onChooseColor: func.isRequired,
onValidateColorValue: func.isRequired,
onUpdateColorValue: func.isRequired,
onDeleteThreshold: func.isRequired,
}
export default GaugeThreshold

View File

@ -1,29 +1,35 @@
import React, {PropTypes} from 'react'
import classnames from 'classnames'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import {graphTypes} from 'src/dashboards/graphics/graph'
import {GRAPH_TYPES} from 'src/dashboards/graphics/graph'
const GraphTypeSelector = ({selectedGraphType, onSelectGraphType}) =>
<div className="display-options--cell display-options--cellx2">
<h5 className="display-options--header">Visualization Type</h5>
<div className="viz-type-selector">
{graphTypes.map(graphType =>
<div
key={graphType.type}
className={classnames('viz-type-selector--option', {
active: graphType.type === selectedGraphType,
})}
>
<div onClick={onSelectGraphType(graphType.type)}>
{graphType.graphic}
<p>
{graphType.menuOption}
</p>
<FancyScrollbar
className="display-options--cell display-options--cellx2"
autoHide={false}
>
<div className="display-options--cell-wrapper">
<h5 className="display-options--header">Visualization Type</h5>
<div className="viz-type-selector">
{GRAPH_TYPES.map(graphType =>
<div
key={graphType.type}
className={classnames('viz-type-selector--option', {
active: graphType.type === selectedGraphType,
})}
>
<div onClick={onSelectGraphType(graphType.type)}>
{graphType.graphic}
<p>
{graphType.menuOption}
</p>
</div>
</div>
</div>
)}
)}
</div>
</div>
</div>
</FancyScrollbar>
const {func, string} = PropTypes

View File

@ -39,7 +39,7 @@ const OverlayControls = ({
})}
onClick={onClickDisplayOptions(true)}
>
Options
Visualization
</li>
</ul>
<div className="overlay-controls--right">

View File

@ -8,12 +8,14 @@ const DashVisualization = (
axes,
type,
name,
colors,
templates,
timeRange,
autoRefresh,
onCellRename,
queryConfigs,
editQueryStatus,
resizerTopHeight,
},
{source: {links: {proxy}}}
) =>
@ -21,12 +23,14 @@ const DashVisualization = (
<VisualizationName defaultName={name} onCellRename={onCellRename} />
<div className="graph-container">
<RefreshingGraph
colors={colors}
axes={axes}
type={type}
queries={buildQueries(proxy, queryConfigs, timeRange)}
templates={templates}
autoRefresh={autoRefresh}
editQueryStatus={editQueryStatus}
resizerTopHeight={resizerTopHeight}
/>
</div>
</div>
@ -55,6 +59,16 @@ DashVisualization.propTypes = {
}),
}),
onCellRename: func,
resizerTopHeight: number,
colors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: string.isRequired,
}).isRequired
),
}
DashVisualization.contextTypes = {

View File

@ -0,0 +1,106 @@
export const MAX_THRESHOLDS = 5
export const MIN_THRESHOLDS = 2
export const COLOR_TYPE_MIN = 'min'
export const DEFAULT_VALUE_MIN = '0'
export const COLOR_TYPE_MAX = 'max'
export const DEFAULT_VALUE_MAX = '100'
export const COLOR_TYPE_THRESHOLD = 'threshold'
export const GAUGE_COLORS = [
{
hex: '#BF3D5E',
name: 'ruby',
},
{
hex: '#DC4E58',
name: 'fire',
},
{
hex: '#F95F53',
name: 'curacao',
},
{
hex: '#F48D38',
name: 'tiger',
},
{
hex: '#FFB94A',
name: 'pineapple',
},
{
hex: '#FFD255',
name: 'thunder',
},
{
hex: '#7CE490',
name: 'honeydew',
},
{
hex: '#4ED8A0',
name: 'rainforest',
},
{
hex: '#32B08C',
name: 'viridian',
},
{
hex: '#4591ED',
name: 'ocean',
},
{
hex: '#22ADF6',
name: 'pool',
},
{
hex: '#00C9FF',
name: 'laser',
},
{
hex: '#513CC6',
name: 'planet',
},
{
hex: '#7A65F2',
name: 'star',
},
{
hex: '#9394FF',
name: 'comet',
},
{
hex: '#383846',
name: 'pepper',
},
{
hex: '#545667',
name: 'graphite',
},
]
export const DEFAULT_COLORS = [
{
type: COLOR_TYPE_MIN,
hex: GAUGE_COLORS[11].hex,
id: '0',
name: GAUGE_COLORS[11].name,
value: DEFAULT_VALUE_MIN,
},
{
type: COLOR_TYPE_MAX,
hex: GAUGE_COLORS[14].hex,
id: '1',
name: GAUGE_COLORS[14].name,
value: DEFAULT_VALUE_MAX,
},
]
export const validateColors = colors => {
if (!colors) {
return false
}
const hasMin = colors.some(color => color.type === COLOR_TYPE_MIN)
const hasMax = colors.some(color => color.type === COLOR_TYPE_MAX)
return hasMin && hasMax
}

View File

@ -1,9 +1,9 @@
import React from 'react'
export const graphTypes = [
export const GRAPH_TYPES = [
{
type: 'line',
menuOption: 'Line',
menuOption: 'Line Graph',
graphic: (
<div className="viz-type-selector--graphic">
<svg
@ -13,32 +13,32 @@ export const graphTypes = [
id="Line"
x="0px"
y="0px"
viewBox="0 0 300 150"
viewBox="0 0 150 150"
preserveAspectRatio="none meet"
>
<polyline
className="viz-type-selector--graphic-line graphic-line-a"
points="5,122.2 63,81.2 121,95.5 179,40.2 237,108.5 295,83.2"
/>
<polygon
className="viz-type-selector--graphic-fill graphic-fill-a"
points="5,122.2 5,145 295,145 295,83.2 237,108.5 179,40.2 121,95.5 63,81.2"
points="148,40 111.5,47.2 75,25 38.5,90.8 2,111.8 2,125 148,125 "
/>
<polyline
className="viz-type-selector--graphic-line graphic-line-b"
points="5,88.5 63,95 121,36.2 179,19 237,126.2 295,100.8"
className="viz-type-selector--graphic-line graphic-line-a"
points="2,111.8 38.5,90.8 75,25 111.5,47.2 148,40 "
/>
<polygon
className="viz-type-selector--graphic-fill graphic-fill-b"
points="5,88.5 5,145 295,145 295,100.8 237,126.2 179,19 121,36.2 63,95"
points="148,88.2 111.5,95.5 75,61.7 38.5,49.3 2,90.8 2,125 148,125 "
/>
<polyline
className="viz-type-selector--graphic-line graphic-line-c"
points="5,76.2 63,90.2 121,59.2 179,31.5 237,79.8 295,93.5"
className="viz-type-selector--graphic-line graphic-line-b"
points="2,90.8 38.5,49.3 75,61.7 111.5,95.5 148,88.2 "
/>
<polygon
className="viz-type-selector--graphic-fill graphic-fill-c"
points="5,76.2 5,145 295,145 295,93.5 237,79.8 179,31.5 121,59.2 63,90.2"
points="148,96 111.5,106.3 75,85.7 38.5,116.5 2,115 2,125 148,125 "
/>
<polyline
className="viz-type-selector--graphic-line graphic-line-c"
points="2,115 38.5,116.5 75,85.7 111.5,106.3 148,96 "
/>
</svg>
</div>
@ -46,7 +46,7 @@ export const graphTypes = [
},
{
type: 'line-stacked',
menuOption: 'Stacked',
menuOption: 'Stacked Graph',
graphic: (
<div className="viz-type-selector--graphic">
<svg
@ -56,32 +56,32 @@ export const graphTypes = [
id="LineStacked"
x="0px"
y="0px"
viewBox="0 0 300 150"
viewBox="0 0 150 150"
preserveAspectRatio="none meet"
>
<polyline
className="viz-type-selector--graphic-line graphic-line-a"
points="5,97.5 63,111.8 121,36.2 179,51 237,102.9 295,70.2"
/>
<polyline
className="viz-type-selector--graphic-line graphic-line-b"
points="5,58.8 63,81.2 121,5 179,40.2 237,96.2 295,49"
/>
<polyline
className="viz-type-selector--graphic-line graphic-line-c"
points="5,107.5 63,128.5 121,79.8 179,76.5 237,113.2 295,93.5"
/>
<polygon
className="viz-type-selector--graphic-fill graphic-fill-a"
points="179,51 121,36.2 63,111.8 5,97.5 5,107.5 63,128.5 121,79.8 179,76.5 237,113.2 295,93.5 295,70.2 237,102.9"
points="148,25 111.5,25 75,46 38.5,39.1 2,85.5 2,125 148,125 "
/>
<polyline
className="viz-type-selector--graphic-line graphic-line-a"
points="2,85.5 38.5,39.1 75,46 111.5,25 148,25 "
/>
<polygon
className="viz-type-selector--graphic-fill graphic-fill-b"
points="237,96.2 179,40.2 121,5 63,81.2 5,58.8 5,97.5 63,111.8 121,36.2 179,51 237,102.9 295,70.2 295,49"
points="148,53 111.5,49.9 75,88.5 38.5,71 2,116 2,125 148,125 "
/>
<polyline
className="viz-type-selector--graphic-line graphic-line-b"
points="2,116 38.5,71 75,88.5 111.5,49.9 148,53 "
/>
<polygon
className="viz-type-selector--graphic-fill graphic-fill-c"
points="179,76.5 121,79.8 63,128.5 5,107.5 5,145 295,145 295,93.5 237,113.2"
points="148,86.2 111.5,88.6 75,108.6 38.5,98 2,121.1 2,125 148,125 "
/>
<polyline
className="viz-type-selector--graphic-line graphic-line-c"
points="2,121.1 38.5,98 75,108.6 111.5,88.6 148,86.2 "
/>
</svg>
</div>
@ -89,7 +89,7 @@ export const graphTypes = [
},
{
type: 'line-stepplot',
menuOption: 'Step-Plot',
menuOption: 'Step-Plot Graph',
graphic: (
<div className="viz-type-selector--graphic">
<svg
@ -99,24 +99,32 @@ export const graphTypes = [
id="StepPlot"
x="0px"
y="0px"
viewBox="0 0 300 150"
viewBox="0 0 150 150"
preserveAspectRatio="none meet"
>
<polygon
className="viz-type-selector--graphic-fill graphic-fill-a"
points="295,85.5 266,85.5 266,108.5 208,108.5 208,94.5 150,94.5 150,41 92,41 92,66.6 34,66.6 34,54.8 5,54.8 5,145 295,145"
points="148,61.9 129.8,61.9 129.8,25 93.2,25 93.2,40.6 56.8,40.6 56.8,25 20.2,25 20.2,67.8 2,67.8 2,125 148,125 "
/>
<polyline
className="viz-type-selector--graphic-line graphic-line-a"
points="5,54.8 34,54.8 34,66.6 92,66.6 92,41 150,41 150,94.5 208,94.5 208,108.5 266,108.5 266,85.5 295,85.5"
points="2,67.8 20.2,67.8 20.2,25 56.8,25 56.8,40.6 93.2,40.6 93.2,25 129.8,25 129.8,61.9 148,61.9 "
/>
<polygon
className="viz-type-selector--graphic-fill graphic-fill-b"
points="34,111 34,85.8 92,85.8 92,5 150,5 150,24.5 208,24.5 208,128.2 266,128.2 266,75 295,75 295,145 5,145 5,111"
points="148,91.9 129.8,91.9 129.8,70.2 93.2,70.2 93.2,67 56.8,67 56.8,50.1 20.2,50.1 20.2,87 2,87 2,125 148,125 "
/>
<polyline
className="viz-type-selector--graphic-line graphic-line-b"
points="5,111 34,111 34,85.8 92,85.8 92,5 150,5 150,24.5 208,24.5 208,128.2 266,128.2 266,75 295,75"
points="2,87 20.2,87 20.2,50.1 56.8,50.1 56.8,67 93.2,67 93.2,70.2 129.8,70.2 129.8,91.9 148,91.9 "
/>
<polygon
className="viz-type-selector--graphic-fill graphic-fill-c"
points="148,103.5 129.8,103.5 129.8,118.2 93.2,118.2 93.2,84.5 56.8,84.5 56.8,75 20.2,75 20.2,100.2 2,100.2 2,125 148,125 "
/>
<polyline
className="viz-type-selector--graphic-line graphic-line-c"
points="2,100.2 20.2,100.2 20.2,75 56.8,75 56.8,84.5 93.2,84.5 93.2,118.2 129.8,118.2 129.8,103.5 148,103.5 "
/>
</svg>
</div>
@ -124,7 +132,7 @@ export const graphTypes = [
},
{
type: 'single-stat',
menuOption: 'SingleStat',
menuOption: 'Single Stat',
graphic: (
<div className="viz-type-selector--graphic">
<svg
@ -134,24 +142,32 @@ export const graphTypes = [
id="SingleStat"
x="0px"
y="0px"
viewBox="0 0 300 150"
viewBox="0 0 150 150"
preserveAspectRatio="none meet"
>
<path
className="viz-type-selector--graphic-line graphic-line-a"
d="M243.3,39.6h-37.9v32.7c0-6.3,5.1-11.4,11.4-11.4h15.2c6.3,0,11.4,5.1,11.4,11.4v26.8c0,6.3-5.1,11.4-11.4,11.4 h-15.2c-6.3,0-11.4-5.1-11.4-11.4V88.6"
/>
<polyline
className="viz-type-selector--graphic-line graphic-line-a"
points="94.6,89.1 56.7,89.1 83.2,39.6 83.2,110.4 "
className="viz-type-selector--graphic-fill graphic-fill-d"
d="M35.6,80.4h4.9v1.1h-4.9v7.8h-1.1v-7.8H20.7v-0.6l13.6-20.1h1.3V80.4z M22.4,80.4h12.1V62.1l-1.6,2.7 L22.4,80.4z"
/>
<path
className="viz-type-selector--graphic-line graphic-line-a"
d="M144.2,77.8c0,6.3-5.1,11.4-11.4,11.4h-15.2c-6.3,0-11.4-5.1-11.4-11.4V50.9c0-6.3,5.1-11.4,11.4-11.4h15.2 c6.3,0,11.4,5.1,11.4,11.4v48.1c0,6.3-5.1,11.4-11.4,11.4h-15.2c-6.3,0-11.4-5.1-11.4-11.4"
className="viz-type-selector--graphic-fill graphic-fill-d"
d="M58.6,75.1c-0.7,1.5-1.8,2.7-3.2,3.6c-1.5,0.9-3.1,1.4-4.9,1.4c-1.6,0-3-0.4-4.2-1.3s-2.2-2-2.9-3.5 c-0.7-1.5-1.1-3.1-1.1-4.8c0-1.9,0.4-3.6,1.1-5.1c0.7-1.6,1.7-2.8,3-3.7c1.3-0.9,2.7-1.3,4.3-1.3c2.9,0,5.2,1,6.7,2.9 c1.5,1.9,2.3,4.7,2.3,8.3v3.3c0,4.8-1.1,8.5-3.2,11c-2.1,2.5-5.3,3.8-9.4,3.9H46l0-1.1h0.8c3.8,0,6.7-1.2,8.7-3.5 C57.6,82.8,58.6,79.5,58.6,75.1z M50.4,79c1.9,0,3.6-0.6,5.1-1.7s2.5-2.6,3-4.5v-1.2c0-3.3-0.7-5.8-2-7.5c-1.4-1.7-3.3-2.6-5.8-2.6 c-1.4,0-2.7,0.4-3.8,1.2s-2,1.9-2.6,3.3c-0.6,1.4-0.9,2.9-0.9,4.5c0,1.5,0.3,3,0.9,4.3c0.6,1.3,1.5,2.4,2.5,3.1 C47.8,78.7,49.1,79,50.4,79z"
/>
<path
className="viz-type-selector--graphic-line graphic-line-a"
d="M155.8,50.9c0-6.3,5.1-11.4,11.4-11.4h15.2c6.3,0,11.4,5.1,11.4,11.4c0,24.1-37.9,24.8-37.9,59.5h37.9"
className="viz-type-selector--graphic-fill graphic-fill-d"
d="M81.3,89.2h-17v-1.1L74,77c1.6-1.9,2.8-3.5,3.5-5c0.8-1.4,1.2-2.8,1.2-4c0-2.1-0.6-3.7-1.8-4.9 c-1.2-1.2-2.9-1.7-5.1-1.7c-1.3,0-2.5,0.3-3.6,1c-1.1,0.6-2,1.5-2.6,2.6c-0.6,1.1-0.9,2.4-0.9,3.8h-1.1c0-1.5,0.4-2.9,1.1-4.2 c0.7-1.3,1.7-2.3,2.9-3.1s2.6-1.1,4.2-1.1c2.5,0,4.5,0.7,5.9,2c1.4,1.3,2.1,3.2,2.1,5.6c0,2.2-1.2,4.9-3.7,7.9l-1.8,2.2l-8.6,10 h15.6V89.2z"
/>
<path
className="viz-type-selector--graphic-fill graphic-fill-d"
d="M85.3,88.3c0-0.3,0.1-0.6,0.3-0.8c0.2-0.2,0.5-0.3,0.8-0.3c0.3,0,0.6,0.1,0.8,0.3s0.3,0.5,0.3,0.8 c0,0.3-0.1,0.6-0.3,0.8s-0.5,0.3-0.8,0.3c-0.3,0-0.6-0.1-0.8-0.3C85.4,88.8,85.3,88.6,85.3,88.3z"
/>
<path
className="viz-type-selector--graphic-fill graphic-fill-d"
d="M92.7,74.3L94,60.8h13.9v1.1H95l-1.2,11.4c0.7-0.6,1.6-1,2.7-1.4s2.2-0.5,3.3-0.5c2.6,0,4.6,0.8,6.1,2.4 c1.5,1.6,2.3,3.8,2.3,6.4c0,3.1-0.7,5.4-2.1,7c-1.4,1.6-3.4,2.4-5.9,2.4c-2.4,0-4.4-0.7-5.9-2.1c-1.5-1.4-2.3-3.3-2.5-5.8h1.1 c0.2,2.2,0.9,3.9,2.2,5.1c1.2,1.2,3,1.7,5.2,1.7c2.3,0,4.1-0.7,5.2-2.1c1.1-1.4,1.7-3.5,1.7-6.2c0-2.4-0.7-4.3-2-5.7 c-1.3-1.4-3.1-2.1-5.3-2.1c-1.4,0-2.6,0.2-3.6,0.5c-1,0.4-1.9,0.9-2.7,1.7L92.7,74.3z"
/>
<path
className="viz-type-selector--graphic-fill graphic-fill-d"
d="M113.8,74.3l1.3-13.6H129v1.1h-12.9l-1.2,11.4c0.7-0.6,1.6-1,2.7-1.4s2.2-0.5,3.3-0.5c2.6,0,4.6,0.8,6.1,2.4 c1.5,1.6,2.3,3.8,2.3,6.4c0,3.1-0.7,5.4-2.1,7c-1.4,1.6-3.4,2.4-5.9,2.4c-2.4,0-4.4-0.7-5.9-2.1c-1.5-1.4-2.3-3.3-2.5-5.8h1.1 c0.2,2.2,0.9,3.9,2.2,5.1c1.2,1.2,3,1.7,5.2,1.7c2.3,0,4.1-0.7,5.2-2.1c1.1-1.4,1.7-3.5,1.7-6.2c0-2.4-0.7-4.3-2-5.7 c-1.3-1.4-3.1-2.1-5.3-2.1c-1.4,0-2.6,0.2-3.6,0.5c-1,0.4-1.9,0.9-2.7,1.7L113.8,74.3z"
/>
</svg>
</div>
@ -159,7 +175,7 @@ export const graphTypes = [
},
{
type: 'line-plus-single-stat',
menuOption: 'Line + Stat',
menuOption: 'Line Graph + Single Stat',
graphic: (
<div className="viz-type-selector--graphic">
<svg
@ -169,40 +185,42 @@ export const graphTypes = [
id="LineAndSingleStat"
x="0px"
y="0px"
viewBox="0 0 300 150"
viewBox="0 0 150 150"
preserveAspectRatio="none meet"
>
<polygon
className="viz-type-selector--graphic-fill graphic-fill-b"
points="5,122.2 5,145 295,145 295,38.3 237,41.3 179,50 121,126.3 63,90.7"
/>
<polyline
className="viz-type-selector--graphic-line graphic-line-b"
points="5,122.2 63,90.7 121,126.3 179,50 237,41.3 295,38.3"
/>
<polygon
className="viz-type-selector--graphic-fill graphic-fill-c"
points="5,26.2 5,145 295,145 295,132.3 239.3,113.3 179,15 121,25 63,71.7"
/>
<polyline
className="viz-type-selector--graphic-line graphic-line-c"
points="5,26.2 63,71.7 121,25 179,15 239.3,113.3 295,132.3"
<g>
<polygon
className="viz-type-selector--graphic-fill graphic-fill-c"
points="148,88.2 111.5,95.5 75,25 38.5,54.7 2,66.7 2,125 148,125"
/>
<polyline
className="viz-type-selector--graphic-line graphic-line-c"
points="2,66.7 38.5,54.7 75,25 111.5,95.5 148,88.2"
/>
</g>
<path
className="viz-type-selector--graphic-fill graphic-fill-d"
d="M35.6,80.4h4.9v1.1h-4.9v7.8h-1.1v-7.8H20.7v-0.6l13.6-20.1h1.3V80.4z M22.4,80.4h12.1V62.1l-1.6,2.7 L22.4,80.4z"
/>
<path
className="viz-type-selector--graphic-line graphic-line-a"
d="M243.3,39.6h-37.9v32.7c0-6.3,5.1-11.4,11.4-11.4h15.2c6.3,0,11.4,5.1,11.4,11.4v26.8 c0,6.3-5.1,11.4-11.4,11.4h-15.2c-6.3,0-11.4-5.1-11.4-11.4V88.6"
/>
<polyline
className="viz-type-selector--graphic-line graphic-line-a"
points="94.6,89.1 56.7,89.1 83.2,39.6 83.2,110.4"
className="viz-type-selector--graphic-fill graphic-fill-d"
d="M58.6,75.1c-0.7,1.5-1.8,2.7-3.2,3.6c-1.5,0.9-3.1,1.4-4.9,1.4c-1.6,0-3-0.4-4.2-1.3s-2.2-2-2.9-3.5 c-0.7-1.5-1.1-3.1-1.1-4.8c0-1.9,0.4-3.6,1.1-5.1c0.7-1.6,1.7-2.8,3-3.7c1.3-0.9,2.7-1.3,4.3-1.3c2.9,0,5.2,1,6.7,2.9 c1.5,1.9,2.3,4.7,2.3,8.3v3.3c0,4.8-1.1,8.5-3.2,11c-2.1,2.5-5.3,3.8-9.4,3.9H46l0-1.1h0.8c3.8,0,6.7-1.2,8.7-3.5 C57.6,82.8,58.6,79.5,58.6,75.1z M50.4,79c1.9,0,3.6-0.6,5.1-1.7s2.5-2.6,3-4.5v-1.2c0-3.3-0.7-5.8-2-7.5c-1.4-1.7-3.3-2.6-5.8-2.6 c-1.4,0-2.7,0.4-3.8,1.2s-2,1.9-2.6,3.3c-0.6,1.4-0.9,2.9-0.9,4.5c0,1.5,0.3,3,0.9,4.3c0.6,1.3,1.5,2.4,2.5,3.1 C47.8,78.7,49.1,79,50.4,79z"
/>
<path
className="viz-type-selector--graphic-line graphic-line-a"
d="M144.2,77.8c0,6.3-5.1,11.4-11.4,11.4h-15.2c-6.3,0-11.4-5.1-11.4-11.4V50.9c0-6.3,5.1-11.4,11.4-11.4h15.2 c6.3,0,11.4,5.1,11.4,11.4v48.1c0,6.3-5.1,11.4-11.4,11.4h-15.2c-6.3,0-11.4-5.1-11.4-11.4"
className="viz-type-selector--graphic-fill graphic-fill-d"
d="M81.3,89.2h-17v-1.1L74,77c1.6-1.9,2.8-3.5,3.5-5c0.8-1.4,1.2-2.8,1.2-4c0-2.1-0.6-3.7-1.8-4.9 c-1.2-1.2-2.9-1.7-5.1-1.7c-1.3,0-2.5,0.3-3.6,1c-1.1,0.6-2,1.5-2.6,2.6c-0.6,1.1-0.9,2.4-0.9,3.8h-1.1c0-1.5,0.4-2.9,1.1-4.2 c0.7-1.3,1.7-2.3,2.9-3.1s2.6-1.1,4.2-1.1c2.5,0,4.5,0.7,5.9,2c1.4,1.3,2.1,3.2,2.1,5.6c0,2.2-1.2,4.9-3.7,7.9l-1.8,2.2l-8.6,10 h15.6V89.2z"
/>
<path
className="viz-type-selector--graphic-line graphic-line-a"
d="M155.8,50.9c0-6.3,5.1-11.4,11.4-11.4h15.2c6.3,0,11.4,5.1,11.4,11.4c0,24.1-37.9,24.8-37.9,59.5h37.9"
className="viz-type-selector--graphic-fill graphic-fill-d"
d="M85.3,88.3c0-0.3,0.1-0.6,0.3-0.8c0.2-0.2,0.5-0.3,0.8-0.3c0.3,0,0.6,0.1,0.8,0.3s0.3,0.5,0.3,0.8 c0,0.3-0.1,0.6-0.3,0.8s-0.5,0.3-0.8,0.3c-0.3,0-0.6-0.1-0.8-0.3C85.4,88.8,85.3,88.6,85.3,88.3z"
/>
<path
className="viz-type-selector--graphic-fill graphic-fill-d"
d="M92.7,74.3L94,60.8h13.9v1.1H95l-1.2,11.4c0.7-0.6,1.6-1,2.7-1.4s2.2-0.5,3.3-0.5c2.6,0,4.6,0.8,6.1,2.4 c1.5,1.6,2.3,3.8,2.3,6.4c0,3.1-0.7,5.4-2.1,7c-1.4,1.6-3.4,2.4-5.9,2.4c-2.4,0-4.4-0.7-5.9-2.1c-1.5-1.4-2.3-3.3-2.5-5.8h1.1 c0.2,2.2,0.9,3.9,2.2,5.1c1.2,1.2,3,1.7,5.2,1.7c2.3,0,4.1-0.7,5.2-2.1c1.1-1.4,1.7-3.5,1.7-6.2c0-2.4-0.7-4.3-2-5.7 c-1.3-1.4-3.1-2.1-5.3-2.1c-1.4,0-2.6,0.2-3.6,0.5c-1,0.4-1.9,0.9-2.7,1.7L92.7,74.3z"
/>
<path
className="viz-type-selector--graphic-fill graphic-fill-d"
d="M113.8,74.3l1.3-13.6H129v1.1h-12.9l-1.2,11.4c0.7-0.6,1.6-1,2.7-1.4s2.2-0.5,3.3-0.5c2.6,0,4.6,0.8,6.1,2.4 c1.5,1.6,2.3,3.8,2.3,6.4c0,3.1-0.7,5.4-2.1,7c-1.4,1.6-3.4,2.4-5.9,2.4c-2.4,0-4.4-0.7-5.9-2.1c-1.5-1.4-2.3-3.3-2.5-5.8h1.1 c0.2,2.2,0.9,3.9,2.2,5.1c1.2,1.2,3,1.7,5.2,1.7c2.3,0,4.1-0.7,5.2-2.1c1.1-1.4,1.7-3.5,1.7-6.2c0-2.4-0.7-4.3-2-5.7 c-1.3-1.4-3.1-2.1-5.3-2.1c-1.4,0-2.6,0.2-3.6,0.5c-1,0.4-1.9,0.9-2.7,1.7L113.8,74.3z"
/>
</svg>
</div>
@ -210,7 +228,7 @@ export const graphTypes = [
},
{
type: 'bar',
menuOption: 'Bar',
menuOption: 'Bar Graph',
graphic: (
<div className="viz-type-selector--graphic">
<svg
@ -220,56 +238,222 @@ export const graphTypes = [
id="Bar"
x="0px"
y="0px"
viewBox="0 0 300 150"
viewBox="0 0 150 150"
preserveAspectRatio="none meet"
>
<path
<rect
x="2"
y="108.4"
className="viz-type-selector--graphic-line graphic-line-a"
width="26.8"
height="16.6"
/>
<rect
x="31.8"
y="82.4"
className="viz-type-selector--graphic-line graphic-line-b"
width="26.8"
height="42.6"
/>
<rect
x="61.6"
y="28.8"
className="viz-type-selector--graphic-line graphic-line-c"
width="26.8"
height="96.2"
/>
<rect
x="91.4"
y="47.9"
className="viz-type-selector--graphic-line graphic-line-a"
width="26.8"
height="77.1"
/>
<rect
x="121.2"
y="25"
className="viz-type-selector--graphic-line graphic-line-b"
width="26.8"
height="100"
/>
<rect
x="2"
y="108.4"
className="viz-type-selector--graphic-fill graphic-fill-a"
d="M145,7c0-1.1-0.9-2-2-2h-36c-1.1,0-2,0.9-2,2v136c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V7z"
width="26.8"
height="16.6"
/>
<path
className="viz-type-selector--graphic-fill graphic-fill-c"
d="M195,57c0-1.1-0.9-2-2-2h-36c-1.1,0-2,0.9-2,2v86c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V57z"
/>
<path
<rect
x="31.8"
y="82.4"
className="viz-type-selector--graphic-fill graphic-fill-b"
d="M245,117c0-1.1-0.9-2-2-2h-36c-1.1,0-2,0.9-2,2v26c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V117z"
width="26.8"
height="42.6"
/>
<rect
x="61.6"
y="28.8"
className="viz-type-selector--graphic-fill graphic-fill-c"
width="26.8"
height="96.2"
/>
<rect
x="91.4"
y="47.9"
className="viz-type-selector--graphic-fill graphic-fill-a"
width="26.8"
height="77.1"
/>
<rect
x="121.2"
y="25"
className="viz-type-selector--graphic-fill graphic-fill-b"
width="26.8"
height="100"
/>
</svg>
</div>
),
},
{
type: 'gauge',
menuOption: 'Gauge',
graphic: (
<div className="viz-type-selector--graphic">
<svg
width="100%"
height="100%"
version="1.1"
id="Bar"
x="0px"
y="0px"
viewBox="0 0 150 150"
preserveAspectRatio="none meet"
>
<g>
<path
className="viz-type-selector--graphic-line graphic-line-d"
d="M110.9,110.9c19.9-19.9,19.9-52,0-71.9s-52-19.9-71.9,0s-19.9,52,0,71.9"
/>
<line
className="viz-type-selector--graphic-line graphic-line-d"
x1="39.1"
y1="110.9"
x2="35"
y2="115"
/>
<line
className="viz-type-selector--graphic-line graphic-line-d"
x1="110.9"
y1="110.9"
x2="115"
y2="115"
/>
<line
className="viz-type-selector--graphic-line graphic-line-d"
x1="122"
y1="94.5"
x2="127.2"
y2="96.6"
/>
<line
className="viz-type-selector--graphic-line graphic-line-d"
x1="125.8"
y1="75"
x2="131.5"
y2="75"
/>
<line
className="viz-type-selector--graphic-line graphic-line-d"
x1="122"
y1="55.5"
x2="127.2"
y2="53.4"
/>
<line
className="viz-type-selector--graphic-line graphic-line-d"
x1="110.9"
y1="39.1"
x2="115"
y2="35"
/>
<line
className="viz-type-selector--graphic-line graphic-line-d"
x1="94.5"
y1="28"
x2="96.6"
y2="22.8"
/>
<line
className="viz-type-selector--graphic-line graphic-line-d"
x1="75"
y1="24.2"
x2="75"
y2="18.5"
/>
<line
className="viz-type-selector--graphic-line graphic-line-d"
x1="55.5"
y1="28"
x2="53.4"
y2="22.8"
/>
<line
className="viz-type-selector--graphic-line graphic-line-d"
x1="39.1"
y1="39.1"
x2="35"
y2="35"
/>
<line
className="viz-type-selector--graphic-line graphic-line-d"
x1="28"
y1="55.5"
x2="22.8"
y2="53.4"
/>
<line
className="viz-type-selector--graphic-line graphic-line-d"
x1="24.2"
y1="75"
x2="18.5"
y2="75"
/>
<line
className="viz-type-selector--graphic-line graphic-line-d"
x1="28"
y1="94.5"
x2="22.8"
y2="96.6"
/>
</g>
<path
className="viz-type-selector--graphic-fill graphic-fill-d"
d="M78.6,73.4L75,56.3l-3.6,17.1c-0.2,0.5-0.3,1-0.3,1.6c0,2.2,1.8,3.9,3.9,3.9s3.9-1.8,3.9-3.9C78.9,74.4,78.8,73.9,78.6,73.4z"
/>
<path
className="viz-type-selector--graphic-fill graphic-fill-a"
d="M295,107c0-1.1-0.9-2-2-2h-36c-1.1,0-2,0.9-2,2v36c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V107z"
d="M58.9,58.9c8.9-8.9,23.4-8.9,32.3,0l17.1-17.1c-18.4-18.4-48.2-18.4-66.5,0C32.5,50.9,27.9,63,27.9,75h24.2C52.2,69.2,54.4,63.3,58.9,58.9z"
/>
<path
className="viz-type-selector--graphic-line graphic-line-a"
d="M58.9,58.9c8.9-8.9,23.4-8.9,32.3,0l17.1-17.1c-18.4-18.4-48.2-18.4-66.5,0C32.5,50.9,27.9,63,27.9,75h24.2C52.2,69.2,54.4,63.3,58.9,58.9z"
/>
<path
className="viz-type-selector--graphic-fill graphic-fill-b"
d="M95,87c0-1.1-0.9-2-2-2H57c-1.1,0-2,0.9-2,2v56c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V87z"
d="M58.9,91.1c-4.5-4.5-6.7-10.3-6.7-16.1H27.9c0,12,4.6,24.1,13.8,33.3L58.9,91.1z"
/>
<path
className="viz-type-selector--graphic-line graphic-line-b"
d="M58.9,91.1c-4.5-4.5-6.7-10.3-6.7-16.1H27.9c0,12,4.6,24.1,13.8,33.3L58.9,91.1z"
/>
<path
className="viz-type-selector--graphic-fill graphic-fill-c"
d="M45,130c0-1.1-0.9-2-2-2H7c-1.1,0-2,0.9-2,2v13c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V130z"
/>
<path
className="viz-type-selector--graphic-line graphic-line-a"
d="M145,7c0-1.1-0.9-2-2-2h-36c-1.1,0-2,0.9-2,2v136c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V7z"
d="M91.1,91.1l17.1,17.1c18.4-18.4,18.4-48.2,0-66.6L91.1,58.9C100.1,67.8,100.1,82.2,91.1,91.1z"
/>
<path
className="viz-type-selector--graphic-line graphic-line-c"
d="M195,57c0-1.1-0.9-2-2-2h-36c-1.1,0-2,0.9-2,2v86c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V57z"
/>
<path
className="viz-type-selector--graphic-line graphic-line-b"
d="M245,117c0-1.1-0.9-2-2-2h-36c-1.1,0-2,0.9-2,2v26c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V117z"
/>
<path
className="viz-type-selector--graphic-line graphic-line-a"
d="M295,107c0-1.1-0.9-2-2-2h-36c-1.1,0-2,0.9-2,2v36c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V107z"
/>
<path
className="viz-type-selector--graphic-line graphic-line-b"
d="M95,87c0-1.1-0.9-2-2-2H57c-1.1,0-2,0.9-2,2v56c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V87z"
/>
<path
className="viz-type-selector--graphic-line graphic-line-c"
d="M45,130c0-1.1-0.9-2-2-2H7c-1.1,0-2,0.9-2,2v13c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V130z"
d="M91.1,91.1l17.1,17.1c18.4-18.4,18.4-48.2,0-66.6L91.1,58.9C100.1,67.8,100.1,82.2,91.1,91.1z"
/>
</svg>
</div>

View File

@ -20,13 +20,13 @@ class HostRow extends Component {
const {colName, colStatus, colCPU, colLoad} = HOSTS_TABLE
return (
<tr>
<td style={{width: colName}}>
<div className="hosts-table--tr">
<div className="hosts-table--td" style={{width: colName}}>
<Link to={`/sources/${source.id}/hosts/${name}`}>
{name}
</Link>
</td>
<td style={{width: colStatus}}>
</div>
<div className="hosts-table--td" style={{width: colStatus}}>
<div
className={classnames(
'table-dot',
@ -35,14 +35,14 @@ class HostRow extends Component {
: 'dot-critical'
)}
/>
</td>
<td style={{width: colCPU}} className="monotype">
</div>
<div style={{width: colCPU}} className="monotype hosts-table--td">
{isNaN(cpu) ? 'N/A' : `${cpu.toFixed(2)}%`}
</td>
<td style={{width: colLoad}} className="monotype">
</div>
<div style={{width: colLoad}} className="monotype hosts-table--td">
{isNaN(load) ? 'N/A' : `${load.toFixed(2)}`}
</td>
<td>
</div>
<div className="hosts-table--td">
{apps.map((app, index) => {
return (
<span key={app}>
@ -59,8 +59,8 @@ class HostRow extends Component {
</span>
)
})}
</td>
</tr>
</div>
</div>
)
}
}

View File

@ -3,6 +3,7 @@ import _ from 'lodash'
import SearchBar from 'src/hosts/components/SearchBar'
import HostRow from 'src/hosts/components/HostRow'
import InfiniteScroll from 'shared/components/InfiniteScroll'
import {HOSTS_TABLE} from 'src/hosts/constants/tableSizing'
@ -67,11 +68,11 @@ class HostsTable extends Component {
sortableClasses = key => {
if (this.state.sortKey === key) {
if (this.state.sortDirection === 'asc') {
return 'sortable-header sorting-ascending'
return 'hosts-table--th sortable-header sorting-ascending'
}
return 'sortable-header sorting-descending'
return 'hosts-table--th sortable-header sorting-descending'
}
return 'sortable-header'
return 'hosts-table--th sortable-header'
}
render() {
@ -110,47 +111,48 @@ class HostsTable extends Component {
</div>
<div className="panel-body">
{hostCount > 0 && !hostsError.length
? <table className="table v-center table-highlight">
<thead>
<tr>
<th
? <div className="hosts-table">
<div className="hosts-table--thead">
<div className="hosts-table--tr">
<div
onClick={this.updateSort('name')}
className={this.sortableClasses('name')}
style={{width: colName}}
>
Host
</th>
<th
</div>
<div
onClick={this.updateSort('deltaUptime')}
className={this.sortableClasses('deltaUptime')}
style={{width: colStatus}}
>
Status
</th>
<th
</div>
<div
onClick={this.updateSort('cpu')}
className={this.sortableClasses('cpu')}
style={{width: colCPU}}
>
CPU
</th>
<th
</div>
<div
onClick={this.updateSort('load')}
className={this.sortableClasses('load')}
style={{width: colLoad}}
>
Load
</th>
<th>Apps</th>
</tr>
</thead>
<tbody>
{sortedHosts.map(h =>
</div>
<div className="hosts-table--th">Apps</div>
</div>
</div>
<InfiniteScroll
items={sortedHosts.map(h =>
<HostRow key={h.name} host={h} source={source} />
)}
</tbody>
</table>
itemHeight={26}
className="hosts-table--tbody"
/>
</div>
: <div className="generic-empty-state">
<h4 style={{margin: '90px 0'}}>No Hosts found</h4>
</div>}

View File

@ -2,7 +2,6 @@ import React, {PropTypes, Component} from 'react'
import _ from 'lodash'
import HostsTable from 'src/hosts/components/HostsTable'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import SourceIndicator from 'shared/components/SourceIndicator'
import {getCpuAndLoadForHosts, getLayouts, getAppsForHosts} from '../apis'
@ -66,7 +65,7 @@ class HostsPage extends Component {
const {source} = this.props
const {hosts, hostsLoading, hostsError} = this.state
return (
<div className="page">
<div className="page hosts-list-page">
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
@ -77,7 +76,7 @@ class HostsPage extends Component {
</div>
</div>
</div>
<FancyScrollbar className="page-contents">
<div className="page-contents">
<div className="container-fluid">
<div className="row">
<div className="col-md-12">
@ -90,7 +89,7 @@ class HostsPage extends Component {
</div>
</div>
</div>
</FancyScrollbar>
</div>
</div>
)
}

View File

@ -1,6 +1,6 @@
import React, {PropTypes} from 'react'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import InfiniteScroll from 'shared/components/InfiniteScroll'
import LogsTableRow from 'src/kapacitor/components/LogsTableRow'
const LogsTable = ({logs}) =>
@ -8,18 +8,17 @@ const LogsTable = ({logs}) =>
<div className="logs-table--header">
<h2 className="panel-title">Logs</h2>
</div>
<FancyScrollbar
className="logs-table--panel fancy-scroll--kapacitor"
autoHide={false}
>
<div className="logs-table">
{logs.length
? logs.map((log, i) =>
<div className="logs-table--panel fancy-scroll--kapacitor">
{logs.length
? <InfiniteScroll
className="logs-table"
itemHeight={87}
items={logs.map((log, i) =>
<LogsTableRow key={log.key} logItem={log} index={i} />
)
: <div className="page-spinner" />}
</div>
</FancyScrollbar>
)}
/>
: <div className="page-spinner" />}
</div>
</div>
const {arrayOf, shape, string} = PropTypes

View File

@ -44,7 +44,8 @@ class TickscriptPage extends Component {
})
notify(
'warning',
'Could not use logging, requires Kapacitor version 1.4'
'Could not use logging, requires Kapacitor version 1.4',
{once: true}
)
return
}
@ -101,13 +102,13 @@ class TickscriptPage extends Component {
}
this.setState({
logs: [...this.state.logs, ...logs],
logs: [...logs, ...this.state.logs],
failStr,
})
} catch (err) {
console.warn(err, failStr)
this.setState({
logs: [...this.state.logs, ...logs],
logs: [...logs, ...this.state.logs],
failStr,
})
}

View File

@ -52,6 +52,7 @@ export const saveToLocalStorage = ({
timeRange,
dataExplorer,
dashTimeV1: {ranges},
dismissedNotifications,
}) => {
try {
const appPersisted = Object.assign({}, {app: {persisted}})
@ -66,6 +67,7 @@ export const saveToLocalStorage = ({
dataExplorer,
VERSION, // eslint-disable-line no-undef
dashTimeV1,
dismissedNotifications,
})
)
} catch (err) {

View File

@ -1,4 +1,4 @@
export function publishNotification(type, message) {
export function publishNotification(type, message, options = {once: false}) {
// this validator is purely for development purposes. It might make sense to move this to a middleware.
const validTypes = ['error', 'success', 'warning']
if (!validTypes.includes(type) || message === undefined) {
@ -10,6 +10,7 @@ export function publishNotification(type, message) {
payload: {
type,
message,
once: options.once,
},
}
}

View File

@ -0,0 +1,109 @@
import React, {Component, PropTypes} from 'react'
import classnames from 'classnames'
import OnClickOutside from 'shared/components/OnClickOutside'
import FancyScrollbar from 'shared/components/FancyScrollbar'
class ColorDropdown extends Component {
constructor(props) {
super(props)
this.state = {
visible: false,
}
}
handleToggleMenu = () => {
const {disabled} = this.props
if (disabled) {
return
}
this.setState({visible: !this.state.visible})
}
handleClickOutside = () => {
this.setState({visible: false})
}
handleColorClick = color => () => {
this.props.onChoose(color)
this.setState({visible: false})
}
render() {
const {visible} = this.state
const {colors, selected, disabled} = this.props
const dropdownClassNames = visible
? 'color-dropdown open'
: 'color-dropdown'
const toggleClassNames = classnames(
'btn btn-sm btn-default color-dropdown--toggle',
{active: visible, 'color-dropdown__disabled': disabled}
)
return (
<div className={dropdownClassNames}>
<div
className={toggleClassNames}
onClick={this.handleToggleMenu}
disabled={disabled}
>
<div
className="color-dropdown--swatch"
style={{backgroundColor: selected.hex}}
/>
<div className="color-dropdown--name">
{selected.name}
</div>
<span className="caret" />
</div>
{visible
? <div className="color-dropdown--menu">
<FancyScrollbar autoHide={false} autoHeight={true}>
{colors.map((color, i) =>
<div
className={
color.name === selected.name
? 'color-dropdown--item active'
: 'color-dropdown--item'
}
key={i}
onClick={this.handleColorClick(color)}
>
<span
className="color-dropdown--swatch"
style={{backgroundColor: color.hex}}
/>
<span className="color-dropdown--name">
{color.name}
</span>
</div>
)}
</FancyScrollbar>
</div>
: null}
</div>
)
}
}
const {arrayOf, bool, func, shape, string} = PropTypes
ColorDropdown.propTypes = {
selected: shape({
hex: string.isRequired,
name: string.isRequired,
}).isRequired,
onChoose: func.isRequired,
colors: arrayOf(
shape({
hex: string.isRequired,
name: string.isRequired,
})
).isRequired,
disabled: bool,
}
export default OnClickOutside(ColorDropdown)

View File

@ -0,0 +1,340 @@
import React, {Component, PropTypes} from 'react'
import _ from 'lodash'
import {GAUGE_SPECS} from 'shared/constants/gaugeSpecs'
import {
COLOR_TYPE_MIN,
COLOR_TYPE_MAX,
MIN_THRESHOLDS,
} from 'src/dashboards/constants/gaugeColors'
class Gauge extends Component {
constructor(props) {
super(props)
}
componentDidMount() {
this.updateCanvas()
}
componentDidUpdate() {
this.updateCanvas()
}
resetCanvas = (canvas, context) => {
context.setTransform(1, 0, 0, 1, 0, 0)
context.clearRect(0, 0, canvas.width, canvas.height)
}
updateCanvas = () => {
const canvas = this.canvasRef
canvas.width = canvas.height * (canvas.clientWidth / canvas.clientHeight)
const ctx = canvas.getContext('2d')
this.resetCanvas(canvas, ctx)
const centerX = canvas.width / 2
const centerY = canvas.height / 2 * 1.13
const radius = Math.min(canvas.width, canvas.height) / 2 * 0.5
const {minLineWidth, minFontSize} = GAUGE_SPECS
const gradientThickness = Math.max(minLineWidth, radius / 4)
const labelValueFontSize = Math.max(minFontSize, radius / 4)
const {colors} = this.props
if (!colors || colors.length === 0) {
return
}
// Distill out max and min values
const minValue = Number(
colors.find(color => color.type === COLOR_TYPE_MIN).value
)
const maxValue = Number(
colors.find(color => color.type === COLOR_TYPE_MAX).value
)
// The following functions must be called in the specified order
if (colors.length === MIN_THRESHOLDS) {
this.drawGradientGauge(ctx, centerX, centerY, radius, gradientThickness)
} else {
this.drawSegmentedGauge(
ctx,
centerX,
centerY,
radius,
minValue,
maxValue,
gradientThickness
)
}
this.drawGaugeLines(ctx, centerX, centerY, radius, gradientThickness)
this.drawGaugeLabels(
ctx,
centerX,
centerY,
radius,
gradientThickness,
minValue,
maxValue
)
this.drawGaugeValue(ctx, radius, labelValueFontSize)
this.drawNeedle(ctx, radius, minValue, maxValue)
}
drawGradientGauge = (ctx, xc, yc, r, gradientThickness) => {
const {colors} = this.props
const sortedColors = _.sortBy(colors, color => Number(color.value))
const arcStart = Math.PI * 0.75
const arcEnd = arcStart + Math.PI * 1.5
// Determine coordinates for gradient
const xStart = xc + Math.cos(arcStart) * r
const yStart = yc + Math.sin(arcStart) * r
const xEnd = xc + Math.cos(arcEnd) * r
const yEnd = yc + Math.sin(arcEnd) * r
const gradient = ctx.createLinearGradient(xStart, yStart, xEnd, yEnd)
gradient.addColorStop(0, sortedColors[0].hex)
gradient.addColorStop(1.0, sortedColors[1].hex)
ctx.beginPath()
ctx.lineWidth = gradientThickness
ctx.strokeStyle = gradient
ctx.arc(xc, yc, r, arcStart, arcEnd)
ctx.stroke()
}
drawSegmentedGauge = (
ctx,
xc,
yc,
r,
minValue,
maxValue,
gradientThickness
) => {
const {colors} = this.props
const sortedColors = _.sortBy(colors, color => Number(color.value))
const trueValueRange = Math.abs(maxValue - minValue)
const totalArcLength = Math.PI * 1.5
let startingPoint = Math.PI * 0.75
// Iterate through colors, draw arc for each
for (let c = 0; c < sortedColors.length - 1; c++) {
// Use this color and the next to determine arc length
const color = sortedColors[c]
const nextColor = sortedColors[c + 1]
// adjust values by subtracting minValue from them
const adjustedValue = Number(color.value) - minValue
const adjustedNextValue = Number(nextColor.value) - minValue
const thisArc = Math.abs(adjustedValue - adjustedNextValue)
// Multiply by arcLength to determine this arc's length
const arcLength = totalArcLength * (thisArc / trueValueRange)
// Draw arc
ctx.beginPath()
ctx.lineWidth = gradientThickness
ctx.strokeStyle = color.hex
ctx.arc(xc, yc, r, startingPoint, startingPoint + arcLength)
ctx.stroke()
// Add this arc's length to starting point
startingPoint += arcLength
}
}
drawGaugeLines = (ctx, xc, yc, radius, gradientThickness) => {
const {
degree,
lineCount,
lineColor,
lineStrokeSmall,
lineStrokeLarge,
tickSizeSmall,
tickSizeLarge,
} = GAUGE_SPECS
const arcStart = Math.PI * 0.75
const arcLength = Math.PI * 1.5
const arcStop = arcStart + arcLength
const lineSmallCount = lineCount * 5
const startDegree = degree * 135
const arcLargeIncrement = arcLength / lineCount
const arcSmallIncrement = arcLength / lineSmallCount
// Semi-circle
const arcRadius = radius + gradientThickness * 0.8
ctx.beginPath()
ctx.arc(xc, yc, arcRadius, arcStart, arcStop)
ctx.lineWidth = 3
ctx.lineCap = 'round'
ctx.strokeStyle = lineColor
ctx.stroke()
ctx.closePath()
// Match center of canvas to center of gauge
ctx.translate(xc, yc)
// Draw Large ticks
for (let lt = 0; lt <= lineCount; lt++) {
// Rototion before drawing line
ctx.rotate(startDegree)
ctx.rotate(lt * arcLargeIncrement)
// Draw line
ctx.beginPath()
ctx.lineWidth = lineStrokeLarge
ctx.lineCap = 'round'
ctx.strokeStyle = lineColor
ctx.moveTo(arcRadius, 0)
ctx.lineTo(arcRadius + tickSizeLarge, 0)
ctx.stroke()
ctx.closePath()
// Return to starting rotation
ctx.rotate(-lt * arcLargeIncrement)
ctx.rotate(-startDegree)
}
// Draw Small ticks
for (let lt = 0; lt <= lineSmallCount; lt++) {
// Rototion before drawing line
ctx.rotate(startDegree)
ctx.rotate(lt * arcSmallIncrement)
// Draw line
ctx.beginPath()
ctx.lineWidth = lineStrokeSmall
ctx.lineCap = 'round'
ctx.strokeStyle = lineColor
ctx.moveTo(arcRadius, 0)
ctx.lineTo(arcRadius + tickSizeSmall, 0)
ctx.stroke()
ctx.closePath()
// Return to starting rotation
ctx.rotate(-lt * arcSmallIncrement)
ctx.rotate(-startDegree)
}
}
drawGaugeLabels = (
ctx,
xc,
yc,
radius,
gradientThickness,
minValue,
maxValue
) => {
const {degree, lineCount, labelColor, labelFontSize} = GAUGE_SPECS
const incrementValue = (maxValue - minValue) / lineCount
const gaugeValues = []
for (let g = minValue; g < maxValue; g += incrementValue) {
const roundedValue = Math.round(g * 100) / 100
gaugeValues.push(roundedValue.toString())
}
gaugeValues.push((Math.round(maxValue * 100) / 100).toString())
const startDegree = degree * 135
const arcLength = Math.PI * 1.5
const arcIncrement = arcLength / lineCount
// Format labels text
ctx.font = `bold ${labelFontSize}px Helvetica`
ctx.fillStyle = labelColor
ctx.textBaseline = 'middle'
ctx.textAlign = 'right'
let labelRadius
for (let i = 0; i <= lineCount; i++) {
if (i === 3) {
ctx.textAlign = 'center'
labelRadius = radius + gradientThickness + 30
} else {
labelRadius = radius + gradientThickness + 23
}
if (i > 3) {
ctx.textAlign = 'left'
}
ctx.rotate(startDegree)
ctx.rotate(i * arcIncrement)
ctx.translate(labelRadius, 0)
ctx.rotate(i * -arcIncrement)
ctx.rotate(-startDegree)
ctx.fillText(gaugeValues[i], 0, 0)
ctx.rotate(startDegree)
ctx.rotate(i * arcIncrement)
ctx.translate(-labelRadius, 0)
ctx.rotate(i * -arcIncrement)
ctx.rotate(-startDegree)
}
}
drawGaugeValue = (ctx, radius, labelValueFontSize) => {
const {gaugePosition} = this.props
const {valueColor} = GAUGE_SPECS
ctx.font = `${labelValueFontSize}px Roboto`
ctx.fillStyle = valueColor
ctx.textBaseline = 'middle'
ctx.textAlign = 'center'
const textY = radius
ctx.fillText(gaugePosition.toString(), 0, textY)
}
drawNeedle = (ctx, radius, minValue, maxValue) => {
const {gaugePosition} = this.props
const {degree, needleColor0, needleColor1} = GAUGE_SPECS
const arcDistance = Math.PI * 1.5
const needleRotation = (gaugePosition - minValue) / (maxValue - minValue)
const needleGradient = ctx.createLinearGradient(0, -10, 0, radius)
needleGradient.addColorStop(0, needleColor0)
needleGradient.addColorStop(1, needleColor1)
// Starting position of needle is at minimum
ctx.rotate(degree * 45)
ctx.rotate(arcDistance * needleRotation)
ctx.beginPath()
ctx.fillStyle = needleGradient
ctx.arc(0, 0, 10, 0, Math.PI, true)
ctx.lineTo(0, radius)
ctx.lineTo(10, 0)
ctx.fill()
}
render() {
const {width, height} = this.props
return (
<canvas
className="gauge"
width={width}
height={height}
ref={r => (this.canvasRef = r)}
/>
)
}
}
const {arrayOf, number, shape, string} = PropTypes
Gauge.propTypes = {
width: string.isRequired,
height: string.isRequired,
gaugePosition: number.isRequired,
colors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: string.isRequired,
}).isRequired
).isRequired,
}
export default Gauge

View File

@ -0,0 +1,83 @@
import React, {PropTypes, PureComponent} from 'react'
import lastValues from 'shared/parsing/lastValues'
import Gauge from 'shared/components/Gauge'
import {DEFAULT_COLORS} from 'src/dashboards/constants/gaugeColors'
import {DASHBOARD_LAYOUT_ROW_HEIGHT} from 'shared/constants'
class GaugeChart extends PureComponent {
render() {
const {
data,
cellHeight,
isFetchingInitially,
colors,
resizeCoords,
resizerTopHeight,
} = this.props
// If data for this graph is being fetched for the first time, show a graph-wide spinner.
if (isFetchingInitially) {
return (
<div className="graph-empty">
<h3 className="graph-spinner" />
</div>
)
}
const lastValue = lastValues(data)[1]
const precision = 100.0
const roundedValue = Math.round(+lastValue * precision) / precision
// When a new height is passed the Gauge component resizes internally
// Passing in a new often ensures the gauge appears sharp
const initialCellHeight =
cellHeight && (cellHeight * DASHBOARD_LAYOUT_ROW_HEIGHT).toString()
const resizeCoordsHeight =
resizeCoords && (resizeCoords.h * DASHBOARD_LAYOUT_ROW_HEIGHT).toString()
const height = (resizeCoordsHeight ||
initialCellHeight ||
resizerTopHeight ||
300)
.toString()
return (
<div className="single-stat">
<Gauge
width="900"
height={height}
colors={colors}
gaugePosition={roundedValue}
/>
</div>
)
}
}
const {arrayOf, bool, number, shape, string} = PropTypes
GaugeChart.defaultProps = {
colors: DEFAULT_COLORS,
}
GaugeChart.propTypes = {
data: arrayOf(shape()).isRequired,
isFetchingInitially: bool,
cellHeight: number,
resizerTopHeight: number,
resizeCoords: shape(),
colors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: string.isRequired,
}).isRequired
),
}
export default GaugeChart

View File

@ -0,0 +1,131 @@
import React, {Component, PropTypes} from 'react'
import classnames from 'classnames'
import {Scrollbars} from 'react-custom-scrollbars'
import _ from 'lodash'
const {arrayOf, number, shape, string} = PropTypes
class InfiniteScroll extends Component {
// Cache values from Scrollbars events that need to be independent of render
// Should not be setState as need not trigger a re-render
scrollbarsScrollTop = 0
scrollbarsClientHeight = 0
state = {
topIndex: 0,
bottomIndex: 0,
topPadding: 0,
bottomPadding: 0,
windowHeight: window.innerHeight,
}
windowing = (props, state) => {
const {itemHeight, items} = props
const {bottomIndex} = state
const itemDistance = Math.round(this.scrollbarsScrollTop / itemHeight)
const itemCount = Math.round(this.scrollbarsClientHeight / itemHeight) + 1
// If state is the same, do not setState to the same value multiple times.
// Improves performance and prevents errors.
if (bottomIndex === itemDistance + itemCount) {
return
}
this.setState({
// Number of items from top
topIndex: itemDistance,
// Number of items that can fit inside the container div
bottomIndex: itemDistance + itemCount,
// Offset list from top
topPadding: itemDistance * itemHeight,
// Provide scrolling room at the bottom of the list
bottomPadding: (items.length - itemDistance - itemCount) * itemHeight,
})
}
handleScroll = ({clientHeight, scrollTop}) => {
let shouldUpdate = false
if (
(typeof clientHeight !== 'undefined' &&
this.scrollbarsClientHeight !== clientHeight) ||
(typeof scrollTop !== 'undefined' &&
this.scrollbarsScrollTop !== scrollTop)
) {
shouldUpdate = true
}
this.scrollbarsClientHeight = clientHeight
this.scrollbarsScrollTop = scrollTop
if (shouldUpdate) {
this.windowing(this.props, this.state)
}
}
throttledHandleScroll = _.throttle(this.handleScroll, 100)
handleResize = () => {
this.setState({windowHeight: window.innerHeight})
}
throttledHandleResize = _.throttle(this.handleResize, 100)
handleMakeDiv = className => props =>
<div {...props} className={`fancy-scroll--${className}`} />
componentDidMount() {
window.addEventListener('resize', this.handleResize, true)
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize, true)
}
componentWillReceiveProps(nextProps, nextState) {
// Updates values if new items are added
this.windowing(nextProps, nextState)
}
render() {
const {className, items} = this.props
const {
topIndex,
bottomIndex,
topPadding,
bottomPadding,
windowHeight,
} = this.state
return (
<Scrollbars
className={classnames(className, 'fancy-scroll--container')}
autoHide={true}
autoHideTimeout={1000}
autoHideDuration={250}
autoHeight={false}
renderTrackHorizontal={this.handleMakeDiv('track-h')}
renderTrackVertical={this.handleMakeDiv('track-v')}
renderThumbHorizontal={this.handleMakeDiv('thumb-h')}
renderThumbVertical={this.handleMakeDiv('thumb-v')}
renderView={this.handleMakeDiv('view')}
onScrollFrame={this.throttledHandleScroll}
onUpdate={this.throttledHandleScroll}
key={windowHeight}
>
<div style={{height: topPadding}} />
{items.filter((_item, i) => i >= topIndex && i <= bottomIndex)}
<div style={{height: bottomPadding}} />
</Scrollbars>
)
}
}
InfiniteScroll.propTypes = {
itemHeight: number.isRequired,
items: arrayOf(shape()).isRequired,
className: string,
}
export default InfiniteScroll

View File

@ -43,7 +43,7 @@ const Layout = (
{
host,
cell,
cell: {h, axes, type},
cell: {h, axes, type, colors},
source,
sources,
onZoom,
@ -75,6 +75,7 @@ const Layout = (
{cell.isWidget
? <WidgetCell cell={cell} timeRange={timeRange} source={source} />
: <RefreshingGraph
colors={colors}
axes={axes}
type={type}
cellHeight={h}
@ -125,6 +126,15 @@ const propTypes = {
i: string.isRequired,
name: string.isRequired,
type: string.isRequired,
colors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: string.isRequired,
}).isRequired
),
}).isRequired,
templates: arrayOf(shape()),
host: string,

View File

@ -4,6 +4,8 @@ import {withRouter} from 'react-router'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {getNotificationID} from 'src/shared/reducers/notifications'
import {
publishNotification as publishNotificationAction,
dismissNotification as dismissNotificationAction,
@ -25,7 +27,10 @@ class Notifications extends Component {
}
renderNotification(type, message) {
if (!message) {
const isDismissed = this.props.dismissedNotifications[
getNotificationID(message, type)
]
if (!message || isDismissed) {
return null
}
const cls = classnames('alert', {
@ -86,10 +91,12 @@ Notifications.propTypes = {
error: string,
warning: string,
}),
dismissedNotifications: shape({}),
}
const mapStateToProps = ({notifications}) => ({
const mapStateToProps = ({notifications, dismissedNotifications}) => ({
notifications,
dismissedNotifications,
})
const mapDispatchToProps = dispatch => ({

View File

@ -5,19 +5,23 @@ import {emptyGraphCopy} from 'src/shared/copy/cell'
import AutoRefresh from 'shared/components/AutoRefresh'
import LineGraph from 'shared/components/LineGraph'
import SingleStat from 'shared/components/SingleStat'
import GaugeChart from 'shared/components/GaugeChart'
const RefreshingLineGraph = AutoRefresh(LineGraph)
const RefreshingSingleStat = AutoRefresh(SingleStat)
const RefreshingGaugeChart = AutoRefresh(GaugeChart)
const RefreshingGraph = ({
axes,
type,
colors,
onZoom,
queries,
templates,
timeRange,
cellHeight,
autoRefresh,
resizerTopHeight,
manualRefresh, // when changed, re-mounts the component
synchronizer,
resizeCoords,
@ -46,6 +50,21 @@ const RefreshingGraph = ({
)
}
if (type === 'gauge') {
return (
<RefreshingGaugeChart
colors={colors}
key={manualRefresh}
queries={[queries[0]]}
templates={templates}
autoRefresh={autoRefresh}
cellHeight={cellHeight}
resizerTopHeight={resizerTopHeight}
resizeCoords={resizeCoords}
/>
)
}
const displayOptions = {
stepPlot: type === 'line-stepplot',
stackedGraph: type === 'line-stacked',
@ -83,12 +102,22 @@ RefreshingGraph.propTypes = {
synchronizer: func,
type: string.isRequired,
cellHeight: number,
resizerTopHeight: number,
axes: shape(),
queries: arrayOf(shape()).isRequired,
editQueryStatus: func,
onZoom: func,
resizeCoords: shape(),
grabDataForDownload: func,
colors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: string.isRequired,
}).isRequired
),
}
RefreshingGraph.defaultProps = {

View File

@ -34,6 +34,7 @@ class ResizeContainer extends Component {
componentDidMount() {
this.setState({
bottomHeightPixels: this.bottom.getBoundingClientRect().height,
topHeightPixels: this.top.getBoundingClientRect().height,
})
}
@ -92,11 +93,18 @@ class ResizeContainer extends Component {
topHeight: `${newTopPanelPercent}%`,
bottomHeight: `${newBottomPanelPercent}%`,
bottomHeightPixels,
topHeightPixels,
})
}
render() {
const {bottomHeightPixels, topHeight, bottomHeight, isDragging} = this.state
const {
topHeightPixels,
bottomHeightPixels,
topHeight,
bottomHeight,
isDragging,
} = this.state
const {containerClass, children, theme} = this.props
if (React.Children.count(children) > maximumNumChildren) {
@ -116,9 +124,14 @@ class ResizeContainer extends Component {
onMouseMove={this.handleDrag}
ref={r => (this.resizeContainer = r)}
>
<div className="resize--top" style={{height: topHeight}}>
<div
className="resize--top"
style={{height: topHeight}}
ref={r => (this.top = r)}
>
{React.cloneElement(children[0], {
resizerBottomHeight: bottomHeightPixels,
resizerTopHeight: topHeightPixels,
})}
</div>
<ResizeHandle
@ -134,6 +147,7 @@ class ResizeContainer extends Component {
>
{React.cloneElement(children[1], {
resizerBottomHeight: bottomHeightPixels,
resizerTopHeight: topHeightPixels,
})}
</div>
</div>

View File

@ -0,0 +1,16 @@
export const GAUGE_SPECS = {
degree: Math.PI / 180,
lineCount: 6,
lineColor: '#545667',
labelColor: '#8E91A1',
labelFontSize: 13,
lineStrokeSmall: 1,
lineStrokeLarge: 3,
tickSizeSmall: 9,
tickSizeLarge: 18,
minFontSize: 22,
minLineWidth: 24,
valueColor: '#ffffff',
needleColor0: '#434453',
needleColor1: '#ffffff',
}

View File

@ -2,7 +2,7 @@ import app from './app'
import auth from './auth'
import errors from './errors'
import links from './links'
import notifications from './notifications'
import {notifications, dismissedNotifications} from './notifications'
import sources from './sources'
export default {
@ -11,5 +11,6 @@ export default {
errors,
links,
notifications,
dismissedNotifications,
sources,
}

View File

@ -1,11 +1,7 @@
import u from 'updeep'
import _ from 'lodash'
function getInitialState() {
return {}
}
const initialState = getInitialState()
const notificationsReducer = (state = initialState, action) => {
export const notifications = (state = {}, action) => {
switch (action.type) {
case 'NOTIFICATION_RECEIVED': {
const {type, message} = action.payload
@ -16,11 +12,38 @@ const notificationsReducer = (state = initialState, action) => {
return u(u.omit(type), state)
}
case 'ALL_NOTIFICATIONS_DISMISSED': {
return getInitialState()
// Reset to initial state
return {}
}
}
return state
}
export default notificationsReducer
export const getNotificationID = (message, type) => _.snakeCase(message) + type
export const dismissedNotifications = (state = {}, action) => {
switch (action.type) {
case 'NOTIFICATION_RECEIVED': {
const {type, message, once} = action.payload
if (once) {
// Create a message ID in a deterministic way, also with its type
const messageID = getNotificationID(message, type)
if (state[messageID]) {
// Message action called with once option but we've already seen it
return state
}
// Message action called with once option and it's not present on
// the persisted state
return {
...state,
[messageID]: true,
}
}
// Message action not called with once option
return state
}
}
return state
}

View File

@ -30,6 +30,7 @@
@import 'components/ceo-display-options';
@import 'components/confirm-buttons';
@import 'components/code-mirror-theme';
@import 'components/color-dropdown';
@import 'components/custom-time-range';
@import 'components/dygraphs';
@import 'components/fancy-scrollbars';

View File

@ -2,6 +2,9 @@
Cell Editor Overlay - Display Options
------------------------------------------------------
*/
$graph-type--gutter: 4px;
.display-options {
height: 100%;
display: flex;
@ -11,16 +14,25 @@
align-items: stretch;
}
.display-options--cell {
position: relative;
border-radius: 3px;
background-color: $g3-castle;
padding: 30px;
flex: 1 0 0;
margin-right: 8px;
border-radius: 3px;
background-color: $g3-castle;
&:last-of-type {
margin: 0;
}
}
.display-options--cellx2 {
flex: 2 0 0;
}
.display-options--cell-wrapper {
width: 100%;
position: relative;
display: inline-block;
padding: 30px;
}
.display-options--header {
margin: 0 0 12px 0;
font-weight: 400;
@ -28,41 +40,42 @@
@include no-user-select();
}
.viz-type-selector {
display: flex;
flex-wrap: wrap;
position: absolute;
top: 60px;
left: 26px;
height: calc(100% - 85px);
width: calc(100% - 52px);
margin: 0;
width: 100%;
display: inline-block;
margin: 0 (-$graph-type--gutter / 2);
margin-bottom: -$graph-type--gutter;
}
.viz-type-selector--option {
flex: 1 0 33.3333%;
height: 50%;
padding: 4px;
float: left;
width: 33.3333%;
padding-bottom: 33.3333%;
position: relative;
> div > p {
margin: 0;
font-size: 14px;
font-weight: 900;
position: absolute;
bottom: 6.25%;
left: 50%;
transform: translate(-50%, 50%);
bottom: 18px;
left: 10px;
width: calc(100% - 20px);
text-align: center;
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
// Actual "card"
> div {
background-color: $g3-castle;
border: 2px solid $g4-onyx;
background-color: $g2-kevlar;
color: $g11-sidewalk;
border-radius: 3px;
width: 100%;
height: 100%;
display: block;
position: relative;
border-radius: 4px;
width: calc(100% - #{$graph-type--gutter});
height: calc(100% - #{$graph-type--gutter});
position: absolute;
top: $graph-type--gutter / 2;
left: $graph-type--gutter / 2;
transition:
color 0.25s ease,
border-color 0.25s ease,
@ -71,61 +84,93 @@
&:hover {
cursor: pointer;
background-color: $g4-onyx;
border-color: $g5-pepper;
color: $g15-platinum;
}
}
}
// Increase options per row as screen enlarges
@media only screen and (min-width: 1000px) {
.viz-type-selector--option {
width: 25%;
padding-bottom: 25%;
}
}
@media only screen and (min-width: 1270px) {
.viz-type-selector--option {
width: 20%;
padding-bottom: 20%;
}
}
@media only screen and (min-width: 1600px) {
.viz-type-selector--option {
width: 16.6667%;
padding-bottom: 16.6667%;
}
}
@media only screen and (min-width: 2000px) {
.viz-type-selector--option {
width: 12.5%;
padding-bottom: 12.5%;
}
}
// Active state "card"
.viz-type-selector--option.active > div,
.viz-type-selector--option.active > div:hover {
background-color: $g5-pepper;
border-color: $g7-graphite;
color: $g18-cloud;
}
.viz-type-selector--graphic {
width: calc(100% - 48px);
height: 50%;
width: calc(100% - 54px);
height: calc(100% - 54px);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
> svg {
> svg,
> svg * {
transform: translate3d(0,0,0);
}
> svg {
width: 100%;
height: 100%;
}
}
.viz-type-selector--graphic-line {
stroke-width: 3px;
stroke-width: 2px;
fill: none;
stroke-linecap: round;
stroke-miterlimit: 10;
transition: all 0.5s ease;
// transition: all 0.5s ease;
&.graphic-line-a {stroke: $g11-sidewalk;}
&.graphic-line-b {stroke: $g9-mountain;}
&.graphic-line-c {stroke: $g7-graphite;}
&.graphic-line-d {stroke: $g13-mist;}
}
.viz-type-selector--graphic-fill {
opacity: 0.035;
transition: opacity 0.5s ease;
opacity: 0.045;
// transition: opacity 0.5s ease;
&.graphic-fill-a {fill: $g11-sidewalk;} &.graphic-fill-b {fill: $g9-mountain;}
&.graphic-fill-a {fill: $g11-sidewalk;}
&.graphic-fill-b {fill: $g9-mountain;}
&.graphic-fill-c {fill: $g7-graphite;}
&.graphic-fill-d {fill: $g13-mist; opacity: 1;}
}
.viz-type-selector--option.active .viz-type-selector--graphic {
.viz-type-selector--graphic-line.graphic-line-a {stroke: $c-pool;}
.viz-type-selector--graphic-line.graphic-line-b {stroke: $c-dreamsicle;}
.viz-type-selector--graphic-line.graphic-line-c {stroke: $c-rainforest;}
.viz-type-selector--graphic-line.graphic-line-d {stroke: $g17-whisper;}
.viz-type-selector--graphic-fill.graphic-fill-a {fill: $c-pool;}
.viz-type-selector--graphic-fill.graphic-fill-b {fill: $c-dreamsicle;}
.viz-type-selector--graphic-fill.graphic-fill-c {fill: $c-rainforest;}
.viz-type-selector--graphic-fill.graphic-fill-a,
.viz-type-selector--graphic-fill.graphic-fill-b,
.viz-type-selector--graphic-fill.graphic-fill-c {opacity: 0.18;}
.viz-type-selector--graphic-fill.graphic-fill-c {opacity: 0.22;}
.viz-type-selector--graphic-fill.graphic-fill-d {fill: $g17-whisper; opacity: 1;}
}
@ -150,3 +195,52 @@
padding-left: 6px;
@include no-user-select();
}
/*
Cell Editor Overlay - Gauge Controls
------------------------------------------------------
*/
.gauge-controls {
width: 100%;
}
.gauge-controls--section {
width: 100%;
display: flex;
flex-wrap: nowrap;
align-items: center;
height: 30px;
margin-bottom: 8px;
}
button.btn.btn-primary.btn-sm.gauge-controls--add-threshold {
width: 100%;
}
.gauge-controls--label {
height: 30px;
background-color: $g4-onyx;
font-weight: 600;
color: $g11-sidewalk;
padding: 0 11px;
border-radius: 4px;
line-height: 30px;
@include no-user-select();
width: 120px;
}
.gauge-controls--label-editable {
height: 30px;
font-weight: 600;
color: $g16-pearl;
padding: 0 11px;
border-radius: 4px;
line-height: 30px;
@include no-user-select();
width: 90px;
}
.gauge-controls--input {
flex: 1 0 0;
margin: 0 4px;
}

View File

@ -0,0 +1,98 @@
/*
Color Dropdown
------------------------------------------------------------------------------
*/
$color-dropdown--circle: 14px;
.color-dropdown {
width: 140px;
height: 30px;
position: relative;
}
.color-dropdown--toggle {
width: 100%;
position: relative;
}
.color-dropdown--toggle span.caret {
font-style: normal !important;
position: absolute;
top: 50%;
right: 11px;
transform: translateY(-50%);
}
.color-dropdown--menu {
position: absolute;
top: 30px;
left: 0;
z-index: 2;
width: 100%;
border-radius: 4px;
box-shadow: 0 2px 5px 0.6px fade-out($g0-obsidian, 0.7);
@include gradient-h($g0-obsidian,$g2-kevlar);
}
.color-dropdown--item {
@include no-user-select();
width: 100%;
height: 28px;
position: relative;
color: $g11-sidewalk;
transition:
color 0.25s ease,
background-color 0.25s ease;
&:hover {
background-color: $g4-onyx;
color: $g18-cloud;
}
&:hover,
&:hover > * {
cursor: pointer !important;
}
&.active {
background-color: $g3-castle;
color: $g15-platinum;
}
&:first-child {
border-radius: 4px 4px 0 0;
}
&:last-child {
border-radius: 0 0 4px 4px;
}
}
.color-dropdown--swatch,
.color-dropdown--name {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
.color-dropdown--swatch {
width: $color-dropdown--circle;
height: $color-dropdown--circle;
border-radius: 50%;
left: 11px;
}
.color-dropdown--name {
text-align: left;
right: 11px;
left: 34px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
font-weight: 600;
text-transform: capitalize;
}
.color-dropdown .color-dropdown--menu .fancy-scroll--container .fancy-scroll--track-v .fancy-scroll--thumb-v {
@include gradient-v($g9-mountain,$g7-graphite);
}
.color-dropdown--toggle.color-dropdown__disabled {
color: $g7-graphite;
font-style: italic;
cursor: not-allowed;
}
.color-dropdown--toggle.color-dropdown__disabled > .color-dropdown--swatch {
background-color: $g7-graphite !important;
}

View File

@ -83,6 +83,11 @@
&.graph-single-stat {
top: 0;
}
> canvas.gauge {
width: 100% !important;
height: 100% !important;
}
}
.single-stat--value {
position: absolute;

View File

@ -32,30 +32,13 @@ $logs-margin: 4px;
height: calc(100% - #{$logs-table-header-height}) !important;
}
.logs-table,
.logs-table--row {
display: flex;
align-items: stretch;
flex-direction: column;
}
@keyframes LogsFadeIn {
from {
background-color: $g6-smoke;
}
to {
background-color: transparent;
}
}
.logs-table {
flex-direction: column-reverse;
height: 100%;
}
.logs-table--row {
height: 87px; // Fixed height, required for Infinite Scroll, allows for 2 tags / fields per line
padding: 8px ($logs-table-padding - 16px) 8px ($logs-table-padding / 2);
border-bottom: 2px solid $g3-castle;
animation-name: LogsFadeIn;
animation-duration: 2.5s;
animation-iteration-count: 1;
animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
transition: background-color 0.25s ease;
&:hover {

View File

@ -182,7 +182,8 @@ $table-tab-scrollbar-height: 6px;
Alert History "Page"
----------------------------------------------
*/
.alert-history-page {
.alert-history-page,
.hosts-list-page {
.page-contents > .container-fluid,
.page-contents > .container-fluid > .row,
.page-contents > .container-fluid > .row > .col-md-12,
@ -238,7 +239,7 @@ $table-tab-scrollbar-height: 6px;
color: $g17-whisper;
}
.alert-history-table--tbody {
flex: 1 0 0%;
flex: 1 0 0;
width: 100%;
}
.alert-history-table--tr {
@ -276,3 +277,48 @@ table .table-cell-nowrap {
overflow: hidden;
text-overflow: ellipsis;
}
/*
Hosts "Table"
----------------------------------------------
*/
.hosts-table {
height: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
}
.hosts-table--thead {
display: flex;
width: 100%;
border-bottom: 2px solid $g5-pepper;
}
.hosts-table--th {
@include no-user-select();
padding: 8px;
font-size: 13px;
font-weight: 500;
color: $g17-whisper;
}
.hosts-table--tbody {
flex: 1 0 0;
width: 100%;
}
.hosts-table--tr {
display: flex;
width: 100%;
&:hover {
background-color: $g4-onyx;
}
}
.hosts-table--td {
font-size: 12px;
font-family: $code-font;
font-weight: 500;
padding: 4px 8px;
line-height: 1.42857143em;
color: $g13-mist;
white-space: pre-wrap;
word-break: break-all;
}

View File

@ -45,7 +45,7 @@ $overlay-z: 100;
background-color: $g2-kevlar;
}
.overlay-controls .nav-tablist {
width: 200px;
width: 230px;
li {
white-space: nowrap;