Merge pull request #1996 from influxdata/feature/source-on-cell

FEATURE: Switch InfluxDB sources per chart in Dashboards
pull/10616/head
Deniz Kusefoglu 2017-10-03 16:07:57 -07:00 committed by GitHub
commit 3c204a4d36
48 changed files with 584 additions and 292 deletions

View File

@ -5,6 +5,7 @@
1.[#2015](https://github.com/influxdata/chronograf/pull/2015): Chronograf shows real status for windows hosts when metrics are saved in non-default db - thank you, @ar7z1! 1.[#2015](https://github.com/influxdata/chronograf/pull/2015): Chronograf shows real status for windows hosts when metrics are saved in non-default db - thank you, @ar7z1!
1.[#2019](https://github.com/influxdata/chronograf/pull/2006): Fix false error warning for duplicate kapacitor name 1.[#2019](https://github.com/influxdata/chronograf/pull/2006): Fix false error warning for duplicate kapacitor name
1.[#2018](https://github.com/influxdata/chronograf/pull/2018): Fix unresponsive display options and query builder in dashboards 1.[#2018](https://github.com/influxdata/chronograf/pull/2018): Fix unresponsive display options and query builder in dashboards
1.[#1996](https://github.com/influxdata/chronograf/pull/1996): Able to switch InfluxDB sources on a per graph basis
### Features ### Features
1. [#1885](https://github.com/influxdata/chronograf/pull/1885): Add `fill` options to data explorer and dashboard queries 1. [#1885](https://github.com/influxdata/chronograf/pull/1885): Add `fill` options to data explorer and dashboard queries

View File

@ -195,6 +195,7 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
Command: q.Command, Command: q.Command,
Label: q.Label, Label: q.Label,
Range: r, Range: r,
Source: q.Source,
} }
} }
@ -274,6 +275,7 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error {
queries[j] = chronograf.DashboardQuery{ queries[j] = chronograf.DashboardQuery{
Command: q.Command, Command: q.Command,
Label: q.Label, Label: q.Label,
Source: q.Source,
} }
if q.Range.Upper != q.Range.Lower { if q.Range.Upper != q.Range.Lower {
queries[j].Range = &chronograf.Range{ queries[j].Range = &chronograf.Range{

View File

@ -261,6 +261,7 @@ type Query struct {
Wheres []string `protobuf:"bytes,5,rep,name=Wheres" json:"Wheres,omitempty"` Wheres []string `protobuf:"bytes,5,rep,name=Wheres" json:"Wheres,omitempty"`
Label string `protobuf:"bytes,6,opt,name=Label,proto3" json:"Label,omitempty"` Label string `protobuf:"bytes,6,opt,name=Label,proto3" json:"Label,omitempty"`
Range *Range `protobuf:"bytes,7,opt,name=Range" json:"Range,omitempty"` Range *Range `protobuf:"bytes,7,opt,name=Range" json:"Range,omitempty"`
Source string `protobuf:"bytes,8,opt,name=Source,proto3" json:"Source,omitempty"`
} }
func (m *Query) Reset() { *m = Query{} } func (m *Query) Reset() { *m = Query{} }
@ -327,67 +328,70 @@ func init() {
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) } func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
var fileDescriptorInternal = []byte{ var fileDescriptorInternal = []byte{
// 985 bytes of a gzipped FileDescriptorProto // 1028 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x56, 0xcd, 0x6e, 0x23, 0x45, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x56, 0x4f, 0x6f, 0xe3, 0x44,
0x10, 0x56, 0xcf, 0x8f, 0xed, 0x29, 0x67, 0x03, 0x6a, 0xad, 0xd8, 0x61, 0xb9, 0x98, 0x11, 0x48, 0x14, 0xd7, 0xf8, 0x4f, 0x12, 0xbf, 0x74, 0x0b, 0x1a, 0xad, 0x58, 0xb3, 0x5c, 0x82, 0x05, 0x52,
0x06, 0x41, 0x40, 0xbb, 0x42, 0x42, 0xdc, 0x9c, 0x18, 0xad, 0x42, 0xb2, 0x4b, 0x68, 0x27, 0x81, 0x40, 0x6c, 0x41, 0xbb, 0x42, 0x42, 0xdc, 0xd2, 0x06, 0xad, 0x4a, 0xbb, 0x4b, 0x99, 0xb4, 0xe5,
0x0b, 0x5a, 0xb5, 0xc7, 0x95, 0x64, 0xb4, 0x63, 0xcf, 0xd0, 0x33, 0x13, 0x7b, 0xde, 0x82, 0x27, 0x84, 0x56, 0x13, 0xe7, 0xa5, 0xb5, 0xd6, 0x89, 0xcd, 0xd8, 0x6e, 0xe3, 0x6f, 0xc1, 0x27, 0x40,
0x40, 0x42, 0xe2, 0xc4, 0x81, 0x03, 0x2f, 0xc0, 0x9d, 0xa7, 0x42, 0xd5, 0xdd, 0x33, 0x1e, 0xb3, 0x42, 0xe2, 0xc4, 0x81, 0x03, 0x5f, 0x80, 0xfb, 0x7e, 0x2a, 0xf4, 0x66, 0xc6, 0x8e, 0xc3, 0x76,
0x59, 0xb4, 0x17, 0xf6, 0xd6, 0x5f, 0x55, 0xbb, 0xba, 0xe6, 0xab, 0xfa, 0x3e, 0x19, 0xf6, 0x93, 0xd1, 0x5e, 0xe0, 0x36, 0xbf, 0xf7, 0xc6, 0x6f, 0x66, 0xde, 0xef, 0xfd, 0x7e, 0x09, 0xec, 0x27,
0x55, 0x89, 0x6a, 0x25, 0xd3, 0x83, 0x5c, 0x65, 0x65, 0xc6, 0x07, 0x0d, 0x8e, 0xfe, 0x70, 0xa0, 0xeb, 0x12, 0xd5, 0x5a, 0xa6, 0x07, 0xb9, 0xca, 0xca, 0x8c, 0x0f, 0x1a, 0x1c, 0xfd, 0xe1, 0x40,
0x37, 0xcb, 0x2a, 0x15, 0x23, 0xdf, 0x07, 0xe7, 0x78, 0x1a, 0xb2, 0x11, 0x1b, 0xbb, 0xc2, 0x39, 0x6f, 0x96, 0x55, 0x2a, 0x46, 0xbe, 0x0f, 0xce, 0xf1, 0x34, 0x64, 0x23, 0x36, 0x76, 0x85, 0x73,
0x9e, 0x72, 0x0e, 0xde, 0x33, 0xb9, 0xc4, 0xd0, 0x19, 0xb1, 0x71, 0x20, 0xf4, 0x99, 0x62, 0xe7, 0x3c, 0xe5, 0x1c, 0xbc, 0xe7, 0x72, 0x85, 0xa1, 0x33, 0x62, 0xe3, 0x40, 0xe8, 0x35, 0xc5, 0xce,
0x75, 0x8e, 0xa1, 0x6b, 0x62, 0x74, 0xe6, 0x0f, 0x61, 0x70, 0x51, 0x50, 0xb5, 0x25, 0x86, 0x9e, 0xeb, 0x1c, 0x43, 0xd7, 0xc4, 0x68, 0xcd, 0x1f, 0xc2, 0xe0, 0xa2, 0xa0, 0x6a, 0x2b, 0x0c, 0x3d,
0x8e, 0xb7, 0x98, 0x72, 0x67, 0xb2, 0x28, 0xd6, 0x99, 0x5a, 0x84, 0xbe, 0xc9, 0x35, 0x98, 0xbf, 0x1d, 0x6f, 0x31, 0xe5, 0xce, 0x64, 0x51, 0xdc, 0x66, 0x6a, 0x11, 0xfa, 0x26, 0xd7, 0x60, 0xfe,
0x0d, 0xee, 0x85, 0x38, 0x0d, 0x7b, 0x3a, 0x4c, 0x47, 0x1e, 0x42, 0x7f, 0x8a, 0x57, 0xb2, 0x4a, 0x2e, 0xb8, 0x17, 0xe2, 0x34, 0xec, 0xe9, 0x30, 0x2d, 0x79, 0x08, 0xfd, 0x29, 0x2e, 0x65, 0x95,
0xcb, 0xb0, 0x3f, 0x62, 0xe3, 0x81, 0x68, 0x20, 0xd5, 0x39, 0xc7, 0x14, 0xaf, 0x95, 0xbc, 0x0a, 0x96, 0x61, 0x7f, 0xc4, 0xc6, 0x03, 0xd1, 0x40, 0xaa, 0x73, 0x8e, 0x29, 0x5e, 0x29, 0xb9, 0x0c,
0x07, 0xa6, 0x4e, 0x83, 0xf9, 0x01, 0xf0, 0xe3, 0x55, 0x81, 0x71, 0xa5, 0x70, 0xf6, 0x22, 0xc9, 0x07, 0xa6, 0x4e, 0x83, 0xf9, 0x01, 0xf0, 0xe3, 0x75, 0x81, 0x71, 0xa5, 0x70, 0xf6, 0x32, 0xc9,
0x2f, 0x51, 0x25, 0x57, 0x75, 0x18, 0xe8, 0x02, 0x77, 0x64, 0xe8, 0x95, 0xa7, 0x58, 0x4a, 0x7a, 0x2f, 0x51, 0x25, 0xcb, 0x3a, 0x0c, 0x74, 0x81, 0x3b, 0x32, 0x74, 0xca, 0x33, 0x2c, 0x25, 0x9d,
0x1b, 0x74, 0xa9, 0x06, 0xf2, 0x08, 0xf6, 0x66, 0x37, 0x52, 0xe1, 0x62, 0x86, 0xb1, 0xc2, 0x32, 0x0d, 0xba, 0x54, 0x03, 0x79, 0x04, 0x7b, 0xb3, 0x6b, 0xa9, 0x70, 0x31, 0xc3, 0x58, 0x61, 0x19,
0x1c, 0xea, 0xf4, 0x4e, 0x2c, 0xfa, 0x99, 0x41, 0x30, 0x95, 0xc5, 0xcd, 0x3c, 0x93, 0x6a, 0xf1, 0x0e, 0x75, 0x7a, 0x27, 0x16, 0xfd, 0xcc, 0x20, 0x98, 0xca, 0xe2, 0x7a, 0x9e, 0x49, 0xb5, 0x78,
0x5a, 0x9c, 0x7d, 0x0a, 0x7e, 0x8c, 0x69, 0x5a, 0x84, 0xee, 0xc8, 0x1d, 0x0f, 0x1f, 0x3d, 0x38, 0xab, 0x9e, 0x3d, 0x02, 0x3f, 0xc6, 0x34, 0x2d, 0x42, 0x77, 0xe4, 0x8e, 0x87, 0x8f, 0x1f, 0x1c,
0x68, 0x87, 0xd1, 0xd6, 0x39, 0xc2, 0x34, 0x15, 0xe6, 0x16, 0xff, 0x1c, 0x82, 0x12, 0x97, 0x79, 0xb4, 0x64, 0xb4, 0x75, 0x8e, 0x30, 0x4d, 0x85, 0xd9, 0xc5, 0xbf, 0x80, 0xa0, 0xc4, 0x55, 0x9e,
0x2a, 0x4b, 0x2c, 0x42, 0x4f, 0xff, 0x84, 0x6f, 0x7f, 0x72, 0x6e, 0x53, 0x62, 0x7b, 0x29, 0xfa, 0xca, 0x12, 0x8b, 0xd0, 0xd3, 0x9f, 0xf0, 0xed, 0x27, 0xe7, 0x36, 0x25, 0xb6, 0x9b, 0xa2, 0xdf,
0xdd, 0x81, 0x7b, 0x3b, 0xa5, 0xf8, 0x1e, 0xb0, 0x8d, 0xee, 0xca, 0x17, 0x6c, 0x43, 0xa8, 0xd6, 0x1d, 0xb8, 0xb7, 0x53, 0x8a, 0xef, 0x01, 0xdb, 0xe8, 0x5b, 0xf9, 0x82, 0x6d, 0x08, 0xd5, 0xfa,
0x1d, 0xf9, 0x82, 0xd5, 0x84, 0xd6, 0x7a, 0x7e, 0xbe, 0x60, 0x6b, 0x42, 0x37, 0x7a, 0x6a, 0xbe, 0x46, 0xbe, 0x60, 0x35, 0xa1, 0x5b, 0xcd, 0x9f, 0x2f, 0xd8, 0x2d, 0xa1, 0x6b, 0xcd, 0x9a, 0x2f,
0x60, 0x37, 0xfc, 0x23, 0xe8, 0xff, 0x54, 0xa1, 0x4a, 0xb0, 0x08, 0x7d, 0xfd, 0xf2, 0x5b, 0xdb, 0xd8, 0x35, 0xff, 0x04, 0xfa, 0x3f, 0x55, 0xa8, 0x12, 0x2c, 0x42, 0x5f, 0x9f, 0xfc, 0xce, 0xf6,
0x97, 0xbf, 0xab, 0x50, 0xd5, 0xa2, 0xc9, 0xd3, 0x97, 0xea, 0x89, 0x9b, 0xf1, 0xe9, 0x33, 0xc5, 0xe4, 0xef, 0x2b, 0x54, 0xb5, 0x68, 0xf2, 0xf4, 0x52, 0xcd, 0xb8, 0xa1, 0x4f, 0xaf, 0x29, 0x56,
0x4a, 0xda, 0x8e, 0xbe, 0x89, 0xd1, 0xd9, 0x32, 0x64, 0x66, 0x46, 0x0c, 0x7d, 0x01, 0x9e, 0xdc, 0xd2, 0x74, 0xf4, 0x4d, 0x8c, 0xd6, 0xb6, 0x43, 0x86, 0x33, 0xea, 0xd0, 0x97, 0xe0, 0xc9, 0x0d,
0x60, 0x11, 0x06, 0xba, 0xfe, 0xfb, 0xaf, 0x20, 0xe3, 0x60, 0xb2, 0xc1, 0xe2, 0xeb, 0x55, 0xa9, 0x16, 0x61, 0xa0, 0xeb, 0x7f, 0xf8, 0x86, 0x66, 0x1c, 0x4c, 0x36, 0x58, 0x7c, 0xb3, 0x2e, 0x55,
0x6a, 0xa1, 0xaf, 0x3f, 0x7c, 0x02, 0x41, 0x1b, 0xa2, 0xcd, 0x79, 0x81, 0xb5, 0xfe, 0xc0, 0x40, 0x2d, 0xf4, 0xf6, 0x87, 0x4f, 0x21, 0x68, 0x43, 0x34, 0x39, 0x2f, 0xb1, 0xd6, 0x0f, 0x0c, 0x04,
0xd0, 0x91, 0x7f, 0x00, 0xfe, 0xad, 0x4c, 0x2b, 0x43, 0xfc, 0xf0, 0xd1, 0xfe, 0xb6, 0xec, 0x64, 0x2d, 0xf9, 0x47, 0xe0, 0xdf, 0xc8, 0xb4, 0x32, 0x8d, 0x1f, 0x3e, 0xde, 0xdf, 0x96, 0x9d, 0x6c,
0x93, 0x14, 0xc2, 0x24, 0xbf, 0x72, 0xbe, 0x64, 0xd1, 0x0f, 0xe0, 0x51, 0x88, 0x66, 0x9d, 0xe2, 0x92, 0x42, 0x98, 0xe4, 0xd7, 0xce, 0x57, 0x2c, 0xfa, 0x93, 0x81, 0x47, 0x31, 0x22, 0x3b, 0xc5,
0xb5, 0x8c, 0xeb, 0xc3, 0xac, 0x5a, 0x2d, 0x8a, 0x90, 0x8d, 0xdc, 0xb1, 0x2b, 0x76, 0x62, 0xfc, 0x2b, 0x19, 0xd7, 0x87, 0x59, 0xb5, 0x5e, 0x14, 0x21, 0x1b, 0xb9, 0x63, 0x57, 0xec, 0xc4, 0xf8,
0x1d, 0xe8, 0xcd, 0x4d, 0xd6, 0x19, 0xb9, 0xe3, 0x40, 0x58, 0xc4, 0xef, 0x83, 0x9f, 0xca, 0x39, 0x7b, 0xd0, 0x9b, 0x9b, 0xac, 0x33, 0x72, 0xc7, 0x81, 0xb0, 0x88, 0xdf, 0x07, 0x3f, 0x95, 0x73,
0xa6, 0x56, 0x06, 0x06, 0x44, 0x7f, 0x31, 0x5a, 0x52, 0x33, 0x94, 0xce, 0x62, 0x98, 0xcf, 0x7e, 0x4c, 0xad, 0x0e, 0x0c, 0xa0, 0xdd, 0xb9, 0xc2, 0x65, 0xb2, 0xb1, 0x32, 0xb0, 0x88, 0xe2, 0x45,
0x17, 0x06, 0x34, 0xb0, 0xe7, 0xb7, 0x52, 0xd9, 0xe5, 0xe8, 0x13, 0xbe, 0x94, 0x8a, 0x7f, 0x06, 0xb5, 0xa4, 0xb8, 0x91, 0x80, 0x45, 0xd4, 0xae, 0xb9, 0x2c, 0xda, 0x16, 0xd2, 0x9a, 0x2a, 0x17,
0x3d, 0xdd, 0xde, 0x1d, 0x0b, 0xd2, 0x94, 0xbb, 0xa4, 0xbc, 0xb0, 0xd7, 0x5a, 0x9a, 0xbd, 0x0e, 0xb1, 0x4c, 0x9b, 0x1e, 0x1a, 0x10, 0xfd, 0xc5, 0x68, 0xfe, 0x0d, 0xdf, 0x9d, 0x99, 0x33, 0x1d,
0xcd, 0x6d, 0x4b, 0x7e, 0xa7, 0x25, 0x5a, 0x3d, 0x9a, 0x57, 0xad, 0xa7, 0x74, 0x67, 0x65, 0x33, 0x7d, 0x1f, 0x06, 0x34, 0x0b, 0x2f, 0x6e, 0xa4, 0xb2, 0x73, 0xd7, 0x27, 0x7c, 0x29, 0x15, 0xff,
0x55, 0x73, 0x2b, 0xba, 0x80, 0x7b, 0x3b, 0x2f, 0xb6, 0x2f, 0xb1, 0xdd, 0x97, 0xb6, 0x54, 0x07, 0x1c, 0x7a, 0xfa, 0xe5, 0x77, 0xcc, 0x5e, 0x53, 0xee, 0x92, 0xf2, 0xc2, 0x6e, 0x6b, 0x19, 0xf4,
0x96, 0x5a, 0x12, 0x68, 0x81, 0x29, 0xc6, 0x25, 0x2e, 0x34, 0x2b, 0x03, 0xd1, 0xe2, 0xe8, 0x57, 0x3a, 0x0c, 0xb6, 0x8f, 0xf5, 0xbb, 0x8f, 0x7d, 0x04, 0x3e, 0x8d, 0x42, 0xad, 0x6f, 0x7f, 0x67,
0xb6, 0xad, 0xab, 0xdf, 0x23, 0x09, 0xc6, 0xd9, 0x72, 0x29, 0x57, 0x0b, 0x5b, 0xba, 0x81, 0xc4, 0x65, 0x33, 0x30, 0x66, 0x57, 0x74, 0x01, 0xf7, 0x76, 0x4e, 0x6c, 0x4f, 0x62, 0xbb, 0x27, 0x6d,
0xdb, 0x62, 0x6e, 0x4b, 0x3b, 0x8b, 0x39, 0x61, 0x95, 0x5b, 0x9e, 0x1d, 0x95, 0xf3, 0x11, 0x0c, 0x59, 0x0c, 0x2c, 0x6b, 0xa4, 0xfd, 0x02, 0x53, 0x8c, 0x4b, 0x5c, 0xe8, 0x7e, 0x0f, 0x44, 0x8b,
0x97, 0x28, 0x8b, 0x4a, 0xe1, 0x12, 0x57, 0xa5, 0xa5, 0xa0, 0x1b, 0xe2, 0x0f, 0xa0, 0x5f, 0xca, 0xa3, 0x5f, 0xd9, 0xb6, 0xae, 0x3e, 0x8f, 0xd4, 0x1d, 0x67, 0xab, 0x95, 0x5c, 0x2f, 0x6c, 0xe9,
0xeb, 0xe7, 0xb4, 0x20, 0x86, 0x8b, 0x5e, 0x29, 0xaf, 0x4f, 0xb0, 0xe6, 0xef, 0x41, 0x70, 0x95, 0x06, 0x52, 0xdf, 0x16, 0x73, 0x5b, 0xda, 0x59, 0xcc, 0x09, 0xab, 0xdc, 0x32, 0xe8, 0xa8, 0x9c,
0x60, 0xba, 0xd0, 0x29, 0xb3, 0xb6, 0x03, 0x1d, 0x38, 0xc1, 0x3a, 0xfa, 0x8d, 0x41, 0x6f, 0x86, 0x8f, 0x60, 0xb8, 0x42, 0x59, 0x54, 0x0a, 0x57, 0xb8, 0x2e, 0x6d, 0x0b, 0xba, 0x21, 0xfe, 0x00,
0xea, 0x16, 0xd5, 0x6b, 0x69, 0xba, 0xeb, 0x79, 0xee, 0x7f, 0x78, 0x9e, 0x77, 0xb7, 0xe7, 0xf9, 0xfa, 0xa5, 0xbc, 0x7a, 0x41, 0xb3, 0x67, 0x99, 0x2c, 0xe5, 0xd5, 0x09, 0xd6, 0xfc, 0x03, 0x08,
0x5b, 0xcf, 0xbb, 0x0f, 0xfe, 0x4c, 0xc5, 0xc7, 0x53, 0xdd, 0x91, 0x2b, 0x0c, 0xa0, 0xcd, 0x9b, 0x96, 0x09, 0xa6, 0x0b, 0x9d, 0x32, 0x74, 0x0e, 0x74, 0xe0, 0x04, 0xeb, 0xe8, 0x37, 0x06, 0xbd,
0xc4, 0x65, 0x72, 0x8b, 0xd6, 0x08, 0x2d, 0x8a, 0x7e, 0x61, 0xd0, 0x3b, 0x95, 0x75, 0x56, 0x95, 0x19, 0xaa, 0x1b, 0x54, 0x6f, 0x65, 0x17, 0x5d, 0x3b, 0x75, 0xff, 0xc5, 0x4e, 0xbd, 0xbb, 0xed,
0x2f, 0x6d, 0xd8, 0x08, 0x86, 0x93, 0x3c, 0x4f, 0x93, 0x58, 0x96, 0x49, 0xb6, 0xb2, 0xdd, 0x76, 0xd4, 0xdf, 0xda, 0xe9, 0x7d, 0xf0, 0x67, 0x2a, 0x3e, 0x9e, 0xea, 0x1b, 0xb9, 0xc2, 0x00, 0x9a,
0x43, 0x74, 0xe3, 0x69, 0x87, 0x3b, 0xd3, 0x77, 0x37, 0x44, 0x32, 0x3a, 0xd2, 0x56, 0x65, 0x7c, 0xc6, 0x49, 0x5c, 0x26, 0x37, 0x68, 0x3d, 0xd6, 0xa2, 0xe8, 0x17, 0x06, 0xbd, 0x53, 0x59, 0x67,
0xa7, 0x23, 0x23, 0xe3, 0x50, 0x3a, 0x49, 0x1f, 0x38, 0xa9, 0xca, 0xec, 0x2a, 0xcd, 0xd6, 0xfa, 0x55, 0xf9, 0xda, 0x84, 0x8d, 0x60, 0x38, 0xc9, 0xf3, 0x34, 0x89, 0x65, 0x99, 0x64, 0x6b, 0x7b,
0x4b, 0x06, 0xa2, 0xc5, 0xd1, 0xdf, 0x0e, 0x78, 0x6f, 0xca, 0x82, 0xf6, 0x80, 0x25, 0x76, 0x90, 0xdb, 0x6e, 0x88, 0x76, 0x3c, 0xeb, 0xf4, 0xce, 0xdc, 0xbb, 0x1b, 0x22, 0x85, 0x1e, 0x69, 0x17,
0x2c, 0x69, 0x0d, 0xa9, 0xdf, 0x31, 0xa4, 0x10, 0xfa, 0xb5, 0x92, 0xab, 0x6b, 0x2c, 0xc2, 0x81, 0x34, 0x96, 0xd6, 0x51, 0xa8, 0x31, 0x3f, 0x9d, 0xa4, 0x07, 0x4e, 0xaa, 0x32, 0x5b, 0xa6, 0xd9,
0xd6, 0x77, 0x03, 0x75, 0x46, 0x6b, 0xc4, 0x38, 0x51, 0x20, 0x1a, 0xd8, 0xee, 0x3c, 0x74, 0x76, 0xad, 0x7e, 0xc9, 0x40, 0xb4, 0x38, 0x7a, 0xe5, 0x80, 0xf7, 0x7f, 0xb9, 0xdb, 0x1e, 0xb0, 0xc4,
0xfe, 0x13, 0x6b, 0x5a, 0x43, 0xdd, 0x51, 0xb8, 0x4b, 0xcb, 0xff, 0xe7, 0x55, 0x7f, 0x32, 0xf0, 0x12, 0xc9, 0x92, 0xd6, 0xeb, 0xfa, 0x1d, 0xaf, 0x0b, 0xa1, 0x5f, 0x2b, 0xb9, 0xbe, 0xc2, 0x22,
0x5b, 0xc1, 0x1c, 0xed, 0x0a, 0xe6, 0x68, 0x2b, 0x98, 0xe9, 0x61, 0x23, 0x98, 0xe9, 0x21, 0x61, 0x1c, 0x68, 0xe7, 0x68, 0xa0, 0xce, 0x68, 0x8d, 0x18, 0x93, 0x0b, 0x44, 0x03, 0xdb, 0x99, 0x87,
0x71, 0xd6, 0x08, 0x46, 0x9c, 0xd1, 0xb0, 0x9e, 0xa8, 0xac, 0xca, 0x0f, 0x6b, 0x33, 0xd5, 0x40, 0xce, 0xcc, 0x7f, 0x66, 0xfd, 0x70, 0xa8, 0x6f, 0x14, 0xee, 0xb6, 0xe5, 0xbf, 0xb3, 0xc1, 0x57,
0xb4, 0x98, 0xb6, 0xec, 0xfb, 0x1b, 0x54, 0x96, 0xea, 0x40, 0x58, 0x44, 0x3b, 0x79, 0xaa, 0xcd, 0x0c, 0xfc, 0x56, 0x30, 0x47, 0xbb, 0x82, 0x39, 0xda, 0x0a, 0x66, 0x7a, 0xd8, 0x08, 0x66, 0x7a,
0xc4, 0x90, 0x6b, 0x00, 0xff, 0x10, 0x7c, 0x41, 0xe4, 0x69, 0x86, 0x77, 0xe6, 0xa2, 0xc3, 0xc2, 0x48, 0x58, 0x9c, 0x35, 0x82, 0x11, 0x67, 0x44, 0xd6, 0x53, 0x95, 0x55, 0xf9, 0x61, 0x6d, 0x58,
0x64, 0xa3, 0xc7, 0xf6, 0x1a, 0x55, 0xb9, 0xc8, 0x73, 0x54, 0x56, 0x4a, 0x06, 0xe8, 0xda, 0xd9, 0x0d, 0x44, 0x8b, 0x69, 0xca, 0x7e, 0xb8, 0x46, 0x65, 0x5b, 0x1d, 0x08, 0x8b, 0x68, 0x26, 0x4f,
0x1a, 0x8d, 0x0b, 0xba, 0xc2, 0x80, 0xe8, 0x47, 0x08, 0x26, 0x29, 0xaa, 0x52, 0x54, 0xe9, 0xcb, 0xb5, 0x99, 0x98, 0xe6, 0x1a, 0xc0, 0x3f, 0x06, 0x5f, 0x50, 0xf3, 0x74, 0x87, 0x77, 0x78, 0xd1,
0xde, 0xc9, 0xc1, 0xfb, 0x66, 0xf6, 0xed, 0xb3, 0x46, 0x80, 0x74, 0xde, 0xca, 0xc6, 0xfd, 0x97, 0x61, 0x61, 0xb2, 0x54, 0xd4, 0xfc, 0x57, 0xb1, 0xbf, 0x27, 0x16, 0x45, 0x4f, 0xec, 0xe7, 0x54,
0x6c, 0x4e, 0x64, 0x2e, 0x8f, 0xa7, 0x7a, 0x9f, 0x5c, 0x61, 0x51, 0xf4, 0x31, 0x78, 0x24, 0xcf, 0xfd, 0x22, 0xcf, 0x51, 0x59, 0x89, 0x19, 0xa0, 0xcf, 0xcc, 0x6e, 0xd1, 0xb8, 0xa3, 0x2b, 0x0c,
0x4e, 0x65, 0xef, 0x55, 0xd2, 0x9e, 0xf7, 0xf4, 0x5f, 0xa4, 0xc7, 0xff, 0x04, 0x00, 0x00, 0xff, 0x88, 0x7e, 0x84, 0x60, 0x92, 0xa2, 0x2a, 0x45, 0x95, 0xbe, 0xee, 0xa9, 0x1c, 0xbc, 0x6f, 0x67,
0xff, 0x5a, 0xf0, 0x92, 0xd4, 0x34, 0x09, 0x00, 0x00, 0xdf, 0x3d, 0x6f, 0x84, 0x49, 0xeb, 0xad, 0x9c, 0xdc, 0x7f, 0xc8, 0xe9, 0x44, 0xe6, 0xf2, 0x78,
0xaa, 0xe7, 0xcc, 0x15, 0x16, 0x45, 0x9f, 0x82, 0x47, 0xb2, 0xed, 0x54, 0xf6, 0xde, 0x24, 0xf9,
0x79, 0x4f, 0xff, 0x2b, 0x7b, 0xf2, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb7, 0x59, 0x2e, 0xc0,
0xa7, 0x09, 0x00, 0x00,
} }

View File

@ -108,6 +108,7 @@ message Query {
repeated string Wheres = 5; // Wheres define the restrictions on the query repeated string Wheres = 5; // Wheres define the restrictions on the query
string Label = 6; // Label is the name of the Y-Axis string Label = 6; // Label is the name of the Y-Axis
Range Range = 7; // Range is the upper and lower bound of the Y-Axis Range Range = 7; // Range is the upper and lower bound of the Y-Axis
string Source = 8; // Source is the optional URI to the data source
} }
message Range { message Range {

View File

@ -162,6 +162,7 @@ func Test_MarshalDashboard(t *testing.T) {
Range: &chronograf.Range{ Range: &chronograf.Range{
Upper: int64(100), Upper: int64(100),
}, },
Source: "/chronograf/v1/sources/1",
}, },
}, },
Axes: map[string]chronograf.Axis{ Axes: map[string]chronograf.Axis{

View File

@ -395,6 +395,7 @@ type DashboardQuery struct {
Label string `json:"label,omitempty"` // Label is the Y-Axis label for the data Label string `json:"label,omitempty"` // Label is the Y-Axis label for the data
Range *Range `json:"range,omitempty"` // Range is the default Y-Axis range for the data Range *Range `json:"range,omitempty"` // Range is the default Y-Axis range for the data
QueryConfig QueryConfig `json:"queryConfig,omitempty"` // QueryConfig represents the query state that is understood by the data explorer QueryConfig QueryConfig `json:"queryConfig,omitempty"` // QueryConfig represents the query state that is understood by the data explorer
Source string `json:"source"` // Source is the optional URI to the data source for this queryConfig
} }
// TemplateQuery is used to retrieve choices for template replacement // TemplateQuery is used to retrieve choices for template replacement

View File

@ -217,6 +217,7 @@ func Test_newDashboardResponse(t *testing.T) {
H: 0, H: 0,
Queries: []chronograf.DashboardQuery{ Queries: []chronograf.DashboardQuery{
{ {
Source: "/chronograf/v1/sources/1",
Command: "SELECT donors from hill_valley_preservation_society where time > '1985-10-25 08:00:00'", Command: "SELECT donors from hill_valley_preservation_society where time > '1985-10-25 08:00:00'",
}, },
}, },
@ -236,6 +237,7 @@ func Test_newDashboardResponse(t *testing.T) {
H: 0, H: 0,
Queries: []chronograf.DashboardQuery{ Queries: []chronograf.DashboardQuery{
{ {
Source: "/chronograf/v1/sources/2",
Command: "SELECT winning_horses from grays_sports_alamanc where time > now() - 15m", Command: "SELECT winning_horses from grays_sports_alamanc where time > now() - 15m",
}, },
}, },
@ -256,6 +258,7 @@ func Test_newDashboardResponse(t *testing.T) {
Queries: []chronograf.DashboardQuery{ Queries: []chronograf.DashboardQuery{
{ {
Command: "SELECT donors from hill_valley_preservation_society where time > '1985-10-25 08:00:00'", Command: "SELECT donors from hill_valley_preservation_society where time > '1985-10-25 08:00:00'",
Source: "/chronograf/v1/sources/1",
QueryConfig: chronograf.QueryConfig{ QueryConfig: chronograf.QueryConfig{
RawText: &[]string{"SELECT donors from hill_valley_preservation_society where time > '1985-10-25 08:00:00'"}[0], RawText: &[]string{"SELECT donors from hill_valley_preservation_society where time > '1985-10-25 08:00:00'"}[0],
Fields: []chronograf.Field{}, Fields: []chronograf.Field{},
@ -303,6 +306,7 @@ func Test_newDashboardResponse(t *testing.T) {
Queries: []chronograf.DashboardQuery{ Queries: []chronograf.DashboardQuery{
{ {
Command: "SELECT winning_horses from grays_sports_alamanc where time > now() - 15m", Command: "SELECT winning_horses from grays_sports_alamanc where time > now() - 15m",
Source: "/chronograf/v1/sources/2",
QueryConfig: chronograf.QueryConfig{ QueryConfig: chronograf.QueryConfig{
Measurement: "grays_sports_alamanc", Measurement: "grays_sports_alamanc",
Fields: []chronograf.Field{ Fields: []chronograf.Field{

View File

@ -3766,6 +3766,11 @@
"query": { "query": {
"type": "string" "type": "string"
}, },
"source": {
"type": "string",
"format": "url",
"description": "Optional URI for data source for this query"
},
"queryConfig": { "queryConfig": {
"$ref": "#/definitions/QueryConfig" "$ref": "#/definitions/QueryConfig"
} }

View File

@ -154,7 +154,7 @@ class AdminPage extends Component {
<h1 className="page-header__title">Admin</h1> <h1 className="page-header__title">Admin</h1>
</div> </div>
<div className="page-header__right"> <div className="page-header__right">
<SourceIndicator sourceName={source.name} /> <SourceIndicator />
</div> </div>
</div> </div>
</div> </div>

View File

@ -156,7 +156,7 @@ class AlertsApp extends Component {
<h1 className="page-header__title">Alert History</h1> <h1 className="page-header__title">Alert History</h1>
</div> </div>
<div className="page-header__right"> <div className="page-header__right">
<SourceIndicator sourceName={source.name} /> <SourceIndicator />
<CustomTimeRangeDropdown <CustomTimeRangeDropdown
onApplyTimeRange={this.handleApplyTime} onApplyTimeRange={this.handleApplyTime}
timeRange={timeRange} timeRange={timeRange}

View File

@ -19,7 +19,7 @@ const AxesOptions = ({
const [min, max] = bounds const [min, max] = bounds
return ( return (
<div className="display-options--cell"> <div className="display-options--cell y-axis-controls">
<h5 className="display-options--header">Y Axis Controls</h5> <h5 className="display-options--header">Y Axis Controls</h5>
<form autoComplete="off" style={{margin: '0 -6px'}}> <form autoComplete="off" style={{margin: '0 -6px'}}>
<div className="form-group col-sm-12"> <div className="form-group col-sm-12">

View File

@ -23,10 +23,17 @@ class CellEditorOverlay extends Component {
constructor(props) { constructor(props) {
super(props) super(props)
const {cell: {name, type, queries, axes}} = props const {cell: {name, type, queries, axes}, sources} = props
let source = _.get(queries, ['0', 'source'], null)
source = sources.find(s => s.links.self === source) || props.source
const queriesWorkingDraft = _.cloneDeep( const queriesWorkingDraft = _.cloneDeep(
queries.map(({queryConfig}) => ({...queryConfig, id: uuid.v4()})) queries.map(({queryConfig}) => ({
...queryConfig,
id: uuid.v4(),
source,
}))
) )
this.state = { this.state = {
@ -141,6 +148,7 @@ class CellEditorOverlay extends Component {
return { return {
queryConfig: q, queryConfig: q,
query, query,
source: _.get(q, ['source', 'links', 'self'], null),
} }
}) })
@ -197,6 +205,15 @@ class CellEditorOverlay extends Component {
}) })
} }
handleSetQuerySource = source => {
const queriesWorkingDraft = this.state.queriesWorkingDraft.map(q => ({
..._.cloneDeep(q),
source,
}))
this.setState({queriesWorkingDraft})
}
getActiveQuery = () => { getActiveQuery = () => {
const {queriesWorkingDraft, activeQueryIndex} = this.state const {queriesWorkingDraft, activeQueryIndex} = this.state
const activeQuery = queriesWorkingDraft[activeQueryIndex] const activeQuery = queriesWorkingDraft[activeQueryIndex]
@ -221,9 +238,39 @@ class CellEditorOverlay extends Component {
} }
} }
formatSources = this.props.sources.map(s => ({
...s,
text: `${s.name} @ ${s.url}`,
}))
findSelectedSource = () => {
const {source} = this.props
const sources = this.formatSources
const query = _.get(this.state.queriesWorkingDraft, 0, false)
if (!query || !query.source) {
const defaultSource = sources.find(s => s.id === source.id)
return (defaultSource && defaultSource.text) || 'No sources'
}
const selected = sources.find(s => s.id === query.source.id)
return (selected && selected.text) || 'No sources'
}
getSource = () => {
const {source, sources} = this.props
const query = _.get(this.state.queriesWorkingDraft, 0, false)
if (!query || !query.source) {
return source
}
const querySource = sources.find(s => s.id === query.source.id)
return querySource || source
}
render() { render() {
const { const {
source,
onCancel, onCancel,
templates, templates,
timeRange, timeRange,
@ -232,12 +279,12 @@ class CellEditorOverlay extends Component {
} = this.props } = this.props
const { const {
axes,
activeQueryIndex, activeQueryIndex,
cellWorkingName, cellWorkingName,
cellWorkingType, cellWorkingType,
isDisplayOptionsTabActive, isDisplayOptionsTabActive,
queriesWorkingDraft, queriesWorkingDraft,
axes,
} = this.state } = this.state
const queryActions = { const queryActions = {
@ -271,11 +318,14 @@ class CellEditorOverlay extends Component {
/> />
<CEOBottom> <CEOBottom>
<OverlayControls <OverlayControls
onCancel={onCancel}
sources={this.formatSources}
onSave={this.handleSaveCell}
selected={this.findSelectedSource()}
onSetQuerySource={this.handleSetQuerySource}
isSavable={queriesWorkingDraft.every(isQuerySavable)}
isDisplayOptionsTabActive={isDisplayOptionsTabActive} isDisplayOptionsTabActive={isDisplayOptionsTabActive}
onClickDisplayOptions={this.handleClickDisplayOptionsTab} onClickDisplayOptions={this.handleClickDisplayOptionsTab}
onCancel={onCancel}
onSave={this.handleSaveCell}
isSavable={queriesWorkingDraft.every(isQuerySavable)}
/> />
{isDisplayOptionsTabActive {isDisplayOptionsTabActive
? <DisplayOptions ? <DisplayOptions
@ -291,7 +341,7 @@ class CellEditorOverlay extends Component {
onSetYAxisBoundMax={this.handleSetYAxisBoundMax} onSetYAxisBoundMax={this.handleSetYAxisBoundMax}
/> />
: <QueryMaker : <QueryMaker
source={source} source={this.getSource()}
templates={templates} templates={templates}
queries={queriesWorkingDraft} queries={queriesWorkingDraft}
actions={queryActions} actions={queryActions}
@ -343,6 +393,7 @@ CellEditorOverlay.propTypes = {
status: shape({}), status: shape({}),
}).isRequired, }).isRequired,
dashboardID: string.isRequired, dashboardID: string.isRequired,
sources: arrayOf(shape()),
} }
CEOBottom.propTypes = { CEOBottom.propTypes = {

View File

@ -7,6 +7,7 @@ import FancyScrollbar from 'shared/components/FancyScrollbar'
const Dashboard = ({ const Dashboard = ({
source, source,
sources,
onZoom, onZoom,
dashboard, dashboard,
onAddCell, onAddCell,
@ -24,16 +25,11 @@ const Dashboard = ({
}) => { }) => {
const cells = dashboard.cells.map(cell => { const cells = dashboard.cells.map(cell => {
const dashboardCell = {...cell} const dashboardCell = {...cell}
dashboardCell.queries = dashboardCell.queries.map( dashboardCell.queries = dashboardCell.queries.map(q => ({
({label, query, queryConfig, db}) => ({ ...q,
label, database: q.db,
query, text: q.query,
queryConfig, }))
db,
database: db,
text: query,
})
)
return dashboardCell return dashboardCell
}) })
@ -54,17 +50,18 @@ const Dashboard = ({
/>} />}
{cells.length {cells.length
? <LayoutRenderer ? <LayoutRenderer
templates={templatesIncludingDashTime}
isEditable={true}
cells={cells} cells={cells}
onZoom={onZoom}
source={source}
sources={sources}
isEditable={true}
timeRange={timeRange} timeRange={timeRange}
autoRefresh={autoRefresh} autoRefresh={autoRefresh}
source={source}
onPositionChange={onPositionChange}
onDeleteCell={onDeleteCell}
onSummonOverlayTechnologies={onSummonOverlayTechnologies}
synchronizer={synchronizer} synchronizer={synchronizer}
onZoom={onZoom} onDeleteCell={onDeleteCell}
onPositionChange={onPositionChange}
templates={templatesIncludingDashTime}
onSummonOverlayTechnologies={onSummonOverlayTechnologies}
/> />
: <div className="dashboard__empty"> : <div className="dashboard__empty">
<p>This Dashboard has no Cells</p> <p>This Dashboard has no Cells</p>
@ -112,6 +109,7 @@ Dashboard.propTypes = {
proxy: string, proxy: string,
}).isRequired, }).isRequired,
}).isRequired, }).isRequired,
sources: arrayOf(shape({})).isRequired,
autoRefresh: number.isRequired, autoRefresh: number.isRequired,
timeRange: shape({}).isRequired, timeRange: shape({}).isRequired,
onOpenTemplateManager: func.isRequired, onOpenTemplateManager: func.isRequired,

View File

@ -18,7 +18,6 @@ const DashboardHeader = ({
handleChooseTimeRange, handleChooseTimeRange,
handleChooseAutoRefresh, handleChooseAutoRefresh,
handleClickPresentationButton, handleClickPresentationButton,
source,
onAddCell, onAddCell,
onEditDashboard, onEditDashboard,
onToggleTempVarControls, onToggleTempVarControls,
@ -49,7 +48,7 @@ const DashboardHeader = ({
</div> </div>
<div className="page-header__right"> <div className="page-header__right">
<GraphTips /> <GraphTips />
<SourceIndicator sourceName={source.name} /> <SourceIndicator />
{dashboard {dashboard
? <button className="btn btn-primary btn-sm" onClick={onAddCell}> ? <button className="btn btn-primary btn-sm" onClick={onAddCell}>
<span className="icon plus" /> <span className="icon plus" />
@ -107,7 +106,6 @@ DashboardHeader.defaultProps = {
} }
DashboardHeader.propTypes = { DashboardHeader.propTypes = {
sourceID: string,
children: array, children: array,
buttonText: string, buttonText: string,
dashboard: shape({}), dashboard: shape({}),
@ -121,7 +119,6 @@ DashboardHeader.propTypes = {
handleChooseTimeRange: func.isRequired, handleChooseTimeRange: func.isRequired,
handleChooseAutoRefresh: func.isRequired, handleChooseAutoRefresh: func.isRequired,
handleClickPresentationButton: func.isRequired, handleClickPresentationButton: func.isRequired,
source: shape({}),
onAddCell: func, onAddCell: func,
onEditDashboard: func, onEditDashboard: func,
onToggleTempVarControls: func, onToggleTempVarControls: func,

View File

@ -1,26 +1,17 @@
import React, {PropTypes} from 'react' import React from 'react'
import SourceIndicator from 'shared/components/SourceIndicator' import SourceIndicator from 'shared/components/SourceIndicator'
const DashboardsHeader = ({sourceName}) => { const DashboardsHeader = () =>
return (
<div className="page-header"> <div className="page-header">
<div className="page-header__container"> <div className="page-header__container">
<div className="page-header__left"> <div className="page-header__left">
<h1 className="page-header__title">Dashboards</h1> <h1 className="page-header__title">Dashboards</h1>
</div> </div>
<div className="page-header__right"> <div className="page-header__right">
<SourceIndicator sourceName={sourceName} /> <SourceIndicator />
</div> </div>
</div> </div>
</div> </div>
)
}
const {string} = PropTypes
DashboardsHeader.propTypes = {
sourceName: string.isRequired,
}
export default DashboardsHeader export default DashboardsHeader

View File

@ -2,16 +2,24 @@ import React, {PropTypes} from 'react'
import classnames from 'classnames' import classnames from 'classnames'
import ConfirmButtons from 'shared/components/ConfirmButtons' import ConfirmButtons from 'shared/components/ConfirmButtons'
import SourceSelector from 'src/dashboards/components/SourceSelector'
const OverlayControls = ({ const OverlayControls = ({
onCancel,
onSave, onSave,
sources,
selected,
onCancel,
isSavable,
onSetQuerySource,
isDisplayOptionsTabActive, isDisplayOptionsTabActive,
onClickDisplayOptions, onClickDisplayOptions,
isSavable,
}) => }) =>
<div className="overlay-controls"> <div className="overlay-controls">
<h3 className="overlay--graph-name">Cell Editor</h3> <SourceSelector
sources={sources}
selected={selected}
onSetQuerySource={onSetQuerySource}
/>
<ul className="nav nav-tablist nav-tablist-sm"> <ul className="nav nav-tablist nav-tablist-sm">
<li <li
key="queries" key="queries"
@ -29,7 +37,7 @@ const OverlayControls = ({
})} })}
onClick={onClickDisplayOptions(true)} onClick={onClickDisplayOptions(true)}
> >
Display Options Options
</li> </li>
</ul> </ul>
<div className="overlay-controls--right"> <div className="overlay-controls--right">
@ -41,7 +49,7 @@ const OverlayControls = ({
</div> </div>
</div> </div>
const {func, bool} = PropTypes const {arrayOf, bool, func, shape, string} = PropTypes
OverlayControls.propTypes = { OverlayControls.propTypes = {
onCancel: func.isRequired, onCancel: func.isRequired,
@ -49,6 +57,9 @@ OverlayControls.propTypes = {
isDisplayOptionsTabActive: bool.isRequired, isDisplayOptionsTabActive: bool.isRequired,
onClickDisplayOptions: func.isRequired, onClickDisplayOptions: func.isRequired,
isSavable: bool, isSavable: bool,
sources: arrayOf(shape()),
onSetQuerySource: func,
selected: string,
} }
export default OverlayControls export default OverlayControls

View File

@ -13,7 +13,7 @@ const buildText = q =>
q.rawText || buildInfluxQLQuery(q.range || TEMPLATE_RANGE, q) || '' q.rawText || buildInfluxQLQuery(q.range || TEMPLATE_RANGE, q) || ''
const QueryMaker = ({ const QueryMaker = ({
source: {links}, source,
actions, actions,
queries, queries,
timeRange, timeRange,
@ -39,7 +39,7 @@ const QueryMaker = ({
query={buildText(activeQuery)} query={buildText(activeQuery)}
config={activeQuery} config={activeQuery}
onUpdate={rawTextBinder( onUpdate={rawTextBinder(
links, source.links,
activeQuery.id, activeQuery.id,
actions.editRawTextAsync actions.editRawTextAsync
)} )}
@ -49,6 +49,7 @@ const QueryMaker = ({
query={activeQuery} query={activeQuery}
actions={actions} actions={actions}
onAddQuery={onAddQuery} onAddQuery={onAddQuery}
source={source}
/> />
</div> </div>
: <EmptyQuery onAddQuery={onAddQuery} />} : <EmptyQuery onAddQuery={onAddQuery} />}

View File

@ -0,0 +1,28 @@
import React, {PropTypes} from 'react'
import Dropdown from 'shared/components/Dropdown'
const SourceSelector = ({sources = [], selected, onSetQuerySource}) =>
sources.length > 1
? <div className="source-selector">
<h3>Source:</h3>
<Dropdown
items={sources}
buttonSize="btn-sm"
menuClass="dropdown-astronaut"
useAutoComplete={true}
selected={selected}
onChoose={onSetQuerySource}
className="dropdown-240"
/>
</div>
: null
const {arrayOf, func, shape, string} = PropTypes
SourceSelector.propTypes = {
sources: arrayOf(shape()).isRequired,
onSetQuerySource: func.isRequired,
selected: string,
}
export default SourceSelector

View File

@ -190,6 +190,7 @@ class DashboardPage extends Component {
const { const {
source, source,
sources,
timeRange, timeRange,
timeRange: {lower, upper}, timeRange: {lower, upper},
showTemplateControlBar, showTemplateControlBar,
@ -277,6 +278,7 @@ class DashboardPage extends Component {
{selectedCell {selectedCell
? <CellEditorOverlay ? <CellEditorOverlay
source={source} source={source}
sources={sources}
cell={selectedCell} cell={selectedCell}
timeRange={timeRange} timeRange={timeRange}
autoRefresh={autoRefresh} autoRefresh={autoRefresh}
@ -324,6 +326,7 @@ class DashboardPage extends Component {
{dashboard {dashboard
? <Dashboard ? <Dashboard
source={source} source={source}
sources={sources}
dashboard={dashboard} dashboard={dashboard}
timeRange={timeRange} timeRange={timeRange}
autoRefresh={autoRefresh} autoRefresh={autoRefresh}
@ -354,6 +357,7 @@ DashboardPage.propTypes = {
self: string, self: string,
}), }),
}).isRequired, }).isRequired,
sources: arrayOf(shape({})).isRequired,
params: shape({ params: shape({
sourceID: string.isRequired, sourceID: string.isRequired,
dashboardID: string.isRequired, dashboardID: string.isRequired,
@ -415,6 +419,7 @@ const mapStateToProps = state => {
persisted: {autoRefresh, showTemplateControlBar}, persisted: {autoRefresh, showTemplateControlBar},
}, },
dashboardUI: {dashboards, timeRange, cellQueryStatus}, dashboardUI: {dashboards, timeRange, cellQueryStatus},
sources,
} = state } = state
return { return {
@ -424,6 +429,7 @@ const mapStateToProps = state => {
showTemplateControlBar, showTemplateControlBar,
inPresentationMode, inPresentationMode,
cellQueryStatus, cellQueryStatus,
sources,
} }
} }

View File

@ -22,12 +22,6 @@ const Header = React.createClass({
}).isRequired, }).isRequired,
}, },
contextTypes: {
source: shape({
name: string,
}),
},
handleChooseTimeRange(bounds) { handleChooseTimeRange(bounds) {
this.props.actions.setTimeRange(bounds) this.props.actions.setTimeRange(bounds)
}, },
@ -48,7 +42,7 @@ const Header = React.createClass({
</div> </div>
<div className="page-header__right"> <div className="page-header__right">
<GraphTips /> <GraphTips />
<SourceIndicator sourceName={this.context.source.name} /> <SourceIndicator />
<div <div
className="btn btn-sm btn-default" className="btn btn-sm btn-default"
onClick={showWriteForm} onClick={showWriteForm}

View File

@ -84,7 +84,7 @@ export const HostsPage = React.createClass({
<h1 className="page-header__title">Host List</h1> <h1 className="page-header__title">Host List</h1>
</div> </div>
<div className="page-header__right"> <div className="page-header__right">
<SourceIndicator sourceName={source.name} /> <SourceIndicator />
</div> </div>
</div> </div>
</div> </div>

View File

@ -99,7 +99,7 @@ const KapacitorRules = ({
) )
} }
const PageContents = ({children, source}) => const PageContents = ({children}) =>
<div className="page"> <div className="page">
<div className="page-header"> <div className="page-header">
<div className="page-header__container"> <div className="page-header__container">
@ -109,7 +109,7 @@ const PageContents = ({children, source}) =>
</h1> </h1>
</div> </div>
<div className="page-header__right"> <div className="page-header__right">
<SourceIndicator sourceName={source && source.name} /> <SourceIndicator />
</div> </div>
</div> </div>
</div> </div>
@ -139,7 +139,6 @@ KapacitorRules.propTypes = {
PageContents.propTypes = { PageContents.propTypes = {
children: node, children: node,
source: shape(),
onCloseTickscript: func, onCloseTickscript: func,
} }

View File

@ -4,14 +4,13 @@ import TimeRangeDropdown from 'shared/components/TimeRangeDropdown'
import SourceIndicator from 'shared/components/SourceIndicator' import SourceIndicator from 'shared/components/SourceIndicator'
const RuleHeaderSave = ({ const RuleHeaderSave = ({
source,
onSave, onSave,
timeRange, timeRange,
validationError, validationError,
onChooseTimeRange, onChooseTimeRange,
}) => }) =>
<div className="page-header__right"> <div className="page-header__right">
<SourceIndicator sourceName={source.name} /> <SourceIndicator />
<TimeRangeDropdown <TimeRangeDropdown
onChooseTimeRange={onChooseTimeRange} onChooseTimeRange={onChooseTimeRange}
selected={timeRange} selected={timeRange}
@ -41,7 +40,6 @@ const RuleHeaderSave = ({
const {func, shape, string} = PropTypes const {func, shape, string} = PropTypes
RuleHeaderSave.propTypes = { RuleHeaderSave.propTypes = {
source: shape({}).isRequired,
onSave: func.isRequired, onSave: func.isRequired,
validationError: string.isRequired, validationError: string.isRequired,
onChooseTimeRange: func.isRequired, onChooseTimeRange: func.isRequired,

View File

@ -11,7 +11,6 @@ const addName = list => list.map(l => ({...l, name: `${l.db}.${l.rp}`}))
const TickscriptHeader = ({ const TickscriptHeader = ({
task: {id, type, dbrps}, task: {id, type, dbrps},
task, task,
source: {name},
onSave, onSave,
onChangeType, onChangeType,
onChangeID, onChangeID,
@ -26,7 +25,7 @@ const TickscriptHeader = ({
: <TickscriptStaticID id={task.name} />} : <TickscriptStaticID id={task.name} />}
</div> </div>
<div className="page-header__right"> <div className="page-header__right">
<SourceIndicator sourceName={name} /> <SourceIndicator />
<TickscriptType type={type} onChangeType={onChangeType} /> <TickscriptType type={type} onChangeType={onChangeType} />
<MultiSelectDBDropdown <MultiSelectDBDropdown
selectedItems={addName(dbrps)} selectedItems={addName(dbrps)}
@ -48,7 +47,6 @@ const {arrayOf, bool, func, shape, string} = PropTypes
TickscriptHeader.propTypes = { TickscriptHeader.propTypes = {
onSave: func, onSave: func,
source: shape(),
onSelectDbrps: func.isRequired, onSelectDbrps: func.isRequired,
task: shape({ task: shape({
dbrps: arrayOf( dbrps: arrayOf(

View File

@ -221,8 +221,8 @@ const AutoRefresh = ComposedComponent => {
return true return true
} }
return data.every(datum => { return data.every(({response}) => {
return datum.response.results.every(result => { return _.get(response, 'results', []).every(result => {
return ( return (
Object.keys(result).filter(k => k !== 'statement_id').length === 0 Object.keys(result).filter(k => k !== 'statement_id').length === 0
) )

View File

@ -15,7 +15,7 @@ const CustomTimeIndicator = ({queries}) => {
: customLower : customLower
return ( return (
<span className="dash-graph--custom-time"> <span className="custom-indicator">
{customTimeRange} {customTimeRange}
</span> </span>
) )

View File

@ -14,6 +14,17 @@ const DatabaseList = React.createClass({
propTypes: { propTypes: {
query: shape({}).isRequired, query: shape({}).isRequired,
onChooseNamespace: func.isRequired, onChooseNamespace: func.isRequired,
querySource: shape({
links: shape({
proxy: string.isRequired,
}).isRequired,
}),
},
getDefaultProps() {
return {
source: null,
}
}, },
contextTypes: { contextTypes: {
@ -31,8 +42,23 @@ const DatabaseList = React.createClass({
}, },
componentDidMount() { componentDidMount() {
this.getDbRp()
},
componentDidUpdate(prevProps) {
if (_.isEqual(prevProps.querySource, this.props.querySource)) {
return
}
this.getDbRp()
},
getDbRp() {
const {source} = this.context const {source} = this.context
const proxy = source.links.proxy const {querySource} = this.props
const proxy =
_.get(querySource, ['links', 'proxy'], null) || source.links.proxy
showDatabases(proxy).then(resp => { showDatabases(proxy).then(resp => {
const {errors, databases} = showDatabasesParser(resp.data) const {errors, databases} = showDatabasesParser(resp.data)
if (errors.length) { if (errors.length) {

View File

@ -206,6 +206,7 @@ class Dropdown extends Component {
iconName, iconName,
buttonSize, buttonSize,
buttonColor, buttonColor,
toggleStyle,
useAutoComplete, useAutoComplete,
} = this.props } = this.props
const {isOpen, searchTerm, filteredItems} = this.state const {isOpen, searchTerm, filteredItems} = this.state
@ -222,6 +223,7 @@ class Dropdown extends Component {
{useAutoComplete && isOpen {useAutoComplete && isOpen
? <div ? <div
className={`dropdown-autocomplete dropdown-toggle ${buttonSize} ${buttonColor}`} className={`dropdown-autocomplete dropdown-toggle ${buttonSize} ${buttonColor}`}
style={toggleStyle}
> >
<input <input
ref="dropdownAutoComplete" ref="dropdownAutoComplete"
@ -236,7 +238,10 @@ class Dropdown extends Component {
/> />
<span className="caret" /> <span className="caret" />
</div> </div>
: <div className={`btn dropdown-toggle ${buttonSize} ${buttonColor}`}> : <div
className={`btn dropdown-toggle ${buttonSize} ${buttonColor}`}
style={toggleStyle}
>
{iconName {iconName
? <span className={classnames('icon', {[iconName]: true})} /> ? <span className={classnames('icon', {[iconName]: true})} />
: null} : null}
@ -291,6 +296,7 @@ Dropdown.propTypes = {
menuLabel: string, menuLabel: string,
menuClass: string, menuClass: string,
useAutoComplete: bool, useAutoComplete: bool,
toggleStyle: shape(),
} }
export default OnClickOutside(Dropdown) export default OnClickOutside(Dropdown)

View File

@ -1,4 +1,5 @@
import React, {PropTypes, Component} from 'react' import React, {PropTypes, Component} from 'react'
import _ from 'lodash'
import FieldListItem from 'src/data_explorer/components/FieldListItem' import FieldListItem from 'src/data_explorer/components/FieldListItem'
import GroupByTimeDropdown from 'src/data_explorer/components/GroupByTimeDropdown' import GroupByTimeDropdown from 'src/data_explorer/components/GroupByTimeDropdown'
@ -26,7 +27,8 @@ class FieldList extends Component {
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const {database, measurement, retentionPolicy} = this.props.query const {querySource, query} = this.props
const {database, measurement, retentionPolicy} = query
const { const {
database: prevDB, database: prevDB,
measurement: prevMeas, measurement: prevMeas,
@ -39,7 +41,8 @@ class FieldList extends Component {
if ( if (
database === prevDB && database === prevDB &&
measurement === prevMeas && measurement === prevMeas &&
retentionPolicy === prevRP retentionPolicy === prevRP &&
_.isEqual(prevProps.querySource, querySource)
) { ) {
return return
} }
@ -58,14 +61,12 @@ class FieldList extends Component {
_getFields = () => { _getFields = () => {
const {database, measurement, retentionPolicy} = this.props.query const {database, measurement, retentionPolicy} = this.props.query
const {source} = this.context const {source} = this.context
const proxySource = source.links.proxy const {querySource} = this.props
showFieldKeys( const proxy =
proxySource, _.get(querySource, ['links', 'proxy'], null) || source.links.proxy
database,
measurement, showFieldKeys(proxy, database, measurement, retentionPolicy).then(resp => {
retentionPolicy
).then(resp => {
const {errors, fieldSets} = showFieldKeysParser(resp.data) const {errors, fieldSets} = showFieldKeysParser(resp.data)
if (errors.length) { if (errors.length) {
console.error('Error parsing fields keys: ', errors) console.error('Error parsing fields keys: ', errors)
@ -171,6 +172,11 @@ FieldList.propTypes = {
applyFuncsToField: func.isRequired, applyFuncsToField: func.isRequired,
isKapacitorRule: bool, isKapacitorRule: bool,
isInDataExplorer: bool, isInDataExplorer: bool,
querySource: shape({
links: shape({
proxy: string.isRequired,
}).isRequired,
}),
} }
export default FieldList export default FieldList

View File

@ -4,7 +4,7 @@ import ReactTooltip from 'react-tooltip'
const GraphTips = React.createClass({ const GraphTips = React.createClass({
render() { render() {
const graphTipsText = const graphTipsText =
'<p><b>Graph Tips:</b><br/><br/><code>Click + Drag</code> Zoom in (X or Y)</p><p><code>Shift + Click</code> Pan Graph Window</p><p><code>Double Click</code> Reset Graph Window</p>' '<h1>Graph Tips:</h1><p><code>Click + Drag</code> Zoom in (X or Y)</p><p><code>Shift + Click</code> Pan Graph Window</p><p><code>Double Click</code> Reset Graph Window</p>'
return ( return (
<div <div
className="graph-tips" className="graph-tips"

View File

@ -4,11 +4,24 @@ import LayoutCell from 'shared/components/LayoutCell'
import RefreshingGraph from 'shared/components/RefreshingGraph' import RefreshingGraph from 'shared/components/RefreshingGraph'
import {buildQueriesForLayouts} from 'utils/influxql' import {buildQueriesForLayouts} from 'utils/influxql'
const Layout = ({ import _ from 'lodash'
const getSource = (cell, source, sources, defaultSource) => {
const s = _.get(cell, ['queries', '0', 'source'], null)
if (!s) {
return source
}
return sources.find(src => src.links.self === s) || defaultSource
}
const Layout = (
{
host, host,
cell, cell,
cell: {h, axes, type}, cell: {h, axes, type},
source, source,
sources,
onZoom, onZoom,
templates, templates,
timeRange, timeRange,
@ -20,14 +33,16 @@ const Layout = ({
resizeCoords, resizeCoords,
onCancelEditCell, onCancelEditCell,
onSummonOverlayTechnologies, onSummonOverlayTechnologies,
}) => },
{source: defaultSource}
) =>
<LayoutCell <LayoutCell
onCancelEditCell={onCancelEditCell} cell={cell}
isEditable={isEditable} isEditable={isEditable}
onEditCell={onEditCell} onEditCell={onEditCell}
onDeleteCell={onDeleteCell} onDeleteCell={onDeleteCell}
onCancelEditCell={onCancelEditCell}
onSummonOverlayTechnologies={onSummonOverlayTechnologies} onSummonOverlayTechnologies={onSummonOverlayTechnologies}
cell={cell}
> >
{cell.isWidget {cell.isWidget
? <WidgetCell cell={cell} timeRange={timeRange} source={source} /> ? <WidgetCell cell={cell} timeRange={timeRange} source={source} />
@ -36,17 +51,27 @@ const Layout = ({
type={type} type={type}
cellHeight={h} cellHeight={h}
onZoom={onZoom} onZoom={onZoom}
sources={sources}
timeRange={timeRange} timeRange={timeRange}
templates={templates} templates={templates}
autoRefresh={autoRefresh} autoRefresh={autoRefresh}
synchronizer={synchronizer} synchronizer={synchronizer}
resizeCoords={resizeCoords} resizeCoords={resizeCoords}
queries={buildQueriesForLayouts(cell, source, timeRange, host)} queries={buildQueriesForLayouts(
cell,
getSource(cell, source, sources, defaultSource),
timeRange,
host
)}
/>} />}
</LayoutCell> </LayoutCell>
const {arrayOf, bool, func, number, shape, string} = PropTypes const {arrayOf, bool, func, number, shape, string} = PropTypes
Layout.contextTypes = {
source: shape(),
}
Layout.propTypes = { Layout.propTypes = {
autoRefresh: number.isRequired, autoRefresh: number.isRequired,
timeRange: shape({ timeRange: shape({
@ -87,6 +112,7 @@ Layout.propTypes = {
onCancelEditCell: func, onCancelEditCell: func,
resizeCoords: shape(), resizeCoords: shape(),
onZoom: func, onZoom: func,
sources: arrayOf(shape()),
} }
export default Layout export default Layout

View File

@ -40,16 +40,16 @@ class LayoutCell extends Component {
<div className="dash-graph"> <div className="dash-graph">
<LayoutCellMenu <LayoutCellMenu
cell={cell} cell={cell}
onDeleteClick={this.handleDeleteClick}
onDelete={this.handleDeleteCell}
isDeleting={isDeleting} isDeleting={isDeleting}
isEditable={isEditable} isEditable={isEditable}
handleClickOutside={this.closeMenu} onDelete={this.handleDeleteCell}
onEdit={this.handleSummonOverlay} onEdit={this.handleSummonOverlay}
handleClickOutside={this.closeMenu}
onDeleteClick={this.handleDeleteClick}
/> />
<LayoutCellHeader <LayoutCellHeader
cellName={cell.name}
queries={queries} queries={queries}
cellName={cell.name}
isEditable={isEditable} isEditable={isEditable}
/> />
<div className="dash-graph--container"> <div className="dash-graph--container">
@ -69,7 +69,7 @@ class LayoutCell extends Component {
} }
} }
const {array, bool, func, node, number, shape, string} = PropTypes const {arrayOf, bool, func, node, number, shape, string} = PropTypes
LayoutCell.propTypes = { LayoutCell.propTypes = {
cell: shape({ cell: shape({
@ -77,7 +77,7 @@ LayoutCell.propTypes = {
isEditing: bool, isEditing: bool,
x: number.isRequired, x: number.isRequired,
y: number.isRequired, y: number.isRequired,
queries: array, queries: arrayOf(shape()),
}).isRequired, }).isRequired,
children: node.isRequired, children: node.isRequired,
onDeleteCell: func, onDeleteCell: func,

View File

@ -1,5 +1,4 @@
import React, {PropTypes} from 'react' import React, {PropTypes} from 'react'
import classnames from 'classnames'
import CustomTimeIndicator from 'shared/components/CustomTimeIndicator' import CustomTimeIndicator from 'shared/components/CustomTimeIndicator'
@ -8,12 +7,12 @@ import {NEW_DEFAULT_DASHBOARD_CELL} from 'src/dashboards/constants/index'
const LayoutCellHeader = ({queries, isEditable, cellName}) => { const LayoutCellHeader = ({queries, isEditable, cellName}) => {
const cellNameIsDefault = cellName === NEW_DEFAULT_DASHBOARD_CELL.name const cellNameIsDefault = cellName === NEW_DEFAULT_DASHBOARD_CELL.name
const headingClass = `dash-graph--heading ${isEditable
? 'dash-graph--heading-draggable'
: ''}`
return ( return (
<div <div className={headingClass}>
className={classnames('dash-graph--heading', {
'dash-graph--heading-draggable': isEditable,
})}
>
<span <span
className={ className={
cellNameIsDefault cellNameIsDefault
@ -22,18 +21,20 @@ const LayoutCellHeader = ({queries, isEditable, cellName}) => {
} }
> >
{cellName} {cellName}
<div className="dash-graph--custom-indicators">
{queries && queries.length {queries && queries.length
? <CustomTimeIndicator queries={queries} /> ? <CustomTimeIndicator queries={queries} />
: null} : null}
</div>
</span> </span>
</div> </div>
) )
} }
const {array, bool, string} = PropTypes const {arrayOf, bool, shape, string} = PropTypes
LayoutCellHeader.propTypes = { LayoutCellHeader.propTypes = {
queries: array, queries: arrayOf(shape()),
isEditable: bool, isEditable: bool,
cellName: string, cellName: string,
} }

View File

@ -1,6 +1,7 @@
import React, {Component, PropTypes} from 'react' import React, {Component, PropTypes} from 'react'
import ReactGridLayout, {WidthProvider} from 'react-grid-layout' import ReactGridLayout, {WidthProvider} from 'react-grid-layout'
import Resizeable from 'react-component-resizable' import Resizeable from 'react-component-resizable'
import _ from 'lodash' import _ from 'lodash'
import Layout from 'src/shared/components/Layout' import Layout from 'src/shared/components/Layout'
@ -26,11 +27,6 @@ class LayoutRenderer extends Component {
} }
} }
// idea adopted from https://stackoverflow.com/questions/36862334/get-viewport-window-height-in-reactjs
updateWindowDimensions = () => {
this.setState({rowHeight: this.calculateRowHeight()})
}
handleLayoutChange = layout => { handleLayoutChange = layout => {
if (!this.props.onPositionChange) { if (!this.props.onPositionChange) {
return return
@ -72,6 +68,7 @@ class LayoutRenderer extends Component {
host, host,
cells, cells,
source, source,
sources,
onZoom, onZoom,
templates, templates,
timeRange, timeRange,
@ -88,7 +85,7 @@ class LayoutRenderer extends Component {
const isDashboard = !!this.props.onPositionChange const isDashboard = !!this.props.onPositionChange
return ( return (
<Resizeable onResize={this.updateWindowDimensions}> <Resizeable onResize={this.handleCellResize}>
<GridLayout <GridLayout
layout={cells} layout={cells}
cols={12} cols={12}
@ -110,6 +107,7 @@ class LayoutRenderer extends Component {
host={host} host={host}
source={source} source={source}
onZoom={onZoom} onZoom={onZoom}
sources={sources}
templates={templates} templates={templates}
timeRange={timeRange} timeRange={timeRange}
isEditable={isEditable} isEditable={isEditable}
@ -172,6 +170,7 @@ LayoutRenderer.propTypes = {
isEditable: bool, isEditable: bool,
onCancelEditCell: func, onCancelEditCell: func,
onZoom: func, onZoom: func,
sources: arrayOf(shape({})),
} }
export default LayoutRenderer export default LayoutRenderer

View File

@ -1,5 +1,6 @@
import React, {PropTypes} from 'react' import React, {PropTypes} from 'react'
import classnames from 'classnames' import classnames from 'classnames'
import _ from 'lodash'
import {showMeasurements} from 'shared/apis/metaQuery' import {showMeasurements} from 'shared/apis/metaQuery'
import showMeasurementsParser from 'shared/parsing/showMeasurements' import showMeasurementsParser from 'shared/parsing/showMeasurements'
@ -19,6 +20,11 @@ const MeasurementList = React.createClass({
onChooseTag: func.isRequired, onChooseTag: func.isRequired,
onToggleTagAcceptance: func.isRequired, onToggleTagAcceptance: func.isRequired,
onGroupByTag: func.isRequired, onGroupByTag: func.isRequired,
querySource: shape({
links: shape({
proxy: string.isRequired,
}).isRequired,
}),
}, },
contextTypes: { contextTypes: {
@ -36,6 +42,12 @@ const MeasurementList = React.createClass({
} }
}, },
getDefaultProps() {
return {
querySource: null,
}
},
componentDidMount() { componentDidMount() {
if (!this.props.query.database) { if (!this.props.query.database) {
return return
@ -45,13 +57,16 @@ const MeasurementList = React.createClass({
}, },
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const {query} = this.props const {query, querySource} = this.props
if (!query.database) { if (!query.database) {
return return
} }
if (prevProps.query.database === query.database) { if (
prevProps.query.database === query.database &&
_.isEqual(prevProps.querySource, querySource)
) {
return return
} }
@ -181,7 +196,11 @@ const MeasurementList = React.createClass({
_getMeasurements() { _getMeasurements() {
const {source} = this.context const {source} = this.context
const proxy = source.links.proxy const {querySource} = this.props
const proxy =
_.get(querySource, ['links', 'proxy'], null) || source.links.proxy
showMeasurements(proxy, this.props.query.database).then(resp => { showMeasurements(proxy, this.props.query.database).then(resp => {
const {errors, measurementSets} = showMeasurementsParser(resp.data) const {errors, measurementSets} = showMeasurementsParser(resp.data)
if (errors.length) { if (errors.length) {

View File

@ -9,6 +9,7 @@ const actionBinder = (id, action) => item => action(id, item)
const SchemaExplorer = ({ const SchemaExplorer = ({
query, query,
query: {id}, query: {id},
source,
actions: { actions: {
chooseTag, chooseTag,
groupByTag, groupByTag,
@ -24,17 +25,22 @@ const SchemaExplorer = ({
<div className="query-builder"> <div className="query-builder">
<DatabaseList <DatabaseList
query={query} query={query}
querySource={source}
onChooseNamespace={actionBinder(id, chooseNamespace)} onChooseNamespace={actionBinder(id, chooseNamespace)}
/> />
<MeasurementList <MeasurementList
source={source}
query={query} query={query}
querySource={source}
onChooseTag={actionBinder(id, chooseTag)} onChooseTag={actionBinder(id, chooseTag)}
onGroupByTag={actionBinder(id, groupByTag)} onGroupByTag={actionBinder(id, groupByTag)}
onChooseMeasurement={actionBinder(id, chooseMeasurement)} onChooseMeasurement={actionBinder(id, chooseMeasurement)}
onToggleTagAcceptance={actionBinder(id, toggleTagAcceptance)} onToggleTagAcceptance={actionBinder(id, toggleTagAcceptance)}
/> />
<FieldList <FieldList
source={source}
query={query} query={query}
querySource={source}
onToggleField={actionBinder(id, toggleFieldWithGroupByInterval)} onToggleField={actionBinder(id, toggleFieldWithGroupByInterval)}
onFill={actionBinder(id, fill)} onFill={actionBinder(id, fill)}
onGroupByTime={actionBinder(id, groupByTime)} onGroupByTime={actionBinder(id, groupByTime)}
@ -60,6 +66,7 @@ SchemaExplorer.propTypes = {
fill: func.isRequired, fill: func.isRequired,
editRawTextAsync: func.isRequired, editRawTextAsync: func.isRequired,
}).isRequired, }).isRequired,
source: shape({}),
} }
export default SchemaExplorer export default SchemaExplorer

View File

@ -1,26 +1,32 @@
import React, {PropTypes} from 'react' import React, {PropTypes} from 'react'
import _ from 'lodash'
import uuid from 'node-uuid'
import ReactTooltip from 'react-tooltip' import ReactTooltip from 'react-tooltip'
const SourceIndicator = React.createClass({ const SourceIndicator = ({sourceOverride}, {source: {name, url}}) => {
propTypes: { const sourceName = _.get(sourceOverride, 'name', null)
sourceName: PropTypes.string, ? sourceOverride.name
}, : name
const sourceUrl = _.get(sourceOverride, 'url', null)
? sourceOverride.url
: url
render() {
const {sourceName} = this.props
if (!sourceName) { if (!sourceName) {
return null return null
} }
const sourceNameTooltip = `Connected to <code>${sourceName}</code>` const sourceNameTooltip = `<h1>Connected to Source:</h1><p><code>${sourceName} @ ${sourceUrl}</code></p>`
const uuidTooltip = uuid.v4()
return ( return (
<div <div
className="source-indicator" className="source-indicator"
data-for="source-indicator-tooltip" data-for={uuidTooltip}
data-tip={sourceNameTooltip} data-tip={sourceNameTooltip}
> >
<span className="icon server2" /> <span className="icon server2" />
<ReactTooltip <ReactTooltip
id="source-indicator-tooltip" id={uuidTooltip}
effect="solid" effect="solid"
html={true} html={true}
offset={{top: 2}} offset={{top: 2}}
@ -29,7 +35,22 @@ const SourceIndicator = React.createClass({
/> />
</div> </div>
) )
}, }
})
const {shape, string} = PropTypes
SourceIndicator.propTypes = {
sourceOverride: shape({
name: string,
url: string,
}),
}
SourceIndicator.contextTypes = {
source: shape({
name: string,
url: string,
}),
}
export default SourceIndicator export default SourceIndicator

View File

@ -62,7 +62,7 @@ class ManageSources extends Component {
<h1 className="page-header__title">Configuration</h1> <h1 className="page-header__title">Configuration</h1>
</div> </div>
<div className="page-header__right"> <div className="page-header__right">
<SourceIndicator sourceName={source.name} /> <SourceIndicator />
</div> </div>
</div> </div>
</div> </div>

View File

@ -125,7 +125,7 @@ export const SourcePage = React.createClass({
</h1> </h1>
</div> </div>
<div className="page-header__right"> <div className="page-header__right">
<SourceIndicator sourceName={source.name} /> <SourceIndicator />
</div> </div>
</div> </div>
</div> </div>

View File

@ -56,7 +56,7 @@ class StatusPage extends Component {
<h1 className="page-header__title">Status</h1> <h1 className="page-header__title">Status</h1>
</div> </div>
<div className="page-header__right"> <div className="page-header__right">
<SourceIndicator sourceName={source.name} /> <SourceIndicator />
</div> </div>
</div> </div>
</div> </div>

View File

@ -50,6 +50,7 @@
@import 'components/resizer'; @import 'components/resizer';
@import 'components/search-widget'; @import 'components/search-widget';
@import 'components/source-indicator'; @import 'components/source-indicator';
@import 'components/source-selector';
@import 'components/tables'; @import 'components/tables';
// Pages // Pages

View File

@ -17,8 +17,6 @@
padding: 30px; padding: 30px;
flex: 1 0 0; flex: 1 0 0;
margin-right: 8px; margin-right: 8px;
&:last-child { margin-right: 0; }
} }
.display-options--cellx2 { .display-options--cellx2 {
flex: 2 0 0; flex: 2 0 0;
@ -29,8 +27,6 @@
color: $g11-sidewalk; color: $g11-sidewalk;
@include no-user-select(); @include no-user-select();
} }
.viz-type-selector { .viz-type-selector {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

View File

@ -33,6 +33,15 @@ $tooltip-code-color: $c-potassium;
text-transform: none !important; text-transform: none !important;
cursor: default; cursor: default;
h1 {
font-size: 14px;
font-weight: 600;
margin: 0 0 6px 0;
line-height: 1.125em;
letter-spacing: 0;
font-family: $default-font;
}
p { p {
margin: 0; margin: 0;
width: 100%; width: 100%;
@ -70,7 +79,6 @@ $tooltip-code-color: $c-potassium;
border-color: transparent transparent $tooltip-accent transparent !important; border-color: transparent transparent $tooltip-accent transparent !important;
} }
/* Kapacitor Style Tooltip */ /* Kapacitor Style Tooltip */
&.kapacitor-tooltip { &.kapacitor-tooltip {
border-color: $c-rainforest !important; border-color: $c-rainforest !important;
@ -90,14 +98,13 @@ $tooltip-code-color: $c-potassium;
.influx-tooltip__hover { .influx-tooltip__hover {
@extend .influx-tooltip; @extend .influx-tooltip;
pointer-events: auto!important; pointer-events: auto !important;
&:hover { &:hover {
visibility: visible!important; visibility: visible !important;
opacity: 1!important; opacity: 1 !important;
} }
} }
/* /*
Question Mark Tooltip Question Mark Tooltip
---------------------------------------------- ----------------------------------------------
@ -121,8 +128,7 @@ $qmark-tooltip-size: 15px;
background-color: $g10-wolf; background-color: $g10-wolf;
color: $g0-obsidian; color: $g0-obsidian;
margin: 0 5px; margin: 0 5px;
transition: transition: background-color 0.25s ease;
background-color 0.25s ease;
} }
.question-mark-tooltip:hover { .question-mark-tooltip:hover {
cursor: default; cursor: default;

View File

@ -0,0 +1,18 @@
/*
Source Selector component styles
----------------------------------------------------------------
*/
.source-selector {
display: flex;
align-items: center;
flex-wrap: nowrap;
flex: 1 0 0;
h3 {
margin: 0 4px 0 0;
font-size: 17px;
color: $g13-mist;
@include no-user-select();
}
}

View File

@ -11,22 +11,49 @@ $dash-graph-options-arrow: 8px;
------------------------------------------------------ ------------------------------------------------------
*/ */
@keyframes refreshingSpinnerA { @keyframes refreshingSpinnerA {
0% { transform: translate(-50%,-50%) scale(1.75); background-color: $g7-graphite; } 0% {
33% { transform: translate(-50%,-50%) scale(1,1); } transform: translate(-50%, -50%) scale(1.75);
66% { transform: translate(-50%,-50%) scale(1,1); } background-color: $g7-graphite;
100% { transform: translate(-50%,-50%) scale(1,1); } }
33% {
transform: translate(-50%, -50%) scale(1, 1);
}
66% {
transform: translate(-50%, -50%) scale(1, 1);
}
100% {
transform: translate(-50%, -50%) scale(1, 1);
}
} }
@keyframes refreshingSpinnerB { @keyframes refreshingSpinnerB {
0% { transform: translate(-50%,-50%) scale(1,1); } 0% {
33% { transform: translate(-50%,-50%) scale(1.75); background-color: $g7-graphite; } transform: translate(-50%, -50%) scale(1, 1);
66% { transform: translate(-50%,-50%) scale(1,1); } }
100% { transform: translate(-50%,-50%) scale(1,1); } 33% {
transform: translate(-50%, -50%) scale(1.75);
background-color: $g7-graphite;
}
66% {
transform: translate(-50%, -50%) scale(1, 1);
}
100% {
transform: translate(-50%, -50%) scale(1, 1);
}
} }
@keyframes refreshingSpinnerC { @keyframes refreshingSpinnerC {
0% { transform: translate(-50%,-50%) scale(1,1); } 0% {
33% { transform: translate(-50%,-50%) scale(1,1); } transform: translate(-50%, -50%) scale(1, 1);
66% { transform: translate(-50%,-50%) scale(1.75); background-color: $g7-graphite; } }
100% { transform: translate(-50%,-50%) scale(1,1); } 33% {
transform: translate(-50%, -50%) scale(1, 1);
}
66% {
transform: translate(-50%, -50%) scale(1.75);
background-color: $g7-graphite;
}
100% {
transform: translate(-50%, -50%) scale(1, 1);
}
} }
/* /*
@ -93,7 +120,6 @@ $dash-graph-options-arrow: 8px;
top: 0; top: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.dygraph .dygraph-child { .dygraph .dygraph-child {
position: absolute; position: absolute;
@ -124,9 +150,7 @@ $dash-graph-options-arrow: 8px;
font-weight: 600; font-weight: 600;
font-size: 13px; font-size: 13px;
color: $g14-chromium; color: $g14-chromium;
transition: transition: color 0.25s ease, background-color 0.25s ease;
color 0.25s ease,
background-color 0.25s ease;
&.dash-graph--heading-draggable:hover { &.dash-graph--heading-draggable:hover {
cursor: move; cursor: move;
background-color: $g5-pepper; background-color: $g5-pepper;
@ -146,30 +170,41 @@ $dash-graph-options-arrow: 8px;
line-height: $dash-graph-heading; line-height: $dash-graph-heading;
width: calc(100% - 53px); width: calc(100% - 53px);
padding-left: 10px; padding-left: 10px;
transition: transition: color 0.25s ease, background-color 0.25s ease,
color 0.25s ease,
background-color 0.25s ease,
border-color 0.25s ease; border-color 0.25s ease;
} }
.dash-graph--name.dash-graph--name__default { .dash-graph--name.dash-graph--name__default {
font-style: italic; font-style: italic;
} }
.dash-graph--custom-indicators {
height: 24px;
border-radius: 3px;
position: absolute;
top: 3px;
right: 0;
display: flex;
.dash-graph--custom-time { > .custom-indicator,
> .source-indicator {
font-size: 10px;
line-height: 24px;
padding: 0 7px;
font-style: normal; font-style: normal;
font-family: $code-font; font-family: $code-font;
color: $c-pool; color: $c-pool;
background-color: $g2-kevlar; background-color: $g2-kevlar;
height: 24px; margin-right: 2px;
font-size: 10px;
line-height: 24px;
border-radius: 3px; border-radius: 3px;
padding: 0 7px; }
position: absolute; > .source-indicator {
top: 3px; height: 24px;
right: 2px;
}
> .icon {
font-size: 12px;
margin: 0;
}
}
}
.dash-graph-context { .dash-graph-context {
z-index: 2; z-index: 2;
position: absolute; position: absolute;
@ -187,9 +222,7 @@ $dash-graph-options-arrow: 8px;
font-size: 12px; font-size: 12px;
position: relative; position: relative;
color: $g11-sidewalk; color: $g11-sidewalk;
transition: transition: color 0.25s ease, background-color 0.25s ease;
color 0.25s ease,
background-color 0.25s ease;
&:hover, &:hover,
&.active { &.active {
@ -197,13 +230,15 @@ $dash-graph-options-arrow: 8px;
color: $g20-white; color: $g20-white;
background-color: $g5-pepper; background-color: $g5-pepper;
} }
&:first-child {margin-right: 2px;} &:first-child {
margin-right: 2px;
}
> .icon { > .icon {
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%,-50%); transform: translate(-50%, -50%);
} }
} }
.dash-graph-context--confirm { .dash-graph-context--confirm {
@ -230,7 +265,7 @@ $dash-graph-options-arrow: 8px;
border-bottom-color: $c-curacao; border-bottom-color: $c-curacao;
left: 50%; left: 50%;
top: 0; top: 0;
transform: translate(-50%,-100%); transform: translate(-50%, -100%);
transition: border-color 0.25s ease; transition: border-color 0.25s ease;
} }
@ -241,7 +276,6 @@ $dash-graph-options-arrow: 8px;
&:hover:before { &:hover:before {
border-bottom-color: $c-dreamsicle; border-bottom-color: $c-dreamsicle;
} }
} }
/* Presentation Mode */ /* Presentation Mode */
@ -257,7 +291,7 @@ $dash-graph-options-arrow: 8px;
.graph-panel__refreshing { .graph-panel__refreshing {
position: absolute; position: absolute;
top: -18px !important; top: -18px !important;
transform: translate(0,0); transform: translate(0, 0);
right: 50%; right: 50%;
transform: translateX(50%); transform: translateX(50%);
width: 16px; width: 16px;
@ -270,12 +304,24 @@ $dash-graph-options-arrow: 8px;
border-radius: 50%; border-radius: 50%;
position: absolute; position: absolute;
top: 50%; top: 50%;
transform: translate(-50%,-50%); transform: translate(-50%, -50%);
} }
div:nth-child(1) {left: 0; animation: refreshingSpinnerA 0.8s cubic-bezier(0.645, 0.045, 0.355, 1) infinite; } div:nth-child(1) {
div:nth-child(2) {left: 50%; animation: refreshingSpinnerB 0.8s cubic-bezier(0.645, 0.045, 0.355, 1) infinite; } left: 0;
div:nth-child(3) {left: 100%; animation: refreshingSpinnerC 0.8s cubic-bezier(0.645, 0.045, 0.355, 1) infinite;} animation: refreshingSpinnerA 0.8s cubic-bezier(0.645, 0.045, 0.355, 1)
infinite;
}
div:nth-child(2) {
left: 50%;
animation: refreshingSpinnerB 0.8s cubic-bezier(0.645, 0.045, 0.355, 1)
infinite;
}
div:nth-child(3) {
left: 100%;
animation: refreshingSpinnerC 0.8s cubic-bezier(0.645, 0.045, 0.355, 1)
infinite;
}
} }
/* /*
@ -291,7 +337,7 @@ $dash-graph-options-arrow: 8px;
} }
.react-grid-item { .react-grid-item {
&.resizing { &.resizing {
background-color: fade-out($g3-castle,0.09); background-color: fade-out($g3-castle, 0.09);
border-color: $c-pool; border-color: $c-pool;
border-image-slice: 3%; border-image-slice: 3%;
border-image-repeat: initial; border-image-repeat: initial;
@ -301,13 +347,14 @@ $dash-graph-options-arrow: 8px;
z-index: 3; z-index: 3;
& > .react-resizable-handle { & > .react-resizable-handle {
&:before, &:after { &:before,
&:after {
background-color: $c-comet; background-color: $c-comet;
} }
} }
} }
&.react-draggable-dragging { &.react-draggable-dragging {
background-color: fade-out($g3-castle,0.09); background-color: fade-out($g3-castle, 0.09);
border-color: $c-pool; border-color: $c-pool;
border-image-slice: 3%; border-image-slice: 3%;
border-image-repeat: initial; border-image-repeat: initial;
@ -341,14 +388,15 @@ $dash-graph-options-arrow: 8px;
} }
&:before { &:before {
width: 20px; width: 20px;
transform: translate(-50%,-50%) rotate(-45deg); transform: translate(-50%, -50%) rotate(-45deg);
} }
&:after { &:after {
width: 12px; width: 12px;
transform: translate(-3px,2px) rotate(-45deg); transform: translate(-3px, 2px) rotate(-45deg);
} }
&:hover { &:hover {
&:before, &:after { &:before,
&:after {
background-color: $c-comet; background-color: $c-comet;
} }
} }

View File

@ -44,10 +44,21 @@ $overlay-z: 100;
border: 0; border: 0;
background-color: $g2-kevlar; background-color: $g2-kevlar;
} }
.overlay-controls .nav-tablist {
width: 200px;
li {
white-space: nowrap;
justify-content: center;
flex: 1 0 50%;
}
}
.overlay-controls--right { .overlay-controls--right {
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: nowrap; flex-wrap: nowrap;
justify-content: flex-end;
flex: 1 0 0;
.toggle { .toggle {
margin: 0 0 0 5px; margin: 0 0 0 5px;

View File

@ -8,7 +8,17 @@ const buildQueries = (proxy, queryConfigs, timeRange) => {
}) })
const queries = statements.filter(s => s.text !== null).map(s => { const queries = statements.filter(s => s.text !== null).map(s => {
return {host: [proxy], text: s.text, id: s.id, queryConfig: s.queryConfig} let queryProxy = ''
if (s.queryConfig.source) {
queryProxy = `${s.queryConfig.source.links.proxy}`
}
return {
host: [queryProxy || proxy],
text: s.text,
id: s.id,
queryConfig: s.queryConfig,
}
}) })
return queries return queries