Merge branch 'master' into multitenancy
commit
31f012ff8f
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ const OverlayControls = ({
|
|||
})}
|
||||
onClick={onClickDisplayOptions(true)}
|
||||
>
|
||||
Options
|
||||
Visualization
|
||||
</li>
|
||||
</ul>
|
||||
<div className="overlay-controls--right">
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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,
|
||||
|
|
|
@ -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 => ({
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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',
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -83,6 +83,11 @@
|
|||
&.graph-single-stat {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
> canvas.gauge {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
}
|
||||
.single-stat--value {
|
||||
position: absolute;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ $overlay-z: 100;
|
|||
background-color: $g2-kevlar;
|
||||
}
|
||||
.overlay-controls .nav-tablist {
|
||||
width: 200px;
|
||||
width: 230px;
|
||||
|
||||
li {
|
||||
white-space: nowrap;
|
||||
|
|
Loading…
Reference in New Issue